Skip to content

Commit

Permalink
Streamlined event delegation from models through collections. Added d…
Browse files Browse the repository at this point in the history
…ocumentation for 0.2.0 enhancements.
  • Loading branch information
jashkenas committed Oct 25, 2010
1 parent a7195a9 commit 6e7894d
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 37 deletions.
20 changes: 7 additions & 13 deletions backbone.js
Original file line number Diff line number Diff line change
Expand Up @@ -495,20 +495,14 @@
},

// Internal method called every time a model in the set fires an event.
// Sets need to update their indexes when models change ids.
_onModelEvent : function(ev, model, error) {
switch (ev) {
case 'change':
if (model.hasChanged('id')) {
delete this._byId[model.previous('id')];
this._byId[model.id] = model;
}
this.trigger('change', model);
break;
case 'error':
this.trigger('error', model, error);
break;
// Sets need to update their indexes when models change ids. All other
// events simply proxy through.
_onModelEvent : function(ev, model) {
if (ev === 'change:id') {
delete this._byId[model.previous('id')];
this._byId[model.id] = model;
}
this.trigger.apply(this, arguments);
}

});
Expand Down
84 changes: 60 additions & 24 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@
<li><a href="#Model-destroy">destroy</a></li>
<li><a href="#Model-validate">validate</a></li>
<li><a href="#Model-url">url</a></li>
<li><a href="#Model-parse">parse</a></li>
<li><a href="#Model-clone">clone</a></li>
<li><a href="#Model-isNew">isNew</a></li>
<li><a href="#Model-change">change</a></li>
Expand Down Expand Up @@ -192,6 +193,7 @@
<li><a href="#Collection-sort">sort</a></li>
<li><a href="#Collection-pluck">pluck</a></li>
<li><a href="#Collection-url">url</a></li>
<li><a href="#Collection-parse">parse</a></li>
<li><a href="#Collection-fetch">fetch</a></li>
<li><a href="#Collection-refresh">refresh</a></li>
<li><a href="#Collection-create">create</a></li>
Expand All @@ -212,7 +214,7 @@
<li><a href="#View-jQuery">$ (jQuery)</a></li>
<li><a href="#View-render">render</a></li>
<li><a href="#View-make">make</a></li>
<li><a href="#View-handleEvents">handleEvents</a></li>
<li><a href="#View-delegateEvents">delegateEvents</a></li>
</ul>
<a class="toc_title" href="#examples">
Examples
Expand Down Expand Up @@ -508,8 +510,10 @@ <h2 id="Model">Backbone.Model</h2>
<b class="header">set</b><code>model.set(attributes, [options])</code>
<br />
Set a hash of attributes (one or many) on the model. If any of the attributes
change the models state, a <tt>"change"</tt> event will be fired, unless
<tt>{silent: true}</tt> is passed as an option.
change the models state, a <tt>"change"</tt> event will be triggered, unless
<tt>{silent: true}</tt> is passed as an option. Change events for specific
attributes are also triggered, and you can bind to those as well, for example:
<tt>change:title</tt>, and <tt>change:content</tt>.
</p>

<pre>
Expand Down Expand Up @@ -715,14 +719,26 @@ <h2 id="Model">Backbone.Model</h2>
the server. If your models are located somewhere else, override this method
with the correct logic. Generates URLs of the form: <tt>"/[collection]/[id]"</tt>.
</p>

<p>
Delegates to <a href="#Collection-url">Collection#url</a> to generate the
URL, so make sure that you have it defined.
A model with an id of <tt>101</tt>, stored in a
<a href="#Collection">Backbone.Collection</a> with a <tt>url</tt> of <tt>"/notes"</tt>,
would have this URL: <tt>"/notes/101"</tt>
</p>

<p id="Model-parse">
<b class="header">parse</b><code>model.parse(response)</code>
<br />
<b>parse</b> is called whenever a model's data is returned by the
server, in <a href="#Model-fetch">fetch</a>, and <a href="#Model-save">save</a>.
The function is passed the raw <tt>response</tt> object, and should return
the attributes hash to be <a href="#Model-set">set</a> on the model. The
default implementation is a no-op, simply passing through the JSON response.
Override this if you need to work with a preexisting API, or better namespace
your responses.
</p>

<p id="Model-clone">
<b class="header">clone</b><code>model.clone()</code>
Expand Down Expand Up @@ -801,12 +817,17 @@ <h2 id="Model">Backbone.Model</h2>
<h2 id="Collection">Backbone.Collection</h2>

<p>
Collections are ordered sets of models. You can bind callbacks to be notified
when any model in the collection is changed, listen for <tt>"add"</tt> and
<tt>"remove"</tt> events, <tt>fetch</tt> the collection from the server,
and use a full suite of
Collections are ordered sets of models. You can to bind <tt>"change"</tt> events
to be notified when any model in the collection has been modified,
listen for <tt>"add"</tt> and <tt>"remove"</tt> events, <tt>fetch</tt>
the collection from the server, and use a full suite of
<a href="#Collection-Underscore-Methods">Underscore.js methods</a>.
</p>

