[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/js/ -> backbone.js (source)

   1  //     Backbone.js 1.3.3
   2  
   3  //     (c) 2010-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
   4  //     Backbone may be freely distributed under the MIT license.
   5  //     For all details and documentation:
   6  //     http://backbonejs.org
   7  
   8  (function(factory) {
   9  
  10    // Establish the root object, `window` (`self`) in the browser, or `global` on the server.
  11    // We use `self` instead of `window` for `WebWorker` support.
  12    var root = (typeof self == 'object' && self.self === self && self) ||
  13              (typeof global == 'object' && global.global === global && global);
  14  
  15    // Set up Backbone appropriately for the environment. Start with AMD.
  16    if (typeof define === 'function' && define.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  
  23    // Next for Node.js or CommonJS. jQuery may not be needed as a module.
  24    } else if (typeof exports !== 'undefined') {
  25      var _ = require('underscore'), $;
  26      try { $ = require('jquery'); } catch (e) {}
  27      factory(root, exports, _, $);
  28  
  29    // Finally, as a browser global.
  30    } else {
  31      root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$));
  32    }
  33  
  34  })(function(root, Backbone, _, $) {
  35  
  36    // Initial Setup
  37    // -------------
  38  
  39    // Save the previous value of the `Backbone` variable, so that it can be
  40    // restored later on, if `noConflict` is used.
  41    var previousBackbone = root.Backbone;
  42  
  43    // Create a local reference to a common array method we'll want to use later.
  44    var slice = Array.prototype.slice;
  45  
  46    // Current version of the library. Keep in sync with `package.json`.
  47    Backbone.VERSION = '1.3.3';
  48  
  49    // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
  50    // the `$` variable.
  51    Backbone.$ = $;
  52  
  53    // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
  54    // to its previous owner. Returns a reference to this Backbone object.
  55    Backbone.noConflict = function() {
  56      root.Backbone = previousBackbone;
  57      return this;
  58    };
  59  
  60    // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
  61    // will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and
  62    // set a `X-Http-Method-Override` header.
  63    Backbone.emulateHTTP = false;
  64  
  65    // Turn on `emulateJSON` to support legacy servers that can't deal with direct
  66    // `application/json` requests ... this will encode the body as
  67    // `application/x-www-form-urlencoded` instead and will send the model in a
  68    // form param named `model`.
  69    Backbone.emulateJSON = false;
  70  
  71    // Proxy Backbone class methods to Underscore functions, wrapping the model's
  72    // `attributes` object or collection's `models` array behind the scenes.
  73    //
  74    // collection.filter(function(model) { return model.get('age') > 10 });
  75    // collection.each(this.addView);
  76    //
  77    // `Function#apply` can be slow so we use the method's arg count, if we know it.
  78    var addMethod = function(length, method, attribute) {
  79      switch (length) {
  80        case 1: return function() {
  81          return _[method](this[attribute]);
  82        };
  83        case 2: return function(value) {
  84          return _[method](this[attribute], value);
  85        };
  86        case 3: return function(iteratee, context) {
  87          return _[method](this[attribute], cb(iteratee, this), context);
  88        };
  89        case 4: return function(iteratee, defaultVal, context) {
  90          return _[method](this[attribute], cb(iteratee, this), defaultVal, context);
  91        };
  92        default: return function() {
  93          var args = slice.call(arguments);
  94          args.unshift(this[attribute]);
  95          return _[method].apply(_, args);
  96        };
  97      }
  98    };
  99    var addUnderscoreMethods = function(Class, methods, attribute) {
 100      _.each(methods, function(length, method) {
 101        if (_[method]) Class.prototype[method] = addMethod(length, method, attribute);
 102      });
 103    };
 104  
 105    // Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`.
 106    var cb = function(iteratee, instance) {
 107      if (_.isFunction(iteratee)) return iteratee;
 108      if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee);
 109      if (_.isString(iteratee)) return function(model) { return model.get(iteratee); };
 110      return iteratee;
 111    };
 112    var modelMatcher = function(attrs) {
 113      var matcher = _.matches(attrs);
 114      return function(model) {
 115        return matcher(model.attributes);
 116      };
 117    };
 118  
 119    // Backbone.Events
 120    // ---------------
 121  
 122    // A module that can be mixed in to *any object* in order to provide it with
 123    // a custom event channel. You may bind a callback to an event with `on` or
 124    // remove with `off`; `trigger`-ing an event fires all callbacks in
 125    // succession.
 126    //
 127    //     var object = {};
 128    //     _.extend(object, Backbone.Events);
 129    //     object.on('expand', function(){ alert('expanded'); });
 130    //     object.trigger('expand');
 131    //
 132    var Events = Backbone.Events = {};
 133  
 134    // Regular expression used to split event strings.
 135    var eventSplitter = /\s+/;
 136  
 137    // Iterates over the standard `event, callback` (as well as the fancy multiple
 138    // space-separated events `"change blur", callback` and jQuery-style event
 139    // maps `{event: callback}`).
 140    var eventsApi = function(iteratee, events, name, callback, opts) {
 141      var i = 0, names;
 142      if (name && typeof name === 'object') {
 143        // Handle event maps.
 144        if (callback !== void 0 && 'context' in opts && opts.context === void 0) opts.context = callback;
 145        for (names = _.keys(name); i < names.length ; i++) {
 146          events = eventsApi(iteratee, events, names[i], name[names[i]], opts);
 147        }
 148      } else if (name && eventSplitter.test(name)) {
 149        // Handle space-separated event names by delegating them individually.
 150        for (names = name.split(eventSplitter); i < names.length; i++) {
 151          events = iteratee(events, names[i], callback, opts);
 152        }
 153      } else {
 154        // Finally, standard events.
 155        events = iteratee(events, name, callback, opts);
 156      }
 157      return events;
 158    };
 159  
 160    // Bind an event to a `callback` function. Passing `"all"` will bind
 161    // the callback to all events fired.
 162    Events.on = function(name, callback, context) {
 163      return internalOn(this, name, callback, context);
 164    };
 165  
 166    // Guard the `listening` argument from the public API.
 167    var internalOn = function(obj, name, callback, context, listening) {
 168      obj._events = eventsApi(onApi, obj._events || {}, name, callback, {
 169        context: context,
 170        ctx: obj,
 171        listening: listening
 172      });
 173  
 174      if (listening) {
 175        var listeners = obj._listeners || (obj._listeners = {});
 176        listeners[listening.id] = listening;
 177      }
 178  
 179      return obj;
 180    };
 181  
 182    // Inversion-of-control versions of `on`. Tell *this* object to listen to
 183    // an event in another object... keeping track of what it's listening to
 184    // for easier unbinding later.
 185    Events.listenTo = function(obj, name, callback) {
 186      if (!obj) return this;
 187      var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
 188      var listeningTo = this._listeningTo || (this._listeningTo = {});
 189      var listening = listeningTo[id];
 190  
 191      // This object is not listening to any other events on `obj` yet.
 192      // Setup the necessary references to track the listening callbacks.
 193      if (!listening) {
 194        var thisId = this._listenId || (this._listenId = _.uniqueId('l'));
 195        listening = listeningTo[id] = {obj: obj, objId: id, id: thisId, listeningTo: listeningTo, count: 0};
 196      }
 197  
 198      // Bind callbacks on obj, and keep track of them on listening.
 199      internalOn(obj, name, callback, this, listening);
 200      return this;
 201    };
 202  
 203    // The reducing API that adds a callback to the `events` object.
 204    var onApi = function(events, name, callback, options) {
 205      if (callback) {
 206        var handlers = events[name] || (events[name] = []);
 207        var context = options.context, ctx = options.ctx, listening = options.listening;
 208        if (listening) listening.count++;
 209  
 210        handlers.push({callback: callback, context: context, ctx: context || ctx, listening: listening});
 211      }
 212      return events;
 213    };
 214  
 215    // Remove one or many callbacks. If `context` is null, removes all
 216    // callbacks with that function. If `callback` is null, removes all
 217    // callbacks for the event. If `name` is null, removes all bound
 218    // callbacks for all events.
 219    Events.off = function(name, callback, context) {
 220      if (!this._events) return this;
 221      this._events = eventsApi(offApi, this._events, name, callback, {
 222        context: context,
 223        listeners: this._listeners
 224      });
 225      return this;
 226    };
 227  
 228    // Tell this object to stop listening to either specific events ... or
 229    // to every object it's currently listening to.
 230    Events.stopListening = function(obj, name, callback) {
 231      var listeningTo = this._listeningTo;
 232      if (!listeningTo) return this;
 233  
 234      var ids = obj ? [obj._listenId] : _.keys(listeningTo);
 235  
 236      for (var i = 0; i < ids.length; i++) {
 237        var listening = listeningTo[ids[i]];
 238  
 239        // If listening doesn't exist, this object is not currently
 240        // listening to obj. Break out early.
 241        if (!listening) break;
 242  
 243        listening.obj.off(name, callback, this);
 244      }
 245  
 246      return this;
 247    };
 248  
 249    // The reducing API that removes a callback from the `events` object.
 250    var offApi = function(events, name, callback, options) {
 251      if (!events) return;
 252  
 253      var i = 0, listening;
 254      var context = options.context, listeners = options.listeners;
 255  
 256      // Delete all events listeners and "drop" events.
 257      if (!name && !callback && !context) {
 258        var ids = _.keys(listeners);
 259        for (; i < ids.length; i++) {
 260          listening = listeners[ids[i]];
 261          delete listeners[listening.id];
 262          delete listening.listeningTo[listening.objId];
 263        }
 264        return;
 265      }
 266  
 267      var names = name ? [name] : _.keys(events);
 268      for (; i < names.length; i++) {
 269        name = names[i];
 270        var handlers = events[name];
 271  
 272        // Bail out if there are no events stored.
 273        if (!handlers) break;
 274  
 275        // Replace events if there are any remaining.  Otherwise, clean up.
 276        var remaining = [];
 277        for (var j = 0; j < handlers.length; j++) {
 278          var handler = handlers[j];
 279          if (
 280            callback && callback !== handler.callback &&
 281              callback !== handler.callback._callback ||
 282                context && context !== handler.context
 283          ) {
 284            remaining.push(handler);
 285          } else {
 286            listening = handler.listening;
 287            if (listening && --listening.count === 0) {
 288              delete listeners[listening.id];
 289              delete listening.listeningTo[listening.objId];
 290            }
 291          }
 292        }
 293  
 294        // Update tail event if the list has any events.  Otherwise, clean up.
 295        if (remaining.length) {
 296          events[name] = remaining;
 297        } else {
 298          delete events[name];
 299        }
 300      }
 301      return events;
 302    };
 303  
 304    // Bind an event to only be triggered a single time. After the first time
 305    // the callback is invoked, its listener will be removed. If multiple events
 306    // are passed in using the space-separated syntax, the handler will fire
 307    // once for each event, not once for a combination of all events.
 308    Events.once = function(name, callback, context) {
 309      // Map the event into a `{event: once}` object.
 310      var events = eventsApi(onceMap, {}, name, callback, _.bind(this.off, this));
 311      if (typeof name === 'string' && context == null) callback = void 0;
 312      return this.on(events, callback, context);
 313    };
 314  
 315    // Inversion-of-control versions of `once`.
 316    Events.listenToOnce = function(obj, name, callback) {
 317      // Map the event into a `{event: once}` object.
 318      var events = eventsApi(onceMap, {}, name, callback, _.bind(this.stopListening, this, obj));
 319      return this.listenTo(obj, events);
 320    };
 321  
 322    // Reduces the event callbacks into a map of `{event: onceWrapper}`.
 323    // `offer` unbinds the `onceWrapper` after it has been called.
 324    var onceMap = function(map, name, callback, offer) {
 325      if (callback) {
 326        var once = map[name] = _.once(function() {
 327          offer(name, once);
 328          callback.apply(this, arguments);
 329        });
 330        once._callback = callback;
 331      }
 332      return map;
 333    };
 334  
 335    // Trigger one or many events, firing all bound callbacks. Callbacks are
 336    // passed the same arguments as `trigger` is, apart from the event name
 337    // (unless you're listening on `"all"`, which will cause your callback to
 338    // receive the true name of the event as the first argument).
 339    Events.trigger = function(name) {
 340      if (!this._events) return this;
 341  
 342      var length = Math.max(0, arguments.length - 1);
 343      var args = Array(length);
 344      for (var i = 0; i < length; i++) args[i] = arguments[i + 1];
 345  
 346      eventsApi(triggerApi, this._events, name, void 0, args);
 347      return this;
 348    };
 349  
 350    // Handles triggering the appropriate event callbacks.
 351    var triggerApi = function(objEvents, name, callback, args) {
 352      if (objEvents) {
 353        var events = objEvents[name];
 354        var allEvents = objEvents.all;
 355        if (events && allEvents) allEvents = allEvents.slice();
 356        if (events) triggerEvents(events, args);
 357        if (allEvents) triggerEvents(allEvents, [name].concat(args));
 358      }
 359      return objEvents;
 360    };
 361  
 362    // A difficult-to-believe, but optimized internal dispatch function for
 363    // triggering events. Tries to keep the usual cases speedy (most internal
 364    // Backbone events have 3 arguments).
 365    var triggerEvents = function(events, args) {
 366      var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
 367      switch (args.length) {
 368        case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
 369        case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
 370        case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
 371        case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
 372        default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return;
 373      }
 374    };
 375  
 376    // Aliases for backwards compatibility.
 377    Events.bind   = Events.on;
 378    Events.unbind = Events.off;
 379  
 380    // Allow the `Backbone` object to serve as a global event bus, for folks who
 381    // want global "pubsub" in a convenient place.
 382    _.extend(Backbone, Events);
 383  
 384    // Backbone.Model
 385    // --------------
 386  
 387    // Backbone **Models** are the basic data object in the framework --
 388    // frequently representing a row in a table in a database on your server.
 389    // A discrete chunk of data and a bunch of useful, related methods for
 390    // performing computations and transformations on that data.
 391  
 392    // Create a new model with the specified attributes. A client id (`cid`)
 393    // is automatically generated and assigned for you.
 394    var Model = Backbone.Model = function(attributes, options) {
 395      var attrs = attributes || {};
 396      options || (options = {});
 397      this.cid = _.uniqueId(this.cidPrefix);
 398      this.attributes = {};
 399      if (options.collection) this.collection = options.collection;
 400      if (options.parse) attrs = this.parse(attrs, options) || {};
 401      var defaults = _.result(this, 'defaults');
 402      attrs = _.defaults(_.extend({}, defaults, attrs), defaults);
 403      this.set(attrs, options);
 404      this.changed = {};
 405      this.initialize.apply(this, arguments);
 406    };
 407  
 408    // Attach all inheritable methods to the Model prototype.
 409    _.extend(Model.prototype, Events, {
 410  
 411      // A hash of attributes whose current and previous value differ.
 412      changed: null,
 413  
 414      // The value returned during the last failed validation.
 415      validationError: null,
 416  
 417      // The default name for the JSON `id` attribute is `"id"`. MongoDB and
 418      // CouchDB users may want to set this to `"_id"`.
 419      idAttribute: 'id',
 420  
 421      // The prefix is used to create the client id which is used to identify models locally.
 422      // You may want to override this if you're experiencing name clashes with model ids.
 423      cidPrefix: 'c',
 424  
 425      // Initialize is an empty function by default. Override it with your own
 426      // initialization logic.
 427      initialize: function(){},
 428  
 429      // Return a copy of the model's `attributes` object.
 430      toJSON: function(options) {
 431        return _.clone(this.attributes);
 432      },
 433  
 434      // Proxy `Backbone.sync` by default -- but override this if you need
 435      // custom syncing semantics for *this* particular model.
 436      sync: function() {
 437        return Backbone.sync.apply(this, arguments);
 438      },
 439  
 440      // Get the value of an attribute.
 441      get: function(attr) {
 442        return this.attributes[attr];
 443      },
 444  
 445      // Get the HTML-escaped value of an attribute.
 446      escape: function(attr) {
 447        return _.escape(this.get(attr));
 448      },
 449  
 450      // Returns `true` if the attribute contains a value that is not null
 451      // or undefined.
 452      has: function(attr) {
 453        return this.get(attr) != null;
 454      },
 455  
 456      // Special-cased proxy to underscore's `_.matches` method.
 457      matches: function(attrs) {
 458        return !!_.iteratee(attrs, this)(this.attributes);
 459      },
 460  
 461      // Set a hash of model attributes on the object, firing `"change"`. This is
 462      // the core primitive operation of a model, updating the data and notifying
 463      // anyone who needs to know about the change in state. The heart of the beast.
 464      set: function(key, val, options) {
 465        if (key == null) return this;
 466  
 467        // Handle both `"key", value` and `{key: value}` -style arguments.
 468        var attrs;
 469        if (typeof key === 'object') {
 470          attrs = key;
 471          options = val;
 472        } else {
 473          (attrs = {})[key] = val;
 474        }
 475  
 476        options || (options = {});
 477  
 478        // Run validation.
 479        if (!this._validate(attrs, options)) return false;
 480  
 481        // Extract attributes and options.
 482        var unset      = options.unset;
 483        var silent     = options.silent;
 484        var changes    = [];
 485        var changing   = this._changing;
 486        this._changing = true;
 487  
 488        if (!changing) {
 489          this._previousAttributes = _.clone(this.attributes);
 490          this.changed = {};
 491        }
 492  
 493        var current = this.attributes;
 494        var changed = this.changed;
 495        var prev    = this._previousAttributes;
 496  
 497        // For each `set` attribute, update or delete the current value.
 498        for (var attr in attrs) {
 499          val = attrs[attr];
 500          if (!_.isEqual(current[attr], val)) changes.push(attr);
 501          if (!_.isEqual(prev[attr], val)) {
 502            changed[attr] = val;
 503          } else {
 504            delete changed[attr];
 505          }
 506          unset ? delete current[attr] : current[attr] = val;
 507        }
 508  
 509        // Update the `id`.
 510        if (this.idAttribute in attrs) this.id = this.get(this.idAttribute);
 511  
 512        // Trigger all relevant attribute changes.
 513        if (!silent) {
 514          if (changes.length) this._pending = options;
 515          for (var i = 0; i < changes.length; i++) {
 516            this.trigger('change:' + changes[i], this, current[changes[i]], options);
 517          }
 518        }
 519  
 520        // You might be wondering why there's a `while` loop here. Changes can
 521        // be recursively nested within `"change"` events.
 522        if (changing) return this;
 523        if (!silent) {
 524          while (this._pending) {
 525            options = this._pending;
 526            this._pending = false;
 527            this.trigger('change', this, options);
 528          }
 529        }
 530        this._pending = false;
 531        this._changing = false;
 532        return this;
 533      },
 534  
 535      // Remove an attribute from the model, firing `"change"`. `unset` is a noop
 536      // if the attribute doesn't exist.
 537      unset: function(attr, options) {
 538        return this.set(attr, void 0, _.extend({}, options, {unset: true}));
 539      },
 540  
 541      // Clear all attributes on the model, firing `"change"`.
 542      clear: function(options) {
 543        var attrs = {};
 544        for (var key in this.attributes) attrs[key] = void 0;
 545        return this.set(attrs, _.extend({}, options, {unset: true}));
 546      },
 547  
 548      // Determine if the model has changed since the last `"change"` event.
 549      // If you specify an attribute name, determine if that attribute has changed.
 550      hasChanged: function(attr) {
 551        if (attr == null) return !_.isEmpty(this.changed);
 552        return _.has(this.changed, attr);
 553      },
 554  
 555      // Return an object containing all the attributes that have changed, or
 556      // false if there are no changed attributes. Useful for determining what
 557      // parts of a view need to be updated and/or what attributes need to be
 558      // persisted to the server. Unset attributes will be set to undefined.
 559      // You can also pass an attributes object to diff against the model,
 560      // determining if there *would be* a change.
 561      changedAttributes: function(diff) {
 562        if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
 563        var old = this._changing ? this._previousAttributes : this.attributes;
 564        var changed = {};
 565        for (var attr in diff) {
 566          var val = diff[attr];
 567          if (_.isEqual(old[attr], val)) continue;
 568          changed[attr] = val;
 569        }
 570        return _.size(changed) ? changed : false;
 571      },
 572  
 573      // Get the previous value of an attribute, recorded at the time the last
 574      // `"change"` event was fired.
 575      previous: function(attr) {
 576        if (attr == null || !this._previousAttributes) return null;
 577        return this._previousAttributes[attr];
 578      },
 579  
 580      // Get all of the attributes of the model at the time of the previous
 581      // `"change"` event.
 582      previousAttributes: function() {
 583        return _.clone(this._previousAttributes);
 584      },
 585  
 586      // Fetch the model from the server, merging the response with the model's
 587      // local attributes. Any changed attributes will trigger a "change" event.
 588      fetch: function(options) {
 589        options = _.extend({parse: true}, options);
 590        var model = this;
 591        var success = options.success;
 592        options.success = function(resp) {
 593          var serverAttrs = options.parse ? model.parse(resp, options) : resp;
 594          if (!model.set(serverAttrs, options)) return false;
 595          if (success) success.call(options.context, model, resp, options);
 596          model.trigger('sync', model, resp, options);
 597        };
 598        wrapError(this, options);
 599        return this.sync('read', this, options);
 600      },
 601  
 602      // Set a hash of model attributes, and sync the model to the server.
 603      // If the server returns an attributes hash that differs, the model's
 604      // state will be `set` again.
 605      save: function(key, val, options) {
 606        // Handle both `"key", value` and `{key: value}` -style arguments.
 607        var attrs;
 608        if (key == null || typeof key === 'object') {
 609          attrs = key;
 610          options = val;
 611        } else {
 612          (attrs = {})[key] = val;
 613        }
 614  
 615        options = _.extend({validate: true, parse: true}, options);
 616        var wait = options.wait;
 617  
 618        // If we're not waiting and attributes exist, save acts as
 619        // `set(attr).save(null, opts)` with validation. Otherwise, check if
 620        // the model will be valid when the attributes, if any, are set.
 621        if (attrs && !wait) {
 622          if (!this.set(attrs, options)) return false;
 623        } else if (!this._validate(attrs, options)) {
 624          return false;
 625        }
 626  
 627        // After a successful server-side save, the client is (optionally)
 628        // updated with the server-side state.
 629        var model = this;
 630        var success = options.success;
 631        var attributes = this.attributes;
 632        options.success = function(resp) {
 633          // Ensure attributes are restored during synchronous saves.
 634          model.attributes = attributes;
 635          var serverAttrs = options.parse ? model.parse(resp, options) : resp;
 636          if (wait) serverAttrs = _.extend({}, attrs, serverAttrs);
 637          if (serverAttrs && !model.set(serverAttrs, options)) return false;
 638          if (success) success.call(options.context, model, resp, options);
 639          model.trigger('sync', model, resp, options);
 640        };
 641        wrapError(this, options);
 642  
 643        // Set temporary attributes if `{wait: true}` to properly find new ids.
 644        if (attrs && wait) this.attributes = _.extend({}, attributes, attrs);
 645  
 646        var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
 647        if (method === 'patch' && !options.attrs) options.attrs = attrs;
 648        var xhr = this.sync(method, this, options);
 649  
 650        // Restore attributes.
 651        this.attributes = attributes;
 652  
 653        return xhr;
 654      },
 655  
 656      // Destroy this model on the server if it was already persisted.
 657      // Optimistically removes the model from its collection, if it has one.
 658      // If `wait: true` is passed, waits for the server to respond before removal.
 659      destroy: function(options) {
 660        options = options ? _.clone(options) : {};
 661        var model = this;
 662        var success = options.success;
 663        var wait = options.wait;
 664  
 665        var destroy = function() {
 666          model.stopListening();
 667          model.trigger('destroy', model, model.collection, options);
 668        };
 669  
 670        options.success = function(resp) {
 671          if (wait) destroy();
 672          if (success) success.call(options.context, model, resp, options);
 673          if (!model.isNew()) model.trigger('sync', model, resp, options);
 674        };
 675  
 676        var xhr = false;
 677        if (this.isNew()) {
 678          _.defer(options.success);
 679        } else {
 680          wrapError(this, options);
 681          xhr = this.sync('delete', this, options);
 682        }
 683        if (!wait) destroy();
 684        return xhr;
 685      },
 686  
 687      // Default URL for the model's representation on the server -- if you're
 688      // using Backbone's restful methods, override this to change the endpoint
 689      // that will be called.
 690      url: function() {
 691        var base =
 692          _.result(this, 'urlRoot') ||
 693          _.result(this.collection, 'url') ||
 694          urlError();
 695        if (this.isNew()) return base;
 696        var id = this.get(this.idAttribute);
 697        return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(id);
 698      },
 699  
 700      // **parse** converts a response into the hash of attributes to be `set` on
 701      // the model. The default implementation is just to pass the response along.
 702      parse: function(resp, options) {
 703        return resp;
 704      },
 705  
 706      // Create a new model with identical attributes to this one.
 707      clone: function() {
 708        return new this.constructor(this.attributes);
 709      },
 710  
 711      // A model is new if it has never been saved to the server, and lacks an id.
 712      isNew: function() {
 713        return !this.has(this.idAttribute);
 714      },
 715  
 716      // Check if the model is currently in a valid state.
 717      isValid: function(options) {
 718        return this._validate({}, _.extend({}, options, {validate: true}));
 719      },
 720  
 721      // Run validation against the next complete set of model attributes,
 722      // returning `true` if all is well. Otherwise, fire an `"invalid"` event.
 723      _validate: function(attrs, options) {
 724        if (!options.validate || !this.validate) return true;
 725        attrs = _.extend({}, this.attributes, attrs);
 726        var error = this.validationError = this.validate(attrs, options) || null;
 727        if (!error) return true;
 728        this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
 729        return false;
 730      }
 731  
 732    });
 733  
 734    // Underscore methods that we want to implement on the Model, mapped to the
 735    // number of arguments they take.
 736    var modelMethods = {keys: 1, values: 1, pairs: 1, invert: 1, pick: 0,
 737        omit: 0, chain: 1, isEmpty: 1};
 738  
 739    // Mix in each Underscore method as a proxy to `Model#attributes`.
 740    addUnderscoreMethods(Model, modelMethods, 'attributes');
 741  
 742    // Backbone.Collection
 743    // -------------------
 744  
 745    // If models tend to represent a single row of data, a Backbone Collection is
 746    // more analogous to a table full of data ... or a small slice or page of that
 747    // table, or a collection of rows that belong together for a particular reason
 748    // -- all of the messages in this particular folder, all of the documents
 749    // belonging to this particular author, and so on. Collections maintain
 750    // indexes of their models, both in order, and for lookup by `id`.
 751  
 752    // Create a new **Collection**, perhaps to contain a specific type of `model`.
 753    // If a `comparator` is specified, the Collection will maintain
 754    // its models in sort order, as they're added and removed.
 755    var Collection = Backbone.Collection = function(models, options) {
 756      options || (options = {});
 757      if (options.model) this.model = options.model;
 758      if (options.comparator !== void 0) this.comparator = options.comparator;
 759      this._reset();
 760      this.initialize.apply(this, arguments);
 761      if (models) this.reset(models, _.extend({silent: true}, options));
 762    };
 763  
 764    // Default options for `Collection#set`.
 765    var setOptions = {add: true, remove: true, merge: true};
 766    var addOptions = {add: true, remove: false};
 767  
 768    // Splices `insert` into `array` at index `at`.
 769    var splice = function(array, insert, at) {
 770      at = Math.min(Math.max(at, 0), array.length);
 771      var tail = Array(array.length - at);
 772      var length = insert.length;
 773      var i;
 774      for (i = 0; i < tail.length; i++) tail[i] = array[i + at];
 775      for (i = 0; i < length; i++) array[i + at] = insert[i];
 776      for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i];
 777    };
 778  
 779    // Define the Collection's inheritable methods.
 780    _.extend(Collection.prototype, Events, {
 781  
 782      // The default model for a collection is just a **Backbone.Model**.
 783      // This should be overridden in most cases.
 784      model: Model,
 785  
 786      // Initialize is an empty function by default. Override it with your own
 787      // initialization logic.
 788      initialize: function(){},
 789  
 790      // The JSON representation of a Collection is an array of the
 791      // models' attributes.
 792      toJSON: function(options) {
 793        return this.map(function(model) { return model.toJSON(options); });
 794      },
 795  
 796      // Proxy `Backbone.sync` by default.
 797      sync: function() {
 798        return Backbone.sync.apply(this, arguments);
 799      },
 800  
 801      // Add a model, or list of models to the set. `models` may be Backbone
 802      // Models or raw JavaScript objects to be converted to Models, or any
 803      // combination of the two.
 804      add: function(models, options) {
 805        return this.set(models, _.extend({merge: false}, options, addOptions));
 806      },
 807  
 808      // Remove a model, or a list of models from the set.
 809      remove: function(models, options) {
 810        options = _.extend({}, options);
 811        var singular = !_.isArray(models);
 812        models = singular ? [models] : models.slice();
 813        var removed = this._removeModels(models, options);
 814        if (!options.silent && removed.length) {
 815          options.changes = {added: [], merged: [], removed: removed};
 816          this.trigger('update', this, options);
 817        }
 818        return singular ? removed[0] : removed;
 819      },
 820  
 821      // Update a collection by `set`-ing a new list of models, adding new ones,
 822      // removing models that are no longer present, and merging models that
 823      // already exist in the collection, as necessary. Similar to **Model#set**,
 824      // the core operation for updating the data contained by the collection.
 825      set: function(models, options) {
 826        if (models == null) return;
 827  
 828        options = _.extend({}, setOptions, options);
 829        if (options.parse && !this._isModel(models)) {
 830          models = this.parse(models, options) || [];
 831        }
 832  
 833        var singular = !_.isArray(models);
 834        models = singular ? [models] : models.slice();
 835  
 836        var at = options.at;
 837        if (at != null) at = +at;
 838        if (at > this.length) at = this.length;
 839        if (at < 0) at += this.length + 1;
 840  
 841        var set = [];
 842        var toAdd = [];
 843        var toMerge = [];
 844        var toRemove = [];
 845        var modelMap = {};
 846  
 847        var add = options.add;
 848        var merge = options.merge;
 849        var remove = options.remove;
 850  
 851        var sort = false;
 852        var sortable = this.comparator && at == null && options.sort !== false;
 853        var sortAttr = _.isString(this.comparator) ? this.comparator : null;
 854  
 855        // Turn bare objects into model references, and prevent invalid models
 856        // from being added.
 857        var model, i;
 858        for (i = 0; i < models.length; i++) {
 859          model = models[i];
 860  
 861          // If a duplicate is found, prevent it from being added and
 862          // optionally merge it into the existing model.
 863          var existing = this.get(model);
 864          if (existing) {
 865            if (merge && model !== existing) {
 866              var attrs = this._isModel(model) ? model.attributes : model;
 867              if (options.parse) attrs = existing.parse(attrs, options);
 868              existing.set(attrs, options);
 869              toMerge.push(existing);
 870              if (sortable && !sort) sort = existing.hasChanged(sortAttr);
 871            }
 872            if (!modelMap[existing.cid]) {
 873              modelMap[existing.cid] = true;
 874              set.push(existing);
 875            }
 876            models[i] = existing;
 877  
 878          // If this is a new, valid model, push it to the `toAdd` list.
 879          } else if (add) {
 880            model = models[i] = this._prepareModel(model, options);
 881            if (model) {
 882              toAdd.push(model);
 883              this._addReference(model, options);
 884              modelMap[model.cid] = true;
 885              set.push(model);
 886            }
 887          }
 888        }
 889  
 890        // Remove stale models.
 891        if (remove) {
 892          for (i = 0; i < this.length; i++) {
 893            model = this.models[i];
 894            if (!modelMap[model.cid]) toRemove.push(model);
 895          }
 896          if (toRemove.length) this._removeModels(toRemove, options);
 897        }
 898  
 899        // See if sorting is needed, update `length` and splice in new models.
 900        var orderChanged = false;
 901        var replace = !sortable && add && remove;
 902        if (set.length && replace) {
 903          orderChanged = this.length !== set.length || _.some(this.models, function(m, index) {
 904            return m !== set[index];
 905          });
 906          this.models.length = 0;
 907          splice(this.models, set, 0);
 908          this.length = this.models.length;
 909        } else if (toAdd.length) {
 910          if (sortable) sort = true;
 911          splice(this.models, toAdd, at == null ? this.length : at);
 912          this.length = this.models.length;
 913        }
 914  
 915        // Silently sort the collection if appropriate.
 916        if (sort) this.sort({silent: true});
 917  
 918        // Unless silenced, it's time to fire all appropriate add/sort/update events.
 919        if (!options.silent) {
 920          for (i = 0; i < toAdd.length; i++) {
 921            if (at != null) options.index = at + i;
 922            model = toAdd[i];
 923            model.trigger('add', model, this, options);
 924          }
 925          if (sort || orderChanged) this.trigger('sort', this, options);
 926          if (toAdd.length || toRemove.length || toMerge.length) {
 927            options.changes = {
 928              added: toAdd,
 929              removed: toRemove,
 930              merged: toMerge
 931            };
 932            this.trigger('update', this, options);
 933          }
 934        }
 935  
 936        // Return the added (or merged) model (or models).
 937        return singular ? models[0] : models;
 938      },
 939  
 940      // When you have more items than you want to add or remove individually,
 941      // you can reset the entire set with a new list of models, without firing
 942      // any granular `add` or `remove` events. Fires `reset` when finished.
 943      // Useful for bulk operations and optimizations.
 944      reset: function(models, options) {
 945        options = options ? _.clone(options) : {};
 946        for (var i = 0; i < this.models.length; i++) {
 947          this._removeReference(this.models[i], options);
 948        }
 949        options.previousModels = this.models;
 950        this._reset();
 951        models = this.add(models, _.extend({silent: true}, options));
 952        if (!options.silent) this.trigger('reset', this, options);
 953        return models;
 954      },
 955  
 956      // Add a model to the end of the collection.
 957      push: function(model, options) {
 958        return this.add(model, _.extend({at: this.length}, options));
 959      },
 960  
 961      // Remove a model from the end of the collection.
 962      pop: function(options) {
 963        var model = this.at(this.length - 1);
 964        return this.remove(model, options);
 965      },
 966  
 967      // Add a model to the beginning of the collection.
 968      unshift: function(model, options) {
 969        return this.add(model, _.extend({at: 0}, options));
 970      },
 971  
 972      // Remove a model from the beginning of the collection.
 973      shift: function(options) {
 974        var model = this.at(0);
 975        return this.remove(model, options);
 976      },
 977  
 978      // Slice out a sub-array of models from the collection.
 979      slice: function() {
 980        return slice.apply(this.models, arguments);
 981      },
 982  
 983      // Get a model from the set by id, cid, model object with id or cid
 984      // properties, or an attributes object that is transformed through modelId.
 985      get: function(obj) {
 986        if (obj == null) return void 0;
 987        return this._byId[obj] ||
 988          this._byId[this.modelId(obj.attributes || obj)] ||
 989          obj.cid && this._byId[obj.cid];
 990      },
 991  
 992      // Returns `true` if the model is in the collection.
 993      has: function(obj) {
 994        return this.get(obj) != null;
 995      },
 996  
 997      // Get the model at the given index.
 998      at: function(index) {
 999        if (index < 0) index += this.length;
1000        return this.models[index];
1001      },
1002  
1003      // Return models with matching attributes. Useful for simple cases of
1004      // `filter`.
1005      where: function(attrs, first) {
1006        return this[first ? 'find' : 'filter'](attrs);
1007      },
1008  
1009      // Return the first model with matching attributes. Useful for simple cases
1010      // of `find`.
1011      findWhere: function(attrs) {
1012        return this.where(attrs, true);
1013      },
1014  
1015      // Force the collection to re-sort itself. You don't need to call this under
1016      // normal circumstances, as the set will maintain sort order as each item
1017      // is added.
1018      sort: function(options) {
1019        var comparator = this.comparator;
1020        if (!comparator) throw new Error('Cannot sort a set without a comparator');
1021        options || (options = {});
1022  
1023        var length = comparator.length;
1024        if (_.isFunction(comparator)) comparator = _.bind(comparator, this);
1025  
1026        // Run sort based on type of `comparator`.
1027        if (length === 1 || _.isString(comparator)) {
1028          this.models = this.sortBy(comparator);
1029        } else {
1030          this.models.sort(comparator);
1031        }
1032        if (!options.silent) this.trigger('sort', this, options);
1033        return this;
1034      },
1035  
1036      // Pluck an attribute from each model in the collection.
1037      pluck: function(attr) {
1038        return this.map(attr + '');
1039      },
1040  
1041      // Fetch the default set of models for this collection, resetting the
1042      // collection when they arrive. If `reset: true` is passed, the response
1043      // data will be passed through the `reset` method instead of `set`.
1044      fetch: function(options) {
1045        options = _.extend({parse: true}, options);
1046        var success = options.success;
1047        var collection = this;
1048        options.success = function(resp) {
1049          var method = options.reset ? 'reset' : 'set';
1050          collection[method](resp, options);
1051          if (success) success.call(options.context, collection, resp, options);
1052          collection.trigger('sync', collection, resp, options);
1053        };
1054        wrapError(this, options);
1055        return this.sync('read', this, options);
1056      },
1057  
1058      // Create a new instance of a model in this collection. Add the model to the
1059      // collection immediately, unless `wait: true` is passed, in which case we
1060      // wait for the server to agree.
1061      create: function(model, options) {
1062        options = options ? _.clone(options) : {};
1063        var wait = options.wait;
1064        model = this._prepareModel(model, options);
1065        if (!model) return false;
1066        if (!wait) this.add(model, options);
1067        var collection = this;
1068        var success = options.success;
1069        options.success = function(m, resp, callbackOpts) {
1070          if (wait) collection.add(m, callbackOpts);
1071          if (success) success.call(callbackOpts.context, m, resp, callbackOpts);
1072        };
1073        model.save(null, options);
1074        return model;
1075      },
1076  
1077      // **parse** converts a response into a list of models to be added to the
1078      // collection. The default implementation is just to pass it through.
1079      parse: function(resp, options) {
1080        return resp;
1081      },
1082  
1083      // Create a new collection with an identical list of models as this one.
1084      clone: function() {
1085        return new this.constructor(this.models, {
1086          model: this.model,
1087          comparator: this.comparator
1088        });
1089      },
1090  
1091      // Define how to uniquely identify models in the collection.
1092      modelId: function(attrs) {
1093        return attrs[this.model.prototype.idAttribute || 'id'];
1094      },
1095  
1096      // Private method to reset all internal state. Called when the collection
1097      // is first initialized or reset.
1098      _reset: function() {
1099        this.length = 0;
1100        this.models = [];
1101        this._byId  = {};
1102      },
1103  
1104      // Prepare a hash of attributes (or other model) to be added to this
1105      // collection.
1106      _prepareModel: function(attrs, options) {
1107        if (this._isModel(attrs)) {
1108          if (!attrs.collection) attrs.collection = this;
1109          return attrs;
1110        }
1111        options = options ? _.clone(options) : {};
1112        options.collection = this;
1113        var model = new this.model(attrs, options);
1114        if (!model.validationError) return model;
1115        this.trigger('invalid', this, model.validationError, options);
1116        return false;
1117      },
1118  
1119      // Internal method called by both remove and set.
1120      _removeModels: function(models, options) {
1121        var removed = [];
1122        for (var i = 0; i < models.length; i++) {
1123          var model = this.get(models[i]);
1124          if (!model) continue;
1125  
1126          var index = this.indexOf(model);
1127          this.models.splice(index, 1);
1128          this.length--;
1129  
1130          // Remove references before triggering 'remove' event to prevent an
1131          // infinite loop. #3693
1132          delete this._byId[model.cid];
1133          var id = this.modelId(model.attributes);
1134          if (id != null) delete this._byId[id];
1135  
1136          if (!options.silent) {
1137            options.index = index;
1138            model.trigger('remove', model, this, options);
1139          }
1140  
1141          removed.push(model);
1142          this._removeReference(model, options);
1143        }
1144        return removed;
1145      },
1146  
1147      // Method for checking whether an object should be considered a model for
1148      // the purposes of adding to the collection.
1149      _isModel: function(model) {
1150        return model instanceof Model;
1151      },
1152  
1153      // Internal method to create a model's ties to a collection.
1154      _addReference: function(model, options) {
1155        this._byId[model.cid] = model;
1156        var id = this.modelId(model.attributes);
1157        if (id != null) this._byId[id] = model;
1158        model.on('all', this._onModelEvent, this);
1159      },
1160  
1161      // Internal method to sever a model's ties to a collection.
1162      _removeReference: function(model, options) {
1163        delete this._byId[model.cid];
1164        var id = this.modelId(model.attributes);
1165        if (id != null) delete this._byId[id];
1166        if (this === model.collection) delete model.collection;
1167        model.off('all', this._onModelEvent, this);
1168      },
1169  
1170      // Internal method called every time a model in the set fires an event.
1171      // Sets need to update their indexes when models change ids. All other
1172      // events simply proxy through. "add" and "remove" events that originate
1173      // in other collections are ignored.
1174      _onModelEvent: function(event, model, collection, options) {
1175        if (model) {
1176          if ((event === 'add' || event === 'remove') && collection !== this) return;
1177          if (event === 'destroy') this.remove(model, options);
1178          if (event === 'change') {
1179            var prevId = this.modelId(model.previousAttributes());
1180            var id = this.modelId(model.attributes);
1181            if (prevId !== id) {
1182              if (prevId != null) delete this._byId[prevId];
1183              if (id != null) this._byId[id] = model;
1184            }
1185          }
1186        }
1187        this.trigger.apply(this, arguments);
1188      }
1189  
1190    });
1191  
1192    // Underscore methods that we want to implement on the Collection.
1193    // 90% of the core usefulness of Backbone Collections is actually implemented
1194    // right here:
1195    var collectionMethods = {forEach: 3, each: 3, map: 3, collect: 3, reduce: 0,
1196        foldl: 0, inject: 0, reduceRight: 0, foldr: 0, find: 3, detect: 3, filter: 3,
1197        select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3,
1198        contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3,
1199        head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3,
1200        without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3,
1201        isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3,
1202        sortBy: 3, indexBy: 3, findIndex: 3, findLastIndex: 3};
1203  
1204    // Mix in each Underscore method as a proxy to `Collection#models`.
1205    addUnderscoreMethods(Collection, collectionMethods, 'models');
1206  
1207    // Backbone.View
1208    // -------------
1209  
1210    // Backbone Views are almost more convention than they are actual code. A View
1211    // is simply a JavaScript object that represents a logical chunk of UI in the
1212    // DOM. This might be a single item, an entire list, a sidebar or panel, or
1213    // even the surrounding frame which wraps your whole app. Defining a chunk of
1214    // UI as a **View** allows you to define your DOM events declaratively, without
1215    // having to worry about render order ... and makes it easy for the view to
1216    // react to specific changes in the state of your models.
1217  
1218    // Creating a Backbone.View creates its initial element outside of the DOM,
1219    // if an existing element is not provided...
1220    var View = Backbone.View = function(options) {
1221      this.cid = _.uniqueId('view');
1222      _.extend(this, _.pick(options, viewOptions));
1223      this._ensureElement();
1224      this.initialize.apply(this, arguments);
1225    };
1226  
1227    // Cached regex to split keys for `delegate`.
1228    var delegateEventSplitter = /^(\S+)\s*(.*)$/;
1229  
1230    // List of view options to be set as properties.
1231    var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
1232  
1233    // Set up all inheritable **Backbone.View** properties and methods.
1234    _.extend(View.prototype, Events, {
1235  
1236      // The default `tagName` of a View's element is `"div"`.
1237      tagName: 'div',
1238  
1239      // jQuery delegate for element lookup, scoped to DOM elements within the
1240      // current view. This should be preferred to global lookups where possible.
1241      $: function(selector) {
1242        return this.$el.find(selector);
1243      },
1244  
1245      // Initialize is an empty function by default. Override it with your own
1246      // initialization logic.
1247      initialize: function(){},
1248  
1249      // **render** is the core function that your view should override, in order
1250      // to populate its element (`this.el`), with the appropriate HTML. The
1251      // convention is for **render** to always return `this`.
1252      render: function() {
1253        return this;
1254      },
1255  
1256      // Remove this view by taking the element out of the DOM, and removing any
1257      // applicable Backbone.Events listeners.
1258      remove: function() {
1259        this._removeElement();
1260        this.stopListening();
1261        return this;
1262      },
1263  
1264      // Remove this view's element from the document and all event listeners
1265      // attached to it. Exposed for subclasses using an alternative DOM
1266      // manipulation API.
1267      _removeElement: function() {
1268        this.$el.remove();
1269      },
1270  
1271      // Change the view's element (`this.el` property) and re-delegate the
1272      // view's events on the new element.
1273      setElement: function(element) {
1274        this.undelegateEvents();
1275        this._setElement(element);
1276        this.delegateEvents();
1277        return this;
1278      },
1279  
1280      // Creates the `this.el` and `this.$el` references for this view using the
1281      // given `el`. `el` can be a CSS selector or an HTML string, a jQuery
1282      // context or an element. Subclasses can override this to utilize an
1283      // alternative DOM manipulation API and are only required to set the
1284      // `this.el` property.
1285      _setElement: function(el) {
1286        this.$el = el instanceof Backbone.$ ? el : Backbone.$(el);
1287        this.el = this.$el[0];
1288      },
1289  
1290      // Set callbacks, where `this.events` is a hash of
1291      //
1292      // *{"event selector": "callback"}*
1293      //
1294      //     {
1295      //       'mousedown .title':  'edit',
1296      //       'click .button':     'save',
1297      //       'click .open':       function(e) { ... }
1298      //     }
1299      //
1300      // pairs. Callbacks will be bound to the view, with `this` set properly.
1301      // Uses event delegation for efficiency.
1302      // Omitting the selector binds the event to `this.el`.
1303      delegateEvents: function(events) {
1304        events || (events = _.result(this, 'events'));
1305        if (!events) return this;
1306        this.undelegateEvents();
1307        for (var key in events) {
1308          var method = events[key];
1309          if (!_.isFunction(method)) method = this[method];
1310          if (!method) continue;
1311          var match = key.match(delegateEventSplitter);
1312          this.delegate(match[1], match[2], _.bind(method, this));
1313        }
1314        return this;
1315      },
1316  
1317      // Add a single event listener to the view's element (or a child element
1318      // using `selector`). This only works for delegate-able events: not `focus`,
1319      // `blur`, and not `change`, `submit`, and `reset` in Internet Explorer.
1320      delegate: function(eventName, selector, listener) {
1321        this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener);
1322        return this;
1323      },
1324  
1325      // Clears all callbacks previously bound to the view by `delegateEvents`.
1326      // You usually don't need to use this, but may wish to if you have multiple
1327      // Backbone views attached to the same DOM element.
1328      undelegateEvents: function() {
1329        if (this.$el) this.$el.off('.delegateEvents' + this.cid);
1330        return this;
1331      },
1332  
1333      // A finer-grained `undelegateEvents` for removing a single delegated event.
1334      // `selector` and `listener` are both optional.
1335      undelegate: function(eventName, selector, listener) {
1336        this.$el.off(eventName + '.delegateEvents' + this.cid, selector, listener);
1337        return this;
1338      },
1339  
1340      // Produces a DOM element to be assigned to your view. Exposed for
1341      // subclasses using an alternative DOM manipulation API.
1342      _createElement: function(tagName) {
1343        return document.createElement(tagName);
1344      },
1345  
1346      // Ensure that the View has a DOM element to render into.
1347      // If `this.el` is a string, pass it through `$()`, take the first
1348      // matching element, and re-assign it to `el`. Otherwise, create
1349      // an element from the `id`, `className` and `tagName` properties.
1350      _ensureElement: function() {
1351        if (!this.el) {
1352          var attrs = _.extend({}, _.result(this, 'attributes'));
1353          if (this.id) attrs.id = _.result(this, 'id');
1354          if (this.className) attrs['class'] = _.result(this, 'className');
1355          this.setElement(this._createElement(_.result(this, 'tagName')));
1356          this._setAttributes(attrs);
1357        } else {
1358          this.setElement(_.result(this, 'el'));
1359        }
1360      },
1361  
1362      // Set attributes from a hash on this view's element.  Exposed for
1363      // subclasses using an alternative DOM manipulation API.
1364      _setAttributes: function(attributes) {
1365        this.$el.attr(attributes);
1366      }
1367  
1368    });
1369  
1370    // Backbone.sync
1371    // -------------
1372  
1373    // Override this function to change the manner in which Backbone persists
1374    // models to the server. You will be passed the type of request, and the
1375    // model in question. By default, makes a RESTful Ajax request
1376    // to the model's `url()`. Some possible customizations could be:
1377    //
1378    // * Use `setTimeout` to batch rapid-fire updates into a single request.
1379    // * Send up the models as XML instead of JSON.
1380    // * Persist models via WebSockets instead of Ajax.
1381    //
1382    // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
1383    // as `POST`, with a `_method` parameter containing the true HTTP method,
1384    // as well as all requests with the body as `application/x-www-form-urlencoded`
1385    // instead of `application/json` with the model in a param named `model`.
1386    // Useful when interfacing with server-side languages like **PHP** that make
1387    // it difficult to read the body of `PUT` requests.
1388    Backbone.sync = function(method, model, options) {
1389      var type = methodMap[method];
1390  
1391      // Default options, unless specified.
1392      _.defaults(options || (options = {}), {
1393        emulateHTTP: Backbone.emulateHTTP,
1394        emulateJSON: Backbone.emulateJSON
1395      });
1396  
1397      // Default JSON-request options.
1398      var params = {type: type, dataType: 'json'};
1399  
1400      // Ensure that we have a URL.
1401      if (!options.url) {
1402        params.url = _.result(model, 'url') || urlError();
1403      }
1404  
1405      // Ensure that we have the appropriate request data.
1406      if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
1407        params.contentType = 'application/json';
1408        params.data = JSON.stringify(options.attrs || model.toJSON(options));
1409      }
1410  
1411      // For older servers, emulate JSON by encoding the request into an HTML-form.
1412      if (options.emulateJSON) {
1413        params.contentType = 'application/x-www-form-urlencoded';
1414        params.data = params.data ? {model: params.data} : {};
1415      }
1416  
1417      // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1418      // And an `X-HTTP-Method-Override` header.
1419      if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
1420        params.type = 'POST';
1421        if (options.emulateJSON) params.data._method = type;
1422        var beforeSend = options.beforeSend;
1423        options.beforeSend = function(xhr) {
1424          xhr.setRequestHeader('X-HTTP-Method-Override', type);
1425          if (beforeSend) return beforeSend.apply(this, arguments);
1426        };
1427      }
1428  
1429      // Don't process data on a non-GET request.
1430      if (params.type !== 'GET' && !options.emulateJSON) {
1431        params.processData = false;
1432      }
1433  
1434      // Pass along `textStatus` and `errorThrown` from jQuery.
1435      var error = options.error;
1436      options.error = function(xhr, textStatus, errorThrown) {
1437        options.textStatus = textStatus;
1438        options.errorThrown = errorThrown;
1439        if (error) error.call(options.context, xhr, textStatus, errorThrown);
1440      };
1441  
1442      // Make the request, allowing the user to override any Ajax options.
1443      var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
1444      model.trigger('request', model, xhr, options);
1445      return xhr;
1446    };
1447  
1448    // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1449    var methodMap = {
1450      'create': 'POST',
1451      'update': 'PUT',
1452      'patch': 'PATCH',
1453      'delete': 'DELETE',
1454      'read': 'GET'
1455    };
1456  
1457    // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
1458    // Override this if you'd like to use a different library.
1459    Backbone.ajax = function() {
1460      return Backbone.$.ajax.apply(Backbone.$, arguments);
1461    };
1462  
1463    // Backbone.Router
1464    // ---------------
1465  
1466    // Routers map faux-URLs to actions, and fire events when routes are
1467    // matched. Creating a new one sets its `routes` hash, if not set statically.
1468    var Router = Backbone.Router = function(options) {
1469      options || (options = {});
1470      if (options.routes) this.routes = options.routes;
1471      this._bindRoutes();
1472      this.initialize.apply(this, arguments);
1473    };
1474  
1475    // Cached regular expressions for matching named param parts and splatted
1476    // parts of route strings.
1477    var optionalParam = /\((.*?)\)/g;
1478    var namedParam    = /(\(\?)?:\w+/g;
1479    var splatParam    = /\*\w+/g;
1480    var escapeRegExp  = /[\-{}\[\]+?.,\\\^$|#\s]/g;
1481  
1482    // Set up all inheritable **Backbone.Router** properties and methods.
1483    _.extend(Router.prototype, Events, {
1484  
1485      // Initialize is an empty function by default. Override it with your own
1486      // initialization logic.
1487      initialize: function(){},
1488  
1489      // Manually bind a single named route to a callback. For example:
1490      //
1491      //     this.route('search/:query/p:num', 'search', function(query, num) {
1492      //       ...
1493      //     });
1494      //
1495      route: function(route, name, callback) {
1496        if (!_.isRegExp(route)) route = this._routeToRegExp(route);
1497        if (_.isFunction(name)) {
1498          callback = name;
1499          name = '';
1500        }
1501        if (!callback) callback = this[name];
1502        var router = this;
1503        Backbone.history.route(route, function(fragment) {
1504          var args = router._extractParameters(route, fragment);
1505          if (router.execute(callback, args, name) !== false) {
1506            router.trigger.apply(router, ['route:' + name].concat(args));
1507            router.trigger('route', name, args);
1508            Backbone.history.trigger('route', router, name, args);
1509          }
1510        });
1511        return this;
1512      },
1513  
1514      // Execute a route handler with the provided parameters.  This is an
1515      // excellent place to do pre-route setup or post-route cleanup.
1516      execute: function(callback, args, name) {
1517        if (callback) callback.apply(this, args);
1518      },
1519  
1520      // Simple proxy to `Backbone.history` to save a fragment into the history.
1521      navigate: function(fragment, options) {
1522        Backbone.history.navigate(fragment, options);
1523        return this;
1524      },
1525  
1526      // Bind all defined routes to `Backbone.history`. We have to reverse the
1527      // order of the routes here to support behavior where the most general
1528      // routes can be defined at the bottom of the route map.
1529      _bindRoutes: function() {
1530        if (!this.routes) return;
1531        this.routes = _.result(this, 'routes');
1532        var route, routes = _.keys(this.routes);
1533        while ((route = routes.pop()) != null) {
1534          this.route(route, this.routes[route]);
1535        }
1536      },
1537  
1538      // Convert a route string into a regular expression, suitable for matching
1539      // against the current location hash.
1540      _routeToRegExp: function(route) {
1541        route = route.replace(escapeRegExp, '\\$&')
1542                     .replace(optionalParam, '(?:$1)?')
1543                     .replace(namedParam, function(match, optional) {
1544                       return optional ? match : '([^/?]+)';
1545                     })
1546                     .replace(splatParam, '([^?]*?)');
1547        return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$');
1548      },
1549  
1550      // Given a route, and a URL fragment that it matches, return the array of
1551      // extracted decoded parameters. Empty or unmatched parameters will be
1552      // treated as `null` to normalize cross-browser behavior.
1553      _extractParameters: function(route, fragment) {
1554        var params = route.exec(fragment).slice(1);
1555        return _.map(params, function(param, i) {
1556          // Don't decode the search params.
1557          if (i === params.length - 1) return param || null;
1558          return param ? decodeURIComponent(param) : null;
1559        });
1560      }
1561  
1562    });
1563  
1564    // Backbone.History
1565    // ----------------
1566  
1567    // Handles cross-browser history management, based on either
1568    // [pushState](http://diveintohtml5.info/history.html) and real URLs, or
1569    // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
1570    // and URL fragments. If the browser supports neither (old IE, natch),
1571    // falls back to polling.
1572    var History = Backbone.History = function() {
1573      this.handlers = [];
1574      this.checkUrl = _.bind(this.checkUrl, this);
1575  
1576      // Ensure that `History` can be used outside of the browser.
1577      if (typeof window !== 'undefined') {
1578        this.location = window.location;
1579        this.history = window.history;
1580      }
1581    };
1582  
1583    // Cached regex for stripping a leading hash/slash and trailing space.
1584    var routeStripper = /^[#\/]|\s+$/g;
1585  
1586    // Cached regex for stripping leading and trailing slashes.
1587    var rootStripper = /^\/+|\/+$/g;
1588  
1589    // Cached regex for stripping urls of hash.
1590    var pathStripper = /#.*$/;
1591  
1592    // Has the history handling already been started?
1593    History.started = false;
1594  
1595    // Set up all inheritable **Backbone.History** properties and methods.
1596    _.extend(History.prototype, Events, {
1597  
1598      // The default interval to poll for hash changes, if necessary, is
1599      // twenty times a second.
1600      interval: 50,
1601  
1602      // Are we at the app root?
1603      atRoot: function() {
1604        var path = this.location.pathname.replace(/[^\/]$/, '$&/');
1605        return path === this.root && !this.getSearch();
1606      },
1607  
1608      // Does the pathname match the root?
1609      matchRoot: function() {
1610        var path = this.decodeFragment(this.location.pathname);
1611        var rootPath = path.slice(0, this.root.length - 1) + '/';
1612        return rootPath === this.root;
1613      },
1614  
1615      // Unicode characters in `location.pathname` are percent encoded so they're
1616      // decoded for comparison. `%25` should not be decoded since it may be part
1617      // of an encoded parameter.
1618      decodeFragment: function(fragment) {
1619        return decodeURI(fragment.replace(/%25/g, '%2525'));
1620      },
1621  
1622      // In IE6, the hash fragment and search params are incorrect if the
1623      // fragment contains `?`.
1624      getSearch: function() {
1625        var match = this.location.href.replace(/#.*/, '').match(/\?.+/);
1626        return match ? match[0] : '';
1627      },
1628  
1629      // Gets the true hash value. Cannot use location.hash directly due to bug
1630      // in Firefox where location.hash will always be decoded.
1631      getHash: function(window) {
1632        var match = (window || this).location.href.match(/#(.*)$/);
1633        return match ? match[1] : '';
1634      },
1635  
1636      // Get the pathname and search params, without the root.
1637      getPath: function() {
1638        var path = this.decodeFragment(
1639          this.location.pathname + this.getSearch()
1640        ).slice(this.root.length - 1);
1641        return path.charAt(0) === '/' ? path.slice(1) : path;
1642      },
1643  
1644      // Get the cross-browser normalized URL fragment from the path or hash.
1645      getFragment: function(fragment) {
1646        if (fragment == null) {
1647          if (this._usePushState || !this._wantsHashChange) {
1648            fragment = this.getPath();
1649          } else {
1650            fragment = this.getHash();
1651          }
1652        }
1653        return fragment.replace(routeStripper, '');
1654      },
1655  
1656      // Start the hash change handling, returning `true` if the current URL matches
1657      // an existing route, and `false` otherwise.
1658      start: function(options) {
1659        if (History.started) throw new Error('Backbone.history has already been started');
1660        History.started = true;
1661  
1662        // Figure out the initial configuration. Do we need an iframe?
1663        // Is pushState desired ... is it available?
1664        this.options          = _.extend({root: '/'}, this.options, options);
1665        this.root             = this.options.root;
1666        this._wantsHashChange = this.options.hashChange !== false;
1667        this._hasHashChange   = 'onhashchange' in window && (document.documentMode === void 0 || document.documentMode > 7);
1668        this._useHashChange   = this._wantsHashChange && this._hasHashChange;
1669        this._wantsPushState  = !!this.options.pushState;
1670        this._hasPushState    = !!(this.history && this.history.pushState);
1671        this._usePushState    = this._wantsPushState && this._hasPushState;
1672        this.fragment         = this.getFragment();
1673  
1674        // Normalize root to always include a leading and trailing slash.
1675        this.root = ('/' + this.root + '/').replace(rootStripper, '/');
1676  
1677        // Transition from hashChange to pushState or vice versa if both are
1678        // requested.
1679        if (this._wantsHashChange && this._wantsPushState) {
1680  
1681          // If we've started off with a route from a `pushState`-enabled
1682          // browser, but we're currently in a browser that doesn't support it...
1683          if (!this._hasPushState && !this.atRoot()) {
1684            var rootPath = this.root.slice(0, -1) || '/';
1685            this.location.replace(rootPath + '#' + this.getPath());
1686            // Return immediately as browser will do redirect to new url
1687            return true;
1688  
1689          // Or if we've started out with a hash-based route, but we're currently
1690          // in a browser where it could be `pushState`-based instead...
1691          } else if (this._hasPushState && this.atRoot()) {
1692            this.navigate(this.getHash(), {replace: true});
1693          }
1694  
1695        }
1696  
1697        // Proxy an iframe to handle location events if the browser doesn't
1698        // support the `hashchange` event, HTML5 history, or the user wants
1699        // `hashChange` but not `pushState`.
1700        if (!this._hasHashChange && this._wantsHashChange && !this._usePushState) {
1701          this.iframe = document.createElement('iframe');
1702          this.iframe.src = 'javascript:0';
1703          this.iframe.style.display = 'none';
1704          this.iframe.tabIndex = -1;
1705          var body = document.body;
1706          // Using `appendChild` will throw on IE < 9 if the document is not ready.
1707          var iWindow = body.insertBefore(this.iframe, body.firstChild).contentWindow;
1708          iWindow.document.open();
1709          iWindow.document.close();
1710          iWindow.location.hash = '#' + this.fragment;
1711        }
1712  
1713        // Add a cross-platform `addEventListener` shim for older browsers.
1714        var addEventListener = window.addEventListener || function(eventName, listener) {
1715          return attachEvent('on' + eventName, listener);
1716        };
1717  
1718        // Depending on whether we're using pushState or hashes, and whether
1719        // 'onhashchange' is supported, determine how we check the URL state.
1720        if (this._usePushState) {
1721          addEventListener('popstate', this.checkUrl, false);
1722        } else if (this._useHashChange && !this.iframe) {
1723          addEventListener('hashchange', this.checkUrl, false);
1724        } else if (this._wantsHashChange) {
1725          this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
1726        }
1727  
1728        if (!this.options.silent) return this.loadUrl();
1729      },
1730  
1731      // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
1732      // but possibly useful for unit testing Routers.
1733      stop: function() {
1734        // Add a cross-platform `removeEventListener` shim for older browsers.
1735        var removeEventListener = window.removeEventListener || function(eventName, listener) {
1736          return detachEvent('on' + eventName, listener);
1737        };
1738  
1739        // Remove window listeners.
1740        if (this._usePushState) {
1741          removeEventListener('popstate', this.checkUrl, false);
1742        } else if (this._useHashChange && !this.iframe) {
1743          removeEventListener('hashchange', this.checkUrl, false);
1744        }
1745  
1746        // Clean up the iframe if necessary.
1747        if (this.iframe) {
1748          document.body.removeChild(this.iframe);
1749          this.iframe = null;
1750        }
1751  
1752        // Some environments will throw when clearing an undefined interval.
1753        if (this._checkUrlInterval) clearInterval(this._checkUrlInterval);
1754        History.started = false;
1755      },
1756  
1757      // Add a route to be tested when the fragment changes. Routes added later
1758      // may override previous routes.
1759      route: function(route, callback) {
1760        this.handlers.unshift({route: route, callback: callback});
1761      },
1762  
1763      // Checks the current URL to see if it has changed, and if it has,
1764      // calls `loadUrl`, normalizing across the hidden iframe.
1765      checkUrl: function(e) {
1766        var current = this.getFragment();
1767  
1768        // If the user pressed the back button, the iframe's hash will have
1769        // changed and we should use that for comparison.
1770        if (current === this.fragment && this.iframe) {
1771          current = this.getHash(this.iframe.contentWindow);
1772        }
1773  
1774        if (current === this.fragment) return false;
1775        if (this.iframe) this.navigate(current);
1776        this.loadUrl();
1777      },
1778  
1779      // Attempt to load the current URL fragment. If a route succeeds with a
1780      // match, returns `true`. If no defined routes matches the fragment,
1781      // returns `false`.
1782      loadUrl: function(fragment) {
1783        // If the root doesn't match, no routes can match either.
1784        if (!this.matchRoot()) return false;
1785        fragment = this.fragment = this.getFragment(fragment);
1786        return _.some(this.handlers, function(handler) {
1787          if (handler.route.test(fragment)) {
1788            handler.callback(fragment);
1789            return true;
1790          }
1791        });
1792      },
1793  
1794      // Save a fragment into the hash history, or replace the URL state if the
1795      // 'replace' option is passed. You are responsible for properly URL-encoding
1796      // the fragment in advance.
1797      //
1798      // The options object can contain `trigger: true` if you wish to have the
1799      // route callback be fired (not usually desirable), or `replace: true`, if
1800      // you wish to modify the current URL without adding an entry to the history.
1801      navigate: function(fragment, options) {
1802        if (!History.started) return false;
1803        if (!options || options === true) options = {trigger: !!options};
1804  
1805        // Normalize the fragment.
1806        fragment = this.getFragment(fragment || '');
1807  
1808        // Don't include a trailing slash on the root.
1809        var rootPath = this.root;
1810        if (fragment === '' || fragment.charAt(0) === '?') {
1811          rootPath = rootPath.slice(0, -1) || '/';
1812        }
1813        var url = rootPath + fragment;
1814  
1815        // Strip the hash and decode for matching.
1816        fragment = this.decodeFragment(fragment.replace(pathStripper, ''));
1817  
1818        if (this.fragment === fragment) return;
1819        this.fragment = fragment;
1820  
1821        // If pushState is available, we use it to set the fragment as a real URL.
1822        if (this._usePushState) {
1823          this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
1824  
1825        // If hash changes haven't been explicitly disabled, update the hash
1826        // fragment to store history.
1827        } else if (this._wantsHashChange) {
1828          this._updateHash(this.location, fragment, options.replace);
1829          if (this.iframe && fragment !== this.getHash(this.iframe.contentWindow)) {
1830            var iWindow = this.iframe.contentWindow;
1831  
1832            // Opening and closing the iframe tricks IE7 and earlier to push a
1833            // history entry on hash-tag change.  When replace is true, we don't
1834            // want this.
1835            if (!options.replace) {
1836              iWindow.document.open();
1837              iWindow.document.close();
1838            }
1839  
1840            this._updateHash(iWindow.location, fragment, options.replace);
1841          }
1842  
1843        // If you've told us that you explicitly don't want fallback hashchange-
1844        // based history, then `navigate` becomes a page refresh.
1845        } else {
1846          return this.location.assign(url);
1847        }
1848        if (options.trigger) return this.loadUrl(fragment);
1849      },
1850  
1851      // Update the hash location, either replacing the current entry, or adding
1852      // a new one to the browser history.
1853      _updateHash: function(location, fragment, replace) {
1854        if (replace) {
1855          var href = location.href.replace(/(javascript:|#).*$/, '');
1856          location.replace(href + '#' + fragment);
1857        } else {
1858          // Some browsers require that `hash` contains a leading #.
1859          location.hash = '#' + fragment;
1860        }
1861      }
1862  
1863    });
1864  
1865    // Create the default Backbone.history.
1866    Backbone.history = new History;
1867  
1868    // Helpers
1869    // -------
1870  
1871    // Helper function to correctly set up the prototype chain for subclasses.
1872    // Similar to `goog.inherits`, but uses a hash of prototype properties and
1873    // class properties to be extended.
1874    var extend = function(protoProps, staticProps) {
1875      var parent = this;
1876      var child;
1877  
1878      // The constructor function for the new subclass is either defined by you
1879      // (the "constructor" property in your `extend` definition), or defaulted
1880      // by us to simply call the parent constructor.
1881      if (protoProps && _.has(protoProps, 'constructor')) {
1882        child = protoProps.constructor;
1883      } else {
1884        child = function(){ return parent.apply(this, arguments); };
1885      }
1886  
1887      // Add static properties to the constructor function, if supplied.
1888      _.extend(child, parent, staticProps);
1889  
1890      // Set the prototype chain to inherit from `parent`, without calling
1891      // `parent`'s constructor function and add the prototype properties.
1892      child.prototype = _.create(parent.prototype, protoProps);
1893      child.prototype.constructor = child;
1894  
1895      // Set a convenience property in case the parent's prototype is needed
1896      // later.
1897      child.__super__ = parent.prototype;
1898  
1899      return child;
1900    };
1901  
1902    // Set up inheritance for the model, collection, router, view and history.
1903    Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
1904  
1905    // Throw an error when a URL is needed, and none is supplied.
1906    var urlError = function() {
1907      throw new Error('A "url" property or function must be specified');
1908    };
1909  
1910    // Wrap an optional error callback with a fallback error event.
1911    var wrapError = function(model, options) {
1912      var error = options.error;
1913      options.error = function(resp) {
1914        if (error) error.call(options.context, model, resp, options);
1915        model.trigger('error', model, resp, options);
1916      };
1917    };
1918  
1919    return Backbone;
1920  });


Generated: Tue Sep 17 01:00:03 2019 Cross-referenced by PHPXref 0.7.1