summaryrefslogtreecommitdiff
path: root/webapp/vendor/backbone-1.1.2.js
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/vendor/backbone-1.1.2.js')
-rw-r--r--webapp/vendor/backbone-1.1.2.js1608
1 files changed, 1608 insertions, 0 deletions
diff --git a/webapp/vendor/backbone-1.1.2.js b/webapp/vendor/backbone-1.1.2.js
new file mode 100644
index 0000000..24a550a
--- /dev/null
+++ b/webapp/vendor/backbone-1.1.2.js
@@ -0,0 +1,1608 @@
1// Backbone.js 1.1.2
2
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
7
8(function(root, factory) {
9
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, _, $);
16 });
17
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, _);
22
23 // Finally, as a browser global.
24 } else {
25 root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$));
26 }
27
28}(this, function(root, Backbone, _, $) {
29
30 // Initial Setup
31 // -------------
32
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;
36
37 // Create local references to array methods we'll want to use later.
38 var array = [];
39 var push = array.push;
40 var slice = array.slice;
41 var splice = array.splice;
42
43 // Current version of the library. Keep in sync with `package.json`.
44 Backbone.VERSION = '1.1.2';
45
46 // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
47 // the `$` variable.
48 Backbone.$ = $;
49
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;
54 return this;
55 };
56
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;
61
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;
67
68 // Backbone.Events
69 // ---------------
70
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
74 // succession.
75 //
76 // var object = {};
77 // _.extend(object, Backbone.Events);
78 // object.on('expand', function(){ alert('expanded'); });
79 // object.trigger('expand');
80 //
81 var Events = Backbone.Events = {
82
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});
90 return this;
91 },
92
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;
97 var self = this;
98 var once = _.once(function() {
99 self.off(name, once);
100 callback.apply(this, arguments);
101 });
102 once._callback = callback;
103 return this.on(name, once, context);
104 },
105
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;
115 return this;
116 }
117 names = name ? [name] : _.keys(this._events);
118 for (i = 0, l = names.length; i < l; i++) {
119 name = names[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++) {
124 ev = events[j];
125 if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
126 (context && context !== ev.context)) {
127 retain.push(ev);
128 }
129 }
130 }
131 if (!retain.length) delete this._events[name];
132 }
133 }
134
135 return this;
136 },
137
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);
150 return this;
151 },
152
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];
165 }
166 return this;
167 }
168
169 };
170
171 // Regular expression used to split event strings.
172 var eventSplitter = /\s+/;
173
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;
179
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));
184 }
185 return false;
186 }
187
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));
193 }
194 return false;
195 }
196
197 return true;
198 };
199
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;
211 }
212 };
213
214 var listenMethods = {listenTo: 'on', listenToOnce: 'once'};
215
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
218 // listening to.
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);
226 return this;
227 };
228 });
229
230 // Aliases for backwards compatibility.
231 Events.bind = Events.on;
232 Events.unbind = Events.off;
233
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);
237
238 // Backbone.Model
239 // --------------
240
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.
245
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);
257 this.changed = {};
258 this.initialize.apply(this, arguments);
259 };
260
261 // Attach all inheritable methods to the Model prototype.
262 _.extend(Model.prototype, Events, {
263
264 // A hash of attributes whose current and previous value differ.
265 changed: null,
266
267 // The value returned during the last failed validation.
268 validationError: null,
269
270 // The default name for the JSON `id` attribute is `"id"`. MongoDB and
271 // CouchDB users may want to set this to `"_id"`.
272 idAttribute: 'id',
273
274 // Initialize is an empty function by default. Override it with your own
275 // initialization logic.
276 initialize: function(){},
277
278 // Return a copy of the model's `attributes` object.
279 toJSON: function(options) {
280 return _.clone(this.attributes);
281 },
282
283 // Proxy `Backbone.sync` by default -- but override this if you need
284 // custom syncing semantics for *this* particular model.
285 sync: function() {
286 return Backbone.sync.apply(this, arguments);
287 },
288
289 // Get the value of an attribute.
290 get: function(attr) {
291 return this.attributes[attr];
292 },
293
294 // Get the HTML-escaped value of an attribute.
295 escape: function(attr) {
296 return _.escape(this.get(attr));
297 },
298
299 // Returns `true` if the attribute contains a value that is not null
300 // or undefined.
301 has: function(attr) {
302 return this.get(attr) != null;
303 },
304
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;
311
312 // Handle both `"key", value` and `{key: value}` -style arguments.
313 if (typeof key === 'object') {
314 attrs = key;
315 options = val;
316 } else {
317 (attrs = {})[key] = val;
318 }
319
320 options || (options = {});
321
322 // Run validation.
323 if (!this._validate(attrs, options)) return false;
324
325 // Extract attributes and options.
326 unset = options.unset;
327 silent = options.silent;
328 changes = [];
329 changing = this._changing;
330 this._changing = true;
331
332 if (!changing) {
333 this._previousAttributes = _.clone(this.attributes);
334 this.changed = {};
335 }
336 current = this.attributes, prev = this._previousAttributes;
337
338 // Check for changes of `id`.
339 if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
340
341 // For each `set` attribute, update or delete the current value.
342 for (attr in attrs) {
343 val = attrs[attr];
344 if (!_.isEqual(current[attr], val)) changes.push(attr);
345 if (!_.isEqual(prev[attr], val)) {
346 this.changed[attr] = val;
347 } else {
348 delete this.changed[attr];
349 }
350 unset ? delete current[attr] : current[attr] = val;
351 }
352
353 // Trigger all relevant attribute changes.
354 if (!silent) {
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);
358 }
359 }
360
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;
364 if (!silent) {
365 while (this._pending) {
366 options = this._pending;
367 this._pending = false;
368 this.trigger('change', this, options);
369 }
370 }
371 this._pending = false;
372 this._changing = false;
373 return this;
374 },
375
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}));
380 },
381
382 // Clear all attributes on the model, firing `"change"`.
383 clear: function(options) {
384 var attrs = {};
385 for (var key in this.attributes) attrs[key] = void 0;
386 return this.set(attrs, _.extend({}, options, {unset: true}));
387 },
388
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);
394 },
395
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;
409 }
410 return changed;
411 },
412
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];
418 },
419
420 // Get all of the attributes of the model at the time of the previous
421 // `"change"` event.
422 previousAttributes: function() {
423 return _.clone(this._previousAttributes);
424 },
425
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;
432 var model = this;
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);
438 };
439 wrapError(this, options);
440 return this.sync('read', this, options);
441 },
442
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;
448
449 // Handle both `"key", value` and `{key: value}` -style arguments.
450 if (key == null || typeof key === 'object') {
451 attrs = key;
452 options = val;
453 } else {
454 (attrs = {})[key] = val;
455 }
456
457 options = _.extend({validate: true}, options);
458
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;
464 } else {
465 if (!this._validate(attrs, options)) return false;
466 }
467
468 // Set temporary attributes if `{wait: true}`.
469 if (attrs && options.wait) {
470 this.attributes = _.extend({}, attributes, attrs);
471 }
472
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;
476 var model = this;
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)) {
484 return false;
485 }
486 if (success) success(model, resp, options);
487 model.trigger('sync', model, resp, options);
488 };
489 wrapError(this, options);
490
491 method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
492 if (method === 'patch') options.attrs = attrs;
493 xhr = this.sync(method, this, options);
494
495 // Restore attributes.
496 if (attrs && options.wait) this.attributes = attributes;
497
498 return xhr;
499 },
500
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) : {};
506 var model = this;
507 var success = options.success;
508
509 var destroy = function() {
510 model.trigger('destroy', model, model.collection, options);
511 };
512
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);
517 };
518
519 if (this.isNew()) {
520 options.success();
521 return false;
522 }
523 wrapError(this, options);
524
525 var xhr = this.sync('delete', this, options);
526 if (!options.wait) destroy();
527 return xhr;
528 },
529
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.
533 url: function() {
534 var base =
535 _.result(this, 'urlRoot') ||
536 _.result(this.collection, 'url') ||
537 urlError();
538 if (this.isNew()) return base;
539 return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id);
540 },
541
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) {
545 return resp;
546 },
547
548 // Create a new model with identical attributes to this one.
549 clone: function() {
550 return new this.constructor(this.attributes);
551 },
552
553 // A model is new if it has never been saved to the server, and lacks an id.
554 isNew: function() {
555 return !this.has(this.idAttribute);
556 },
557
558 // Check if the model is currently in a valid state.
559 isValid: function(options) {
560 return this._validate({}, _.extend(options || {}, { validate: true }));
561 },
562
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}));
571 return false;
572 }
573
574 });
575
576 // Underscore methods that we want to implement on the Model.
577 var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit'];
578
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);
585 };
586 });
587
588 // Backbone.Collection
589 // -------------------
590
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`.
597
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;
605 this._reset();
606 this.initialize.apply(this, arguments);
607 if (models) this.reset(models, _.extend({silent: true}, options));
608 };
609
610 // Default options for `Collection#set`.
611 var setOptions = {add: true, remove: true, merge: true};
612 var addOptions = {add: true, remove: false};
613
614 // Define the Collection's inheritable methods.
615 _.extend(Collection.prototype, Events, {
616
617 // The default model for a collection is just a **Backbone.Model**.
618 // This should be overridden in most cases.
619 model: Model,
620
621 // Initialize is an empty function by default. Override it with your own
622 // initialization logic.
623 initialize: function(){},
624
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); });
629 },
630
631 // Proxy `Backbone.sync` by default.
632 sync: function() {
633 return Backbone.sync.apply(this, arguments);
634 },
635
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));
639 },
640
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);
654 this.length--;
655 if (!options.silent) {
656 options.index = index;
657 model.trigger('remove', model, this, options);
658 }
659 this._removeReference(model, options);
660 }
661 return singular ? models[0] : models;
662 },
663
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;
674 var at = options.at;
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;
681
682 // Turn bare objects into model references, and prevent invalid models
683 // from being added.
684 for (i = 0, l = models.length; i < l; i++) {
685 attrs = models[i] || {};
686 if (attrs instanceof Model) {
687 id = model = attrs;
688 } else {
689 id = attrs[targetModel.prototype.idAttribute || 'id'];
690 }
691
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;
696 if (merge) {
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;
701 }
702 models[i] = existing;
703
704 // If this is a new, valid model, push it to the `toAdd` list.
705 } else if (add) {
706 model = models[i] = this._prepareModel(attrs, options);
707 if (!model) continue;
708 toAdd.push(model);
709 this._addReference(model, options);
710 }
711
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;
716 }
717
718 // Remove nonexistent models if appropriate.
719 if (remove) {
720 for (i = 0, l = this.length; i < l; ++i) {
721 if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
722 }
723 if (toRemove.length) this.remove(toRemove, options);
724 }
725
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;
730 if (at != null) {
731 for (i = 0, l = toAdd.length; i < l; i++) {
732 this.models.splice(at + i, 0, toAdd[i]);
733 }
734 } else {
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]);
739 }
740 }
741 }
742
743 // Silently sort the collection if appropriate.
744 if (sort) this.sort({silent: true});
745
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);
750 }
751 if (sort || (order && order.length)) this.trigger('sort', this, options);
752 }
753
754 // Return the added (or merged) model (or models).
755 return singular ? models[0] : models;
756 },
757
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);
766 }
767 options.previousModels = this.models;
768 this._reset();
769 models = this.add(models, _.extend({silent: true}, options));
770 if (!options.silent) this.trigger('reset', this, options);
771 return models;
772 },
773
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));
777 },
778
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);
783 return model;
784 },
785
786 // Add a model to the beginning of the collection.
787 unshift: function(model, options) {
788 return this.add(model, _.extend({at: 0}, options));
789 },
790
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);
795 return model;
796 },
797
798 // Slice out a sub-array of models from the collection.
799 slice: function() {
800 return slice.apply(this.models, arguments);
801 },
802
803 // Get a model from the set by id.
804 get: function(obj) {
805 if (obj == null) return void 0;
806 return this._byId[obj] || this._byId[obj.id] || this._byId[obj.cid];
807 },
808
809 // Get the model at the given index.
810 at: function(index) {
811 return this.models[index];
812 },
813
814 // Return models with matching attributes. Useful for simple cases of
815 // `filter`.
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;
821 }
822 return true;
823 });
824 },
825
826 // Return the first model with matching attributes. Useful for simple cases
827 // of `find`.
828 findWhere: function(attrs) {
829 return this.where(attrs, true);
830 },
831
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
834 // is added.
835 sort: function(options) {
836 if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
837 options || (options = {});
838
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);
842 } else {
843 this.models.sort(_.bind(this.comparator, this));
844 }
845
846 if (!options.silent) this.trigger('sort', this, options);
847 return this;
848 },
849
850 // Pluck an attribute from each model in the collection.
851 pluck: function(attr) {
852 return _.invoke(this.models, 'get', attr);
853 },
854
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);
868 };
869 wrapError(this, options);
870 return this.sync('read', this, options);
871 },
872
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);
885 };
886 model.save(null, options);
887 return model;
888 },
889
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) {
893 return resp;
894 },
895
896 // Create a new collection with an identical list of models as this one.
897 clone: function() {
898 return new this.constructor(this.models);
899 },
900
901 // Private method to reset all internal state. Called when the collection
902 // is first initialized or reset.
903 _reset: function() {
904 this.length = 0;
905 this.models = [];
906 this._byId = {};
907 },
908
909 // Prepare a hash of attributes (or other model) to be added to this
910 // collection.
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);
918 return false;
919 },
920
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);
927 },
928
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);
933 },
934
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;
945 }
946 this.trigger.apply(this, arguments);
947 }
948
949 });
950
951 // Underscore methods that we want to implement on the Collection.
952 // 90% of the core usefulness of Backbone Collections is actually implemented
953 // right here:
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'];
960
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);
967 };
968 });
969
970 // Underscore methods that take a property name as an argument.
971 var attributeMethods = ['groupBy', 'countBy', 'sortBy', 'indexBy'];
972
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);
978 };
979 return _[method](this.models, iterator, context);
980 };
981 });
982
983 // Backbone.View
984 // -------------
985
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.
993
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();
1003 };
1004
1005 // Cached regex to split keys for `delegate`.
1006 var delegateEventSplitter = /^(\S+)\s*(.*)$/;
1007
1008 // List of view options to be merged as properties.
1009 var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
1010
1011 // Set up all inheritable **Backbone.View** properties and methods.
1012 _.extend(View.prototype, Events, {
1013
1014 // The default `tagName` of a View's element is `"div"`.
1015 tagName: 'div',
1016
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);
1021 },
1022
1023 // Initialize is an empty function by default. Override it with your own
1024 // initialization logic.
1025 initialize: function(){},
1026
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() {
1031 return this;
1032 },
1033
1034 // Remove this view by taking the element out of the DOM, and removing any
1035 // applicable Backbone.Events listeners.
1036 remove: function() {
1037 this.$el.remove();
1038 this.stopListening();
1039 return this;
1040 },
1041
1042 // Change the view's element (`this.el` property), including event
1043 // re-delegation.
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();
1049 return this;
1050 },
1051
1052 // Set callbacks, where `this.events` is a hash of
1053 //
1054 // *{"event selector": "callback"}*
1055 //
1056 // {
1057 // 'mousedown .title': 'edit',
1058 // 'click .button': 'save',
1059 // 'click .open': function(e) { ... }
1060 // }
1061 //
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;
1074
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);
1081 } else {
1082 this.$el.on(eventName, selector, method);
1083 }
1084 }
1085 return this;
1086 },
1087
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);
1093 return this;
1094 },
1095
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() {
1101 if (!this.el) {
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);
1107 } else {
1108 this.setElement(_.result(this, 'el'), false);
1109 }
1110 }
1111
1112 });
1113
1114 // Backbone.sync
1115 // -------------
1116
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:
1121 //
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.
1125 //
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];
1134
1135 // Default options, unless specified.
1136 _.defaults(options || (options = {}), {
1137 emulateHTTP: Backbone.emulateHTTP,
1138 emulateJSON: Backbone.emulateJSON
1139 });
1140
1141 // Default JSON-request options.
1142 var params = {type: type, dataType: 'json'};
1143
1144 // Ensure that we have a URL.
1145 if (!options.url) {
1146 params.url = _.result(model, 'url') || urlError();
1147 }
1148
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));
1153 }
1154
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} : {};
1159 }
1160
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);
1170 };
1171 }
1172
1173 // Don't process data on a non-GET request.
1174 if (params.type !== 'GET' && !options.emulateJSON) {
1175 params.processData = false;
1176 }
1177
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");
1184 };
1185 }
1186
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);
1190 return xhr;
1191 };
1192
1193 var noXhrPatch =
1194 typeof window !== 'undefined' && !!window.ActiveXObject &&
1195 !(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent);
1196
1197 // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1198 var methodMap = {
1199 'create': 'POST',
1200 'update': 'PUT',
1201 'patch': 'PATCH',
1202 'delete': 'DELETE',
1203 'read': 'GET'
1204 };
1205
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);
1210 };
1211
1212 // Backbone.Router
1213 // ---------------
1214
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;
1220 this._bindRoutes();
1221 this.initialize.apply(this, arguments);
1222 };
1223
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;
1230
1231 // Set up all inheritable **Backbone.Router** properties and methods.
1232 _.extend(Router.prototype, Events, {
1233
1234 // Initialize is an empty function by default. Override it with your own
1235 // initialization logic.
1236 initialize: function(){},
1237
1238 // Manually bind a single named route to a callback. For example:
1239 //
1240 // this.route('search/:query/p:num', 'search', function(query, num) {
1241 // ...
1242 // });
1243 //
1244 route: function(route, name, callback) {
1245 if (!_.isRegExp(route)) route = this._routeToRegExp(route);
1246 if (_.isFunction(name)) {
1247 callback = name;
1248 name = '';
1249 }
1250 if (!callback) callback = this[name];
1251 var router = this;
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);
1258 });
1259 return this;
1260 },
1261
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);
1266 },
1267
1268 // Simple proxy to `Backbone.history` to save a fragment into the history.
1269 navigate: function(fragment, options) {
1270 Backbone.history.navigate(fragment, options);
1271 return this;
1272 },
1273
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]);
1283 }
1284 },
1285
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 : '([^/?]+)';
1293 })
1294 .replace(splatParam, '([^?]*?)');
1295 return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$');
1296 },
1297
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;
1307 });
1308 }
1309
1310 });
1311
1312 // Backbone.History
1313 // ----------------
1314
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() {
1321 this.handlers = [];
1322 _.bindAll(this, 'checkUrl');
1323
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;
1328 }
1329 };
1330
1331 // Cached regex for stripping a leading hash/slash and trailing space.
1332 var routeStripper = /^[#\/]|\s+$/g;
1333
1334 // Cached regex for stripping leading and trailing slashes.
1335 var rootStripper = /^\/+|\/+$/g;
1336
1337 // Cached regex for detecting MSIE.
1338 var isExplorer = /msie [\w.]+/;
1339
1340 // Cached regex for removing a trailing slash.
1341 var trailingSlash = /\/$/;
1342
1343 // Cached regex for stripping urls of hash.
1344 var pathStripper = /#.*$/;
1345
1346 // Has the history handling already been started?
1347 History.started = false;
1348
1349 // Set up all inheritable **Backbone.History** properties and methods.
1350 _.extend(History.prototype, Events, {
1351
1352 // The default interval to poll for hash changes, if necessary, is
1353 // twenty times a second.
1354 interval: 50,
1355
1356 // Are we at the app root?
1357 atRoot: function() {
1358 return this.location.pathname.replace(/[^\/]$/, '$&/') === this.root;
1359 },
1360
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] : '';
1366 },
1367
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);
1376 } else {
1377 fragment = this.getHash();
1378 }
1379 }
1380 return fragment.replace(routeStripper, '');
1381 },
1382
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;
1388
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));
1399
1400 // Normalize root to always include a leading and trailing slash.
1401 this.root = ('/' + this.root + '/').replace(rootStripper, '/');
1402
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);
1407 }
1408
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);
1417 }
1418
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;
1423
1424 // Transition from hashChange to pushState or vice versa if both are
1425 // requested.
1426 if (this._wantsHashChange && this._wantsPushState) {
1427
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
1434 return true;
1435
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);
1441 }
1442
1443 }
1444
1445 if (!this.options.silent) return this.loadUrl();
1446 },
1447
1448 // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
1449 // but possibly useful for unit testing Routers.
1450 stop: function() {
1451 Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl);
1452 if (this._checkUrlInterval) clearInterval(this._checkUrlInterval);
1453 History.started = false;
1454 },
1455
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});
1460 },
1461
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));
1468 }
1469 if (current === this.fragment) return false;
1470 if (this.iframe) this.navigate(current);
1471 this.loadUrl();
1472 },
1473
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,
1476 // returns `false`.
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);
1482 return true;
1483 }
1484 });
1485 },
1486
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.
1490 //
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};
1497
1498 var url = this.root + (fragment = this.getFragment(fragment || ''));
1499
1500 // Strip the hash for matching.
1501 fragment = fragment.replace(pathStripper, '');
1502
1503 if (this.fragment === fragment) return;
1504 this.fragment = fragment;
1505
1506 // Don't include a trailing slash on the root.
1507 if (fragment === '' && url !== '/') url = url.slice(0, -1);
1508
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);
1512
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
1520 // want this.
1521 if(!options.replace) this.iframe.document.open().close();
1522 this._updateHash(this.iframe.location, fragment, options.replace);
1523 }
1524
1525 // If you've told us that you explicitly don't want fallback hashchange-
1526 // based history, then `navigate` becomes a page refresh.
1527 } else {
1528 return this.location.assign(url);
1529 }
1530 if (options.trigger) return this.loadUrl(fragment);
1531 },
1532
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) {
1536 if (replace) {
1537 var href = location.href.replace(/(javascript:|#).*$/, '');
1538 location.replace(href + '#' + fragment);
1539 } else {
1540 // Some browsers require that `hash` contains a leading #.
1541 location.hash = '#' + fragment;
1542 }
1543 }
1544
1545 });
1546
1547 // Create the default Backbone.history.
1548 Backbone.history = new History;
1549
1550 // Helpers
1551 // -------
1552
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) {
1557 var parent = this;
1558 var child;
1559
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;
1565 } else {
1566 child = function(){ return parent.apply(this, arguments); };
1567 }
1568
1569 // Add static properties to the constructor function, if supplied.
1570 _.extend(child, parent, staticProps);
1571
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;
1577
1578 // Add prototype properties (instance properties) to the subclass,
1579 // if supplied.
1580 if (protoProps) _.extend(child.prototype, protoProps);
1581
1582 // Set a convenience property in case the parent's prototype is needed
1583 // later.
1584 child.__super__ = parent.prototype;
1585
1586 return child;
1587 };
1588
1589 // Set up inheritance for the model, collection, router, view and history.
1590 Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
1591
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');
1595 };
1596
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);
1603 };
1604 };
1605
1606 return Backbone;
1607
1608}));