Viking.js is an open-source web framework for JavaScript.
Built on Backbone.js and inspired by Ruby on Rails; it makes it easier to write client side JavaScript applications.
Github Download 0.8.0

Getting Started

Viking.js makes it easy to work with Rails and REST APIs. It attempts to follow the philosophies of Ruby On Rails.

Installation

The following libraries are required for Viking.js

<script type="text/javascript" src="/jquery.js"></script>
<script type="text/javascript" src="/strftime.js"></script>
<script type="text/javascript" src="/underscore.js"></script>
<script type="text/javascript" src="/underscore.inflection.js"></script>
<script type="text/javascript" src="/backbone.js"></script>
<script type="text/javascript" src="/viking.js"></script>

Models

Viking.Model extends Backbone.Model adding naming, associations, data type coerions, selection, and modifies sync to work with Ruby on Rails out of the box.

 

Your are not required to pass a name. In this example the name "ship" allows Viking automatically read and build the associations and generate any urls.

The schema defines the types to serialize to and from JSON as well as converting attributes to the appropriate type when they are set by the user.

Ship = Viking.Model.extend('ship', {
    
    schema: {
        name: {type: 'string'},
        size: {type: 'number'},
        roles: {type: 'string', array: true},
        attributes: {type: 'json'},
        last_serviced_at: {type: 'date'},
        operational: {type: 'boolean', "default": false}
    },
    
    belongsTo: ['fleet'],
    
    hasMany: [['sailors', {model: 'Human'}]],
    
    initialize: function () {
        // ...
    }
});

Defaults

Instead of setting defaults like in Backbone.js the default values for attributes are defined in the schema.

Ship = Viking.Model.extend('ship', {
    schema: {
        operational: { type: 'boolean', "default": false },
        created_at:  { type: 'date', "default": function () { 
            return new Date();
        } }
    }
});

Associations

belongsTo takes and array of names for the associations. By default a get('fleet') in the example would return undefined on a new instance of the model.

You can set the fleet with the normal set function or through the inital attributes. Both accept either an model or the attributes used to construct the model.

Ship = Viking.Model.extend('ship', {
    belongsTo: ['fleet']
});

If the model name is different than the association name you can pass the model name as an option.

belongsTo: ['village'];
// or
belongsTo: [['owner', {model: 'Village'}]];

Viking also supports polymorphic relations in a similar fashion to Rails. If you specify polymorphic: true in the options of a belongsTo relation.

In the example when setting or initalizing the model, if subject is a normal Javascript Object Viking will look for subject_type in the attributes to know what type of model subject needs to instantiated as.

When setting or initalizing the model, if subject is a Viking.Model then subject_type will be set to the modelName of the value.

Model = Viking.Model.except({
	belongsTo: [['subject', {polymorphic: true}]];
});

var model = new Model({
	subject_type: 'ship',
	subject: {callsign: 'TREX'}
});
model.get('subject').modelName instanceof Ship // => true

model.set('subject', new Car())
model.get('subject_type') // => 'car'

hasMany relationships are similar except they default to an empty collection. By default a get('ships') in the example above would return a ShipCollection on a new instance of the model.

// TODO: talk about setting a collection

Fleet = Viking.Model.extend('fleet', {
    hasMany: ['ships']
});

If the collection or model name is different than the association name you can pass the model name as an option.

hasMany: ['ships'];
// => uses `ShipCollection`

hasMany: [['ships', {model: 'Ship'}]];
// => uses `ShipCollection`

hasMany: [['ships', {collection: 'Ships'}]];
// => uses `Ships`

Single Table Inheritance

Viking.Model supports Single table inheritance in a similar fashion to Rails' STI.

 

Viking.Model allows inheritance by storing the name of the model in the type attribute (can be changed by setting inheritanceAttribute on the model).

In the example when you do new Destroyer({name: "37signals"}), the object will have the type attribute set to "destroyer".

// TODO: mention about Ship.all returning all STI models and // Destroyer.all returning Destroyers and GuidedMissileDestroyers

// TODO: mention about changing the `type` and perhaps // implement the becomes method internally

Ship = Viking.Model.extend('ship', {
    hasMany: [['sailors', {model: 'Human'}]],
});

Destroyer = Ship.extend('destroyer', {
    hasMany: ['guns', 'torpedos'],
});

GuidedMissileDestroyer = Destroyer.extend('guided_missile_destroyer', {
    hasMany: ['missles'],
});

Collections

Viking.Collection extends Backbone.Collection, adding predicates, selections, and modifies fetch to cancel any current request if a new fetch is triggered.

FleetCollection = Viking.Collection.extend({
    model: Ship
});

You can attach a predicate to a collection.

If a predicate is set it's paramaters will be passed under the where key when querying the server. When the predicate changes the collection is re-fetched.

To remove a predeicate call #setPredicate with a falsey value.

var fleet = new FleetCollection({predicate: {}});

// or
var predicate = new Viking.Predicate();
var fleet = new FleetCollection({predicate: predicate});

// triggers a fetch and includes {type: 'battleship'} in the query params
predicate.set('type', 'battleship');

Paginated Collections

In addition if you want paginate your results you can also use a Viking.PaginatedCollection

A Viking.PaginatedCollection expects the response from the server to be similar to the example.

The paginated collection will have a Viking.Cursor available at collection.curosr. Any changes to the attributes page, offset, or per_page will trigger a fetch on the collection.

The cursor has several helper functions to allow you to navigate. The functions are reset, incrementPage, decrementPage, and goToPage.

FleetCollection = Viking.PaginatedCollection.extend({
    model: Ship
});
{
    page: 1,
    offset: 0,
    per_page: 25,
    total_pages: 479,
    total: 11974,
    count: 25,
    ships: [
        // ...
    ]
}

Controllers

routers...

//TODO

Templates

Templates are functions stored under the Viking.View.templates object. The templates function accepts bindings that can be used when compiling the template.

Templates can be rendered via Viking.View.Helpers.render.

Viking.View.templates['my/template'] = function (bindings) {
    return 'My template';
};

Viking.View.Helpers.render('my/template'); // => 'My template'

Views

Viking.View provides additional functionality ontop of Backbone.View. Helper functions include Viking.View#subView, Viking.View#renderTemplate and inheritance of View events.

WorldView = Viking.View.extend({
    events: {
        'click .region': 'zoomIn'
    },
    
    zoomIn: function () {
        // ...
    }
});

FleetView = WorldView.extend({
    
    // FleetView inherits from WorldView which is a
    // Viking.View so it will still have the
    // 'click .region' event, unless it is overridden
    events: {
        'click .ship': 'showShipInfo'
    },
    
    showShipInfo: function () {
        // ...
    }
});

Viking.View#renderTemplate is an alias for Viking.View.Helpers.render(template, locals). Viking.View.Helpers.render(template, locals) allows you pass locals but also includes all the Viking.View.Helpers for use in your template

Viking.View.templates['fleet/overview'] = function () {
    
    return formTag({action: '/inspect'}, function() {
        return labelTag("Ship " + ship.name) + submitTag('Inspect');
    });
    
};

FleetView = Viking.View.extend({
    
    render: function () {
        this.$el.html(this.renderTemplate('fleet/overview'))
        return this;
    }
    
});

Form

routers...

[[ formFor(listing, function(f) { ]]
    <div class="section">
        [[= f.label('address') ]]
        [[= f.textField('address')]]

        [[= f.label('space_type') ]]
        [[= f.select('space_type', ["lease", "sublease"]) ]]
    </div>
[[ }) ]]

Loading data from a server

coming...

//TODO

Errors

coming...

//TODO

Support

coming...

//TODO