PageRenderTime 359ms CodeModel.GetById 201ms app.highlight 111ms RepoModel.GetById 30ms app.codeStats 1ms

/learn.tddjs/code/test_deps/backbone.js

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