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...
//TODO
coming...
//TODO
coming...
//TODO