WordPress 源代码——主干(backbone-1.0.js)

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

联系我们
文章看不懂?联系我们为您免费解答!免费助力个人,小企站点!
电话:020-2206-9892
QQ咨询:1025174874
邮件:info@361sale.com
工作时间:周一至周五,9:30-18:30,节假日休息
发布者:光子波动,转转请注明出处:https://www.361sale.com/11653/

(0)
上一篇 2024年 6月 19日 上午9:56
下一篇 2024年 6月 19日 上午11:07

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

联系我们

020-2206-9892

QQ咨询:1025174874

邮件:info@361sale.com

工作时间:周一至周五,9:30-18:30,节假日休息

客服微信
为方便全球用户注册登录,我们已取消电话登录功能。如遇登录问题,请联系客服协助绑定邮箱。