PageRenderTime 206ms CodeModel.GetById 40ms app.highlight 131ms RepoModel.GetById 20ms app.codeStats 0ms

/src/PluginTestBed/Content/js/backbone-0.5.0.js

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