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