Viking.js makes it easy to work with Rails and REST APIs. It attempts to follow the philosophies of Ruby On Rails.
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>
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 () {
// ...
}
});
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();
} }
}
});
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`
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'],
});
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');
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: [
// ...
]
}routers...
//TODO
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'
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;
}
});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>
[[ }) ]]coming...
//TODOcoming...
//TODOcoming...
//TODO