-
Notifications
You must be signed in to change notification settings - Fork 0
@page Why Why CanJS? @parent guides 3
@body
Your library should not break-down as your application and organization grow and technologies change. CanJS’s flexibility will keep it valuable to you far into the future.
Want to share code between a Zepto mobile app and a jQuery desktop app? No problem. CanJS code (especially models) can be shared across libraries, and so can skill sets! Working on a Dojo project today and a YUI one tomorrow? Don’t throw away all of your skills.
CanJS is extracted from JavaScriptMVC, but currently supports almost all of its MVC functionality through plugins. Start small, with its basic functionality, and extend it with plugins that handle things like:
- setters
- serialization / deserialization
- jQuery plugin generation
- validations
- calling super methods
These plugins have forced the core to be quite extendable, making 3rd party plugin development easy.
CanJS’s tools are designed to work under almost every situation. Your server sends back XML with strange urls? That’s ok, overwrite can.Model.findAll or can.Model.models. Want some special teardown code for a control? Overwrite can.Control:destroy.
But our favorite bit of flexibility is how can.Observe works with nested data. It converts nested objects into observes automatically. For example:
@codestart var person = new can.Observe({ name: { first: 'Justin', last: 'Meyer' }, hobbies: [ 'programming', 'party rocking' ] })
person.attr( 'name.first' ) //-> 'Justin' person.attr( 'hobbies.0' ) //-> 'programming' @codeend
But most important, change
events bubble, letting observes listen for when a nested property changes:
@codestart person.bind( 'change', function( ev, attr, how, newVal, oldVal ) { attr //-> 'name.last' how //-> 'set' newVal //-> 'Meyer' oldVal //-> 'Myer' });
person.attr( 'name.last', 'Meyer' ); @codeend
Memory safety is really important, especially in long-lived, dynamic pages. CanJS combats this menace in two important and unique ways:
Using templated event binding, Controls can listen to events on objects other than their element. For example, a tooltip listening to the window looks like:
@codestart var Tooltip = can.Control.extend({ '{window} click': function( el, ev ) { // hide only if we clicked outside the tooltip if (!this.element.has( ev.target ) { this.element.remove(); } } })
// create a Tooltip var tooltipElement = $( '<div>INFO</div>' ).appendTo( el ) var tooltipInstance = new Tooltip( tooltipElement ); @codeend
window
now has a reference to the control which keeps the tooltipInstance
and everything the tooltip instance might reference in memory. CanJS overwrites each library’s element remove functionality to destroy controls. Destroying a control unbinds all of its event handlers, removing any memory leaks auto-magically.
It’s relatively common to load the same model instance multiple times on a single page. For example, an app might request todos due today and high-priority todos and render them like:
@codestart can.view( 'todosList.ejs', { todaysTodos: Todo.findAll( { due: 'today' } ), criticalTodos: Todo.findAll( { type: 'critical' } ) }).then(function( frag ) { $( '#todos' ).html( frag ); }) @codeend
todosList.ejs
might look like:
@codestart <h2>Due Today</h2> <% list( todaysTodos, function( todo ) { %> <li <%= (el) -> el.data( 'todo', todo ) %>> <%= todo.attr( 'name' ) %> </li> <% } ) %> <h2>Critical Todos <% list( criticalTodos, function( todo ) { %> <li <%= (el) -> el.data( 'todo', todo ) %>> <%= todo.attr( 'name' ) %> </li> <% } ) %> @codeend
If the result for of Todo.findAll( { due: 'today' } )
and Todo.findAll( { type: 'critical' } )
both share a todo instance like:
@codestart { "id" : 5, "name" : "do dishes", "due" : "today", "type" : "critical" } @codeend
[Models can.Model] knows that this data represents the same todo and only creates one instance. This means that a single model instance is in both lists. By changing the todo’s name or destroying it, both lists will be changed.
However, model only stores these model instances while something is binding to them. Once nothing is bound to the model instance, they are removed from the store, freeing their memory for garbage collection.
This site highlights the most important features of CanJS. The library comes with thorough documentation and examples on the CanJS documentation page. There are example apps for each library and several example for jQuery.
CanJS is also supported by Bitovi. We are extremely active on the forums. And should the need arise, we provide support, training, and development.
On top of jQuery, CanJS is ~11k. Here’s some other frameworks for comparison:
- Backbone 8.97kb (with Underscore.js)
- Angular 24kb
- Knockout 13kb
- Ember 37kb
- Batman 15kb
Size is not everything. It really is what’s inside that counts. And that’s where we think CanJS really delivers a lot of bang for your buck.
The importance of performance is almost impossible to exaggerate. CanJS’s guts are highly optimized. See how:
[Controls can.Control] pre-processes event handlers so binding is super fast. Compare initializing a can.Control, Backbone.View and Ember.View tabs widget:
This makes a big difference for page initialization if your site has lots of controls.
CanJS’s live-binding is very fast. It only updates what’s necessary when it’s necessary. Compare its template rendering performance with three other common MVC frameworks:
In this test, CanJS has the fastest live-binding. Backbone and YUI are not doing live-binding, but CanJS is still the fastest.
In the popular counting circle example, Knockout visually appears the fastest, followed by CanJS.
This means that CanJS and Knockout are slightly faster at different things, but are likely tied for the fastest live-binding libraries.
Note: AngularJS throttles updates, which means it doesn’t fit well with these tests.
[Deferreds Deferreds] are simply awesome for handling asynchronous behavior. [Models can.Model] produces deferreds and can.view consumes them. With the view modifiers plugin, you can load a template and its data in parallel and render it into an element with:
@codestart $( '#todos' ).html( 'todos.ejs', Todo.findAll() ); @codeend
Hot. You can do this without the view modifiers plugin like:
@codestart can.view( 'todos.ejs', Todo.findAll() ).then(function( frag ) { $( '#todos' ).html( frag ); }) @codeend
Although [EJS can.EJS’s] live-binding is super fast, setting up live data binding can be too slow in certain situations (like rendering a list of 1000 items). EJS’s live binding is opt-in. It only turns on if you are using the attr
method. If the following template binds to a todo
's name
…
@codestart <li> <%= todo.attr('name') %> </li> @codeend
… the following doesn’t setup live-binding and renders much faster …
@codestart <li> <%= todo.name %> </li> @codeend