From 6e7894d8fad10bae0c73dcdf8ee7b1954ecf428c Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 25 Oct 2010 11:23:04 -0400 Subject: [PATCH] Streamlined event delegation from models through collections. Added documentation for 0.2.0 enhancements. --- backbone.js | 20 ++++------- index.html | 84 +++++++++++++++++++++++++++++++++------------- test/collection.js | 13 +++++++ 3 files changed, 80 insertions(+), 37 deletions(-) diff --git a/backbone.js b/backbone.js index 8e48b324e..4e4272258 100644 --- a/backbone.js +++ b/backbone.js @@ -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); } }); diff --git a/index.html b/index.html index 5dae9f4fe..5d2d6d2d9 100644 --- a/index.html +++ b/index.html @@ -164,6 +164,7 @@
  • destroy
  • validate
  • url
  • +
  • parse
  • clone
  • isNew
  • change
  • @@ -192,6 +193,7 @@
  • sort
  • pluck
  • url
  • +
  • parse
  • fetch
  • refresh
  • create
  • @@ -212,7 +214,7 @@
  • $ (jQuery)
  • render
  • make
  • -
  • handleEvents
  • +
  • delegateEvents
  • Examples @@ -508,8 +510,10 @@

    Backbone.Model

    setmodel.set(attributes, [options])
    Set a hash of attributes (one or many) on the model. If any of the attributes - change the models state, a "change" event will be fired, unless - {silent: true} is passed as an option. + change the models state, a "change" event will be triggered, unless + {silent: true} is passed as an option. Change events for specific + attributes are also triggered, and you can bind to those as well, for example: + change:title, and change:content.

    @@ -715,7 +719,7 @@ 

    Backbone.Model

    the server. If your models are located somewhere else, override this method with the correct logic. Generates URLs of the form: "/[collection]/[id]".

    - +

    Delegates to Collection#url to generate the URL, so make sure that you have it defined. @@ -723,6 +727,18 @@

    Backbone.Model

    Backbone.Collection with a url of "/notes", would have this URL: "/notes/101"

    + +

    + parsemodel.parse(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 attributes hash 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 work with a preexisting API, or better namespace + your responses. +

    clonemodel.clone() @@ -801,12 +817,17 @@

    Backbone.Model

    Backbone.Collection

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

    + +

    + Collections may also listen for changes to specific attributes in their + models, for example: Documents.bind("change:selected", ...) +

    extendBackbone.Collection.extend(properties, [classProperties]) @@ -1080,6 +1101,27 @@

    Backbone.Collection

    return this.document.url() + '/notes'; } }); +
    + +

    + parsecollection.parse(response) +
    + parse is called by Backbone whenever a collection's models are + returned by the server, in fetch. + The function is passed the raw response object, and should return + the array of model attributes to be added + 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. +

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

    @@ -1093,15 +1135,8 @@

    Backbone.Collection

    refresh. Delegates to Backbone.sync under the covers, for custom persistence strategies. -

    - -

    - The server handler for fetch requests should return a JSON list of - models, namespaced under "models": {"models": [...]} — - 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 fetch requests should return a JSON array of + models.

    @@ -1258,7 +1293,7 @@ 

    Backbone.View


    Get started with views by creating a custom view class. You'll want to override the render function, specify your - declarative events, and perhaps the + declarative events, and perhaps the tagName, className, or id of the View's root element.

    @@ -1406,23 +1441,25 @@

    Backbone.View

    -

    - handleEventshandleEvents([events]) +

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

    - Using handleEvents provides a number of advantages over manually + Using delegateEvents provides a number of advantages over manually using jQuery to bind events to child elements during render. All attached callbacks are bound to the view before being handed off to jQuery, so when the callbacks are invoked, this continues to refer to the view object. When - handleEvents is run again, perhaps with a different events + delegateEvents is run again, perhaps with a different events hash, all callbacks are removed and delegated afresh — useful for views which need to behave differently when in different modes.

    @@ -1446,7 +1483,6 @@

    Backbone.View

    render: function() { $(this.el).html(this.template(this.model.toJSON())); - this.handleEvents(); return this; }, diff --git a/test/collection.js b/test/collection.js index 46dd0f5a1..59d0f22da 100644 --- a/test/collection.js +++ b/test/collection.js @@ -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); });