What follows is a 10-minute tutorial on learning the basic structure of a skit project. We will build an example site with two pages, sharing a common API layer and base page style.
If you just want to check out the finished code, here it is:
Browse the finished tutorial code
… check out the other examples in example also, including the code for this site.
$ npm install skit
(We will need .bin/skit
from your node_modules
directory in the next step. If you have a separately configured npm prefix, ignore the wonky ./node_modules/.bin
part below.)
$ mkdir helloworld $ mkdir helloworld/public $ touch helloworld/public/Home.js
helloworld/public/Home.js
in your favorite editor, and paste
the following:
// Load the Controller module from the skit library. var Controller = skit.platform.Controller; // Create a new Controller subclass and set it as this module's exports. module.exports = Controller.create({ __body__: function() { return '<p>Hello world!</p>'; } });
$ ./node_modules/.bin/skit run helloworld --debug Skit server listening on 0.0.0.0:3001
http://localhost:3001
in a browser to see
“Hello world!”
<p>Hello world!</p>
is not the entire body of the response, rather it is inserted into the
<body>
followed by code to load the appropriate
JavaScript resources from the skit server as well, including the
Home.js
file you just wrote.
Expanding on the MVP, we can add more files to organize the project a bit better.
Home.html
in the same directory as Home.js
.
Files sharing the same prefix (anything before the first underscore)
become part of the same skit module.
<p>Hello, world! I’m a template.</p> <p>Here’s a random number: {{ random }}</p>
Home.js
to match the following:
var Controller = skit.platform.Controller; // Loads the "Home.html" file, a Handlebars template. var template = __module__.html; module.exports = Controller.create({ __title__: function() { return 'Home'; }, __body__: function() { // Handlebars templates are functions that return HTML. return template({random: Math.random()}); } });
http://localhost:3001
to see the changes: Now there
should be a <title>
on the page, and you should see
a random number output on the page that’s different with every
page load.
Now that we have templates, we can easily add more markup and will quickly need to start dealing with it. You can use whatever library you want for client-side stuff — you might’ve heard of one or two options.
That being said, skit includes a simple DOM and events library, because the public demanded more.
Home.html
:
<p> Hello, world! I’m a template. <button id="reload">Reload</button> </p> <p>Here’s a random number: {{ random }}</p>
Home.js
to listen for clicks on id="reload"
:
// Load DOM and events from skit.browser. var dom = skit.browser.dom; var events = skit.browser.events; // Includes we already needed. var Controller = skit.platform.Controller; var template = __module__.html; module.exports = Controller.create({ __title__: function() { return 'Home'; }, __body__: function() { return template({random: Math.random()}); }, __ready__: function() { // dom.get() returns a matching element, or null if none is found. var reload = dom.get('#reload'); events.bind(reload, 'click', this.reload, this); } });
http://localhost:3001
to see the changes: Now we have
a button, and when you click it, a new number appears.
OK, now for the cool part. Let’s load some content over the network and include it in our server-side rendered response. GitHub has been gracious enough to allow us to access their JSON API without authentication — yay!
To plug this in, we’ll need a new module in skit.platform called skit.platform.net
.
Home.js
to include the net module and load some content:
// Includes we already needed. var dom = skit.browser.dom; var events = skit.browser.events; var Controller = skit.platform.Controller; // Add skit.platform.net for making HTTP requests. var net = skit.platform.net; var template = __module__.html; // This GitHub API returns a list of public gists. var GITHUB_URL = 'https://api.github.com/gists/public'; module.exports = Controller.create({ __preload__: function(loaded) { net.send(GITHUB_URL, { success: function(response) { this.gists = response.body; }, error: function(response) { console.log('Error loading:', response.code, 'body:', response.body); }, complete: loaded, context: this }); }, __title__: function() { return 'Home'; }, __body__: function() { return template({gists: this.gists}); }, __ready__: function() { var reload = dom.get('#reload'); events.bind(reload, 'click', this.reload, this); } });
<p> Hello, world! I’m a template. <button id="reload">Reload</button> </p> <ul> {{#each gists }} <li> <a href="{{ html_url }}"> {{#each files }} {{ filename }} {{/each}} </a> {{#if owner.login }} by <a href="{{ owner.html_url }}">{{ owner.login }}</a> {{/if}} </li> {{/each}} </ul>
http://localhost:3001
to see the changes.
Now we have a list of links, generated server-side, to gists returned by the GitHub API.
Pressing “Reload” will reload the data from the client side
and rerender the page with the new content, if there is any.
OK, let’s clean this up for a second.
<p> Hello, world! I’m a template. <button id="reload">Reload</button> </p> <ul> {{#each gists }} {{> __module__.item.html }} {{/each}} </ul>
<li> <a href="{{ html_url }}"> {{#each files }} {{ filename }} {{/each}} </a> {{#if owner.login }} by <a href="{{ owner.html_url }}">{{ owner.login }}</a> {{/if}} </li>
http://localhost:3001
— it should look the same.
Automatic partials!
Note that you can also reference this template from the JavaScript file
as __module__.item.html
in case you want to replace
an individual <li>
or somesuch.
So far we’ve only used files in public
, which is
actually a special directory in skit — public
is the
base of our public URL structure. (More on that later.)
Let’s move the API code into a new module, called GitHubAPIClient, and then include it in Home.js.
var dom = skit.browser.dom;
var events = skit.browser.events;
var Controller = skit.platform.Controller;
// Add GitHubAPIClient for loading GitHub content.
var GitHubAPIClient = library.GitHubAPIClient;
var template = __module__.html;
module.exports = Controller.create({
__preload__: function(loaded) {
GitHubAPIClient.loadGists(function(gists) {
this.gists = gists;
loaded();
}, this);
},
__title__: function() {
return 'Home';
},
__body__: function() {
return template({gists: this.gists});
},
__ready__: function() {
var reload = dom.get('#reload');
events.bind(reload, 'click', this.reload, this);
}
});
library
, and add a new file,
GitHubAPIClient.js
.
$ mkdir helloworld/library $ touch helloworld/library/GitHubAPIClient.js
var net = skit.platform.net; var GITHUB_URL = 'https://api.github.com/gists/public'; var logError = function(response) { console.log('Error loading:', response.code, 'body:', response.body); }; module.exports = { loadGists: function(apiCallback, context) { var gists = []; var done = function() { apiCallback.call(context, gists); }; net.send(GITHUB_URL, { success: function(response) { gists = response.body; }, error: logError, complete: done }); } };
http://localhost:3001
— it should look the same
for now, but we’re about to add another page that will also
use the same shared library module.
In step 7 I mentioned the public
directory’s structure
dictated the URLs we handle, so let’s add another directory inside
to see what I mean.
gist
, and add two new files,
Gist.js
and Gist.html
:
$ mkdir helloworld/public/gist $ touch helloworld/public/gist/Gist.js $ touch helloworld/public/gist/Gist.html
<li> <a href="/gist?id={{ id }}"> {{#each files }} {{ filename }} {{/each}} </a> {{#if owner.login }} by <a href="{{ owner.html_url }}">{{ owner.login }}</a> {{/if}} </li>
var dom = skit.browser.dom; var events = skit.browser.events; var Controller = skit.platform.Controller; var net = skit.platform.net; var GITHUB_BASE_URL = 'https://api.github.com/gists/'; var logError = function(response) { console.log('Error loading:', response.code, 'body:', response.body); }; module.exports = { loadGists: function(apiCallback, context) { var gists = []; var done = function() { apiCallback.call(context, gists); }; net.send(GITHUB_BASE_URL + 'public', { success: function(response) { gists = response.body; }, error: logError, complete: done }); }, loadGist: function(gistId, apiCallback, context) { var gist = null; var done = function() { apiCallback.call(context, gist); }; net.send(GITHUB_BASE_URL + encodeURIComponent(gistId), { success: function(response) { gist = response.body; }, error: logError, complete: done }) } };
<a href="/">← Home</a> <h1>Gist by {{ gistOwner }}</h1> <h2>Gist description</h2> <p>{{ gist.description }}</p> <p><a href="{{ gist.html_url }}">View on GitHub</a></p> {{#each gist.files }} <h3>{{@key}}</h3> <p>{{ size }} bytes, {{ type }} ({{ language }})</p> <pre>{{ content }}</pre> {{/each}}
var Controller = skit.platform.Controller; // skit.platform.string gives us string utilities. var string = skit.platform.string; // skit.platform.navigation tells us about the current URL. var navigation = skit.platform.navigation; var GitHubAPIClient = library.GitHubAPIClient; var template = __module__.html; module.exports = Controller.create({ __preload__: function(loaded) { var query = navigation.query(); GitHubAPIClient.loadGist(query['id'], function(gist) { if (!gist) { // On the server side, this issues a 404. navigation.notFound(); } else { this.gist = gist; } loaded(); }, this); }, __title__: function() { return 'Gist by ' + string.escapeHtml(this.gistOwner()); }, __body__: function() { return template({gist: this.gist, gistOwner: this.gistOwner()}); }, gistOwner: function() { if (this.gist['owner']) { return this.gist['owner']['login']; } return 'anonymous'; } });
http://localhost:3001
and click on one of the gist
URLs to see the new subpage in action. Note that an invalid ?id=
passed in the URL results in a server-side 404.
Even though Home.js
and Gist.js
both exist in the
same website, they don’t share any behavior yet. For this, we can
introduce hierarchy to Controller
classes.
BaseController.js
, BaseController.css
,
and BaseController.html
, in library
:
$ touch helloworld/library/BaseController.js $ touch helloworld/library/BaseController.html $ touch helloworld/library/BaseController.css
<div id="content"> <!-- Triple curlies inserts HTML unescaped. --> {{{ childHtml }}} </div>
var Controller = skit.platform.Controller; var template = __module__.html; module.exports = Controller.create({ __title__: function(childTitle) { // Parents get the title generated by children as an argument. return childTitle + ' | Hello World'; }, __body__: function(childHtml) { // Parents get the HTML generated by children as an argument. return template({childHtml: childHtml}); }, __ready__: function() { // Parents also get __preload__, __load__ and __ready__ calls. } });
/* CSS files are automatically included along with modules. */
body {
font-family: arial, sans-serif;
font-size: 14px;
}
#content {
max-width: 640px;
margin: 20px auto;
}
var BaseController = library.BaseController;
Controller.create()
to look like this:
// Controller.create's first argument becomes the parent class.
module.exports = Controller.create(BaseController, {
http://localhost:3001
to see the new shared container
for the main page and detail page — they should both have a max-width
and share a new sans-serif font.
For more, check out the full API Reference and have a look at the project on GitHub.