PageRenderTime 73ms CodeModel.GetById 137ms app.highlight 320ms RepoModel.GetById 134ms app.codeStats 1ms

/examples/Backbone/Scripts/lib/backbone.js

http://github.com/andrewdavey/cassette
JavaScript | 1290 lines | 785 code | 164 blank | 341 comment | 285 complexity | 3d6d999ff8d7c29bc28c3c2a87ef39cf MD5 | raw file
   1//     Backbone.js 0.9.1
   2
   3//     (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
   4//     Backbone may be freely distributed under the MIT license.
   5//     For all details and documentation:
   6//     http://backbonejs.org
   7
   8(function(){
   9
  10  // Initial Setup
  11  // -------------
  12
  13  // Save a reference to the global object (`window` in the browser, `global`
  14  // on the server).
  15  var root = this;
  16
  17  // Save the previous value of the `Backbone` variable, so that it can be
  18  // restored later on, if `noConflict` is used.
  19  var previousBackbone = root.Backbone;
  20
  21  // Create a local reference to slice/splice.
  22  var slice = Array.prototype.slice;
  23  var splice = Array.prototype.splice;
  24
  25  // The top-level namespace. All public Backbone classes and modules will
  26  // be attached to this. Exported for both CommonJS and the browser.
  27  var Backbone;
  28  if (typeof exports !== 'undefined') {
  29    Backbone = exports;
  30  } else {
  31    Backbone = root.Backbone = {};
  32  }
  33
  34  // Current version of the library. Keep in sync with `package.json`.
  35  Backbone.VERSION = '0.9.1';
  36
  37  // Require Underscore, if we're on the server, and it's not already present.
  38  var _ = root._;
  39  if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
  40
  41  // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
  42  var $ = root.jQuery || root.Zepto || root.ender;
  43
  44  // Set the JavaScript library that will be used for DOM manipulation and
  45  // Ajax calls (a.k.a. the `$` variable). By default Backbone will use: jQuery,
  46  // Zepto, or Ender; but the `setDomLibrary()` method lets you inject an
  47  // alternate JavaScript library (or a mock library for testing your views
  48  // outside of a browser).
  49  Backbone.setDomLibrary = function(lib) {
  50    $ = lib;
  51  };
  52
  53  // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
  54  // to its previous owner. Returns a reference to this Backbone object.
  55  Backbone.noConflict = function() {
  56    root.Backbone = previousBackbone;
  57    return this;
  58  };
  59
  60  // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
  61  // will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
  62  // set a `X-Http-Method-Override` header.
  63  Backbone.emulateHTTP = false;
  64
  65  // Turn on `emulateJSON` to support legacy servers that can't deal with direct
  66  // `application/json` requests ... will encode the body as
  67  // `application/x-www-form-urlencoded` instead and will send the model in a
  68  // form param named `model`.
  69  Backbone.emulateJSON = false;
  70
  71  // Backbone.Events
  72  // -----------------
  73
  74  // A module that can be mixed in to *any object* in order to provide it with
  75  // custom events. You may bind with `on` or remove with `off` callback functions
  76  // to an event; trigger`-ing an event fires all callbacks in succession.
  77  //
  78  //     var object = {};
  79  //     _.extend(object, Backbone.Events);
  80  //     object.on('expand', function(){ alert('expanded'); });
  81  //     object.trigger('expand');
  82  //
  83  Backbone.Events = {
  84
  85    // Bind an event, specified by a string name, `ev`, to a `callback`
  86    // function. Passing `"all"` will bind the callback to all events fired.
  87    on: function(events, callback, context) {
  88      var ev;
  89      events = events.split(/\s+/);
  90      var calls = this._callbacks || (this._callbacks = {});
  91      while (ev = events.shift()) {
  92        // Create an immutable callback list, allowing traversal during
  93        // modification.  The tail is an empty object that will always be used
  94        // as the next node.
  95        var list  = calls[ev] || (calls[ev] = {});
  96        var tail = list.tail || (list.tail = list.next = {});
  97        tail.callback = callback;
  98        tail.context = context;
  99        list.tail = tail.next = {};
 100      }
 101      return this;
 102    },
 103
 104    // Remove one or many callbacks. If `context` is null, removes all callbacks
 105    // with that function. If `callback` is null, removes all callbacks for the
 106    // event. If `ev` is null, removes all bound callbacks for all events.
 107    off: function(events, callback, context) {
 108      var ev, calls, node;
 109      if (!events) {
 110        delete this._callbacks;
 111      } else if (calls = this._callbacks) {
 112        events = events.split(/\s+/);
 113        while (ev = events.shift()) {
 114          node = calls[ev];
 115          delete calls[ev];
 116          if (!callback || !node) continue;
 117          // Create a new list, omitting the indicated event/context pairs.
 118          while ((node = node.next) && node.next) {
 119            if (node.callback === callback &&
 120              (!context || node.context === context)) continue;
 121            this.on(ev, node.callback, node.context);
 122          }
 123        }
 124      }
 125      return this;
 126    },
 127
 128    // Trigger an event, firing all bound callbacks. Callbacks are passed the
 129    // same arguments as `trigger` is, apart from the event name.
 130    // Listening for `"all"` passes the true event name as the first argument.
 131    trigger: function(events) {
 132      var event, node, calls, tail, args, all, rest;
 133      if (!(calls = this._callbacks)) return this;
 134      all = calls['all'];
 135      (events = events.split(/\s+/)).push(null);
 136      // Save references to the current heads & tails.
 137      while (event = events.shift()) {
 138        if (all) events.push({next: all.next, tail: all.tail, event: event});
 139        if (!(node = calls[event])) continue;
 140        events.push({next: node.next, tail: node.tail});
 141      }
 142      // Traverse each list, stopping when the saved tail is reached.
 143      rest = slice.call(arguments, 1);
 144      while (node = events.pop()) {
 145        tail = node.tail;
 146        args = node.event ? [node.event].concat(rest) : rest;
 147        while ((node = node.next) !== tail) {
 148          node.callback.apply(node.context || this, args);
 149        }
 150      }
 151      return this;
 152    }
 153
 154  };
 155
 156  // Aliases for backwards compatibility.
 157  Backbone.Events.bind   = Backbone.Events.on;
 158  Backbone.Events.unbind = Backbone.Events.off;
 159
 160  // Backbone.Model
 161  // --------------
 162
 163  // Create a new model, with defined attributes. A client id (`cid`)
 164  // is automatically generated and assigned for you.
 165  Backbone.Model = function(attributes, options) {
 166    var defaults;
 167    attributes || (attributes = {});
 168    if (options && options.parse) attributes = this.parse(attributes);
 169    if (defaults = getValue(this, 'defaults')) {
 170      attributes = _.extend({}, defaults, attributes);
 171    }
 172    if (options && options.collection) this.collection = options.collection;
 173    this.attributes = {};
 174    this._escapedAttributes = {};
 175    this.cid = _.uniqueId('c');
 176    if (!this.set(attributes, {silent: true})) {
 177      throw new Error("Can't create an invalid model");
 178    }
 179    delete this._changed;
 180    this._previousAttributes = _.clone(this.attributes);
 181    this.initialize.apply(this, arguments);
 182  };
 183
 184  // Attach all inheritable methods to the Model prototype.
 185  _.extend(Backbone.Model.prototype, Backbone.Events, {
 186
 187    // The default name for the JSON `id` attribute is `"id"`. MongoDB and
 188    // CouchDB users may want to set this to `"_id"`.
 189    idAttribute: 'id',
 190
 191    // Initialize is an empty function by default. Override it with your own
 192    // initialization logic.
 193    initialize: function(){},
 194
 195    // Return a copy of the model's `attributes` object.
 196    toJSON: function() {
 197      return _.clone(this.attributes);
 198    },
 199
 200    // Get the value of an attribute.
 201    get: function(attr) {
 202      return this.attributes[attr];
 203    },
 204
 205    // Get the HTML-escaped value of an attribute.
 206    escape: function(attr) {
 207      var html;
 208      if (html = this._escapedAttributes[attr]) return html;
 209      var val = this.attributes[attr];
 210      return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
 211    },
 212
 213    // Returns `true` if the attribute contains a value that is not null
 214    // or undefined.
 215    has: function(attr) {
 216      return this.attributes[attr] != null;
 217    },
 218
 219    // Set a hash of model attributes on the object, firing `"change"` unless
 220    // you choose to silence it.
 221    set: function(key, value, options) {
 222      var attrs, attr, val;
 223      if (_.isObject(key) || key == null) {
 224        attrs = key;
 225        options = value;
 226      } else {
 227        attrs = {};
 228        attrs[key] = value;
 229      }
 230
 231      // Extract attributes and options.
 232      options || (options = {});
 233      if (!attrs) return this;
 234      if (attrs instanceof Backbone.Model) attrs = attrs.attributes;
 235      if (options.unset) for (attr in attrs) attrs[attr] = void 0;
 236
 237      // Run validation.
 238      if (!this._validate(attrs, options)) return false;
 239
 240      // Check for changes of `id`.
 241      if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
 242
 243      var now = this.attributes;
 244      var escaped = this._escapedAttributes;
 245      var prev = this._previousAttributes || {};
 246      var alreadySetting = this._setting;
 247      this._changed || (this._changed = {});
 248      this._setting = true;
 249
 250      // Update attributes.
 251      for (attr in attrs) {
 252        val = attrs[attr];
 253        if (!_.isEqual(now[attr], val)) delete escaped[attr];
 254        options.unset ? delete now[attr] : now[attr] = val;
 255        if (this._changing && !_.isEqual(this._changed[attr], val)) {
 256          this.trigger('change:' + attr, this, val, options);
 257          this._moreChanges = true;
 258        }
 259        delete this._changed[attr];
 260        if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
 261          this._changed[attr] = val;
 262        }
 263      }
 264
 265      // Fire the `"change"` events, if the model has been changed.
 266      if (!alreadySetting) {
 267        if (!options.silent && this.hasChanged()) this.change(options);
 268        this._setting = false;
 269      }
 270      return this;
 271    },
 272
 273    // Remove an attribute from the model, firing `"change"` unless you choose
 274    // to silence it. `unset` is a noop if the attribute doesn't exist.
 275    unset: function(attr, options) {
 276      (options || (options = {})).unset = true;
 277      return this.set(attr, null, options);
 278    },
 279
 280    // Clear all attributes on the model, firing `"change"` unless you choose
 281    // to silence it.
 282    clear: function(options) {
 283      (options || (options = {})).unset = true;
 284      return this.set(_.clone(this.attributes), options);
 285    },
 286
 287    // Fetch the model from the server. If the server's representation of the
 288    // model differs from its current attributes, they will be overriden,
 289    // triggering a `"change"` event.
 290    fetch: function(options) {
 291      options = options ? _.clone(options) : {};
 292      var model = this;
 293      var success = options.success;
 294      options.success = function(resp, status, xhr) {
 295        if (!model.set(model.parse(resp, xhr), options)) return false;
 296        if (success) success(model, resp);
 297      };
 298      options.error = Backbone.wrapError(options.error, model, options);
 299      return (this.sync || Backbone.sync).call(this, 'read', this, options);
 300    },
 301
 302    // Set a hash of model attributes, and sync the model to the server.
 303    // If the server returns an attributes hash that differs, the model's
 304    // state will be `set` again.
 305    save: function(key, value, options) {
 306      var attrs, current;
 307      if (_.isObject(key) || key == null) {
 308        attrs = key;
 309        options = value;
 310      } else {
 311        attrs = {};
 312        attrs[key] = value;
 313      }
 314
 315      options = options ? _.clone(options) : {};
 316      if (options.wait) current = _.clone(this.attributes);
 317      var silentOptions = _.extend({}, options, {silent: true});
 318      if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
 319        return false;
 320      }
 321      var model = this;
 322      var success = options.success;
 323      options.success = function(resp, status, xhr) {
 324        var serverAttrs = model.parse(resp, xhr);
 325        if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
 326        if (!model.set(serverAttrs, options)) return false;
 327        if (success) {
 328          success(model, resp);
 329        } else {
 330          model.trigger('sync', model, resp, options);
 331        }
 332      };
 333      options.error = Backbone.wrapError(options.error, model, options);
 334      var method = this.isNew() ? 'create' : 'update';
 335      var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
 336      if (options.wait) this.set(current, silentOptions);
 337      return xhr;
 338    },
 339
 340    // Destroy this model on the server if it was already persisted.
 341    // Optimistically removes the model from its collection, if it has one.
 342    // If `wait: true` is passed, waits for the server to respond before removal.
 343    destroy: function(options) {
 344      options = options ? _.clone(options) : {};
 345      var model = this;
 346      var success = options.success;
 347
 348      var triggerDestroy = function() {
 349        model.trigger('destroy', model, model.collection, options);
 350      };
 351
 352      if (this.isNew()) return triggerDestroy();
 353      options.success = function(resp) {
 354        if (options.wait) triggerDestroy();
 355        if (success) {
 356          success(model, resp);
 357        } else {
 358          model.trigger('sync', model, resp, options);
 359        }
 360      };
 361      options.error = Backbone.wrapError(options.error, model, options);
 362      var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);
 363      if (!options.wait) triggerDestroy();
 364      return xhr;
 365    },
 366
 367    // Default URL for the model's representation on the server -- if you're
 368    // using Backbone's restful methods, override this to change the endpoint
 369    // that will be called.
 370    url: function() {
 371      var base = getValue(this.collection, 'url') || getValue(this, 'urlRoot') || urlError();
 372      if (this.isNew()) return base;
 373      return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
 374    },
 375
 376    // **parse** converts a response into the hash of attributes to be `set` on
 377    // the model. The default implementation is just to pass the response along.
 378    parse: function(resp, xhr) {
 379      return resp;
 380    },
 381
 382    // Create a new model with identical attributes to this one.
 383    clone: function() {
 384      return new this.constructor(this.attributes);
 385    },
 386
 387    // A model is new if it has never been saved to the server, and lacks an id.
 388    isNew: function() {
 389      return this.id == null;
 390    },
 391
 392    // Call this method to manually fire a `"change"` event for this model and
 393    // a `"change:attribute"` event for each changed attribute.
 394    // Calling this will cause all objects observing the model to update.
 395    change: function(options) {
 396      if (this._changing || !this.hasChanged()) return this;
 397      this._changing = true;
 398      this._moreChanges = true;
 399      for (var attr in this._changed) {
 400        this.trigger('change:' + attr, this, this._changed[attr], options);
 401      }
 402      while (this._moreChanges) {
 403        this._moreChanges = false;
 404        this.trigger('change', this, options);
 405      }
 406      this._previousAttributes = _.clone(this.attributes);
 407      delete this._changed;
 408      this._changing = false;
 409      return this;
 410    },
 411
 412    // Determine if the model has changed since the last `"change"` event.
 413    // If you specify an attribute name, determine if that attribute has changed.
 414    hasChanged: function(attr) {
 415      if (!arguments.length) return !_.isEmpty(this._changed);
 416      return this._changed && _.has(this._changed, attr);
 417    },
 418
 419    // Return an object containing all the attributes that have changed, or
 420    // false if there are no changed attributes. Useful for determining what
 421    // parts of a view need to be updated and/or what attributes need to be
 422    // persisted to the server. Unset attributes will be set to undefined.
 423    // You can also pass an attributes object to diff against the model,
 424    // determining if there *would be* a change.
 425    changedAttributes: function(diff) {
 426      if (!diff) return this.hasChanged() ? _.clone(this._changed) : false;
 427      var val, changed = false, old = this._previousAttributes;
 428      for (var attr in diff) {
 429        if (_.isEqual(old[attr], (val = diff[attr]))) continue;
 430        (changed || (changed = {}))[attr] = val;
 431      }
 432      return changed;
 433    },
 434
 435    // Get the previous value of an attribute, recorded at the time the last
 436    // `"change"` event was fired.
 437    previous: function(attr) {
 438      if (!arguments.length || !this._previousAttributes) return null;
 439      return this._previousAttributes[attr];
 440    },
 441
 442    // Get all of the attributes of the model at the time of the previous
 443    // `"change"` event.
 444    previousAttributes: function() {
 445      return _.clone(this._previousAttributes);
 446    },
 447
 448    // Check if the model is currently in a valid state. It's only possible to
 449    // get into an *invalid* state if you're using silent changes.
 450    isValid: function() {
 451      return !this.validate(this.attributes);
 452    },
 453
 454    // Run validation against a set of incoming attributes, returning `true`
 455    // if all is well. If a specific `error` callback has been passed,
 456    // call that instead of firing the general `"error"` event.
 457    _validate: function(attrs, options) {
 458      if (options.silent || !this.validate) return true;
 459      attrs = _.extend({}, this.attributes, attrs);
 460      var error = this.validate(attrs, options);
 461      if (!error) return true;
 462      if (options && options.error) {
 463        options.error(this, error, options);
 464      } else {
 465        this.trigger('error', this, error, options);
 466      }
 467      return false;
 468    }
 469
 470  });
 471
 472  // Backbone.Collection
 473  // -------------------
 474
 475  // Provides a standard collection class for our sets of models, ordered
 476  // or unordered. If a `comparator` is specified, the Collection will maintain
 477  // its models in sort order, as they're added and removed.
 478  Backbone.Collection = function(models, options) {
 479    options || (options = {});
 480    if (options.comparator) this.comparator = options.comparator;
 481    this._reset();
 482    this.initialize.apply(this, arguments);
 483    if (models) this.reset(models, {silent: true, parse: options.parse});
 484  };
 485
 486  // Define the Collection's inheritable methods.
 487  _.extend(Backbone.Collection.prototype, Backbone.Events, {
 488
 489    // The default model for a collection is just a **Backbone.Model**.
 490    // This should be overridden in most cases.
 491    model: Backbone.Model,
 492
 493    // Initialize is an empty function by default. Override it with your own
 494    // initialization logic.
 495    initialize: function(){},
 496
 497    // The JSON representation of a Collection is an array of the
 498    // models' attributes.
 499    toJSON: function() {
 500      return this.map(function(model){ return model.toJSON(); });
 501    },
 502
 503    // Add a model, or list of models to the set. Pass **silent** to avoid
 504    // firing the `add` event for every new model.
 505    add: function(models, options) {
 506      var i, index, length, model, cid, id, cids = {}, ids = {};
 507      options || (options = {});
 508      models = _.isArray(models) ? models.slice() : [models];
 509
 510      // Begin by turning bare objects into model references, and preventing
 511      // invalid models or duplicate models from being added.
 512      for (i = 0, length = models.length; i < length; i++) {
 513        if (!(model = models[i] = this._prepareModel(models[i], options))) {
 514          throw new Error("Can't add an invalid model to a collection");
 515        }
 516        if (cids[cid = model.cid] || this._byCid[cid] ||
 517          (((id = model.id) != null) && (ids[id] || this._byId[id]))) {
 518          throw new Error("Can't add the same model to a collection twice");
 519        }
 520        cids[cid] = ids[id] = model;
 521      }
 522
 523      // Listen to added models' events, and index models for lookup by
 524      // `id` and by `cid`.
 525      for (i = 0; i < length; i++) {
 526        (model = models[i]).on('all', this._onModelEvent, this);
 527        this._byCid[model.cid] = model;
 528        if (model.id != null) this._byId[model.id] = model;
 529      }
 530
 531      // Insert models into the collection, re-sorting if needed, and triggering
 532      // `add` events unless silenced.
 533      this.length += length;
 534      index = options.at != null ? options.at : this.models.length;
 535      splice.apply(this.models, [index, 0].concat(models));
 536      if (this.comparator) this.sort({silent: true});
 537      if (options.silent) return this;
 538      for (i = 0, length = this.models.length; i < length; i++) {
 539        if (!cids[(model = this.models[i]).cid]) continue;
 540        options.index = i;
 541        model.trigger('add', model, this, options);
 542      }
 543      return this;
 544    },
 545
 546    // Remove a model, or a list of models from the set. Pass silent to avoid
 547    // firing the `remove` event for every model removed.
 548    remove: function(models, options) {
 549      var i, l, index, model;
 550      options || (options = {});
 551      models = _.isArray(models) ? models.slice() : [models];
 552      for (i = 0, l = models.length; i < l; i++) {
 553        model = this.getByCid(models[i]) || this.get(models[i]);
 554        if (!model) continue;
 555        delete this._byId[model.id];
 556        delete this._byCid[model.cid];
 557        index = this.indexOf(model);
 558        this.models.splice(index, 1);
 559        this.length--;
 560        if (!options.silent) {
 561          options.index = index;
 562          model.trigger('remove', model, this, options);
 563        }
 564        this._removeReference(model);
 565      }
 566      return this;
 567    },
 568
 569    // Get a model from the set by id.
 570    get: function(id) {
 571      if (id == null) return null;
 572      return this._byId[id.id != null ? id.id : id];
 573    },
 574
 575    // Get a model from the set by client id.
 576    getByCid: function(cid) {
 577      return cid && this._byCid[cid.cid || cid];
 578    },
 579
 580    // Get the model at the given index.
 581    at: function(index) {
 582      return this.models[index];
 583    },
 584
 585    // Force the collection to re-sort itself. You don't need to call this under
 586    // normal circumstances, as the set will maintain sort order as each item
 587    // is added.
 588    sort: function(options) {
 589      options || (options = {});
 590      if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
 591      var boundComparator = _.bind(this.comparator, this);
 592      if (this.comparator.length == 1) {
 593        this.models = this.sortBy(boundComparator);
 594      } else {
 595        this.models.sort(boundComparator);
 596      }
 597      if (!options.silent) this.trigger('reset', this, options);
 598      return this;
 599    },
 600
 601    // Pluck an attribute from each model in the collection.
 602    pluck: function(attr) {
 603      return _.map(this.models, function(model){ return model.get(attr); });
 604    },
 605
 606    // When you have more items than you want to add or remove individually,
 607    // you can reset the entire set with a new list of models, without firing
 608    // any `add` or `remove` events. Fires `reset` when finished.
 609    reset: function(models, options) {
 610      models  || (models = []);
 611      options || (options = {});
 612      for (var i = 0, l = this.models.length; i < l; i++) {
 613        this._removeReference(this.models[i]);
 614      }
 615      this._reset();
 616      this.add(models, {silent: true, parse: options.parse});
 617      if (!options.silent) this.trigger('reset', this, options);
 618      return this;
 619    },
 620
 621    // Fetch the default set of models for this collection, resetting the
 622    // collection when they arrive. If `add: true` is passed, appends the
 623    // models to the collection instead of resetting.
 624    fetch: function(options) {
 625      options = options ? _.clone(options) : {};
 626      if (options.parse === undefined) options.parse = true;
 627      var collection = this;
 628      var success = options.success;
 629      options.success = function(resp, status, xhr) {
 630        collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
 631        if (success) success(collection, resp);
 632      };
 633      options.error = Backbone.wrapError(options.error, collection, options);
 634      return (this.sync || Backbone.sync).call(this, 'read', this, options);
 635    },
 636
 637    // Create a new instance of a model in this collection. Add the model to the
 638    // collection immediately, unless `wait: true` is passed, in which case we
 639    // wait for the server to agree.
 640    create: function(model, options) {
 641      var coll = this;
 642      options = options ? _.clone(options) : {};
 643      model = this._prepareModel(model, options);
 644      if (!model) return false;
 645      if (!options.wait) coll.add(model, options);
 646      var success = options.success;
 647      options.success = function(nextModel, resp, xhr) {
 648        if (options.wait) coll.add(nextModel, options);
 649        if (success) {
 650          success(nextModel, resp);
 651        } else {
 652          nextModel.trigger('sync', model, resp, options);
 653        }
 654      };
 655      model.save(null, options);
 656      return model;
 657    },
 658
 659    // **parse** converts a response into a list of models to be added to the
 660    // collection. The default implementation is just to pass it through.
 661    parse: function(resp, xhr) {
 662      return resp;
 663    },
 664
 665    // Proxy to _'s chain. Can't be proxied the same way the rest of the
 666    // underscore methods are proxied because it relies on the underscore
 667    // constructor.
 668    chain: function () {
 669      return _(this.models).chain();
 670    },
 671
 672    // Reset all internal state. Called when the collection is reset.
 673    _reset: function(options) {
 674      this.length = 0;
 675      this.models = [];
 676      this._byId  = {};
 677      this._byCid = {};
 678    },
 679
 680    // Prepare a model or hash of attributes to be added to this collection.
 681    _prepareModel: function(model, options) {
 682      if (!(model instanceof Backbone.Model)) {
 683        var attrs = model;
 684        options.collection = this;
 685        model = new this.model(attrs, options);
 686        if (!model._validate(model.attributes, options)) model = false;
 687      } else if (!model.collection) {
 688        model.collection = this;
 689      }
 690      return model;
 691    },
 692
 693    // Internal method to remove a model's ties to a collection.
 694    _removeReference: function(model) {
 695      if (this == model.collection) {
 696        delete model.collection;
 697      }
 698      model.off('all', this._onModelEvent, this);
 699    },
 700
 701    // Internal method called every time a model in the set fires an event.
 702    // Sets need to update their indexes when models change ids. All other
 703    // events simply proxy through. "add" and "remove" events that originate
 704    // in other collections are ignored.
 705    _onModelEvent: function(ev, model, collection, options) {
 706      if ((ev == 'add' || ev == 'remove') && collection != this) return;
 707      if (ev == 'destroy') {
 708        this.remove(model, options);
 709      }
 710      if (model && ev === 'change:' + model.idAttribute) {
 711        delete this._byId[model.previous(model.idAttribute)];
 712        this._byId[model.id] = model;
 713      }
 714      this.trigger.apply(this, arguments);
 715    }
 716
 717  });
 718
 719  // Underscore methods that we want to implement on the Collection.
 720  var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
 721    'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
 722    'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
 723    'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
 724    'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
 725
 726  // Mix in each Underscore method as a proxy to `Collection#models`.
 727  _.each(methods, function(method) {
 728    Backbone.Collection.prototype[method] = function() {
 729      return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
 730    };
 731  });
 732
 733  // Backbone.Router
 734  // -------------------
 735
 736  // Routers map faux-URLs to actions, and fire events when routes are
 737  // matched. Creating a new one sets its `routes` hash, if not set statically.
 738  Backbone.Router = function(options) {
 739    options || (options = {});
 740    if (options.routes) this.routes = options.routes;
 741    this._bindRoutes();
 742    this.initialize.apply(this, arguments);
 743  };
 744
 745  // Cached regular expressions for matching named param parts and splatted
 746  // parts of route strings.
 747  var namedParam    = /:\w+/g;
 748  var splatParam    = /\*\w+/g;
 749  var escapeRegExp  = /[-[\]{}()+?.,\\^$|#\s]/g;
 750
 751  // Set up all inheritable **Backbone.Router** properties and methods.
 752  _.extend(Backbone.Router.prototype, Backbone.Events, {
 753
 754    // Initialize is an empty function by default. Override it with your own
 755    // initialization logic.
 756    initialize: function(){},
 757
 758    // Manually bind a single named route to a callback. For example:
 759    //
 760    //     this.route('search/:query/p:num', 'search', function(query, num) {
 761    //       ...
 762    //     });
 763    //
 764    route: function(route, name, callback) {
 765      Backbone.history || (Backbone.history = new Backbone.History);
 766      if (!_.isRegExp(route)) route = this._routeToRegExp(route);
 767      if (!callback) callback = this[name];
 768      Backbone.history.route(route, _.bind(function(fragment) {
 769        var args = this._extractParameters(route, fragment);
 770        callback && callback.apply(this, args);
 771        this.trigger.apply(this, ['route:' + name].concat(args));
 772        Backbone.history.trigger('route', this, name, args);
 773      }, this));
 774      return this;
 775    },
 776
 777    // Simple proxy to `Backbone.history` to save a fragment into the history.
 778    navigate: function(fragment, options) {
 779      Backbone.history.navigate(fragment, options);
 780    },
 781
 782    // Bind all defined routes to `Backbone.history`. We have to reverse the
 783    // order of the routes here to support behavior where the most general
 784    // routes can be defined at the bottom of the route map.
 785    _bindRoutes: function() {
 786      if (!this.routes) return;
 787      var routes = [];
 788      for (var route in this.routes) {
 789        routes.unshift([route, this.routes[route]]);
 790      }
 791      for (var i = 0, l = routes.length; i < l; i++) {
 792        this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
 793      }
 794    },
 795
 796    // Convert a route string into a regular expression, suitable for matching
 797    // against the current location hash.
 798    _routeToRegExp: function(route) {
 799      route = route.replace(escapeRegExp, '\\$&')
 800                   .replace(namedParam, '([^\/]+)')
 801                   .replace(splatParam, '(.*?)');
 802      return new RegExp('^' + route + '$');
 803    },
 804
 805    // Given a route, and a URL fragment that it matches, return the array of
 806    // extracted parameters.
 807    _extractParameters: function(route, fragment) {
 808      return route.exec(fragment).slice(1);
 809    }
 810
 811  });
 812
 813  // Backbone.History
 814  // ----------------
 815
 816  // Handles cross-browser history management, based on URL fragments. If the
 817  // browser does not support `onhashchange`, falls back to polling.
 818  Backbone.History = function() {
 819    this.handlers = [];
 820    _.bindAll(this, 'checkUrl');
 821  };
 822
 823  // Cached regex for cleaning leading hashes and slashes .
 824  var routeStripper = /^[#\/]/;
 825
 826  // Cached regex for detecting MSIE.
 827  var isExplorer = /msie [\w.]+/;
 828
 829  // Has the history handling already been started?
 830  var historyStarted = false;
 831
 832  // Set up all inheritable **Backbone.History** properties and methods.
 833  _.extend(Backbone.History.prototype, Backbone.Events, {
 834
 835    // The default interval to poll for hash changes, if necessary, is
 836    // twenty times a second.
 837    interval: 50,
 838
 839    // Get the cross-browser normalized URL fragment, either from the URL,
 840    // the hash, or the override.
 841    getFragment: function(fragment, forcePushState) {
 842      if (fragment == null) {
 843        if (this._hasPushState || forcePushState) {
 844          fragment = window.location.pathname;
 845          var search = window.location.search;
 846          if (search) fragment += search;
 847        } else {
 848          fragment = window.location.hash;
 849        }
 850      }
 851      fragment = decodeURIComponent(fragment);
 852      if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
 853      return fragment.replace(routeStripper, '');
 854    },
 855
 856    // Start the hash change handling, returning `true` if the current URL matches
 857    // an existing route, and `false` otherwise.
 858    start: function(options) {
 859
 860      // Figure out the initial configuration. Do we need an iframe?
 861      // Is pushState desired ... is it available?
 862      if (historyStarted) throw new Error("Backbone.history has already been started");
 863      this.options          = _.extend({}, {root: '/'}, this.options, options);
 864      this._wantsHashChange = this.options.hashChange !== false;
 865      this._wantsPushState  = !!this.options.pushState;
 866      this._hasPushState    = !!(this.options.pushState && window.history && window.history.pushState);
 867      var fragment          = this.getFragment();
 868      var docMode           = document.documentMode;
 869      var oldIE             = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
 870      if (oldIE) {
 871        this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
 872        this.navigate(fragment);
 873      }
 874
 875      // Depending on whether we're using pushState or hashes, and whether
 876      // 'onhashchange' is supported, determine how we check the URL state.
 877      if (this._hasPushState) {
 878        $(window).bind('popstate', this.checkUrl);
 879      } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
 880        $(window).bind('hashchange', this.checkUrl);
 881      } else if (this._wantsHashChange) {
 882        this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
 883      }
 884
 885      // Determine if we need to change the base url, for a pushState link
 886      // opened by a non-pushState browser.
 887      this.fragment = fragment;
 888      historyStarted = true;
 889      var loc = window.location;
 890      var atRoot  = loc.pathname == this.options.root;
 891
 892      // If we've started off with a route from a `pushState`-enabled browser,
 893      // but we're currently in a browser that doesn't support it...
 894      if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
 895        this.fragment = this.getFragment(null, true);
 896        window.location.replace(this.options.root + '#' + this.fragment);
 897        // Return immediately as browser will do redirect to new url
 898        return true;
 899
 900      // Or if we've started out with a hash-based route, but we're currently
 901      // in a browser where it could be `pushState`-based instead...
 902      } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
 903        this.fragment = loc.hash.replace(routeStripper, '');
 904        window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
 905      }
 906
 907      if (!this.options.silent) {
 908        return this.loadUrl();
 909      }
 910    },
 911
 912    // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
 913    // but possibly useful for unit testing Routers.
 914    stop: function() {
 915      $(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
 916      clearInterval(this._checkUrlInterval);
 917      historyStarted = false;
 918    },
 919
 920    // Add a route to be tested when the fragment changes. Routes added later
 921    // may override previous routes.
 922    route: function(route, callback) {
 923      this.handlers.unshift({route: route, callback: callback});
 924    },
 925
 926    // Checks the current URL to see if it has changed, and if it has,
 927    // calls `loadUrl`, normalizing across the hidden iframe.
 928    checkUrl: function(e) {
 929      var current = this.getFragment();
 930      if (current == this.fragment && this.iframe) current = this.getFragment(this.iframe.location.hash);
 931      if (current == this.fragment || current == decodeURIComponent(this.fragment)) return false;
 932      if (this.iframe) this.navigate(current);
 933      this.loadUrl() || this.loadUrl(window.location.hash);
 934    },
 935
 936    // Attempt to load the current URL fragment. If a route succeeds with a
 937    // match, returns `true`. If no defined routes matches the fragment,
 938    // returns `false`.
 939    loadUrl: function(fragmentOverride) {
 940      var fragment = this.fragment = this.getFragment(fragmentOverride);
 941      var matched = _.any(this.handlers, function(handler) {
 942        if (handler.route.test(fragment)) {
 943          handler.callback(fragment);
 944          return true;
 945        }
 946      });
 947      return matched;
 948    },
 949
 950    // Save a fragment into the hash history, or replace the URL state if the
 951    // 'replace' option is passed. You are responsible for properly URL-encoding
 952    // the fragment in advance.
 953    //
 954    // The options object can contain `trigger: true` if you wish to have the
 955    // route callback be fired (not usually desirable), or `replace: true`, if
 956    // you which to modify the current URL without adding an entry to the history.
 957    navigate: function(fragment, options) {
 958      if (!historyStarted) return false;
 959      if (!options || options === true) options = {trigger: options};
 960      var frag = (fragment || '').replace(routeStripper, '');
 961      if (this.fragment == frag || this.fragment == decodeURIComponent(frag)) return;
 962
 963      // If pushState is available, we use it to set the fragment as a real URL.
 964      if (this._hasPushState) {
 965        if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
 966        this.fragment = frag;
 967        window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag);
 968
 969      // If hash changes haven't been explicitly disabled, update the hash
 970      // fragment to store history.
 971      } else if (this._wantsHashChange) {
 972        this.fragment = frag;
 973        this._updateHash(window.location, frag, options.replace);
 974        if (this.iframe && (frag != this.getFragment(this.iframe.location.hash))) {
 975          // Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
 976          // When replace is true, we don't want this.
 977          if(!options.replace) this.iframe.document.open().close();
 978          this._updateHash(this.iframe.location, frag, options.replace);
 979        }
 980
 981      // If you've told us that you explicitly don't want fallback hashchange-
 982      // based history, then `navigate` becomes a page refresh.
 983      } else {
 984        window.location.assign(this.options.root + fragment);
 985      }
 986      if (options.trigger) this.loadUrl(fragment);
 987    },
 988
 989    // Update the hash location, either replacing the current entry, or adding
 990    // a new one to the browser history.
 991    _updateHash: function(location, fragment, replace) {
 992      if (replace) {
 993        location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment);
 994      } else {
 995        location.hash = fragment;
 996      }
 997    }
 998  });
 999
