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);
//     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);
// 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,节假日休息
© 转载声明
本文作者:Harry
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容