PageRenderTime 253ms CodeModel.GetById 155ms app.highlight 81ms RepoModel.GetById 1ms app.codeStats 1ms

/templates/public/scripts/ext/backbone.js

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