1000  // Backbone.View
1001  // -------------
1002
1003  // Creating a Backbone.View creates its initial element outside of the DOM,
1004  // if an existing element is not provided...
1005  Backbone.View = function(options) {
1006    this.cid = _.uniqueId('view');
1007    this._configure(options || {});
1008    this._ensureElement();
1009    this.initialize.apply(this, arguments);
1010    this.delegateEvents();
1011  };
1012
1013  // Cached regex to split keys for `delegate`.
1014  var eventSplitter = /^(\S+)\s*(.*)$/;
1015
1016  // List of view options to be merged as properties.
1017  var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
1018
1019  // Set up all inheritable **Backbone.View** properties and methods.
1020  _.extend(Backbone.View.prototype, Backbone.Events, {
1021
1022    // The default `tagName` of a View's element is `"div"`.
1023    tagName: 'div',
1024
1025    // jQuery delegate for element lookup, scoped to DOM elements within the
1026    // current view. This should be prefered to global lookups where possible.
1027    $: function(selector) {
1028      return this.$el.find(selector);
1029    },
1030
1031    // Initialize is an empty function by default. Override it with your own
1032    // initialization logic.
1033    initialize: function(){},
1034
1035    // **render** is the core function that your view should override, in order
1036    // to populate its element (`this.el`), with the appropriate HTML. The
1037    // convention is for **render** to always return `this`.
1038    render: function() {
1039      return this;
1040    },
1041
1042    // Remove this view from the DOM. Note that the view isn't present in the
1043    // DOM by default, so calling this method may be a no-op.
1044    remove: function() {
1045      this.$el.remove();
1046      return this;
1047    },
1048
1049    // For small amounts of DOM Elements, where a full-blown template isn't
1050    // needed, use **make** to manufacture elements, one at a time.
1051    //
1052    //     var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
1053    //
1054    make: function(tagName, attributes, content) {
1055      var el = document.createElement(tagName);
1056      if (attributes) $(el).attr(attributes);
1057      if (content) $(el).html(content);
1058      return el;
1059    },
1060
1061    // Change the view's element (`this.el` property), including event
1062    // re-delegation.
1063    setElement: function(element, delegate) {
1064      this.$el = $(element);
1065      this.el = this.$el[0];
1066      if (delegate !== false) this.delegateEvents();
1067      return this;
1068    },
1069
1070    // Set callbacks, where `this.events` is a hash of
1071    //
1072    // *{"event selector": "callback"}*
1073    //
1074    //     {
1075    //       'mousedown .title':  'edit',
1076    //       'click .button':     'save'
1077    //       'click .open':       function(e) { ... }
1078    //     }
1079    //
1080    // pairs. Callbacks will be bound to the view, with `this` set properly.
1081    // Uses event delegation for efficiency.
1082    // Omitting the selector binds the event to `this.el`.
1083    // This only works for delegate-able events: not `focus`, `blur`, and
1084    // not `change`, `submit`, and `reset` in Internet Explorer.
1085    delegateEvents: function(events) {
1086      if (!(events || (events = getValue(this, 'events')))) return;
1087      this.undelegateEvents();
1088      for (var key in events) {
1089        var method = events[key];
1090        if (!_.isFunction(method)) method = this[events[key]];
1091        if (!method) throw new Error('Event "' + events[key] + '" does not exist');
1092        var match = key.match(eventSplitter);
1093        var eventName = match[1], selector = match[2];
1094        method = _.bind(method, this);
1095        eventName += '.delegateEvents' + this.cid;
1096        if (selector === '') {
1097          this.$el.bind(eventName, method);
1098        } else {
1099          this.$el.delegate(selector, eventName, method);
1100        }
1101      }
1102    },
1103
1104    // Clears all callbacks previously bound to the view with `delegateEvents`.
1105    // You usually don't need to use this, but may wish to if you have multiple
1106    // Backbone views attached to the same DOM element.
1107    undelegateEvents: function() {
1108      this.$el.unbind('.delegateEvents' + this.cid);
1109    },
1110
1111    // Performs the initial configuration of a View with a set of options.
1112    // Keys with special meaning *(model, collection, id, className)*, are
1113    // attached directly to the view.
1114    _configure: function(options) {
1115      if (this.options) options = _.extend({}, this.options, options);
1116      for (var i = 0, l = viewOptions.length; i < l; i++) {
1117        var attr = viewOptions[i];
1118        if (options[attr]) this[attr] = options[attr];
1119      }
1120      this.options = options;
1121    },
1122
1123    // Ensure that the View has a DOM element to render into.
1124    // If `this.el` is a string, pass it through `$()`, take the first
1125    // matching element, and re-assign it to `el`. Otherwise, create
1126    // an element from the `id`, `className` and `tagName` properties.
1127    _ensureElement: function() {
1128      if (!this.el) {
1129        var attrs = getValue(this, 'attributes') || {};
1130        if (this.id) attrs.id = this.id;
1131        if (this.className) attrs['class'] = this.className;
1132        this.setElement(this.make(this.tagName, attrs), false);
1133      } else {
1134        this.setElement(this.el, false);
1135      }
1136    }
1137
1138  });
1139
1140  // The self-propagating extend function that Backbone classes use.
1141  var extend = function (protoProps, classProps) {
1142    var child = inherits(this, protoProps, classProps);
1143    child.extend = this.extend;
1144    return child;
1145  };
1146
1147  // Set up inheritance for the model, collection, and view.
1148  Backbone.Model.extend = Backbone.Collection.extend =
1149    Backbone.Router.extend = Backbone.View.extend = extend;
1150
1151  // Backbone.sync
1152  // -------------
1153
1154  // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1155  var methodMap = {
1156    'create': 'POST',
1157    'update': 'PUT',
1158    'delete': 'DELETE',
1159    'read':   'GET'
1160  };
1161
1162  // Override this function to change the manner in which Backbone persists
1163  // models to the server. You will be passed the type of request, and the
1164  // model in question. By default, makes a RESTful Ajax request
1165  // to the model's `url()`. Some possible customizations could be:
1166  //
1167  // * Use `setTimeout` to batch rapid-fire updates into a single request.
1168  // * Send up the models as XML instead of JSON.
1169  // * Persist models via WebSockets instead of Ajax.
1170  //
1171  // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
1172  // as `POST`, with a `_method` parameter containing the true HTTP method,
1173  // as well as all requests with the body as `application/x-www-form-urlencoded`
1174  // instead of `application/json` with the model in a param named `model`.
1175  // Useful when interfacing with server-side languages like **PHP** that make
1176  // it difficult to read the body of `PUT` requests.
1177  Backbone.sync = function(method, model, options) {
1178    var type = methodMap[method];
1179
1180    // Default JSON-request options.
1181    var params = {type: type, dataType: 'json'};
1182
1183    // Ensure that we have a URL.
1184    if (!options.url) {
1185      params.url = getValue(model, 'url') || urlError();
1186    }
1187
1188    // Ensure that we have the appropriate request data.
1189    if (!options.data && model && (method == 'create' || method == 'update')) {
1190      params.contentType = 'application/json';
1191      params.data = JSON.stringify(model.toJSON());
1192    }
1193
1194    // For older servers, emulate JSON by encoding the request into an HTML-form.
1195    if (Backbone.emulateJSON) {
1196      params.contentType = 'application/x-www-form-urlencoded';
1197      params.data = params.data ? {model: params.data} : {};
1198    }
1199
1200    // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1201    // And an `X-HTTP-Method-Override` header.
1202    if (Backbone.emulateHTTP) {
1203      if (type === 'PUT' || type === 'DELETE') {
1204        if (Backbone.emulateJSON) params.data._method = type;
1205        params.type = 'POST';
1206        params.beforeSend = function(xhr) {
1207          xhr.setRequestHeader('X-HTTP-Method-Override', type);
1208        };
1209      }
1210    }
1211
1212    // Don't process data on a non-GET request.
1213    if (params.type !== 'GET' && !Backbone.emulateJSON) {
1214      params.processData = false;
1215    }
1216
1217    // Make the request, allowing the user to override any Ajax options.
1218    return $.ajax(_.extend(params, options));
1219  };
1220
1221  // Wrap an optional error callback with a fallback error event.
1222  Backbone.wrapError = function(onError, originalModel, options) {
1223    return function(model, resp) {
1224      resp = model === originalModel ? resp : model;
1225      if (onError) {
1226        onError(originalModel, resp, options);
1227      } else {
1228        originalModel.trigger('error', originalModel, resp, options);
1229      }
1230    };
1231  };
1232
1233  // Helpers
1234  // -------
1235
1236  // Shared empty constructor function to aid in prototype-chain creation.
1237  var ctor = function(){};
1238
1239  // Helper function to correctly set up the prototype chain, for subclasses.
1240  // Similar to `goog.inherits`, but uses a hash of prototype properties and
1241  // class properties to be extended.
1242  var inherits = function(parent, protoProps, staticProps) {
1243    var child;
1244
1245    // The constructor function for the new subclass is either defined by you
1246    // (the "constructor" property in your `extend` definition), or defaulted
1247    // by us to simply call the parent's constructor.
1248    if (protoProps && protoProps.hasOwnProperty('constructor')) {
1249      child = protoProps.constructor;
1250    } else {
1251      child = function(){ parent.apply(this, arguments); };
1252    }
1253
1254    // Inherit class (static) properties from parent.
1255    _.extend(child, parent);
1256
1257    // Set the prototype chain to inherit from `parent`, without calling
1258    // `parent`'s constructor function.
1259    ctor.prototype = parent.prototype;
1260    child.prototype = new ctor();
1261
1262    // Add prototype properties (instance properties) to the subclass,
1263    // if supplied.
1264    if (protoProps) _.extend(child.prototype, protoProps);
1265
1266    // Add static properties to the constructor function, if supplied.
1267    if (staticProps) _.extend(child, staticProps);
1268
1269    // Correctly set child's `prototype.constructor`.
1270    child.prototype.constructor = child;
1271
1272    // Set a convenience property in case the parent's prototype is needed later.
1273    child.__super__ = parent.prototype;
1274
1275    return child;
1276  };
1277
1278  // Helper function to get a value from a Backbone object as a property
1279  // or as a function.
1280  var getValue = function(object, prop) {
1281    if (!(object && object[prop])) return null;
1282    return _.isFunction(object[prop]) ? object[prop]() : object[prop];
1283  };
1284
1285  // Throw an error when a URL is needed, and none is supplied.
1286  var urlError = function() {
1287    throw new Error('A "url" property or function must be specified');
1288  };
1289
1290}).call(this);