PageRenderTime 241ms CodeModel.GetById 60ms app.highlight 96ms RepoModel.GetById 65ms app.codeStats 1ms

/fb_twitter_login/client/js/lib/backbone.js

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