PageRenderTime 16ms CodeModel.GetById 3ms app.highlight 93ms RepoModel.GetById 1ms app.codeStats 1ms

/option4_grails_mongo/web-app/backbone.js

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