PageRenderTime 185ms CodeModel.GetById 30ms app.highlight 100ms RepoModel.GetById 41ms app.codeStats 0ms

/lib/backbone.js

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