PageRenderTime 61ms CodeModel.GetById 2ms app.highlight 48ms RepoModel.GetById 1ms app.codeStats 0ms

/pancake-web/pancake/web/static/js/vendor/backbone.js

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