PageRenderTime 103ms CodeModel.GetById 4ms app.highlight 425ms RepoModel.GetById 2ms app.codeStats 2ms

/plugins/static/public/js/libs/backbone/backbone-0.9.1-amd.js

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