PageRenderTime 10ms CodeModel.GetById 3ms app.highlight 67ms RepoModel.GetById 1ms app.codeStats 0ms

/examples/todos/backbone.js

https://github.com/edmarriner/jQ.Mobi
JavaScript | 1318 lines | 804 code | 169 blank | 345 comment | 286 complexity | a47355f6fd6ce097d99e5be5ef7329e8 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

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

Large files files are truncated, but you can click here to view the full file