2 // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
3 // Backbone may be freely distributed under the MIT license.
4 // For all details and documentation:
5 // http://backbonejs.org
12 // Save a reference to the global object (`window` in the browser, `global`
16 // Save the previous value of the `Backbone` variable, so that it can be
17 // restored later on, if `noConflict` is used.
18 var previousBackbone
= root
.Backbone
;
20 // Create a local reference to slice/splice.
21 var slice
= Array
.prototype.slice
;
22 var splice
= Array
.prototype.splice
;
24 // The top-level namespace. All public Backbone classes and modules will
25 // be attached to this. Exported for both CommonJS and the browser.
27 if (typeof exports
!== 'undefined') {
30 Backbone
= root
.Backbone
= {};
33 // Current version of the library. Keep in sync with `package.json`.
34 Backbone
.VERSION
= '0.5.3';
36 // Require Underscore, if we're on the server, and it's not already present.
38 if (!_
&& (typeof require
!== 'undefined')) _
= require('underscore');
40 // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
41 var $ = root
.jQuery
|| root
.Zepto
|| root
.ender
;
43 // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
44 // to its previous owner. Returns a reference to this Backbone object.
45 Backbone
.noConflict = function() {
46 root
.Backbone
= previousBackbone
;
50 // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
51 // will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
52 // set a `X-Http-Method-Override` header.
53 Backbone
.emulateHTTP
= false;
55 // Turn on `emulateJSON` to support legacy servers that can't deal with direct
56 // `application/json` requests ... will encode the body as
57 // `application/x-www-form-urlencoded` instead and will send the model in a
58 // form param named `model`.
59 Backbone
.emulateJSON
= false;
64 // A module that can be mixed in to *any object* in order to provide it with
65 // custom events. You may bind with `on` or remove with `off` callback functions
66 // to an event; trigger`-ing an event fires all callbacks in succession.
69 // _.extend(object, Backbone.Events);
70 // object.on('expand', function(){ alert('expanded'); });
71 // object.trigger('expand');
75 // Bind an event, specified by a string name, `ev`, to a `callback`
76 // function. Passing `"all"` will bind the callback to all events fired.
77 on: function(events
, callback
, context
) {
79 events
= events
.split(/\s+/);
80 var calls
= this._callbacks
|| (this._callbacks
= {});
81 while (ev
= events
.shift()) {
82 // Create an immutable callback list, allowing traversal during
83 // modification. The tail is an empty object that will always be used
85 var list
= calls
[ev
] || (calls
[ev
] = {});
86 var tail
= list
.tail
|| (list
.tail
= list
.next
= {});
87 tail
.callback
= callback
;
88 tail
.context
= context
;
89 list
.tail
= tail
.next
= {};
94 // Remove one or many callbacks. If `context` is null, removes all callbacks
95 // with that function. If `callback` is null, removes all callbacks for the
96 // event. If `ev` is null, removes all bound callbacks for all events.
97 off: function(events
, callback
, context
) {
100 delete this._callbacks
;
101 } else if (calls
= this._callbacks
) {
102 events
= events
.split(/\s+/);
103 while (ev
= events
.shift()) {
106 if (!callback
|| !node
) continue;
107 // Create a new list, omitting the indicated event/context pairs.
108 while ((node
= node
.next
) && node
.next
) {
109 if (node
.callback
=== callback
&&
110 (!context
|| node
.context
=== context
)) continue;
111 this.on(ev
, node
.callback
, node
.context
);
118 // Trigger an event, firing all bound callbacks. Callbacks are passed the
119 // same arguments as `trigger` is, apart from the event name.
120 // Listening for `"all"` passes the true event name as the first argument.
121 trigger: function(events
) {
122 var event
, node
, calls
, tail
, args
, all
, rest
;
123 if (!(calls
= this._callbacks
)) return this;
125 (events
= events
.split(/\s+/)).push(null);
126 // Save references to the current heads & tails.
127 while (event
= events
.shift()) {
128 if (all
) events
.push({next
: all
.next
, tail
: all
.tail
, event
: event
});
129 if (!(node
= calls
[event
])) continue;
130 events
.push({next
: node
.next
, tail
: node
.tail
});
132 // Traverse each list, stopping when the saved tail is reached.
133 rest
= slice
.call(arguments
, 1);
134 while (node
= events
.pop()) {
136 args
= node
.event
? [node
.event
].concat(rest
) : rest
;
137 while ((node
= node
.next
) !== tail
) {
138 node
.callback
.apply(node
.context
|| this, args
);
146 // Aliases for backwards compatibility.
147 Backbone
.Events
.bind
= Backbone
.Events
.on
;
148 Backbone
.Events
.unbind
= Backbone
.Events
.off
;
153 // Create a new model, with defined attributes. A client id (`cid`)
154 // is automatically generated and assigned for you.
155 Backbone
.Model = function(attributes
, options
) {
157 attributes
|| (attributes
= {});
158 if (options
&& options
.parse
) attributes
= this.parse(attributes
);
159 if (defaults
= getValue(this, 'defaults')) {
160 attributes
= _
.extend({}, defaults
, attributes
);
162 if (options
&& options
.collection
) this.collection
= options
.collection
;
163 this.attributes
= {};
164 this._escapedAttributes
= {};
165 this.cid
= _
.uniqueId('c');
166 if (!this.set(attributes
, {silent
: true})) {
167 throw new Error("Can't create an invalid model");
169 this._changed
= false;
170 this._previousAttributes
= _
.clone(this.attributes
);
171 this.initialize
.apply(this, arguments
);
174 // Attach all inheritable methods to the Model prototype.
175 _
.extend(Backbone
.Model
.prototype, Backbone
.Events
, {
177 // Has the item been changed since the last `"change"` event?
180 // The default name for the JSON `id` attribute is `"id"`. MongoDB and
181 // CouchDB users may want to set this to `"_id"`.
184 // Initialize is an empty function by default. Override it with your own
185 // initialization logic.
186 initialize: function(){},
188 // Return a copy of the model's `attributes` object.
190 return _
.clone(this.attributes
);
193 // Get the value of an attribute.
194 get: function(attr
) {
195 return this.attributes
[attr
];
198 // Get the HTML-escaped value of an attribute.
199 escape: function(attr
) {
201 if (html
= this._escapedAttributes
[attr
]) return html
;
202 var val
= this.attributes
[attr
];
203 return this._escapedAttributes
[attr
] = _
.escape(val
== null ? '' : '' + val
);
206 // Returns `true` if the attribute contains a value that is not null
208 has: function(attr
) {
209 return this.attributes
[attr
] != null;
212 // Set a hash of model attributes on the object, firing `"change"` unless
213 // you choose to silence it.
214 set: function(key
, value
, options
) {
215 var attrs
, attr
, val
;
216 if (_
.isObject(key
) || key
== null) {
224 // Extract attributes and options.
225 options
|| (options
= {});
226 if (!attrs
) return this;
227 if (attrs
instanceof Backbone
.Model
) attrs
= attrs
.attributes
;
228 if (options
.unset
) for (var attr
in attrs
) attrs
[attr
] = void 0;
229 var now
= this.attributes
, escaped
= this._escapedAttributes
;
232 if (this.validate
&& !this._performValidation(attrs
, options
)) return false;
234 // Check for changes of `id`.
235 if (this.idAttribute
in attrs
) this.id
= attrs
[this.idAttribute
];
237 // We're about to start triggering change events.
238 var alreadyChanging
= this._changing
;
239 this._changing
= true;
241 // Update attributes.
243 for (attr
in attrs
) {
245 if (!_
.isEqual(now
[attr
], val
) || (options
.unset
&& (attr
in now
))) {
246 delete escaped
[attr
];
247 this._changed
= true;
250 options
.unset
? delete now
[attr
] : now
[attr
] = val
;
253 // Fire `change:attribute` events.
254 for (var attr
in changes
) {
255 if (!options
.silent
) this.trigger('change:' + attr
, this, changes
[attr
], options
);
258 // Fire the `"change"` event, if the model has been changed.
259 if (!alreadyChanging
) {
260 if (!options
.silent
&& this._changed
) this.change(options
);
261 this._changing
= false;
266 // Remove an attribute from the model, firing `"change"` unless you choose
267 // to silence it. `unset` is a noop if the attribute doesn't exist.
268 unset: function(attr
, options
) {
269 (options
|| (options
= {})).unset
= true;
270 return this.set(attr
, null, options
);
273 // Clear all attributes on the model, firing `"change"` unless you choose
275 clear: function(options
) {
276 (options
|| (options
= {})).unset
= true;
277 return this.set(_
.clone(this.attributes
), options
);
280 // Fetch the model from the server. If the server's representation of the
281 // model differs from its current attributes, they will be overriden,
282 // triggering a `"change"` event.
283 fetch: function(options
) {
284 options
= options
? _
.clone(options
) : {};
286 var success
= options
.success
;
287 options
.success = function(resp
, status
, xhr
) {
288 if (!model
.set(model
.parse(resp
, xhr
), options
)) return false;
289 if (success
) success(model
, resp
);
291 options
.error
= Backbone
.wrapError(options
.error
, model
, options
);
292 return (this.sync
|| Backbone
.sync
).call(this, 'read', this, options
);
295 // Set a hash of model attributes, and sync the model to the server.
296 // If the server returns an attributes hash that differs, the model's
297 // state will be `set` again.
298 save: function(key
, value
, options
) {
300 if (_
.isObject(key
) || key
== null) {
308 options
= options
? _
.clone(options
) : {};
309 if (attrs
&& !this[options
.wait
? '_performValidation' : 'set'](attrs
, options
)) return false;
311 var success
= options
.success
;
312 options
.success = function(resp
, status
, xhr
) {
313 var serverAttrs
= model
.parse(resp
, xhr
);
314 if (options
.wait
) serverAttrs
= _
.extend(attrs
|| {}, serverAttrs
);
315 if (!model
.set(serverAttrs
, options
)) return false;
317 success(model
, resp
);
319 model
.trigger('sync', model
, resp
, options
);
322 options
.error
= Backbone
.wrapError(options
.error
, model
, options
);
323 var method
= this.isNew() ? 'create' : 'update';
324 return (this.sync
|| Backbone
.sync
).call(this, method
, this, options
);
327 // Destroy this model on the server if it was already persisted.
328 // Optimistically removes the model from its collection, if it has one.
329 // If `wait: true` is passed, waits for the server to respond before removal.
330 destroy: function(options
) {
331 options
= options
? _
.clone(options
) : {};
333 var success
= options
.success
;
335 var triggerDestroy = function() {
336 model
.trigger('destroy', model
, model
.collection
, options
);
339 if (this.isNew()) return triggerDestroy();
340 options
.success = function(resp
) {
341 if (options
.wait
) triggerDestroy();
343 success(model
, resp
);
345 model
.trigger('sync', model
, resp
, options
);
348 options
.error
= Backbone
.wrapError(options
.error
, model
, options
);
349 var xhr
= (this.sync
|| Backbone
.sync
).call(this, 'delete', this, options
);
350 if (!options
.wait
) triggerDestroy();
354 // Default URL for the model's representation on the server -- if you're
355 // using Backbone's restful methods, override this to change the endpoint
356 // that will be called.
358 var base
= getValue(this.collection
, 'url') || getValue(this, 'urlRoot') || urlError();
359 if (this.isNew()) return base
;
360 return base
+ (base
.charAt(base
.length
- 1) == '/' ? '' : '/') + encodeURIComponent(this.id
);
363 // **parse** converts a response into the hash of attributes to be `set` on
364 // the model. The default implementation is just to pass the response along.
365 parse: function(resp
, xhr
) {
369 // Create a new model with identical attributes to this one.
371 return new this.constructor(this.attributes
);
374 // A model is new if it has never been saved to the server, and lacks an id.
376 return this.id
== null;
379 // Call this method to manually fire a `change` event for this model.
380 // Calling this will cause all objects observing the model to update.
381 change: function(options
) {
382 this.trigger('change', this, options
);
383 this._previousAttributes
= _
.clone(this.attributes
);
384 this._changed
= false;
387 // Determine if the model has changed since the last `"change"` event.
388 // If you specify an attribute name, determine if that attribute has changed.
389 hasChanged: function(attr
) {
390 if (attr
) return !_
.isEqual(this._previousAttributes
[attr
], this.attributes
[attr
]);
391 return this._changed
;
394 // Return an object containing all the attributes that have changed, or
395 // false if there are no changed attributes. Useful for determining what
396 // parts of a view need to be updated and/or what attributes need to be
397 // persisted to the server. Unset attributes will be set to undefined.
398 changedAttributes: function(now
) {
399 if (!this._changed
) return false;
400 now
|| (now
= this.attributes
);
401 var changed
= false, old
= this._previousAttributes
;
402 for (var attr
in now
) {
403 if (_
.isEqual(old
[attr
], now
[attr
])) continue;
404 (changed
|| (changed
= {}))[attr
] = now
[attr
];
406 for (var attr
in old
) {
407 if (!(attr
in now
)) (changed
|| (changed
= {}))[attr
] = void 0;
412 // Get the previous value of an attribute, recorded at the time the last
413 // `"change"` event was fired.
414 previous: function(attr
) {
415 if (!attr
|| !this._previousAttributes
) return null;
416 return this._previousAttributes
[attr
];
419 // Get all of the attributes of the model at the time of the previous
421 previousAttributes: function() {
422 return _
.clone(this._previousAttributes
);
425 // Run validation against a set of incoming attributes, returning `true`
426 // if all is well. If a specific `error` callback has been passed,
427 // call that instead of firing the general `"error"` event.
428 _performValidation: function(attrs
, options
) {
429 var newAttrs
= _
.extend({}, this.attributes
, attrs
);
430 var error
= this.validate(newAttrs
, options
);
433 options
.error(this, error
, options
);
435 this.trigger('error', this, error
, options
);
444 // Backbone.Collection
445 // -------------------
447 // Provides a standard collection class for our sets of models, ordered
448 // or unordered. If a `comparator` is specified, the Collection will maintain
449 // its models in sort order, as they're added and removed.
450 Backbone
.Collection = function(models
, options
) {
451 options
|| (options
= {});
452 if (options
.comparator
) this.comparator
= options
.comparator
;
454 this.initialize
.apply(this, arguments
);
455 if (models
) this.reset(models
, {silent
: true, parse
: options
.parse
});
458 // Define the Collection's inheritable methods.
459 _
.extend(Backbone
.Collection
.prototype, Backbone
.Events
, {
461 // The default model for a collection is just a **Backbone.Model**.
462 // This should be overridden in most cases.
463 model
: Backbone
.Model
,
465 // Initialize is an empty function by default. Override it with your own
466 // initialization logic.
467 initialize: function(){},
469 // The JSON representation of a Collection is an array of the
470 // models' attributes.
472 return this.map(function(model
){ return model
.toJSON(); });
475 // Add a model, or list of models to the set. Pass **silent** to avoid
476 // firing the `add` event for every new model.
477 add: function(models
, options
) {
478 var i
, index
, length
, model
, cids
= {};
479 options
|| (options
= {});
480 models
= _
.isArray(models
) ? models
.slice() : [models
];
482 // Begin by turning bare objects into model references, and preventing
483 // invalid models or duplicate models from being added.
484 for (i
= 0, length
= models
.length
; i
< length
; i
++) {
485 if (!(model
= models
[i
] = this._prepareModel(models
[i
], options
))) {
486 throw new Error("Can't add an invalid model to a collection");
488 var hasId
= model
.id
!= null;
489 if (this._byCid
[model
.cid
] || (hasId
&& this._byId
[model
.id
])) {
490 throw new Error("Can't add the same model to a collection twice");
494 // Listen to added models' events, and index models for lookup by
495 // `id` and by `cid`.
496 for (i
= 0; i
< length
; i
++) {
497 (model
= models
[i
]).on('all', this._onModelEvent
, this);
498 this._byCid
[model
.cid
] = model
;
499 if (model
.id
!= null) this._byId
[model
.id
] = model
;
500 cids
[model
.cid
] = true;
503 // Insert models into the collection, re-sorting if needed, and triggering
504 // `add` events unless silenced.
505 this.length
+= length
;
506 index
= options
.at
!= null ? options
.at
: this.models
.length
;
507 splice
.apply(this.models
, [index
, 0].concat(models
));
508 if (this.comparator
) this.sort({silent
: true});
509 if (options
.silent
) return this;
510 for (i
= 0, length
= this.models
.length
; i
< length
; i
++) {
511 if (!cids
[(model
= this.models
[i
]).cid
]) continue;
513 model
.trigger('add', model
, this, options
);
518 // Remove a model, or a list of models from the set. Pass silent to avoid
519 // firing the `remove` event for every model removed.
520 remove: function(models
, options
) {
521 var i
, l
, index
, model
;
522 options
|| (options
= {});
523 models
= _
.isArray(models
) ? models
.slice() : [models
];
524 for (i
= 0, l
= models
.length
; i
< l
; i
++) {
525 model
= this.getByCid(models
[i
]) || this.get(models
[i
]);
526 if (!model
) continue;
527 delete this._byId
[model
.id
];
528 delete this._byCid
[model
.cid
];
529 index
= this.indexOf(model
);
530 this.models
.splice(index
, 1);
532 if (!options
.silent
) {
533 options
.index
= index
;
534 model
.trigger('remove', model
, this, options
);
536 this._removeReference(model
);
541 // Get a model from the set by id.
543 if (id
== null) return null;
544 return this._byId
[id
.id
!= null ? id
.id
: id
];
547 // Get a model from the set by client id.
548 getByCid: function(cid
) {
549 return cid
&& this._byCid
[cid
.cid
|| cid
];
552 // Get the model at the given index.
553 at: function(index
) {
554 return this.models
[index
];
557 // Force the collection to re-sort itself. You don't need to call this under
558 // normal circumstances, as the set will maintain sort order as each item
560 sort: function(options
) {
561 options
|| (options
= {});
562 if (!this.comparator
) throw new Error('Cannot sort a set without a comparator');
563 var boundComparator
= _
.bind(this.comparator
, this);
564 if (this.comparator
.length
== 1) {
565 this.models
= this.sortBy(boundComparator
);
567 this.models
.sort(boundComparator
);
569 if (!options
.silent
) this.trigger('reset', this, options
);
573 // Pluck an attribute from each model in the collection.
574 pluck: function(attr
) {
575 return _
.map(this.models
, function(model
){ return model
.get(attr
); });
578 // When you have more items than you want to add or remove individually,
579 // you can reset the entire set with a new list of models, without firing
580 // any `add` or `remove` events. Fires `reset` when finished.
581 reset: function(models
, options
) {
582 models
|| (models
= []);
583 options
|| (options
= {});
584 for (var i
= 0, l
= this.models
.length
; i
< l
; i
++) {
585 this._removeReference(this.models
[i
]);
588 this.add(models
, {silent
: true, parse
: options
.parse
});
589 if (!options
.silent
) this.trigger('reset', this, options
);
593 // Fetch the default set of models for this collection, resetting the
594 // collection when they arrive. If `add: true` is passed, appends the
595 // models to the collection instead of resetting.
596 fetch: function(options
) {
597 options
= options
? _
.clone(options
) : {};
598 if (options
.parse
=== undefined) options
.parse
= true;
599 var collection
= this;
600 var success
= options
.success
;
601 options
.success = function(resp
, status
, xhr
) {
602 collection
[options
.add
? 'add' : 'reset'](collection
.parse(resp
, xhr
), options
);
603 if (success
) success(collection
, resp
);
605 options
.error
= Backbone
.wrapError(options
.error
, collection
, options
);
606 return (this.sync
|| Backbone
.sync
).call(this, 'read', this, options
);
609 // Create a new instance of a model in this collection. Add the model to the
610 // collection immediately, unless `wait: true` is passed, in which case we
611 // wait for the server to agree.
612 create: function(model
, options
) {
614 options
= options
? _
.clone(options
) : {};
615 model
= this._prepareModel(model
, options
);
616 if (!model
) return false;
617 if (!options
.wait
) coll
.add(model
, options
);
618 var success
= options
.success
;
619 options
.success = function(nextModel
, resp
, xhr
) {
620 if (options
.wait
) coll
.add(nextModel
, options
);
622 success(nextModel
, resp
);
624 nextModel
.trigger('sync', model
, resp
, options
);
627 model
.save(null, options
);
631 // **parse** converts a response into a list of models to be added to the
632 // collection. The default implementation is just to pass it through.
633 parse: function(resp
, xhr
) {
637 // Proxy to _'s chain. Can't be proxied the same way the rest of the
638 // underscore methods are proxied because it relies on the underscore
641 return _(this.models
).chain();
644 // Reset all internal state. Called when the collection is reset.
645 _reset: function(options
) {
652 // Prepare a model or hash of attributes to be added to this collection.
653 _prepareModel: function(model
, options
) {
654 if (!(model
instanceof Backbone
.Model
)) {
656 options
.collection
= this;
657 model
= new this.model(attrs
, options
);
658 if (model
.validate
&& !model
._performValidation(model
.attributes
, options
)) model
= false;
659 } else if (!model
.collection
) {
660 model
.collection
= this;
665 // Internal method to remove a model's ties to a collection.
666 _removeReference: function(model
) {
667 if (this == model
.collection
) {
668 delete model
.collection
;
670 model
.off('all', this._onModelEvent
, this);
673 // Internal method called every time a model in the set fires an event.
674 // Sets need to update their indexes when models change ids. All other
675 // events simply proxy through. "add" and "remove" events that originate
676 // in other collections are ignored.
677 _onModelEvent: function(ev
, model
, collection
, options
) {
678 if ((ev
== 'add' || ev
== 'remove') && collection
!= this) return;
679 if (ev
== 'destroy') {
680 this.remove(model
, options
);
682 if (model
&& ev
=== 'change:' + model
.idAttribute
) {
683 delete this._byId
[model
.previous(model
.idAttribute
)];
684 this._byId
[model
.id
] = model
;
686 this.trigger
.apply(this, arguments
);
691 // Underscore methods that we want to implement on the Collection.
692 var methods
= ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
693 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
694 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
695 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
696 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
698 // Mix in each Underscore method as a proxy to `Collection#models`.
699 _
.each(methods
, function(method
) {
700 Backbone
.Collection
.prototype[method
] = function() {
701 return _
[method
].apply(_
, [this.models
].concat(_
.toArray(arguments
)));
706 // -------------------
708 // Routers map faux-URLs to actions, and fire events when routes are
709 // matched. Creating a new one sets its `routes` hash, if not set statically.
710 Backbone
.Router = function(options
) {
711 options
|| (options
= {});
712 if (options
.routes
) this.routes
= options
.routes
;
714 this.initialize
.apply(this, arguments
);
717 // Cached regular expressions for matching named param parts and splatted
718 // parts of route strings.
719 var namedParam
= /:\w+/g;
720 var splatParam
= /\*\w+/g;
721 var escapeRegExp
= /[-[\]{}()+?.,\\^$|#\s]/g;
723 // Set up all inheritable **Backbone.Router** properties and methods.
724 _
.extend(Backbone
.Router
.prototype, Backbone
.Events
, {
726 // Initialize is an empty function by default. Override it with your own
727 // initialization logic.
728 initialize: function(){},
730 // Manually bind a single named route to a callback. For example:
732 // this.route('search/:query/p:num', 'search', function(query, num) {
736 route: function(route
, name
, callback
) {
737 Backbone
.history
|| (Backbone
.history
= new Backbone
.History
);
738 if (!_
.isRegExp(route
)) route
= this._routeToRegExp(route
);
739 if (!callback
) callback
= this[name
];
740 Backbone
.history
.route(route
, _
.bind(function(fragment
) {
741 var args
= this._extractParameters(route
, fragment
);
742 callback
&& callback
.apply(this, args
);
743 this.trigger
.apply(this, ['route:' + name
].concat(args
));
744 Backbone
.history
.trigger('route', this, name
, args
);
748 // Simple proxy to `Backbone.history` to save a fragment into the history.
749 navigate: function(fragment
, options
) {
750 Backbone
.history
.navigate(fragment
, options
);
753 // Bind all defined routes to `Backbone.history`. We have to reverse the
754 // order of the routes here to support behavior where the most general
755 // routes can be defined at the bottom of the route map.
756 _bindRoutes: function() {
757 if (!this.routes
) return;
759 for (var route
in this.routes
) {
760 routes
.unshift([route
, this.routes
[route
]]);
762 for (var i
= 0, l
= routes
.length
; i
< l
; i
++) {
763 this.route(routes
[i
][0], routes
[i
][1], this[routes
[i
][1]]);
767 // Convert a route string into a regular expression, suitable for matching
768 // against the current location hash.
769 _routeToRegExp: function(route
) {
770 route
= route
.replace(escapeRegExp
, '\\$&')
771 .replace(namedParam
, '([^\/]+)')
772 .replace(splatParam
, '(.*?)');
773 return new RegExp('^' + route
+ '$');
776 // Given a route, and a URL fragment that it matches, return the array of
777 // extracted parameters.
778 _extractParameters: function(route
, fragment
) {
779 return route
.exec(fragment
).slice(1);
787 // Handles cross-browser history management, based on URL fragments. If the
788 // browser does not support `onhashchange`, falls back to polling.
789 Backbone
.History = function() {
791 _
.bindAll(this, 'checkUrl');
794 // Cached regex for cleaning leading hashes and slashes .
795 var routeStripper
= /^[#\/]/;
797 // Cached regex for detecting MSIE.
798 var isExplorer
= /msie [\w.]+/;
800 // Has the history handling already been started?
801 var historyStarted
= false;
803 // Set up all inheritable **Backbone.History** properties and methods.
804 _
.extend(Backbone
.History
.prototype, Backbone
.Events
, {
806 // The default interval to poll for hash changes, if necessary, is
807 // twenty times a second.
810 // Get the cross-browser normalized URL fragment, either from the URL,
811 // the hash, or the override.
812 getFragment: function(fragment
, forcePushState
) {
813 if (fragment
== null) {
814 if (this._hasPushState
|| forcePushState
) {
815 fragment
= window
.location
.pathname
;
816 var search
= window
.location
.search
;
817 if (search
) fragment
+= search
;
819 fragment
= window
.location
.hash
;
822 fragment
= decodeURIComponent(fragment
.replace(routeStripper
, ''));
823 if (!fragment
.indexOf(this.options
.root
)) fragment
= fragment
.substr(this.options
.root
.length
);
827 // Start the hash change handling, returning `true` if the current URL matches
828 // an existing route, and `false` otherwise.
829 start: function(options
) {
831 // Figure out the initial configuration. Do we need an iframe?
832 // Is pushState desired ... is it available?
833 if (historyStarted
) throw new Error("Backbone.history has already been started");
834 this.options
= _
.extend({}, {root
: '/'}, this.options
, options
);
835 this._wantsHashChange
= this.options
.hashChange
!== false;
836 this._wantsPushState
= !!this.options
.pushState
;
837 this._hasPushState
= !!(this.options
.pushState
&& window
.history
&& window
.history
.pushState
);
838 var fragment
= this.getFragment();
839 var docMode
= document
.documentMode
;
840 var oldIE
= (isExplorer
.exec(navigator
.userAgent
.toLowerCase()) && (!docMode
|| docMode
<= 7));
842 this.iframe
= $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow
;
843 this.navigate(fragment
);
846 // Depending on whether we're using pushState or hashes, and whether
847 // 'onhashchange' is supported, determine how we check the URL state.
848 if (this._hasPushState
) {
849 $(window
).bind('popstate', this.checkUrl
);
850 } else if (this._wantsHashChange
&& ('onhashchange' in window
) && !oldIE
) {
851 $(window
).bind('hashchange', this.checkUrl
);
852 } else if (this._wantsHashChange
) {
853 this._checkUrlInterval
= setInterval(this.checkUrl
, this.interval
);
856 // Determine if we need to change the base url, for a pushState link
857 // opened by a non-pushState browser.
858 this.fragment
= fragment
;
859 historyStarted
= true;
860 var loc
= window
.location
;
861 var atRoot
= loc
.pathname
== this.options
.root
;
863 // If we've started off with a route from a `pushState`-enabled browser,
864 // but we're currently in a browser that doesn't support it...
865 if (this._wantsHashChange
&& this._wantsPushState
&& !this._hasPushState
&& !atRoot
) {
866 this.fragment
= this.getFragment(null, true);
867 window
.location
.replace(this.options
.root
+ '#' + this.fragment
);
868 // Return immediately as browser will do redirect to new url
871 // Or if we've started out with a hash-based route, but we're currently
872 // in a browser where it could be `pushState`-based instead...
873 } else if (this._wantsPushState
&& this._hasPushState
&& atRoot
&& loc
.hash
) {
874 this.fragment
= loc
.hash
.replace(routeStripper
, '');
875 window
.history
.replaceState({}, document
.title
, loc
.protocol
+ '//' + loc
.host
+ this.options
.root
+ this.fragment
);
878 if (!this.options
.silent
) {
879 return this.loadUrl();
883 // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
884 // but possibly useful for unit testing Routers.
886 $(window
).unbind('popstate', this.checkUrl
).unbind('hashchange', this.checkUrl
);
887 clearInterval(this._checkUrlInterval
);
888 historyStarted
= false;
891 // Add a route to be tested when the fragment changes. Routes added later
892 // may override previous routes.
893 route: function(route
, callback
) {
894 this.handlers
.unshift({route
: route
, callback
: callback
});
897 // Checks the current URL to see if it has changed, and if it has,
898 // calls `loadUrl`, normalizing across the hidden iframe.
899 checkUrl: function(e
) {
900 var current
= this.getFragment();
901 if (current
== this.fragment
&& this.iframe
) current
= this.getFragment(this.iframe
.location
.hash
);
902 if (current
== this.fragment
|| current
== decodeURIComponent(this.fragment
)) return false;
903 if (this.iframe
) this.navigate(current
);
904 this.loadUrl() || this.loadUrl(window
.location
.hash
);
907 // Attempt to load the current URL fragment. If a route succeeds with a
908 // match, returns `true`. If no defined routes matches the fragment,
910 loadUrl: function(fragmentOverride
) {
911 var fragment
= this.fragment
= this.getFragment(fragmentOverride
);
912 var matched
= _
.any(this.handlers
, function(handler
) {
913 if (handler
.route
.test(fragment
)) {
914 handler
.callback(fragment
);
921 // Save a fragment into the hash history, or replace the URL state if the
922 // 'replace' option is passed. You are responsible for properly URL-encoding
923 // the fragment in advance.
925 // The options object can contain `trigger: true` if you wish to have the
926 // route callback be fired (not usually desirable), or `replace: true`, if
927 // you which to modify the current URL without adding an entry to the history.
928 navigate: function(fragment
, options
) {
929 if (!historyStarted
) return false;
930 if (!options
|| options
=== true) options
= {trigger
: options
};
931 var frag
= (fragment
|| '').replace(routeStripper
, '');
932 if (this.fragment
== frag
|| this.fragment
== decodeURIComponent(frag
)) return;
934 // If pushState is available, we use it to set the fragment as a real URL.
935 if (this._hasPushState
) {
936 if (frag
.indexOf(this.options
.root
) != 0) frag
= this.options
.root
+ frag
;
937 this.fragment
= frag
;
938 window
.history
[options
.replace
? 'replaceState' : 'pushState']({}, document
.title
, frag
);
940 // If hash changes haven't been explicitly disabled, update the hash
941 // fragment to store history.
942 } else if (this._wantsHashChange
) {
943 this.fragment
= frag
;
944 this._updateHash(window
.location
, frag
, options
.replace
);
945 if (this.iframe
&& (frag
!= this.getFragment(this.iframe
.location
.hash
))) {
946 // Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
947 // When replace is true, we don't want this.
948 if(!options
.replace
) this.iframe
.document
.open().close();
949 this._updateHash(this.iframe
.location
, frag
, options
.replace
);
952 // If you've told us that you explicitly don't want fallback hashchange-
953 // based history, then `navigate` becomes a page refresh.
955 window
.location
.assign(this.options
.root
+ fragment
);
957 if (options
.trigger
) this.loadUrl(fragment
);
960 // Update the hash location, either replacing the current entry, or adding
961 // a new one to the browser history.
962 _updateHash: function(location
, fragment
, replace
) {
964 location
.replace(location
.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment
);
966 location
.hash
= fragment
;
974 // Creating a Backbone.View creates its initial element outside of the DOM,
975 // if an existing element is not provided...
976 Backbone
.View = function(options
) {
977 this.cid
= _
.uniqueId('view');
978 this._configure(options
|| {});
979 this._ensureElement();
980 this.initialize
.apply(this, arguments
);
981 this.delegateEvents();
984 // Cached regex to split keys for `delegate`.
985 var eventSplitter
= /^(\S+)\s*(.*)$/;
987 // List of view options to be merged as properties.
988 var viewOptions
= ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
990 // Set up all inheritable **Backbone.View** properties and methods.
991 _
.extend(Backbone
.View
.prototype, Backbone
.Events
, {
993 // The default `tagName` of a View's element is `"div"`.
996 // jQuery delegate for element lookup, scoped to DOM elements within the
997 // current view. This should be prefered to global lookups where possible.
998 $: function(selector
) {
999 return $(selector
, this.el
);
1002 // Initialize is an empty function by default. Override it with your own
1003 // initialization logic.
1004 initialize: function(){},
1006 // **render** is the core function that your view should override, in order
1007 // to populate its element (`this.el`), with the appropriate HTML. The
1008 // convention is for **render** to always return `this`.
1009 render: function() {
1013 // Remove this view from the DOM. Note that the view isn't present in the
1014 // DOM by default, so calling this method may be a no-op.
1015 remove: function() {
1020 // For small amounts of DOM Elements, where a full-blown template isn't
1021 // needed, use **make** to manufacture elements, one at a time.
1023 // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
1025 make: function(tagName
, attributes
, content
) {
1026 var el
= document
.createElement(tagName
);
1027 if (attributes
) $(el
).attr(attributes
);
1028 if (content
) $(el
).html(content
);
1032 // Change the view's element (`this.el` property), including event
1034 setElement: function(element
, delegate
) {
1035 this.$el
= $(element
);
1036 this.el
= this.$el
[0];
1037 if (delegate
!== false) this.delegateEvents();
1040 // Set callbacks, where `this.events` is a hash of
1042 // *{"event selector": "callback"}*
1045 // 'mousedown .title': 'edit',
1046 // 'click .button': 'save'
1047 // 'click .open': function(e) { ... }
1050 // pairs. Callbacks will be bound to the view, with `this` set properly.
1051 // Uses event delegation for efficiency.
1052 // Omitting the selector binds the event to `this.el`.
1053 // This only works for delegate-able events: not `focus`, `blur`, and
1054 // not `change`, `submit`, and `reset` in Internet Explorer.
1055 delegateEvents: function(events
) {
1056 if (!(events
|| (events
= getValue(this, 'events')))) return;
1057 this.undelegateEvents();
1058 for (var key
in events
) {
1059 var method
= events
[key
];
1060 if (!_
.isFunction(method
)) method
= this[events
[key
]];
1061 if (!method
) throw new Error('Event "' + events
[key
] + '" does not exist');
1062 var match
= key
.match(eventSplitter
);
1063 var eventName
= match
[1], selector
= match
[2];
1064 method
= _
.bind(method
, this);
1065 eventName
+= '.delegateEvents' + this.cid
;
1066 if (selector
=== '') {
1067 this.$el
.bind(eventName
, method
);
1069 this.$el
.delegate(selector
, eventName
, method
);
1074 // Clears all callbacks previously bound to the view with `delegateEvents`.
1075 // You usually don't need to use this, but may wish to if you have multiple
1076 // Backbone views attached to the same DOM element.
1077 undelegateEvents: function() {
1078 this.$el
.unbind('.delegateEvents' + this.cid
);
1081 // Performs the initial configuration of a View with a set of options.
1082 // Keys with special meaning *(model, collection, id, className)*, are
1083 // attached directly to the view.
1084 _configure: function(options
) {
1085 if (this.options
) options
= _
.extend({}, this.options
, options
);
1086 for (var i
= 0, l
= viewOptions
.length
; i
< l
; i
++) {
1087 var attr
= viewOptions
[i
];
1088 if (options
[attr
]) this[attr
] = options
[attr
];
1090 this.options
= options
;
1093 // Ensure that the View has a DOM element to render into.
1094 // If `this.el` is a string, pass it through `$()`, take the first
1095 // matching element, and re-assign it to `el`. Otherwise, create
1096 // an element from the `id`, `className` and `tagName` properties.
1097 _ensureElement: function() {
1099 var attrs
= getValue(this, 'attributes') || {};
1100 if (this.id
) attrs
.id
= this.id
;
1101 if (this.className
) attrs
['class'] = this.className
;
1102 this.setElement(this.make(this.tagName
, attrs
), false);
1104 this.setElement(this.el
, false);
1110 // The self-propagating extend function that Backbone classes use.
1111 var extend = function (protoProps
, classProps
) {
1112 var child
= inherits(this, protoProps
, classProps
);
1113 child
.extend
= this.extend
;
1117 // Set up inheritance for the model, collection, and view.
1118 Backbone
.Model
.extend
= Backbone
.Collection
.extend
=
1119 Backbone
.Router
.extend
= Backbone
.View
.extend
= extend
;
1124 // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1132 // Override this function to change the manner in which Backbone persists
1133 // models to the server. You will be passed the type of request, and the
1134 // model in question. By default, makes a RESTful Ajax request
1135 // to the model's `url()`. Some possible customizations could be:
1137 // * Use `setTimeout` to batch rapid-fire updates into a single request.
1138 // * Send up the models as XML instead of JSON.
1139 // * Persist models via WebSockets instead of Ajax.
1141 // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
1142 // as `POST`, with a `_method` parameter containing the true HTTP method,
1143 // as well as all requests with the body as `application/x-www-form-urlencoded`
1144 // instead of `application/json` with the model in a param named `model`.
1145 // Useful when interfacing with server-side languages like **PHP** that make
1146 // it difficult to read the body of `PUT` requests.
1147 Backbone
.sync = function(method
, model
, options
) {
1148 var type
= methodMap
[method
];
1150 // Default JSON-request options.
1151 var params
= {type
: type
, dataType
: 'json'};
1153 // Ensure that we have a URL.
1155 params
.url
= getValue(model
, 'url') || urlError();
1158 // Ensure that we have the appropriate request data.
1159 if (!options
.data
&& model
&& (method
== 'create' || method
== 'update')) {
1160 params
.contentType
= 'application/json';
1161 params
.data
= JSON
.stringify(model
.toJSON());
1164 // For older servers, emulate JSON by encoding the request into an HTML-form.
1165 if (Backbone
.emulateJSON
) {
1166 params
.contentType
= 'application/x-www-form-urlencoded';
1167 params
.data
= params
.data
? {model
: params
.data
} : {};
1170 // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1171 // And an `X-HTTP-Method-Override` header.
1172 if (Backbone
.emulateHTTP
) {
1173 if (type
=== 'PUT' || type
=== 'DELETE') {
1174 if (Backbone
.emulateJSON
) params
.data
._method
= type
;
1175 params
.type
= 'POST';
1176 params
.beforeSend = function(xhr
) {
1177 xhr
.setRequestHeader('X-HTTP-Method-Override', type
);
1182 // Don't process data on a non-GET request.
1183 if (params
.type
!== 'GET' && !Backbone
.emulateJSON
) {
1184 params
.processData
= false;
1187 // Make the request, allowing the user to override any Ajax options.
1188 return $.ajax(_
.extend(params
, options
));
1191 // Wrap an optional error callback with a fallback error event.
1192 Backbone
.wrapError = function(onError
, originalModel
, options
) {
1193 return function(model
, resp
) {
1194 var resp
= model
=== originalModel
? resp
: model
;
1196 onError(model
, resp
, options
);
1198 originalModel
.trigger('error', model
, resp
, options
);
1206 // Shared empty constructor function to aid in prototype-chain creation.
1207 var ctor = function(){};
1209 // Helper function to correctly set up the prototype chain, for subclasses.
1210 // Similar to `goog.inherits`, but uses a hash of prototype properties and
1211 // class properties to be extended.
1212 var inherits = function(parent
, protoProps
, staticProps
) {
1215 // The constructor function for the new subclass is either defined by you
1216 // (the "constructor" property in your `extend` definition), or defaulted
1217 // by us to simply call the parent's constructor.
1218 if (protoProps
&& protoProps
.hasOwnProperty('constructor')) {
1219 child
= protoProps
.constructor;
1221 child = function(){ parent
.apply(this, arguments
); };
1224 // Inherit class (static) properties from parent.
1225 _
.extend(child
, parent
);
1227 // Set the prototype chain to inherit from `parent`, without calling
1228 // `parent`'s constructor function.
1229 ctor
.prototype = parent
.prototype;
1230 child
.prototype = new ctor();
1232 // Add prototype properties (instance properties) to the subclass,
1234 if (protoProps
) _
.extend(child
.prototype, protoProps
);
1236 // Add static properties to the constructor function, if supplied.
1237 if (staticProps
) _
.extend(child
, staticProps
);
1239 // Correctly set child's `prototype.constructor`.
1240 child
.prototype.constructor = child
;
1242 // Set a convenience property in case the parent's prototype is needed later.
1243 child
.__super__
= parent
.prototype;
1248 // Helper function to get a value from a Backbone object as a property
1249 // or as a function.
1250 var getValue = function(object
, prop
) {
1251 if (!(object
&& object
[prop
])) return null;
1252 return _
.isFunction(object
[prop
]) ? object
[prop
]() : object
[prop
];
1255 // Throw an error when a URL is needed, and none is supplied.
1256 var urlError = function() {
1257 throw new Error('A "url" property or function must be specified');