<p>
Collections may also listen for changes to specific attributes in their
models, for example: <tt>Documents.bind("change:selected", ...)</tt>
</p>

<p id="Collection-extend">
<b class="header">extend</b><code>Backbone.Collection.extend(properties, [classProperties])</code>
Expand Down Expand Up @@ -1080,6 +1101,27 @@ <h2 id="Collection">Backbone.Collection</h2>
return this.document.url() + '/notes';
}
});
</pre>

<p id="Collection-parse">
<b class="header">parse</b><code>collection.parse(response)</code>
<br />
<b>parse</b> is called by Backbone whenever a collection's models are
returned by the server, in <a href="#Collection-fetch">fetch</a>.
The function is passed the raw <tt>response</tt> object, and should return
the array of model attributes to be <a href="#Collection-add">added</a>
to the collection. The default implementation is a no-op, simply passing
through the JSON response. Override this if you need to work with a
preexisting API, or better namespace your responses.
</p>

<pre>
var Tweets = Backbone.Collection.extend({
// The Twitter Search API returns tweets under "results".
parse: function(response) {
return response.results;
}
});
</pre>

<p id="Collection-fetch">
Expand All @@ -1093,15 +1135,8 @@ <h2 id="Collection">Backbone.Collection</h2>
<a href="#Collection-refresh">refresh</a>.
Delegates to <a href="#Sync">Backbone.sync</a>
under the covers, for custom persistence strategies.
</p>

<p>
The server handler for <b>fetch</b> requests should return a JSON list of
models, namespaced under "models": <tt>{"models": [...]}</tt> &mdash;
instead of returning the
array directly, we ask you to namespace your models like this by default,
so that it's possible to send down out-of-band information
for things like pagination or error states.
The server handler for <b>fetch</b> requests should return a JSON array of
models.
</p>

<pre class="runnable">
Expand Down Expand Up @@ -1258,7 +1293,7 @@ <h2 id="View">Backbone.View</h2>
<br />
Get started with views by creating a custom view class. You'll want to
override the <a href="#View-render">render</a> function, specify your
declarative <a href="#View-handleEvents">events</a>, and perhaps the
declarative <a href="#View-delegateEvents">events</a>, and perhaps the
<tt>tagName</tt>, <tt>className</tt>, or <tt>id</tt> of the View's root
element.
</p>
Expand Down Expand Up @@ -1406,23 +1441,25 @@ <h2 id="View">Backbone.View</h2>

<div id="make-demo"></div>

<p id="View-handleEvents">
<b class="header">handleEvents</b><code>handleEvents([events])</code>
<p id="View-delegateEvents">
<b class="header">delegateEvents</b><code>delegateEvents([events])</code>
<br />
Uses jQuery's <tt>delegate</tt> function to provide declarative callbacks
for DOM events within a view.
If an <b>events</b> hash is not passed directly, uses <tt>this.events</tt>
as the source. Events are written in the format <tt>{"event selector": "callback"}</tt>.
Omitting the <tt>selector</tt> causes the event to be bound to the view's
root element (<tt>this.el</tt>).
root element (<tt>this.el</tt>). By default <tt>delegateEvents</tt> is called
within the View's constructor for you, so if you have a simple <tt>events</tt>
hash, all of your DOM events will always already be connected.
</p>

<p>
Using <b>handleEvents</b> provides a number of advantages over manually
Using <b>delegateEvents</b> provides a number of advantages over manually
using jQuery to bind events to child elements during <a href="#View-render">render</a>. All attached
callbacks are bound to the view before being handed off to jQuery, so when
the callbacks are invoked, <tt>this</tt> continues to refer to the view object. When
<b>handleEvents</b> is run again, perhaps with a different <tt>events</tt>
<b>delegateEvents</b> is run again, perhaps with a different <tt>events</tt>
hash, all callbacks are removed and delegated afresh &mdash; useful for
views which need to behave differently when in different modes.
</p>
Expand All @@ -1446,7 +1483,6 @@ <h2 id="View">Backbone.View</h2>

render: function() {
$(this.el).html(this.template(this.model.toJSON()));
this.handleEvents();
return this;
},

Expand Down
13 changes: 13 additions & 0 deletions test/collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,19 @@ $(document).ready(function() {
equals(col.getByCid(col.first().cid), col.first());
});

test("Collection: update index when id changes", function() {
var col = new Backbone.Collection();
col.add([
{id : 1, name : 'one'},
{id : 2, name : 'two'}
]);
var one = col.get(1);
equals(one.get('name'), 'one');
one.set({id : 101});
equals(col.get(1), null);
equals(col.get(101).get('name'), 'one');
});

test("Collection: at", function() {
equals(col.at(2), b);
});
Expand Down

0 comments on commit 6e7894d

Please sign in to comment.