Skip to content


Subversion checkout URL

You can clone with
Download ZIP
Tree: 210721104b
Fetching contributors…
Cannot retrieve contributors at this time
291 lines (214 sloc) 8.09 KB

Your First Shipyard App

Let's learn by example. We'll walk through creating a basic recipe collection. We'll assume you already have Shipyard installed. You can tell it's installed correctly by running shipyard in a terminal and seeing the help information printing.

Setup your project

Shipyard comes with a utilty to get started with a new application very quickly. In a terminal, get to the directory that you would like your application to live, and run this:

$ shipyard startapp recipes

This will create a directory called recipes, as well as some typical files used in an application. You should see a file structure like so:


cd into the directory, and you can immediately run the tests to see how that works.

$ cd recipes
$ shipyard test
Starting Tests
Tests Finished: Passed (Passed: 1, Failed: 0)

Edit Recipe Model

Now, since Shipyard applications are data-driven, let's edit the Recipe Model that got generated for us. We'll add some properties that make sense for a Recipe, such as a title, and ingredients. Edit the file to add these lines in:

module.exports = new Class({

    // ...

    fields: {
        id: fields.NumberField(),
        title: fields.TextField({ required: true }),
        ingredients: fields.TextField()



This Recipe class extends from Model, which among other things, uses a fields properties to know what properties make up the underlying data. The id is a NumberField, and title and ingredients are TextFields. A field type will try to properly convert the data from a JavaScript-usable form to a serializable form (for databases or what-have-you) automatically.

Fields also accept options, and we've used one here to imply that the title is required before the model could save properly.

These fields determine what values you can use via the Model's get and set methods. Try these in a console:

$ shipyard shell
> var Recipe = require('./models/Recipe');
> var r = new Recipe();
> r.set('title', 'French Toast');
> r.get('title') // should return 'French Toast'
> r.set('oops', 'i did it again'); // won't set anything
> r.get('yikes'); // throw an Error about an non-existant 'yikes'

While we're here editing the Model, it would be a good idea to set the toString method to something more useful.

toString: function toString() {
    return this.get('title');   

Most views will default to showing the String representation of a Model if a property isn't specified, so it's a good idea to set up a decent toString method.

A dip into testing

After having set up our Recipe model, go ahead and run the test suite again:

$ shipyard test
Starting Tests

models: Recipe: should have a String representation
     1: Expected toBe [object Recipe], got undefined


Tests Finished: Failed (Passed: 0, Failed: 1)

The single test that shipyard startapp creates by default is a simple toString test meant to be overriden. Since we changed that method, we should update our test so it passes.

Open up the recipes/tests/models/Recipe.js file, and modify the test to have this:

it('should have a String representation', function(expect) {
    var r = new Recipe();
    var title = 'French Toast';
    r.set('title', title);

Another run of the test suite will show our test passes again. Hurray!

Creating Views

Until now, we've been playing with how to handle our data in models. Let's get some stuff showing in the browser, right? Shipyard renders data to the browser via it's View system.

First, we'll create a simple View that will show the title and description of a single recipe. Edit the recipes/index.js file to look like this:

var View = require('shipyard/view/View');
var Recipe = require('./models/Recipe');

var toast = new Recipe({
    title: 'French Toast',
    ingredients: 'Bread, Egg, Milk'

var titleView = new View();
titleView.bind(toast, { 'content': 'title' });

We instantiated a Recipe, passing it initial properties with an object map. Then we created a new basic View, and bound the content property to the title property of our model. View binding means whenever the model changes a property we care about, the view will update immediately.

Check in Browser

In a terminal, run shipyard server from the recipes directory. Now, you open your browser, point it at localhost:8000, and notice that "French Toast" has been printed to the page.

Let's Make a Form

In order to create recipes, let's create a form that can be used to accept user input, and then create new Recipes. Replace the contents of index.js with something like this:

var View = require('shipyard/view/View');
var FormView = require('shipyard/view/FormView');
var TextFieldView = require('shipyard/view/TextFieldView');
var ButtonView = require('shipyard/view/ButtonView');
var Recipe = require('./models/Recipe');

var form = new FormView();

var titleInput = new TextFieldView({ 
    name: 'title',
    placeholder: 'Recipe Title'

var ingredientsInput = new TextFieldView({
    name: 'ingredients',
    placeholder: 'Recipe ingredients'

form.addView(new ButtonView({ content: 'New Recipe' }));

So far, the only new concepts are that we used some new View classes, specifically FormView, which is a Container. It can contain child views, using a addView and removeView pair of methods.

A refresh of the browser shows our form, but it's not tied up to anything. Time to make it useful.

A List of Recipes

First of all, let's listen to the FormView to know when to save a new Recipe. We can listen to the submit event like so:

form.addListener('submit', function() {
    var data = this.serialize();
    var r = new Recipe(data);;

Next up, we'll make a ListView that will contain and show our recipes as we create them. Add this to the index.js after the previous code we wrote:

var ListView = require('shipyard/view/ListView');
var list = new ListView();

And finally, we can listen for save events across any Recipe model like this:

Recipe.addListener('save', function(recipe, isNew) {
    if (isNew) {

Another refresh shows we have a list that fills up as we enter in recipes and click the button.

Saving Data

The last bit to our tutorial is to show how easy it is to sync data to various locations without changing our usage much. We're going to employ a Sync to save to the browser's localStorage. In fact, we've been using a Dummy sync this whole time, which let's our model act like it can sync, without actually saving data anywhere.

Heading back to our recipes/models/Recipe.js file, we're going to edit with these lines:

// ... other requires ...
var BrowserSync = require('shipyard/sync/Browser');

module.exports = new Class({

    // ...

    Sync: {
        'default': {
            driver: BrowserSync 

    // ...


The Sync property of a model should be an object map describing the various locations the model should sync to. There should always be a default sync, as shown. Any others should be named how you like, and then can be accessed specifically be passing { using: 'yourSyncName' } to any Syncable method.

Back in our index.js, let's add in a line to load in our models on page load:

Recipe.find({ callback: function(recipes) {

Save and refresh, then save a couple recipes. Then, you can refresh again to see that they had been persisted.

Ahead full!

With that quick overview, you now have an application working using Shipyard. The concepts are the same as you scale to a full application, but you can always read up more about specific parts if you like the details.

Jump to Line
Something went wrong with that request. Please try again.