3 // (c) 2010-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
4 // Backbone may be freely distributed under the MIT license.
5 // For all details and documentation:
6 // http://backbonejs.org
8 (function(root, factory) {
10 // Set up Backbone appropriately for the environment. Start with AMD.
11 if (typeof define === 'function' && define.amd) {
12 define(['underscore', 'jquery', 'exports'], function(_, $, exports) {
13 // Export global even in AMD case in case this script is loaded with
14 // others that may still expect a global Backbone.
15 root.Backbone = factory(root, exports, _, $);
18 // Next for Node.js or CommonJS. jQuery may not be needed as a module.
19 } else if (typeof exports !== 'undefined') {
20 var _ = require('underscore');
21 factory(root, exports, _);
23 // Finally, as a browser global.
25 root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$));
28 }(this, function(root, Backbone, _, $) {
33 // Save the previous value of the `Backbone` variable, so that it can be
34 // restored later on, if `noConflict` is used.
35 var previousBackbone = root.Backbone;
37 // Create local references to array methods we'll want to use later.
39 var push = array.push;
40 var slice = array.slice;
41 var splice = array.splice;
43 // Current version of the library. Keep in sync with `package.json`.
44 Backbone.VERSION = '1.1.2';
46 // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
50 // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
51 // to its previous owner. Returns a reference to this Backbone object.
52 Backbone.noConflict = function() {
53 root.Backbone = previousBackbone;
57 // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
58 // will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and
59 // set a `X-Http-Method-Override` header.
60 Backbone.emulateHTTP = false;
62 // Turn on `emulateJSON` to support legacy servers that can't deal with direct
63 // `application/json` requests ... will encode the body as
64 // `application/x-www-form-urlencoded` instead and will send the model in a
65 // form param named `model`.
66 Backbone.emulateJSON = false;
71 // A module that can be mixed in to *any object* in order to provide it with
72 // custom events. You may bind with `on` or remove with `off` callback
73 // functions to an event; `trigger`-ing an event fires all callbacks in
77 // _.extend(object, Backbone.Events);
78 // object.on('expand', function(){ alert('expanded'); });
79 // object.trigger('expand');
81 var Events = Backbone.Events = {
83 // Bind an event to a `callback` function. Passing `"all"` will bind
84 // the callback to all events fired.
85 on: function(name, callback, context) {
86 if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
87 this._events || (this._events = {});
88 var events = this._events[name] || (this._events[name] = []);
89 events.push({callback: callback, context: context, ctx: context || this});
93 // Bind an event to only be triggered a single time. After the first time
94 // the callback is invoked, it will be removed.
95 once: function(name, callback, context) {
96 if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
98 var once = _.once(function() {
100 callback.apply(this, arguments);
102 once._callback = callback;
103 return this.on(name, once, context);
106 // Remove one or many callbacks. If `context` is null, removes all
107 // callbacks with that function. If `callback` is null, removes all
108 // callbacks for the event. If `name` is null, removes all bound
109 // callbacks for all events.
110 off: function(name, callback, context) {
111 var retain, ev, events, names, i, l, j, k;
112 if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
113 if (!name && !callback && !context) {
114 this._events = void 0;
117 names = name ? [name] : _.keys(this._events);
118 for (i = 0, l = names.length; i < l; i++) {
120 if (events = this._events[name]) {
121 this._events[name] = retain = [];
122 if (callback || context) {
123 for (j = 0, k = events.length; j < k; j++) {
125 if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
126 (context && context !== ev.context)) {
131 if (!retain.length) delete this._events[name];
138 // Trigger one or many events, firing all bound callbacks. Callbacks are
139 // passed the same arguments as `trigger` is, apart from the event name
140 // (unless you're listening on `"all"`, which will cause your callback to
141 // receive the true name of the event as the first argument).
142 trigger: function(name) {
143 if (!this._events) return this;
144 var args = slice.call(arguments, 1);
145 if (!eventsApi(this, 'trigger', name, args)) return this;
146 var events = this._events[name];
147 var allEvents = this._events.all;
148 if (events) triggerEvents(events, args);
149 if (allEvents) triggerEvents(allEvents, arguments);
153 // Tell this object to stop listening to either specific events ... or
154 // to every object it's currently listening to.
155 stopListening: function(obj, name, callback) {
156 var listeningTo = this._listeningTo;
157 if (!listeningTo) return this;
158 var remove = !name && !callback;
159 if (!callback && typeof name === 'object') callback = this;
160 if (obj) (listeningTo = {})[obj._listenId] = obj;
161 for (var id in listeningTo) {
162 obj = listeningTo[id];
163 obj.off(name, callback, this);
164 if (remove || _.isEmpty(obj._events)) delete this._listeningTo[id];
171 // Regular expression used to split event strings.
172 var eventSplitter = /\s+/;
174 // Implement fancy features of the Events API such as multiple event
175 // names `"change blur"` and jQuery-style event maps `{change: action}`
176 // in terms of the existing API.
177 var eventsApi = function(obj, action, name, rest) {
178 if (!name) return true;
180 // Handle event maps.
181 if (typeof name === 'object') {
182 for (var key in name) {
183 obj[action].apply(obj, [key, name[key]].concat(rest));
188 // Handle space separated event names.
189 if (eventSplitter.test(name)) {
190 var names = name.split(eventSplitter);
191 for (var i = 0, l = names.length; i < l; i++) {
192 obj[action].apply(obj, [names[i]].concat(rest));
200 // A difficult-to-believe, but optimized internal dispatch function for
201 // triggering events. Tries to keep the usual cases speedy (most internal
202 // Backbone events have 3 arguments).
203 var triggerEvents = function(events, args) {
204 var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
205 switch (args.length) {
206 case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
207 case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
208 case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
209 case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
210 default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return;
214 var listenMethods = {listenTo: 'on', listenToOnce: 'once'};
216 // Inversion-of-control versions of `on` and `once`. Tell *this* object to
217 // listen to an event in another object ... keeping track of what it's
219 _.each(listenMethods, function(implementation, method) {
220 Events[method] = function(obj, name, callback) {
221 var listeningTo = this._listeningTo || (this._listeningTo = {});
222 var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
223 listeningTo[id] = obj;
224 if (!callback && typeof name === 'object') callback = this;
225 obj[implementation](name, callback, this);
230 // Aliases for backwards compatibility.
231 Events.bind = Events.on;
232 Events.unbind = Events.off;
234 // Allow the `Backbone` object to serve as a global event bus, for folks who
235 // want global "pubsub" in a convenient place.
236 _.extend(Backbone, Events);
241 // Backbone **Models** are the basic data object in the framework --
242 // frequently representing a row in a table in a database on your server.
243 // A discrete chunk of data and a bunch of useful, related methods for
244 // performing computations and transformations on that data.
246 // Create a new model with the specified attributes. A client id (`cid`)
247 // is automatically generated and assigned for you.
248 var Model = Backbone.Model = function(attributes, options) {
249 var attrs = attributes || {};
250 options || (options = {});
251 this.cid = _.uniqueId('c');
252 this.attributes = {};
253 if (options.collection) this.collection = options.collection;
254 if (options.parse) attrs = this.parse(attrs, options) || {};
255 attrs = _.defaults({}, attrs, _.result(this, 'defaults'));
256 this.set(attrs, options);
258 this.initialize.apply(this, arguments);
261 // Attach all inheritable methods to the Model prototype.
262 _.extend(Model.prototype, Events, {
264 // A hash of attributes whose current and previous value differ.
267 // The value returned during the last failed validation.
268 validationError: null,
270 // The default name for the JSON `id` attribute is `"id"`. MongoDB and
271 // CouchDB users may want to set this to `"_id"`.
274 // Initialize is an empty function by default. Override it with your own
275 // initialization logic.
276 initialize: function(){},
278 // Return a copy of the model's `attributes` object.
279 toJSON: function(options) {
280 return _.clone(this.attributes);
283 // Proxy `Backbone.sync` by default -- but override this if you need
284 // custom syncing semantics for *this* particular model.
286 return Backbone.sync.apply(this, arguments);
289 // Get the value of an attribute.
290 get: function(attr) {
291 return this.attributes[attr];
294 // Get the HTML-escaped value of an attribute.
295 escape: function(attr) {
296 return _.escape(this.get(attr));
299 // Returns `true` if the attribute contains a value that is not null
301 has: function(attr) {
302 return this.get(attr) != null;
305 // Set a hash of model attributes on the object, firing `"change"`. This is
306 // the core primitive operation of a model, updating the data and notifying
307 // anyone who needs to know about the change in state. The heart of the beast.
308 set: function(key, val, options) {
309 var attr, attrs, unset, changes, silent, changing, prev, current;
310 if (key == null) return this;
312 // Handle both `"key", value` and `{key: value}` -style arguments.
313 if (typeof key === 'object') {
317 (attrs = {})[key] = val;
320 options || (options = {});
323 if (!this._validate(attrs, options)) return false;
325 // Extract attributes and options.
326 unset = options.unset;
327 silent = options.silent;
329 changing = this._changing;
330 this._changing = true;
333 this._previousAttributes = _.clone(this.attributes);
336 current = this.attributes, prev = this._previousAttributes;
338 // Check for changes of `id`.
339 if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
341 // For each `set` attribute, update or delete the current value.
342 for (attr in attrs) {
344 if (!_.isEqual(current[attr], val)) changes.push(attr);
345 if (!_.isEqual(prev[attr], val)) {
346 this.changed[attr] = val;
348 delete this.changed[attr];
350 unset ? delete current[attr] : current[attr] = val;
353 // Trigger all relevant attribute changes.
355 if (changes.length) this._pending = options;
356 for (var i = 0, l = changes.length; i < l; i++) {
357 this.trigger('change:' + changes[i], this, current[changes[i]], options);
361 // You might be wondering why there's a `while` loop here. Changes can
362 // be recursively nested within `"change"` events.
363 if (changing) return this;
365 while (this._pending) {
366 options = this._pending;
367 this._pending = false;
368 this.trigger('change', this, options);
371 this._pending = false;
372 this._changing = false;
376 // Remove an attribute from the model, firing `"change"`. `unset` is a noop
377 // if the attribute doesn't exist.
378 unset: function(attr, options) {
379 return this.set(attr, void 0, _.extend({}, options, {unset: true}));
382 // Clear all attributes on the model, firing `"change"`.
383 clear: function(options) {
385 for (var key in this.attributes) attrs[key] = void 0;
386 return this.set(attrs, _.extend({}, options, {unset: true}));
389 // Determine if the model has changed since the last `"change"` event.
390 // If you specify an attribute name, determine if that attribute has changed.
391 hasChanged: function(attr) {
392 if (attr == null) return !_.isEmpty(this.changed);
393 return _.has(this.changed, attr);
396 // Return an object containing all the attributes that have changed, or
397 // false if there are no changed attributes. Useful for determining what
398 // parts of a view need to be updated and/or what attributes need to be
399 // persisted to the server. Unset attributes will be set to undefined.
400 // You can also pass an attributes object to diff against the model,
401 // determining if there *would be* a change.
402 changedAttributes: function(diff) {
403 if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
404 var val, changed = false;
405 var old = this._changing ? this._previousAttributes : this.attributes;
406 for (var attr in diff) {
407 if (_.isEqual(old[attr], (val = diff[attr]))) continue;
408 (changed || (changed = {}))[attr] = val;
413 // Get the previous value of an attribute, recorded at the time the last
414 // `"change"` event was fired.
415 previous: function(attr) {
416 if (attr == null || !this._previousAttributes) return null;
417 return this._previousAttributes[attr];
420 // Get all of the attributes of the model at the time of the previous
422 previousAttributes: function() {
423 return _.clone(this._previousAttributes);
426 // Fetch the model from the server. If the server's representation of the
427 // model differs from its current attributes, they will be overridden,
428 // triggering a `"change"` event.
429 fetch: function(options) {
430 options = options ? _.clone(options) : {};
431 if (options.parse === void 0) options.parse = true;
433 var success = options.success;
434 options.success = function(resp) {
435 if (!model.set(model.parse(resp, options), options)) return false;
436 if (success) success(model, resp, options);
437 model.trigger('sync', model, resp, options);
439 wrapError(this, options);
440 return this.sync('read', this, options);
443 // Set a hash of model attributes, and sync the model to the server.
444 // If the server returns an attributes hash that differs, the model's
445 // state will be `set` again.
446 save: function(key, val, options) {
447 var attrs, method, xhr, attributes = this.attributes;
449 // Handle both `"key", value` and `{key: value}` -style arguments.
450 if (key == null || typeof key === 'object') {
454 (attrs = {})[key] = val;
457 options = _.extend({validate: true}, options);
459 // If we're not waiting and attributes exist, save acts as
460 // `set(attr).save(null, opts)` with validation. Otherwise, check if
461 // the model will be valid when the attributes, if any, are set.
462 if (attrs && !options.wait) {
463 if (!this.set(attrs, options)) return false;
465 if (!this._validate(attrs, options)) return false;
468 // Set temporary attributes if `{wait: true}`.
469 if (attrs && options.wait) {
470 this.attributes = _.extend({}, attributes, attrs);
473 // After a successful server-side save, the client is (optionally)
474 // updated with the server-side state.
475 if (options.parse === void 0) options.parse = true;
477 var success = options.success;
478 options.success = function(resp) {
479 // Ensure attributes are restored during synchronous saves.
480 model.attributes = attributes;
481 var serverAttrs = model.parse(resp, options);
482 if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
483 if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
486 if (success) success(model, resp, options);
487 model.trigger('sync', model, resp, options);
489 wrapError(this, options);
491 method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
492 if (method === 'patch') options.attrs = attrs;
493 xhr = this.sync(method, this, options);
495 // Restore attributes.
496 if (attrs && options.wait) this.attributes = attributes;
501 // Destroy this model on the server if it was already persisted.
502 // Optimistically removes the model from its collection, if it has one.
503 // If `wait: true` is passed, waits for the server to respond before removal.
504 destroy: function(options) {
505 options = options ? _.clone(options) : {};
507 var success = options.success;
509 var destroy = function() {
510 model.trigger('destroy', model, model.collection, options);
513 options.success = function(resp) {
514 if (options.wait || model.isNew()) destroy();
515 if (success) success(model, resp, options);
516 if (!model.isNew()) model.trigger('sync', model, resp, options);
523 wrapError(this, options);
525 var xhr = this.sync('delete', this, options);
526 if (!options.wait) destroy();
530 // Default URL for the model's representation on the server -- if you're
531 // using Backbone's restful methods, override this to change the endpoint
532 // that will be called.
535 _.result(this, 'urlRoot') ||
536 _.result(this.collection, 'url') ||
538 if (this.isNew()) return base;
539 return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id);
542 // **parse** converts a response into the hash of attributes to be `set` on
543 // the model. The default implementation is just to pass the response along.
544 parse: function(resp, options) {
548 // Create a new model with identical attributes to this one.
550 return new this.constructor(this.attributes);
553 // A model is new if it has never been saved to the server, and lacks an id.
555 return !this.has(this.idAttribute);
558 // Check if the model is currently in a valid state.
559 isValid: function(options) {
560 return this._validate({}, _.extend(options || {}, { validate: true }));
563 // Run validation against the next complete set of model attributes,
564 // returning `true` if all is well. Otherwise, fire an `"invalid"` event.
565 _validate: function(attrs, options) {
566 if (!options.validate || !this.validate) return true;
567 attrs = _.extend({}, this.attributes, attrs);
568 var error = this.validationError = this.validate(attrs, options) || null;
569 if (!error) return true;
570 this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
576 // Underscore methods that we want to implement on the Model.
577 var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit'];
579 // Mix in each Underscore method as a proxy to `Model#attributes`.
580 _.each(modelMethods, function(method) {
581 Model.prototype[method] = function() {
582 var args = slice.call(arguments);
583 args.unshift(this.attributes);
584 return _[method].apply(_, args);
588 // Backbone.Collection
589 // -------------------
591 // If models tend to represent a single row of data, a Backbone Collection is
592 // more analagous to a table full of data ... or a small slice or page of that
593 // table, or a collection of rows that belong together for a particular reason
594 // -- all of the messages in this particular folder, all of the documents
595 // belonging to this particular author, and so on. Collections maintain
596 // indexes of their models, both in order, and for lookup by `id`.
598 // Create a new **Collection**, perhaps to contain a specific type of `model`.
599 // If a `comparator` is specified, the Collection will maintain
600 // its models in sort order, as they're added and removed.
601 var Collection = Backbone.Collection = function(models, options) {
602 options || (options = {});
603 if (options.model) this.model = options.model;
604 if (options.comparator !== void 0) this.comparator = options.comparator;
606 this.initialize.apply(this, arguments);
607 if (models) this.reset(models, _.extend({silent: true}, options));
610 // Default options for `Collection#set`.
611 var setOptions = {add: true, remove: true, merge: true};
612 var addOptions = {add: true, remove: false};
614 // Define the Collection's inheritable methods.
615 _.extend(Collection.prototype, Events, {
617 // The default model for a collection is just a **Backbone.Model**.
618 // This should be overridden in most cases.
621 // Initialize is an empty function by default. Override it with your own
622 // initialization logic.
623 initialize: function(){},
625 // The JSON representation of a Collection is an array of the
626 // models' attributes.
627 toJSON: function(options) {
628 return this.map(function(model){ return model.toJSON(options); });
631 // Proxy `Backbone.sync` by default.
633 return Backbone.sync.apply(this, arguments);
636 // Add a model, or list of models to the set.
637 add: function(models, options) {
638 return this.set(models, _.extend({merge: false}, options, addOptions));
641 // Remove a model, or a list of models from the set.
642 remove: function(models, options) {
643 var singular = !_.isArray(models);
644 models = singular ? [models] : _.clone(models);
645 options || (options = {});
646 var i, l, index, model;
647 for (i = 0, l = models.length; i < l; i++) {
648 model = models[i] = this.get(models[i]);
649 if (!model) continue;
650 delete this._byId[model.id];
651 delete this._byId[model.cid];
652 index = this.indexOf(model);
653 this.models.splice(index, 1);
655 if (!options.silent) {
656 options.index = index;
657 model.trigger('remove', model, this, options);
659 this._removeReference(model, options);
661 return singular ? models[0] : models;
664 // Update a collection by `set`-ing a new list of models, adding new ones,
665 // removing models that are no longer present, and merging models that
666 // already exist in the collection, as necessary. Similar to **Model#set**,
667 // the core operation for updating the data contained by the collection.
668 set: function(models, options) {
669 options = _.defaults({}, options, setOptions);
670 if (options.parse) models = this.parse(models, options);
671 var singular = !_.isArray(models);
672 models = singular ? (models ? [models] : []) : _.clone(models);
673 var i, l, id, model, attrs, existing, sort;
675 var targetModel = this.model;
676 var sortable = this.comparator && (at == null) && options.sort !== false;
677 var sortAttr = _.isString(this.comparator) ? this.comparator : null;
678 var toAdd = [], toRemove = [], modelMap = {};
679 var add = options.add, merge = options.merge, remove = options.remove;
680 var order = !sortable && add && remove ? [] : false;
682 // Turn bare objects into model references, and prevent invalid models
684 for (i = 0, l = models.length; i < l; i++) {
685 attrs = models[i] || {};
686 if (attrs instanceof Model) {
689 id = attrs[targetModel.prototype.idAttribute || 'id'];
692 // If a duplicate is found, prevent it from being added and
693 // optionally merge it into the existing model.
694 if (existing = this.get(id)) {
695 if (remove) modelMap[existing.cid] = true;
697 attrs = attrs === model ? model.attributes : attrs;
698 if (options.parse) attrs = existing.parse(attrs, options);
699 existing.set(attrs, options);
700 if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
702 models[i] = existing;
704 // If this is a new, valid model, push it to the `toAdd` list.
706 model = models[i] = this._prepareModel(attrs, options);
707 if (!model) continue;
709 this._addReference(model, options);
712 // Do not add multiple models with the same `id`.
713 model = existing || model;
714 if (order && (model.isNew() || !modelMap[model.id])) order.push(model);
715 modelMap[model.id] = true;
718 // Remove nonexistent models if appropriate.
720 for (i = 0, l = this.length; i < l; ++i) {
721 if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
723 if (toRemove.length) this.remove(toRemove, options);
726 // See if sorting is needed, update `length` and splice in new models.
727 if (toAdd.length || (order && order.length)) {
728 if (sortable) sort = true;
729 this.length += toAdd.length;
731 for (i = 0, l = toAdd.length; i < l; i++) {
732 this.models.splice(at + i, 0, toAdd[i]);
735 if (order) this.models.length = 0;
736 var orderedModels = order || toAdd;
737 for (i = 0, l = orderedModels.length; i < l; i++) {
738 this.models.push(orderedModels[i]);
743 // Silently sort the collection if appropriate.
744 if (sort) this.sort({silent: true});
746 // Unless silenced, it's time to fire all appropriate add/sort events.
747 if (!options.silent) {
748 for (i = 0, l = toAdd.length; i < l; i++) {
749 (model = toAdd[i]).trigger('add', model, this, options);
751 if (sort || (order && order.length)) this.trigger('sort', this, options);
754 // Return the added (or merged) model (or models).
755 return singular ? models[0] : models;
758 // When you have more items than you want to add or remove individually,
759 // you can reset the entire set with a new list of models, without firing
760 // any granular `add` or `remove` events. Fires `reset` when finished.
761 // Useful for bulk operations and optimizations.
762 reset: function(models, options) {
763 options || (options = {});
764 for (var i = 0, l = this.models.length; i < l; i++) {
765 this._removeReference(this.models[i], options);
767 options.previousModels = this.models;
769 models = this.add(models, _.extend({silent: true}, options));
770 if (!options.silent) this.trigger('reset', this, options);
774 // Add a model to the end of the collection.
775 push: function(model, options) {
776 return this.add(model, _.extend({at: this.length}, options));
779 // Remove a model from the end of the collection.
780 pop: function(options) {
781 var model = this.at(this.length - 1);
782 this.remove(model, options);
786 // Add a model to the beginning of the collection.
787 unshift: function(model, options) {
788 return this.add(model, _.extend({at: 0}, options));
791 // Remove a model from the beginning of the collection.
792 shift: function(options) {
793 var model = this.at(0);
794 this.remove(model, options);
798 // Slice out a sub-array of models from the collection.
800 return slice.apply(this.models, arguments);
803 // Get a model from the set by id.
805 if (obj == null) return void 0;
806 return this._byId[obj] || this._byId[obj.id] || this._byId[obj.cid];
809 // Get the model at the given index.
810 at: function(index) {
811 return this.models[index];
814 // Return models with matching attributes. Useful for simple cases of
816 where: function(attrs, first) {
817 if (_.isEmpty(attrs)) return first ? void 0 : [];
818 return this[first ? 'find' : 'filter'](function(model) {
819 for (var key in attrs) {
820 if (attrs[key] !== model.get(key)) return false;
826 // Return the first model with matching attributes. Useful for simple cases
828 findWhere: function(attrs) {
829 return this.where(attrs, true);
832 // Force the collection to re-sort itself. You don't need to call this under
833 // normal circumstances, as the set will maintain sort order as each item
835 sort: function(options) {
836 if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
837 options || (options = {});
839 // Run sort based on type of `comparator`.
840 if (_.isString(this.comparator) || this.comparator.length === 1) {
841 this.models = this.sortBy(this.comparator, this);
843 this.models.sort(_.bind(this.comparator, this));
846 if (!options.silent) this.trigger('sort', this, options);
850 // Pluck an attribute from each model in the collection.
851 pluck: function(attr) {
852 return _.invoke(this.models, 'get', attr);
855 // Fetch the default set of models for this collection, resetting the
856 // collection when they arrive. If `reset: true` is passed, the response
857 // data will be passed through the `reset` method instead of `set`.
858 fetch: function(options) {
859 options = options ? _.clone(options) : {};
860 if (options.parse === void 0) options.parse = true;
861 var success = options.success;
862 var collection = this;
863 options.success = function(resp) {
864 var method = options.reset ? 'reset' : 'set';
865 collection[method](resp, options);
866 if (success) success(collection, resp, options);
867 collection.trigger('sync', collection, resp, options);
869 wrapError(this, options);
870 return this.sync('read', this, options);
873 // Create a new instance of a model in this collection. Add the model to the
874 // collection immediately, unless `wait: true` is passed, in which case we
875 // wait for the server to agree.
876 create: function(model, options) {
877 options = options ? _.clone(options) : {};
878 if (!(model = this._prepareModel(model, options))) return false;
879 if (!options.wait) this.add(model, options);
880 var collection = this;
881 var success = options.success;
882 options.success = function(model, resp) {
883 if (options.wait) collection.add(model, options);
884 if (success) success(model, resp, options);
886 model.save(null, options);
890 // **parse** converts a response into a list of models to be added to the
891 // collection. The default implementation is just to pass it through.
892 parse: function(resp, options) {
896 // Create a new collection with an identical list of models as this one.
898 return new this.constructor(this.models);
901 // Private method to reset all internal state. Called when the collection
902 // is first initialized or reset.
909 // Prepare a hash of attributes (or other model) to be added to this
911 _prepareModel: function(attrs, options) {
912 if (attrs instanceof Model) return attrs;
913 options = options ? _.clone(options) : {};
914 options.collection = this;
915 var model = new this.model(attrs, options);
916 if (!model.validationError) return model;
917 this.trigger('invalid', this, model.validationError, options);
921 // Internal method to create a model's ties to a collection.
922 _addReference: function(model, options) {
923 this._byId[model.cid] = model;
924 if (model.id != null) this._byId[model.id] = model;
925 if (!model.collection) model.collection = this;
926 model.on('all', this._onModelEvent, this);
929 // Internal method to sever a model's ties to a collection.
930 _removeReference: function(model, options) {
931 if (this === model.collection) delete model.collection;
932 model.off('all', this._onModelEvent, this);
935 // Internal method called every time a model in the set fires an event.
936 // Sets need to update their indexes when models change ids. All other
937 // events simply proxy through. "add" and "remove" events that originate
938 // in other collections are ignored.
939 _onModelEvent: function(event, model, collection, options) {
940 if ((event === 'add' || event === 'remove') && collection !== this) return;
941 if (event === 'destroy') this.remove(model, options);
942 if (model && event === 'change:' + model.idAttribute) {
943 delete this._byId[model.previous(model.idAttribute)];
944 if (model.id != null) this._byId[model.id] = model;
946 this.trigger.apply(this, arguments);
951 // Underscore methods that we want to implement on the Collection.
952 // 90% of the core usefulness of Backbone Collections is actually implemented
954 var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
955 'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
956 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
957 'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
958 'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle',
959 'lastIndexOf', 'isEmpty', 'chain', 'sample'];
961 // Mix in each Underscore method as a proxy to `Collection#models`.
962 _.each(methods, function(method) {
963 Collection.prototype[method] = function() {
964 var args = slice.call(arguments);
965 args.unshift(this.models);
966 return _[method].apply(_, args);
970 // Underscore methods that take a property name as an argument.
971 var attributeMethods = ['groupBy', 'countBy', 'sortBy', 'indexBy'];
973 // Use attributes instead of properties.
974 _.each(attributeMethods, function(method) {
975 Collection.prototype[method] = function(value, context) {
976 var iterator = _.isFunction(value) ? value : function(model) {
977 return model.get(value);
979 return _[method](this.models, iterator, context);
986 // Backbone Views are almost more convention than they are actual code. A View
987 // is simply a JavaScript object that represents a logical chunk of UI in the
988 // DOM. This might be a single item, an entire list, a sidebar or panel, or
989 // even the surrounding frame which wraps your whole app. Defining a chunk of
990 // UI as a **View** allows you to define your DOM events declaratively, without
991 // having to worry about render order ... and makes it easy for the view to
992 // react to specific changes in the state of your models.
994 // Creating a Backbone.View creates its initial element outside of the DOM,
995 // if an existing element is not provided...
996 var View = Backbone.View = function(options) {
997 this.cid = _.uniqueId('view');
998 options || (options = {});
999 _.extend(this, _.pick(options, viewOptions));
1000 this._ensureElement();
1001 this.initialize.apply(this, arguments);
1002 this.delegateEvents();
1005 // Cached regex to split keys for `delegate`.
1006 var delegateEventSplitter = /^(\S+)\s*(.*)$/;
1008 // List of view options to be merged as properties.
1009 var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
1011 // Set up all inheritable **Backbone.View** properties and methods.
1012 _.extend(View.prototype, Events, {
1014 // The default `tagName` of a View's element is `"div"`.
1017 // jQuery delegate for element lookup, scoped to DOM elements within the
1018 // current view. This should be preferred to global lookups where possible.
1019 $: function(selector) {
1020 return this.$el.find(selector);
1023 // Initialize is an empty function by default. Override it with your own
1024 // initialization logic.
1025 initialize: function(){},
1027 // **render** is the core function that your view should override, in order
1028 // to populate its element (`this.el`), with the appropriate HTML. The
1029 // convention is for **render** to always return `this`.
1030 render: function() {
1034 // Remove this view by taking the element out of the DOM, and removing any
1035 // applicable Backbone.Events listeners.
1036 remove: function() {
1038 this.stopListening();
1042 // Change the view's element (`this.el` property), including event
1044 setElement: function(element, delegate) {
1045 if (this.$el) this.undelegateEvents();
1046 this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
1047 this.el = this.$el[0];
1048 if (delegate !== false) this.delegateEvents();
1052 // Set callbacks, where `this.events` is a hash of
1054 // *{"event selector": "callback"}*
1057 // 'mousedown .title': 'edit',
1058 // 'click .button': 'save',
1059 // 'click .open': function(e) { ... }
1062 // pairs. Callbacks will be bound to the view, with `this` set properly.
1063 // Uses event delegation for efficiency.
1064 // Omitting the selector binds the event to `this.el`.
1065 // This only works for delegate-able events: not `focus`, `blur`, and
1066 // not `change`, `submit`, and `reset` in Internet Explorer.
1067 delegateEvents: function(events) {
1068 if (!(events || (events = _.result(this, 'events')))) return this;
1069 this.undelegateEvents();
1070 for (var key in events) {
1071 var method = events[key];
1072 if (!_.isFunction(method)) method = this[events[key]];
1073 if (!method) continue;
1075 var match = key.match(delegateEventSplitter);
1076 var eventName = match[1], selector = match[2];
1077 method = _.bind(method, this);
1078 eventName += '.delegateEvents' + this.cid;
1079 if (selector === '') {
1080 this.$el.on(eventName, method);
1082 this.$el.on(eventName, selector, method);
1088 // Clears all callbacks previously bound to the view with `delegateEvents`.
1089 // You usually don't need to use this, but may wish to if you have multiple
1090 // Backbone views attached to the same DOM element.
1091 undelegateEvents: function() {
1092 this.$el.off('.delegateEvents' + this.cid);
1096 // Ensure that the View has a DOM element to render into.
1097 // If `this.el` is a string, pass it through `$()`, take the first
1098 // matching element, and re-assign it to `el`. Otherwise, create
1099 // an element from the `id`, `className` and `tagName` properties.
1100 _ensureElement: function() {
1102 var attrs = _.extend({}, _.result(this, 'attributes'));
1103 if (this.id) attrs.id = _.result(this, 'id');
1104 if (this.className) attrs['class'] = _.result(this, 'className');
1105 var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
1106 this.setElement($el, false);
1108 this.setElement(_.result(this, 'el'), false);
1117 // Override this function to change the manner in which Backbone persists
1118 // models to the server. You will be passed the type of request, and the
1119 // model in question. By default, makes a RESTful Ajax request
1120 // to the model's `url()`. Some possible customizations could be:
1122 // * Use `setTimeout` to batch rapid-fire updates into a single request.
1123 // * Send up the models as XML instead of JSON.
1124 // * Persist models via WebSockets instead of Ajax.
1126 // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
1127 // as `POST`, with a `_method` parameter containing the true HTTP method,
1128 // as well as all requests with the body as `application/x-www-form-urlencoded`
1129 // instead of `application/json` with the model in a param named `model`.
1130 // Useful when interfacing with server-side languages like **PHP** that make
1131 // it difficult to read the body of `PUT` requests.
1132 Backbone.sync = function(method, model, options) {
1133 var type = methodMap[method];
1135 // Default options, unless specified.
1136 _.defaults(options || (options = {}), {
1137 emulateHTTP: Backbone.emulateHTTP,
1138 emulateJSON: Backbone.emulateJSON
1141 // Default JSON-request options.
1142 var params = {type: type, dataType: 'json'};
1144 // Ensure that we have a URL.
1146 params.url = _.result(model, 'url') || urlError();
1149 // Ensure that we have the appropriate request data.
1150 if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
1151 params.contentType = 'application/json';
1152 params.data = JSON.stringify(options.attrs || model.toJSON(options));
1155 // For older servers, emulate JSON by encoding the request into an HTML-form.
1156 if (options.emulateJSON) {
1157 params.contentType = 'application/x-www-form-urlencoded';
1158 params.data = params.data ? {model: params.data} : {};
1161 // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1162 // And an `X-HTTP-Method-Override` header.
1163 if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
1164 params.type = 'POST';
1165 if (options.emulateJSON) params.data._method = type;
1166 var beforeSend = options.beforeSend;
1167 options.beforeSend = function(xhr) {
1168 xhr.setRequestHeader('X-HTTP-Method-Override', type);
1169 if (beforeSend) return beforeSend.apply(this, arguments);
1173 // Don't process data on a non-GET request.
1174 if (params.type !== 'GET' && !options.emulateJSON) {
1175 params.processData = false;
1178 // If we're sending a `PATCH` request, and we're in an old Internet Explorer
1179 // that still has ActiveX enabled by default, override jQuery to use that
1180 // for XHR instead. Remove this line when jQuery supports `PATCH` on IE8.
1181 if (params.type === 'PATCH' && noXhrPatch) {
1182 params.xhr = function() {
1183 return new ActiveXObject("Microsoft.XMLHTTP");
1187 // Make the request, allowing the user to override any Ajax options.
1188 var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
1189 model.trigger('request', model, xhr, options);
1194 typeof window !== 'undefined' && !!window.ActiveXObject &&
1195 !(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent);
1197 // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1206 // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
1207 // Override this if you'd like to use a different library.
1208 Backbone.ajax = function() {
1209 return Backbone.$.ajax.apply(Backbone.$, arguments);
1215 // Routers map faux-URLs to actions, and fire events when routes are
1216 // matched. Creating a new one sets its `routes` hash, if not set statically.
1217 var Router = Backbone.Router = function(options) {
1218 options || (options = {});
1219 if (options.routes) this.routes = options.routes;
1221 this.initialize.apply(this, arguments);
1224 // Cached regular expressions for matching named param parts and splatted
1225 // parts of route strings.
1226 var optionalParam = /\((.*?)\)/g;
1227 var namedParam = /(\(\?)?:\w+/g;
1228 var splatParam = /\*\w+/g;
1229 var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
1231 // Set up all inheritable **Backbone.Router** properties and methods.
1232 _.extend(Router.prototype, Events, {
1234 // Initialize is an empty function by default. Override it with your own
1235 // initialization logic.
1236 initialize: function(){},
1238 // Manually bind a single named route to a callback. For example:
1240 // this.route('search/:query/p:num', 'search', function(query, num) {
1244 route: function(route, name, callback) {
1245 if (!_.isRegExp(route)) route = this._routeToRegExp(route);
1246 if (_.isFunction(name)) {
1250 if (!callback) callback = this[name];
1252 Backbone.history.route(route, function(fragment) {
1253 var args = router._extractParameters(route, fragment);
1254 router.execute(callback, args);
1255 router.trigger.apply(router, ['route:' + name].concat(args));
1256 router.trigger('route', name, args);
1257 Backbone.history.trigger('route', router, name, args);
1262 // Execute a route handler with the provided parameters. This is an
1263 // excellent place to do pre-route setup or post-route cleanup.
1264 execute: function(callback, args) {
1265 if (callback) callback.apply(this, args);
1268 // Simple proxy to `Backbone.history` to save a fragment into the history.
1269 navigate: function(fragment, options) {
1270 Backbone.history.navigate(fragment, options);
1274 // Bind all defined routes to `Backbone.history`. We have to reverse the
1275 // order of the routes here to support behavior where the most general
1276 // routes can be defined at the bottom of the route map.
1277 _bindRoutes: function() {
1278 if (!this.routes) return;
1279 this.routes = _.result(this, 'routes');
1280 var route, routes = _.keys(this.routes);
1281 while ((route = routes.pop()) != null) {
1282 this.route(route, this.routes[route]);
1286 // Convert a route string into a regular expression, suitable for matching
1287 // against the current location hash.
1288 _routeToRegExp: function(route) {
1289 route = route.replace(escapeRegExp, '\\$&')
1290 .replace(optionalParam, '(?:$1)?')
1291 .replace(namedParam, function(match, optional) {
1292 return optional ? match : '([^/?]+)';
1294 .replace(splatParam, '([^?]*?)');
1295 return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$');
1298 // Given a route, and a URL fragment that it matches, return the array of
1299 // extracted decoded parameters. Empty or unmatched parameters will be
1300 // treated as `null` to normalize cross-browser behavior.
1301 _extractParameters: function(route, fragment) {
1302 var params = route.exec(fragment).slice(1);
1303 return _.map(params, function(param, i) {
1304 // Don't decode the search params.
1305 if (i === params.length - 1) return param || null;
1306 return param ? decodeURIComponent(param) : null;
1315 // Handles cross-browser history management, based on either
1316 // [pushState](http://diveintohtml5.info/history.html) and real URLs, or
1317 // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
1318 // and URL fragments. If the browser supports neither (old IE, natch),
1319 // falls back to polling.
1320 var History = Backbone.History = function() {
1322 _.bindAll(this, 'checkUrl');
1324 // Ensure that `History` can be used outside of the browser.
1325 if (typeof window !== 'undefined') {
1326 this.location = window.location;
1327 this.history = window.history;
1331 // Cached regex for stripping a leading hash/slash and trailing space.
1332 var routeStripper = /^[#\/]|\s+$/g;
1334 // Cached regex for stripping leading and trailing slashes.
1335 var rootStripper = /^\/+|\/+$/g;
1337 // Cached regex for detecting MSIE.
1338 var isExplorer = /msie [\w.]+/;
1340 // Cached regex for removing a trailing slash.
1341 var trailingSlash = /\/$/;
1343 // Cached regex for stripping urls of hash.
1344 var pathStripper = /#.*$/;
1346 // Has the history handling already been started?
1347 History.started = false;
1349 // Set up all inheritable **Backbone.History** properties and methods.
1350 _.extend(History.prototype, Events, {
1352 // The default interval to poll for hash changes, if necessary, is
1353 // twenty times a second.
1356 // Are we at the app root?
1357 atRoot: function() {
1358 return this.location.pathname.replace(/[^\/]$/, '$&/') === this.root;
1361 // Gets the true hash value. Cannot use location.hash directly due to bug
1362 // in Firefox where location.hash will always be decoded.
1363 getHash: function(window) {
1364 var match = (window || this).location.href.match(/#(.*)$/);
1365 return match ? match[1] : '';
1368 // Get the cross-browser normalized URL fragment, either from the URL,
1369 // the hash, or the override.
1370 getFragment: function(fragment, forcePushState) {
1371 if (fragment == null) {
1372 if (this._hasPushState || !this._wantsHashChange || forcePushState) {
1373 fragment = decodeURI(this.location.pathname + this.location.search);
1374 var root = this.root.replace(trailingSlash, '');
1375 if (!fragment.indexOf(root)) fragment = fragment.slice(root.length);
1377 fragment = this.getHash();
1380 return fragment.replace(routeStripper, '');
1383 // Start the hash change handling, returning `true` if the current URL matches
1384 // an existing route, and `false` otherwise.
1385 start: function(options) {
1386 if (History.started) throw new Error("Backbone.history has already been started");
1387 History.started = true;
1389 // Figure out the initial configuration. Do we need an iframe?
1390 // Is pushState desired ... is it available?
1391 this.options = _.extend({root: '/'}, this.options, options);
1392 this.root = this.options.root;
1393 this._wantsHashChange = this.options.hashChange !== false;
1394 this._wantsPushState = !!this.options.pushState;
1395 this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState);
1396 var fragment = this.getFragment();
1397 var docMode = document.documentMode;
1398 var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
1400 // Normalize root to always include a leading and trailing slash.
1401 this.root = ('/' + this.root + '/').replace(rootStripper, '/');
1403 if (oldIE && this._wantsHashChange) {
1404 var frame = Backbone.$('<iframe src="javascript:0" tabindex="-1">');
1405 this.iframe = frame.hide().appendTo('body')[0].contentWindow;
1406 this.navigate(fragment);
1409 // Depending on whether we're using pushState or hashes, and whether
1410 // 'onhashchange' is supported, determine how we check the URL state.
1411 if (this._hasPushState) {
1412 Backbone.$(window).on('popstate', this.checkUrl);
1413 } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
1414 Backbone.$(window).on('hashchange', this.checkUrl);
1415 } else if (this._wantsHashChange) {
1416 this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
1419 // Determine if we need to change the base url, for a pushState link
1420 // opened by a non-pushState browser.
1421 this.fragment = fragment;
1422 var loc = this.location;
1424 // Transition from hashChange to pushState or vice versa if both are
1426 if (this._wantsHashChange && this._wantsPushState) {
1428 // If we've started off with a route from a `pushState`-enabled
1429 // browser, but we're currently in a browser that doesn't support it...
1430 if (!this._hasPushState && !this.atRoot()) {
1431 this.fragment = this.getFragment(null, true);
1432 this.location.replace(this.root + '#' + this.fragment);
1433 // Return immediately as browser will do redirect to new url
1436 // Or if we've started out with a hash-based route, but we're currently
1437 // in a browser where it could be `pushState`-based instead...
1438 } else if (this._hasPushState && this.atRoot() && loc.hash) {
1439 this.fragment = this.getHash().replace(routeStripper, '');
1440 this.history.replaceState({}, document.title, this.root + this.fragment);
1445 if (!this.options.silent) return this.loadUrl();
1448 // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
1449 // but possibly useful for unit testing Routers.
1451 Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl);
1452 if (this._checkUrlInterval) clearInterval(this._checkUrlInterval);
1453 History.started = false;
1456 // Add a route to be tested when the fragment changes. Routes added later
1457 // may override previous routes.
1458 route: function(route, callback) {
1459 this.handlers.unshift({route: route, callback: callback});
1462 // Checks the current URL to see if it has changed, and if it has,
1463 // calls `loadUrl`, normalizing across the hidden iframe.
1464 checkUrl: function(e) {
1465 var current = this.getFragment();
1466 if (current === this.fragment && this.iframe) {
1467 current = this.getFragment(this.getHash(this.iframe));
1469 if (current === this.fragment) return false;
1470 if (this.iframe) this.navigate(current);
1474 // Attempt to load the current URL fragment. If a route succeeds with a
1475 // match, returns `true`. If no defined routes matches the fragment,
1477 loadUrl: function(fragment) {
1478 fragment = this.fragment = this.getFragment(fragment);
1479 return _.any(this.handlers, function(handler) {
1480 if (handler.route.test(fragment)) {
1481 handler.callback(fragment);
1487 // Save a fragment into the hash history, or replace the URL state if the
1488 // 'replace' option is passed. You are responsible for properly URL-encoding
1489 // the fragment in advance.
1491 // The options object can contain `trigger: true` if you wish to have the
1492 // route callback be fired (not usually desirable), or `replace: true`, if
1493 // you wish to modify the current URL without adding an entry to the history.
1494 navigate: function(fragment, options) {
1495 if (!History.started) return false;
1496 if (!options || options === true) options = {trigger: !!options};
1498 var url = this.root + (fragment = this.getFragment(fragment || ''));
1500 // Strip the hash for matching.
1501 fragment = fragment.replace(pathStripper, '');
1503 if (this.fragment === fragment) return;
1504 this.fragment = fragment;
1506 // Don't include a trailing slash on the root.
1507 if (fragment === '' && url !== '/') url = url.slice(0, -1);
1509 // If pushState is available, we use it to set the fragment as a real URL.
1510 if (this._hasPushState) {
1511 this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
1513 // If hash changes haven't been explicitly disabled, update the hash
1514 // fragment to store history.
1515 } else if (this._wantsHashChange) {
1516 this._updateHash(this.location, fragment, options.replace);
1517 if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) {
1518 // Opening and closing the iframe tricks IE7 and earlier to push a
1519 // history entry on hash-tag change. When replace is true, we don't
1521 if(!options.replace) this.iframe.document.open().close();
1522 this._updateHash(this.iframe.location, fragment, options.replace);
1525 // If you've told us that you explicitly don't want fallback hashchange-
1526 // based history, then `navigate` becomes a page refresh.
1528 return this.location.assign(url);
1530 if (options.trigger) return this.loadUrl(fragment);
1533 // Update the hash location, either replacing the current entry, or adding
1534 // a new one to the browser history.
1535 _updateHash: function(location, fragment, replace) {
1537 var href = location.href.replace(/(javascript:|#).*$/, '');
1538 location.replace(href + '#' + fragment);
1540 // Some browsers require that `hash` contains a leading #.
1541 location.hash = '#' + fragment;
1547 // Create the default Backbone.history.
1548 Backbone.history = new History;
1553 // Helper function to correctly set up the prototype chain, for subclasses.
1554 // Similar to `goog.inherits`, but uses a hash of prototype properties and
1555 // class properties to be extended.
1556 var extend = function(protoProps, staticProps) {
1560 // The constructor function for the new subclass is either defined by you
1561 // (the "constructor" property in your `extend` definition), or defaulted
1562 // by us to simply call the parent's constructor.
1563 if (protoProps && _.has(protoProps, 'constructor')) {
1564 child = protoProps.constructor;
1566 child = function(){ return parent.apply(this, arguments); };
1569 // Add static properties to the constructor function, if supplied.
1570 _.extend(child, parent, staticProps);
1572 // Set the prototype chain to inherit from `parent`, without calling
1573 // `parent`'s constructor function.
1574 var Surrogate = function(){ this.constructor = child; };
1575 Surrogate.prototype = parent.prototype;
1576 child.prototype = new Surrogate;
1578 // Add prototype properties (instance properties) to the subclass,
1580 if (protoProps) _.extend(child.prototype, protoProps);
1582 // Set a convenience property in case the parent's prototype is needed
1584 child.__super__ = parent.prototype;
1589 // Set up inheritance for the model, collection, router, view and history.
1590 Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
1592 // Throw an error when a URL is needed, and none is supplied.
1593 var urlError = function() {
1594 throw new Error('A "url" property or function must be specified');
1597 // Wrap an optional error callback with a fallback error event.
1598 var wrapError = function(model, options) {
1599 var error = options.error;
1600 options.error = function(resp) {
1601 if (error) error(model, resp, options);
1602 model.trigger('error', model, resp, options);