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

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

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

(2)
上一篇 2024年 6月 18日 上午10:50
下一篇 2024年 6月 19日 上午9:56

相关推荐

发表回复

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

联系我们

020-2206-9892

QQ咨询:1025174874

邮件:info@361sale.com

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

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