PageRenderTime 333ms CodeModel.GetById 81ms app.highlight 164ms RepoModel.GetById 37ms app.codeStats 1ms

/public/js/libs/backbone.js

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