PageRenderTime 9ms CodeModel.GetById 2ms app.highlight 50ms RepoModel.GetById 1ms app.codeStats 1ms

/example/lib/backbone.js

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