PageRenderTime 202ms CodeModel.GetById 67ms app.highlight 96ms RepoModel.GetById 21ms app.codeStats 1ms

/public/javascripts/vendor/backbone.js

https://bitbucket.org/asheriff/bbclonemail-poc
JavaScript | 1256 lines | 761 code | 161 blank | 334 comment | 275 complexity | 4bab67bdca5ddba622caac126a553626 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 (var attr in attrs) attrs[attr] = void 0;
 227
 228      // Run validation.
 229      if (this.validate && !this._performValidation(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 ? '_performValidation' : '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    _performValidation: function(attrs, options) {
 424      var newAttrs = _.extend({}, this.attributes, attrs);
 425      var error = this.validate(newAttrs, options);
 426      if (error) {
 427        if (options.error) {
 428          options.error(this, error, options);
 429        } else {
 430          this.trigger('error', this, error, options);
 431        }
 432        return false;
 433      }
 434      return true;
 435    }
 436
 437  });
 438
 439  // Backbone.Collection
 440  // -------------------
 441
 442  // Provides a standard collection class for our sets of models, ordered
 443  // or unordered. If a `comparator` is specified, the Collection will maintain
 444  // its models in sort order, as they're added and removed.
 445  Backbone.Collection = function(models, options) {
 446    options || (options = {});
 447    if (options.comparator) this.comparator = options.comparator;
 448    this._reset();
 449    this.initialize.apply(this, arguments);
 450    if (models) this.reset(models, {silent: true, parse: options.parse});
 451  };
 452
 453  // Define the Collection's inheritable methods.
 454  _.extend(Backbone.Collection.prototype, Backbone.Events, {
 455
 456    // The default model for a collection is just a **Backbone.Model**.
 457    // This should be overridden in most cases.
 458    model: Backbone.Model,
 459
 460    // Initialize is an empty function by default. Override it with your own
 461    // initialization logic.
 462    initialize: function(){},
 463
 464    // The JSON representation of a Collection is an array of the
 465    // models' attributes.
 466    toJSON: function() {
 467      return this.map(function(model){ return model.toJSON(); });
 468    },
 469
 470    // Add a model, or list of models to the set. Pass **silent** to avoid
 471    // firing the `add` event for every new model.
 472    add: function(models, options) {
 473      var i, index, length, model, cid, id, cids = {}, ids = {};
 474      options || (options = {});
 475      models = _.isArray(models) ? models.slice() : [models];
 476
 477      // Begin by turning bare objects into model references, and preventing
 478      // invalid models or duplicate models from being added.
 479      for (i = 0, length = models.length; i < length; i++) {
 480        if (!(model = models[i] = this._prepareModel(models[i], options))) {
 481          throw new Error("Can't add an invalid model to a collection");
 482        }
 483        if (cids[cid = model.cid] || this._byCid[cid] ||
 484          (((id = model.id) != null) && (ids[id] || this._byId[id]))) {
 485          throw new Error("Can't add the same model to a collection twice");
 486        }
 487        cids[cid] = ids[id] = model;
 488      }
 489
 490      // Listen to added models' events, and index models for lookup by
 491      // `id` and by `cid`.
 492      for (i = 0; i < length; i++) {
 493        (model = models[i]).on('all', this._onModelEvent, this);
 494        this._byCid[model.cid] = model;
 495        if (model.id != null) this._byId[model.id] = model;
 496      }
 497
 498      // Insert models into the collection, re-sorting if needed, and triggering
 499      // `add` events unless silenced.
 500      this.length += length;
 501      index = options.at != null ? options.at : this.models.length;
 502      splice.apply(this.models, [index, 0].concat(models));
 503      if (this.comparator) this.sort({silent: true});
 504      if (options.silent) return this;
 505      for (i = 0, length = this.models.length; i < length; i++) {
 506        if (!cids[(model = this.models[i]).cid]) continue;
 507        options.index = i;
 508        model.trigger('add', model, this, options);
 509      }
 510      return this;
 511    },
 512
 513    // Remove a model, or a list of models from the set. Pass silent to avoid
 514    // firing the `remove` event for every model removed.
 515    remove: function(models, options) {
 516      var i, l, index, model;
 517      options || (options = {});
 518      models = _.isArray(models) ? models.slice() : [models];
 519      for (i = 0, l = models.length; i < l; i++) {
 520        model = this.getByCid(models[i]) || this.get(models[i]);
 521        if (!model) continue;
 522        delete this._byId[model.id];
 523        delete this._byCid[model.cid];
 524        index = this.indexOf(model);
 525        this.models.splice(index, 1);
 526        this.length--;
 527        if (!options.silent) {
 528          options.index = index;
 529          model.trigger('remove', model, this, options);
 530        }
 531        this._removeReference(model);
 532      }
 533      return this;
 534    },
 535
 536    // Get a model from the set by id.
 537    get: function(id) {
 538      if (id == null) return null;
 539      return this._byId[id.id != null ? id.id : id];
 540    },
 541
 542    // Get a model from the set by client id.
 543    getByCid: function(cid) {
 544      return cid && this._byCid[cid.cid || cid];
 545    },
 546
 547    // Get the model at the given index.
 548    at: function(index) {
 549      return this.models[index];
 550    },
 551
 552    // Force the collection to re-sort itself. You don't need to call this under
 553    // normal circumstances, as the set will maintain sort order as each item
 554    // is added.
 555    sort: function(options) {
 556      options || (options = {});
 557      if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
 558      var boundComparator = _.bind(this.comparator, this);
 559      if (this.comparator.length == 1) {
 560        this.models = this.sortBy(boundComparator);
 561      } else {
 562        this.models.sort(boundComparator);
 563      }
 564      if (!options.silent) this.trigger('reset', this, options);
 565      return this;
 566    },
 567
 568    // Pluck an attribute from each model in the collection.
 569    pluck: function(attr) {
 570      return _.map(this.models, function(model){ return model.get(attr); });
 571    },
 572
 573    // When you have more items than you want to add or remove individually,
 574    // you can reset the entire set with a new list of models, without firing
 575    // any `add` or `remove` events. Fires `reset` when finished.
 576    reset: function(models, options) {
 577      models  || (models = []);
 578      options || (options = {});
 579      for (var i = 0, l = this.models.length; i < l; i++) {
 580        this._removeReference(this.models[i]);
 581      }
 582      this._reset();
 583      this.add(models, {silent: true, parse: options.parse});
 584      if (!options.silent) this.trigger('reset', this, options);
 585      return this;
 586    },
 587
 588    // Fetch the default set of models for this collection, resetting the
 589    // collection when they arrive. If `add: true` is passed, appends the
 590    // models to the collection instead of resetting.
 591    fetch: function(options) {
 592      options = options ? _.clone(options) : {};
 593      if (options.parse === undefined) options.parse = true;
 594      var collection = this;
 595      var success = options.success;
 596      options.success = function(resp, status, xhr) {
 597        collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
 598        if (success) success(collection, resp);
 599      };
 600      options.error = Backbone.wrapError(options.error, collection, options);
 601      return (this.sync || Backbone.sync).call(this, 'read', this, options);
 602    },
 603
 604    // Create a new instance of a model in this collection. Add the model to the
 605    // collection immediately, unless `wait: true` is passed, in which case we
 606    // wait for the server to agree.
 607    create: function(model, options) {
 608      var coll = this;
 609      options = options ? _.clone(options) : {};
 610      model = this._prepareModel(model, options);
 611      if (!model) return false;
 612      if (!options.wait) coll.add(model, options);
 613      var success = options.success;
 614      options.success = function(nextModel, resp, xhr) {
 615        if (options.wait) coll.add(nextModel, options);
 616        if (success) {
 617          success(nextModel, resp);
 618        } else {
 619          nextModel.trigger('sync', model, resp, options);
 620        }
 621      };
 622      model.save(null, options);
 623      return model;
 624    },
 625
 626    // **parse** converts a response into a list of models to be added to the
 627    // collection. The default implementation is just to pass it through.
 628    parse: function(resp, xhr) {
 629      return resp;
 630    },
 631
 632    // Proxy to _'s chain. Can't be proxied the same way the rest of the
 633    // underscore methods are proxied because it relies on the underscore
 634    // constructor.
 635    chain: function () {
 636      return _(this.models).chain();
 637    },
 638
 639    // Reset all internal state. Called when the collection is reset.
 640    _reset: function(options) {
 641      this.length = 0;
 642      this.models = [];
 643      this._byId  = {};
 644      this._byCid = {};
 645    },
 646
 647    // Prepare a model or hash of attributes to be added to this collection.
 648    _prepareModel: function(model, options) {
 649      if (!(model instanceof Backbone.Model)) {
 650        var attrs = model;
 651        options.collection = this;
 652        model = new this.model(attrs, options);
 653        if (model.validate && !model._performValidation(model.attributes, options)) model = false;
 654      } else if (!model.collection) {
 655        model.collection = this;
 656      }
 657      return model;
 658    },
 659
 660    // Internal method to remove a model's ties to a collection.
 661    _removeReference: function(model) {
 662      if (this == model.collection) {
 663        delete model.collection;
 664      }
 665      model.off('all', this._onModelEvent, this);
 666    },
 667
 668    // Internal method called every time a model in the set fires an event.
 669    // Sets need to update their indexes when models change ids. All other
 670    // events simply proxy through. "add" and "remove" events that originate
 671    // in other collections are ignored.
 672    _onModelEvent: function(ev, model, collection, options) {
 673      if ((ev == 'add' || ev == 'remove') && collection != this) return;
 674      if (ev == 'destroy') {
 675        this.remove(model, options);
 676      }
 677      if (model && ev === 'change:' + model.idAttribute) {
 678        delete this._byId[model.previous(model.idAttribute)];
 679        this._byId[model.id] = model;
 680      }
 681      this.trigger.apply(this, arguments);
 682    }
 683
 684  });
 685
 686  // Underscore methods that we want to implement on the Collection.
 687  var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
 688    'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
 689    'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
 690    'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
 691    'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
 692
 693  // Mix in each Underscore method as a proxy to `Collection#models`.
 694  _.each(methods, function(method) {
 695    Backbone.Collection.prototype[method] = function() {
 696      return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
 697    };
 698  });
 699
 700  // Backbone.Router
 701  // -------------------
 702
 703  // Routers map faux-URLs to actions, and fire events when routes are
 704  // matched. Creating a new one sets its `routes` hash, if not set statically.
 705  Backbone.Router = function(options) {
 706    options || (options = {});
 707    if (options.routes) this.routes = options.routes;
 708    this._bindRoutes();
 709    this.initialize.apply(this, arguments);
 710  };
 711
 712  // Cached regular expressions for matching named param parts and splatted
 713  // parts of route strings.
 714  var namedParam    = /:\w+/g;
 715  var splatParam    = /\*\w+/g;
 716  var escapeRegExp  = /[-[\]{}()+?.,\\^$|#\s]/g;
 717
 718  // Set up all inheritable **Backbone.Router** properties and methods.
 719  _.extend(Backbone.Router.prototype, Backbone.Events, {
 720
 721    // Initialize is an empty function by default. Override it with your own
 722    // initialization logic.
 723    initialize: function(){},
 724
 725    // Manually bind a single named route to a callback. For example:
 726    //
 727    //     this.route('search/:query/p:num', 'search', function(query, num) {
 728    //       ...
 729    //     });
 730    //
 731    route: function(route, name, callback) {
 732      Backbone.history || (Backbone.history = new Backbone.History);
 733      if (!_.isRegExp(route)) route = this._routeToRegExp(route);
 734      if (!callback) callback = this[name];
 735      Backbone.history.route(route, _.bind(function(fragment) {
 736        var args = this._extractParameters(route, fragment);
 737        callback && callback.apply(this, args);
 738        this.trigger.apply(this, ['route:' + name].concat(args));
 739        Backbone.history.trigger('route', this, name, args);
 740      }, this));
 741      return this;
 742    },
 743
 744    // Simple proxy to `Backbone.history` to save a fragment into the history.
 745    navigate: function(fragment, options) {
 746      Backbone.history.navigate(fragment, options);
 747    },
 748
 749    // Bind all defined routes to `Backbone.history`. We have to reverse the
 750    // order of the routes here to support behavior where the most general
 751    // routes can be defined at the bottom of the route map.
 752    _bindRoutes: function() {
 753      if (!this.routes) return;
 754      var routes = [];
 755      for (var route in this.routes) {
 756        routes.unshift([route, this.routes[route]]);
 757      }
 758      for (var i = 0, l = routes.length; i < l; i++) {
 759        this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
 760      }
 761    },
 762
 763    // Convert a route string into a regular expression, suitable for matching
 764    // against the current location hash.
 765    _routeToRegExp: function(route) {
 766      route = route.replace(escapeRegExp, '\\$&')
 767                   .replace(namedParam, '([^\/]+)')
 768                   .replace(splatParam, '(.*?)');
 769      return new RegExp('^' + route + '$');
 770    },
 771
 772    // Given a route, and a URL fragment that it matches, return the array of
 773    // extracted parameters.
 774    _extractParameters: function(route, fragment) {
 775      return route.exec(fragment).slice(1);
 776    }
 777
 778  });
 779
 780  // Backbone.History
 781  // ----------------
 782
 783  // Handles cross-browser history management, based on URL fragments. If the
 784  // browser does not support `onhashchange`, falls back to polling.
 785  Backbone.History = function() {
 786    this.handlers = [];
 787    _.bindAll(this, 'checkUrl');
 788  };
 789
 790  // Cached regex for cleaning leading hashes and slashes .
 791  var routeStripper = /^[#\/]/;
 792
 793  // Cached regex for detecting MSIE.
 794  var isExplorer = /msie [\w.]+/;
 795
 796  // Has the history handling already been started?
 797  var historyStarted = false;
 798
 799  // Set up all inheritable **Backbone.History** properties and methods.
 800  _.extend(Backbone.History.prototype, Backbone.Events, {
 801
 802    // The default interval to poll for hash changes, if necessary, is
 803    // twenty times a second.
 804    interval: 50,
 805
 806    // Get the cross-browser normalized URL fragment, either from the URL,
 807    // the hash, or the override.
 808    getFragment: function(fragment, forcePushState) {
 809      if (fragment == null) {
 810        if (this._hasPushState || forcePushState) {
 811          fragment = window.location.pathname;
 812          var search = window.location.search;
 813          if (search) fragment += search;
 814        } else {
 815          fragment = window.location.hash;
 816        }
 817      }
 818      fragment = decodeURIComponent(fragment.replace(routeStripper, ''));
 819      if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
 820      return fragment;
 821    },
 822
 823    // Start the hash change handling, returning `true` if the current URL matches
 824    // an existing route, and `false` otherwise.
 825    start: function(options) {
 826
 827      // Figure out the initial configuration. Do we need an iframe?
 828      // Is pushState desired ... is it available?
 829      if (historyStarted) throw new Error("Backbone.history has already been started");
 830      this.options          = _.extend({}, {root: '/'}, this.options, options);
 831      this._wantsHashChange = this.options.hashChange !== false;
 832      this._wantsPushState  = !!this.options.pushState;
 833      this._hasPushState    = !!(this.options.pushState && window.history && window.history.pushState);
 834      var fragment          = this.getFragment();
 835      var docMode           = document.documentMode;
 836      var oldIE             = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
 837      if (oldIE) {
 838        this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
 839        this.navigate(fragment);
 840      }
 841
 842      // Depending on whether we're using pushState or hashes, and whether
 843      // 'onhashchange' is supported, determine how we check the URL state.
 844      if (this._hasPushState) {
 845        $(window).bind('popstate', this.checkUrl);
 846      } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
 847        $(window).bind('hashchange', this.checkUrl);
 848      } else if (this._wantsHashChange) {
 849        this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
 850      }
 851
 852      // Determine if we need to change the base url, for a pushState link
 853      // opened by a non-pushState browser.
 854      this.fragment = fragment;
 855      historyStarted = true;
 856      var loc = window.location;
 857      var atRoot  = loc.pathname == this.options.root;
 858
 859      // If we've started off with a route from a `pushState`-enabled browser,
 860      // but we're currently in a browser that doesn't support it...
 861      if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
 862        this.fragment = this.getFragment(null, true);
 863        window.location.replace(this.options.root + '#' + this.fragment);
 864        // Return immediately as browser will do redirect to new url
 865        return true;
 866
 867      // Or if we've started out with a hash-based route, but we're currently
 868      // in a browser where it could be `pushState`-based instead...
 869      } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
 870        this.fragment = loc.hash.replace(routeStripper, '');
 871        window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
 872      }
 873
 874      if (!this.options.silent) {
 875        return this.loadUrl();
 876      }
 877    },
 878
 879    // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
 880    // but possibly useful for unit testing Routers.
 881    stop: function() {
 882      $(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
 883      clearInterval(this._checkUrlInterval);
 884      historyStarted = false;
 885    },
 886
 887    // Add a route to be tested when the fragment changes. Routes added later
 888    // may override previous routes.
 889    route: function(route, callback) {
 890      this.handlers.unshift({route: route, callback: callback});
 891    },
 892
 893    // Checks the current URL to see if it has changed, and if it has,
 894    // calls `loadUrl`, normalizing across the hidden iframe.
 895    checkUrl: function(e) {
 896      var current = this.getFragment();
 897      if (current == this.fragment && this.iframe) current = this.getFragment(this.iframe.location.hash);
 898      if (current == this.fragment || current == decodeURIComponent(this.fragment)) return false;
 899      if (this.iframe) this.navigate(current);
 900      this.loadUrl() || this.loadUrl(window.location.hash);
 901    },
 902
 903    // Attempt to load the current URL fragment. If a route succeeds with a
 904    // match, returns `true`. If no defined routes matches the fragment,
 905    // returns `false`.
 906    loadUrl: function(fragmentOverride) {
 907      var fragment = this.fragment = this.getFragment(fragmentOverride);
 908      var matched = _.any(this.handlers, function(handler) {
 909        if (handler.route.test(fragment)) {
 910          handler.callback(fragment);
 911          return true;
 912        }
 913      });
 914      return matched;
 915    },
 916
 917    // Save a fragment into the hash history, or replace the URL state if the
 918    // 'replace' option is passed. You are responsible for properly URL-encoding
 919    // the fragment in advance.
 920    //
 921    // The options object can contain `trigger: true` if you wish to have the
 922    // route callback be fired (not usually desirable), or `replace: true`, if
 923    // you which to modify the current URL without adding an entry to the history.
 924    navigate: function(fragment, options) {
 925      if (!historyStarted) return false;
 926      if (!options || options === true) options = {trigger: options};
 927      var frag = (fragment || '').replace(routeStripper, '');
 928      if (this.fragment == frag || this.fragment == decodeURIComponent(frag)) return;
 929
 930      // If pushState is available, we use it to set the fragment as a real URL.
 931      if (this._hasPushState) {
 932        if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
 933        this.fragment = frag;
 934        window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag);
 935
 936      // If hash changes haven't been explicitly disabled, update the hash
 937      // fragment to store history.
 938      } else if (this._wantsHashChange) {
 939        this.fragment = frag;
 940        this._updateHash(window.location, frag, options.replace);
 941        if (this.iframe && (frag != this.getFragment(this.iframe.location.hash))) {
 942          // Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
 943          // When replace is true, we don't want this.
 944          if(!options.replace) this.iframe.document.open().close();
 945          this._updateHash(this.iframe.location, frag, options.replace);
 946        }
 947
 948      // If you've told us that you explicitly don't want fallback hashchange-
 949      // based history, then `navigate` becomes a page refresh.
 950      } else {
 951        window.location.assign(this.options.root + fragment);
 952      }
 953      if (options.trigger) this.loadUrl(fragment);
 954    },
 955
 956    // Update the hash location, either replacing the current entry, or adding
 957    // a new one to the browser history.
 958    _updateHash: function(location, fragment, replace) {
 959      if (replace) {
 960        location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment);
 961      } else {
 962        location.hash = fragment;
 963      }
 964    }
 965  });
 966
 967  // Backbone.View
 968  // -------------
 969
 970  // Creating a Backbone.View creates its initial element outside of the DOM,
 971  // if an existing element is not provided...
 972  Backbone.View = function(options) {
 973    this.cid = _.uniqueId('view');
 974    this._configure(options || {});
 975    this._ensureElement();
 976    this.initialize.apply(this, arguments);
 977    this.delegateEvents();
 978  };
 979
 980  // Cached regex to split keys for `delegate`.
 981  var eventSplitter = /^(\S+)\s*(.*)$/;
 982
 983  // List of view options to be merged as properties.
 984  var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
 985
 986  // Set up all inheritable **Backbone.View** properties and methods.
 987  _.extend(Backbone.View.prototype, Backbone.Events, {
 988
 989    // The default `tagName` of a View's element is `"div"`.
 990    tagName: 'div',
 991
 992    // jQuery delegate for element lookup, scoped to DOM elements within the
 993    // current view. This should be prefered to global lookups where possible.
 994    $: function(selector) {
 995      return this.$el.find(selector);
 996    },
 997
 998    // Initialize is an empty function by default. Override it with your own
 999    // initialization logic.
1000    initialize: function(){},
1001
1002    // **render** is the core function that your view should override, in order
1003    // to populate its element (`this.el`), with the appropriate HTML. The
1004    // convention is for **render** to always return `this`.
1005    render: function() {
1006      return this;
1007    },
1008
1009    // Remove this view from the DOM. Note that the view isn't present in the
1010    // DOM by default, so calling this method may be a no-op.
1011    remove: function() {
1012      this.$el.remove();
1013      return this;
1014    },
1015
1016    // For small amounts of DOM Elements, where a full-blown template isn't
1017    // needed, use **make** to manufacture elements, one at a time.
1018    //
1019    //     var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
1020    //
1021    make: function(tagName, attributes, content) {
1022      var el = document.createElement(tagName);
1023      if (attributes) $(el).attr(attributes);
1024      if (content) $(el).html(content);
1025      return el;
1026    },
1027
1028    // Change the view's element (`this.el` property), including event
1029    // re-delegation.
1030    setElement: function(element, delegate) {
1031      this.$el = $(element);
1032      this.el = this.$el[0];
1033      if (delegate !== false) this.delegateEvents();
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      var 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);