comparison src/DML/VendorAssetsBundle/Resources/assets/backbone/1.1.2_modif/backbone.js @ 0:493bcb69166c

added public content
author Daniel Wolff
date Tue, 09 Feb 2016 20:54:02 +0100
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:493bcb69166c
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 //MODIFIED
252 //this.cid = _.uniqueId('c');
253 this.cid = _.uniqueId(this.cidPrefix || 'c');
254 this.attributes = {};
255 if (options.collection) this.collection = options.collection;
256 if (options.parse) attrs = this.parse(attrs, options) || {};
257 attrs = _.defaults({}, attrs, _.result(this, 'defaults'));
258 this.set(attrs, options);
259 this.changed = {};
260 this.initialize.apply(this, arguments);
261 };
262
263 // Attach all inheritable methods to the Model prototype.
264 _.extend(Model.prototype, Events, {
265
266 // A hash of attributes whose current and previous value differ.
267 changed: null,
268
269 // The value returned during the last failed validation.
270 validationError: null,
271
272 // The default name for the JSON `id` attribute is `"id"`. MongoDB and
273 // CouchDB users may want to set this to `"_id"`.
274 idAttribute: 'id',
275
276 // Initialize is an empty function by default. Override it with your own
277 // initialization logic.
278 initialize: function(){},
279
280 // Return a copy of the model's `attributes` object.
281 toJSON: function(options) {
282 return _.clone(this.attributes);
283 },
284
285 // Proxy `Backbone.sync` by default -- but override this if you need
286 // custom syncing semantics for *this* particular model.
287 sync: function() {
288 return Backbone.sync.apply(this, arguments);
289 },
290
291 // Get the value of an attribute.
292 get: function(attr) {
293 return this.attributes[attr];
294 },
295
296 // Get the HTML-escaped value of an attribute.
297 escape: function(attr) {
298 return _.escape(this.get(attr));
299 },
300
301 // Returns `true` if the attribute contains a value that is not null
302 // or undefined.
303 has: function(attr) {
304 return this.get(attr) != null;
305 },
306
307 // Set a hash of model attributes on the object, firing `"change"`. This is
308 // the core primitive operation of a model, updating the data and notifying
309 // anyone who needs to know about the change in state. The heart of the beast.
310 set: function(key, val, options) {
311 var attr, attrs, unset, changes, silent, changing, prev, current;
312 if (key == null) return this;
313
314 // Handle both `"key", value` and `{key: value}` -style arguments.
315 if (typeof key === 'object') {
316 attrs = key;
317 options = val;
318 } else {
319 (attrs = {})[key] = val;
320 }
321
322 options || (options = {});
323
324 // Run validation.
325 if (!this._validate(attrs, options)) return false;
326
327 // Extract attributes and options.
328 unset = options.unset;
329 silent = options.silent;
330 changes = [];
331 changing = this._changing;
332 this._changing = true;
333
334 if (!changing) {
335 this._previousAttributes = _.clone(this.attributes);
336 this.changed = {};
337 }
338 current = this.attributes, prev = this._previousAttributes;
339
340 // Check for changes of `id`.
341 if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
342
343 // For each `set` attribute, update or delete the current value.
344 for (attr in attrs) {
345 val = attrs[attr];
346 if (!_.isEqual(current[attr], val)) changes.push(attr);
347 if (!_.isEqual(prev[attr], val)) {
348 this.changed[attr] = val;
349 } else {
350 delete this.changed[attr];
351 }
352 unset ? delete current[attr] : current[attr] = val;
353 }
354
355 // Trigger all relevant attribute changes.
356 if (!silent) {
357 if (changes.length) this._pending = options;
358 for (var i = 0, l = changes.length; i < l; i++) {
359 this.trigger('change:' + changes[i], this, current[changes[i]], options);
360 }
361 }
362
363 // You might be wondering why there's a `while` loop here. Changes can
364 // be recursively nested within `"change"` events.
365 if (changing) return this;
366 if (!silent) {
367 while (this._pending) {
368 options = this._pending;
369 this._pending = false;
370 this.trigger('change', this, options);
371 }
372 }
373 this._pending = false;
374 this._changing = false;
375 return this;
376 },
377
378 // Remove an attribute from the model, firing `"change"`. `unset` is a noop
379 // if the attribute doesn't exist.
380 unset: function(attr, options) {
381 return this.set(attr, void 0, _.extend({}, options, {unset: true}));
382 },
383
384 // Clear all attributes on the model, firing `"change"`.
385 clear: function(options) {
386 var attrs = {};
387 for (var key in this.attributes) attrs[key] = void 0;
388 return this.set(attrs, _.extend({}, options, {unset: true}));
389 },
390
391 // Determine if the model has changed since the last `"change"` event.
392 // If you specify an attribute name, determine if that attribute has changed.
393 hasChanged: function(attr) {
394 if (attr == null) return !_.isEmpty(this.changed);
395 return _.has(this.changed, attr);
396 },
397
398 // Return an object containing all the attributes that have changed, or
399 // false if there are no changed attributes. Useful for determining what
400 // parts of a view need to be updated and/or what attributes need to be
401 // persisted to the server. Unset attributes will be set to undefined.
402 // You can also pass an attributes object to diff against the model,
403 // determining if there *would be* a change.
404 changedAttributes: function(diff) {
405 if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
406 var val, changed = false;
407 var old = this._changing ? this._previousAttributes : this.attributes;
408 for (var attr in diff) {
409 if (_.isEqual(old[attr], (val = diff[attr]))) continue;
410 (changed || (changed = {}))[attr] = val;
411 }
412 return changed;
413 },
414
415 // Get the previous value of an attribute, recorded at the time the last
416 // `"change"` event was fired.
417 previous: function(attr) {
418 if (attr == null || !this._previousAttributes) return null;
419 return this._previousAttributes[attr];
420 },
421
422 // Get all of the attributes of the model at the time of the previous
423 // `"change"` event.
424 previousAttributes: function() {
425 return _.clone(this._previousAttributes);
426 },
427
428 // Fetch the model from the server. If the server's representation of the
429 // model differs from its current attributes, they will be overridden,
430 // triggering a `"change"` event.
431 fetch: function(options) {
432 options = options ? _.clone(options) : {};
433 if (options.parse === void 0) options.parse = true;
434 var model = this;
435 var success = options.success;
436 options.success = function(resp) {
437 if (!model.set(model.parse(resp, options), options)) return false;
438 if (success) success(model, resp, options);
439 model.trigger('sync', model, resp, options);
440 };
441 wrapError(this, options);
442 return this.sync('read', this, options);
443 },
444
445 // Set a hash of model attributes, and sync the model to the server.
446 // If the server returns an attributes hash that differs, the model's
447 // state will be `set` again.
448 save: function(key, val, options) {
449 var attrs, method, xhr, attributes = this.attributes;
450
451 // Handle both `"key", value` and `{key: value}` -style arguments.
452 if (key == null || typeof key === 'object') {
453 attrs = key;
454 options = val;
455 } else {
456 (attrs = {})[key] = val;
457 }
458
459 options = _.extend({validate: true}, options);
460
461 // If we're not waiting and attributes exist, save acts as
462 // `set(attr).save(null, opts)` with validation. Otherwise, check if
463 // the model will be valid when the attributes, if any, are set.
464 if (attrs && !options.wait) {
465 if (!this.set(attrs, options)) return false;
466 } else {
467 if (!this._validate(attrs, options)) return false;
468 }
469
470 // Set temporary attributes if `{wait: true}`.
471 if (attrs && options.wait) {
472 this.attributes = _.extend({}, attributes, attrs);
473 }
474
475 // After a successful server-side save, the client is (optionally)
476 // updated with the server-side state.
477 if (options.parse === void 0) options.parse = true;
478 var model = this;
479 var success = options.success;
480 options.success = function(resp) {
481 // Ensure attributes are restored during synchronous saves.
482 model.attributes = attributes;
483 var serverAttrs = model.parse(resp, options);
484 if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
485 if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
486 return false;
487 }
488 if (success) success(model, resp, options);
489 model.trigger('sync', model, resp, options);
490 };
491 wrapError(this, options);
492
493 method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
494 if (method === 'patch') options.attrs = attrs;
495 xhr = this.sync(method, this, options);
496
497 // Restore attributes.
498 if (attrs && options.wait) this.attributes = attributes;
499
500 return xhr;
501 },
502
503 // Destroy this model on the server if it was already persisted.
504 // Optimistically removes the model from its collection, if it has one.
505 // If `wait: true` is passed, waits for the server to respond before removal.
506 destroy: function(options) {
507 options = options ? _.clone(options) : {};
508 var model = this;
509 var success = options.success;
510
511 var destroy = function() {
512 model.trigger('destroy', model, model.collection, options);
513 };
514
515 options.success = function(resp) {
516 if (options.wait || model.isNew()) destroy();
517 if (success) success(model, resp, options);
518 if (!model.isNew()) model.trigger('sync', model, resp, options);
519 };
520
521 if (this.isNew()) {
522 options.success();
523 return false;
524 }
525 wrapError(this, options);
526
527 var xhr = this.sync('delete', this, options);
528 if (!options.wait) destroy();
529 return xhr;
530 },
531
532 // Default URL for the model's representation on the server -- if you're
533 // using Backbone's restful methods, override this to change the endpoint
534 // that will be called.
535 url: function() {
536 var base =
537 _.result(this, 'urlRoot') ||
538 _.result(this.collection, 'url') ||
539 urlError();
540 if (this.isNew()) return base;
541 return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id);
542 },
543
544 // **parse** converts a response into the hash of attributes to be `set` on
545 // the model. The default implementation is just to pass the response along.
546 parse: function(resp, options) {
547 return resp;
548 },
549
550 // Create a new model with identical attributes to this one.
551 clone: function() {
552 return new this.constructor(this.attributes);
553 },
554
555 // A model is new if it has never been saved to the server, and lacks an id.
556 isNew: function() {
557 return !this.has(this.idAttribute);
558 },
559
560 // Check if the model is currently in a valid state.
561 isValid: function(options) {
562 return this._validate({}, _.extend(options || {}, { validate: true }));
563 },
564
565 // Run validation against the next complete set of model attributes,
566 // returning `true` if all is well. Otherwise, fire an `"invalid"` event.
567 _validate: function(attrs, options) {
568 if (!options.validate || !this.validate) return true;
569 attrs = _.extend({}, this.attributes, attrs);
570 var error = this.validationError = this.validate(attrs, options) || null;
571 if (!error) return true;
572 this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
573 return false;
574 }
575
576 });
577
578 // Underscore methods that we want to implement on the Model.
579 var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit'];
580
581 // Mix in each Underscore method as a proxy to `Model#attributes`.
582 _.each(modelMethods, function(method) {
583 Model.prototype[method] = function() {
584 var args = slice.call(arguments);
585 args.unshift(this.attributes);
586 return _[method].apply(_, args);
587 };
588 });
589
590 // Backbone.Collection
591 // -------------------
592
593 // If models tend to represent a single row of data, a Backbone Collection is
594 // more analagous to a table full of data ... or a small slice or page of that
595 // table, or a collection of rows that belong together for a particular reason
596 // -- all of the messages in this particular folder, all of the documents
597 // belonging to this particular author, and so on. Collections maintain
598 // indexes of their models, both in order, and for lookup by `id`.
599
600 // Create a new **Collection**, perhaps to contain a specific type of `model`.
601 // If a `comparator` is specified, the Collection will maintain
602 // its models in sort order, as they're added and removed.
603 var Collection = Backbone.Collection = function(models, options) {
604 options || (options = {});
605 if (options.model) this.model = options.model;
606 if (options.comparator !== void 0) this.comparator = options.comparator;
607 this._reset();
608 this.initialize.apply(this, arguments);
609 if (models) this.reset(models, _.extend({silent: true}, options));
610 };
611
612 // Default options for `Collection#set`.
613 var setOptions = {add: true, remove: true, merge: true};
614 var addOptions = {add: true, remove: false};
615
616 // Define the Collection's inheritable methods.
617 _.extend(Collection.prototype, Events, {
618
619 // The default model for a collection is just a **Backbone.Model**.
620 // This should be overridden in most cases.
621 model: Model,
622
623 // Initialize is an empty function by default. Override it with your own
624 // initialization logic.
625 initialize: function(){},
626
627 // The JSON representation of a Collection is an array of the
628 // models' attributes.
629 toJSON: function(options) {
630 return this.map(function(model){ return model.toJSON(options); });
631 },
632
633 // Proxy `Backbone.sync` by default.
634 sync: function() {
635 return Backbone.sync.apply(this, arguments);
636 },
637
638 // Add a model, or list of models to the set.
639 add: function(models, options) {
640 return this.set(models, _.extend({merge: false}, options, addOptions));
641 },
642
643 // Remove a model, or a list of models from the set.
644 remove: function(models, options) {
645 var singular = !_.isArray(models);
646 models = singular ? [models] : _.clone(models);
647 options || (options = {});
648 var i, l, index, model;
649 for (i = 0, l = models.length; i < l; i++) {
650 model = models[i] = this.get(models[i]);
651 if (!model) continue;
652 delete this._byId[model.id];
653 delete this._byId[model.cid];
654 index = this.indexOf(model);
655 this.models.splice(index, 1);
656 this.length--;
657 if (!options.silent) {
658 options.index = index;
659 model.trigger('remove', model, this, options);
660 }
661 this._removeReference(model, options);
662 }
663 return singular ? models[0] : models;
664 },
665
666 // Update a collection by `set`-ing a new list of models, adding new ones,
667 // removing models that are no longer present, and merging models that
668 // already exist in the collection, as necessary. Similar to **Model#set**,
669 // the core operation for updating the data contained by the collection.
670 set: function(models, options) {
671 options = _.defaults({}, options, setOptions);
672 if (options.parse) models = this.parse(models, options);
673 var singular = !_.isArray(models);
674 models = singular ? (models ? [models] : []) : _.clone(models);
675 var i, l, id, model, attrs, existing, sort;
676 var at = options.at;
677 var targetModel = this.model;
678 var sortable = this.comparator && (at == null) && options.sort !== false;
679 var sortAttr = _.isString(this.comparator) ? this.comparator : null;
680 var toAdd = [], toRemove = [], modelMap = {};
681 var add = options.add, merge = options.merge, remove = options.remove;
682 var order = !sortable && add && remove ? [] : false;
683
684 // Turn bare objects into model references, and prevent invalid models
685 // from being added.
686 for (i = 0, l = models.length; i < l; i++) {
687 attrs = models[i] || {};
688 if (attrs instanceof Model) {
689 id = model = attrs;
690 } else {
691 id = attrs[targetModel.prototype.idAttribute || 'id'];
692 }
693
694 // If a duplicate is found, prevent it from being added and
695 // optionally merge it into the existing model.
696 if (existing = this.get(id)) {
697 if (remove) modelMap[existing.cid] = true;
698 if (merge) {
699 attrs = attrs === model ? model.attributes : attrs;
700 if (options.parse) attrs = existing.parse(attrs, options);
701 existing.set(attrs, options);
702 if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
703 }
704 models[i] = existing;
705
706 // If this is a new, valid model, push it to the `toAdd` list.
707 } else if (add) {
708 model = models[i] = this._prepareModel(attrs, options);
709 if (!model) continue;
710 toAdd.push(model);
711 this._addReference(model, options);
712 }
713
714 // Do not add multiple models with the same `id`.
715 model = existing || model;
716 if (order && (model.isNew() || !modelMap[model.id])) order.push(model);
717 modelMap[model.id] = true;
718 }
719
720 // Remove nonexistent models if appropriate.
721 if (remove) {
722 for (i = 0, l = this.length; i < l; ++i) {
723 if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
724 }
725 if (toRemove.length) this.remove(toRemove, options);
726 }
727
728 // See if sorting is needed, update `length` and splice in new models.
729 if (toAdd.length || (order && order.length)) {
730 if (sortable) sort = true;
731 this.length += toAdd.length;
732 if (at != null) {
733 for (i = 0, l = toAdd.length; i < l; i++) {
734 this.models.splice(at + i, 0, toAdd[i]);
735 }
736 } else {
737 if (order) this.models.length = 0;
738 var orderedModels = order || toAdd;
739 for (i = 0, l = orderedModels.length; i < l; i++) {
740 this.models.push(orderedModels[i]);
741 }
742 }
743 }
744
745 // Silently sort the collection if appropriate.
746 if (sort) this.sort({silent: true});
747
748 // Unless silenced, it's time to fire all appropriate add/sort events.
749 if (!options.silent) {
750 for (i = 0, l = toAdd.length; i < l; i++) {
751 (model = toAdd[i]).trigger('add', model, this, options);
752 }
753 if (sort || (order && order.length)) this.trigger('sort', this, options);
754 }
755
756 // Return the added (or merged) model (or models).
757 return singular ? models[0] : models;
758 },
759
760 // When you have more items than you want to add or remove individually,
761 // you can reset the entire set with a new list of models, without firing
762 // any granular `add` or `remove` events. Fires `reset` when finished.
763 // Useful for bulk operations and optimizations.
764 reset: function(models, options) {
765 options || (options = {});
766 for (var i = 0, l = this.models.length; i < l; i++) {
767 this._removeReference(this.models[i], options);
768 }
769 options.previousModels = this.models;
770 this._reset();
771 models = this.add(models, _.extend({silent: true}, options));
772 if (!options.silent) this.trigger('reset', this, options);
773 return models;
774 },
775
776 // Add a model to the end of the collection.
777 push: function(model, options) {
778 return this.add(model, _.extend({at: this.length}, options));
779 },
780
781 // Remove a model from the end of the collection.
782 pop: function(options) {
783 var model = this.at(this.length - 1);
784 this.remove(model, options);
785 return model;
786 },
787
788 // Add a model to the beginning of the collection.
789 unshift: function(model, options) {
790 return this.add(model, _.extend({at: 0}, options));
791 },
792
793 // Remove a model from the beginning of the collection.
794 shift: function(options) {
795 var model = this.at(0);
796 this.remove(model, options);
797 return model;
798 },
799
800 // Slice out a sub-array of models from the collection.
801 slice: function() {
802 return slice.apply(this.models, arguments);
803 },
804
805 // Get a model from the set by id.
806 get: function(obj) {
807 if (obj == null) return void 0;
808 return this._byId[obj] || this._byId[obj.id] || this._byId[obj.cid];
809 },
810
811 // Get the model at the given index.
812 at: function(index) {
813 return this.models[index];
814 },
815
816 // Return models with matching attributes. Useful for simple cases of
817 // `filter`.
818 where: function(attrs, first) {
819 if (_.isEmpty(attrs)) return first ? void 0 : [];
820 return this[first ? 'find' : 'filter'](function(model) {
821 for (var key in attrs) {
822 if (attrs[key] !== model.get(key)) return false;
823 }
824 return true;
825 });
826 },
827
828 // Return the first model with matching attributes. Useful for simple cases
829 // of `find`.
830 findWhere: function(attrs) {
831 return this.where(attrs, true);
832 },
833
834 // Force the collection to re-sort itself. You don't need to call this under
835 // normal circumstances, as the set will maintain sort order as each item
836 // is added.
837 sort: function(options) {
838 if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
839 options || (options = {});
840
841 // Run sort based on type of `comparator`.
842 if (_.isString(this.comparator) || this.comparator.length === 1) {
843 this.models = this.sortBy(this.comparator, this);
844 } else {
845 this.models.sort(_.bind(this.comparator, this));
846 }
847
848 if (!options.silent) this.trigger('sort', this, options);
849 return this;
850 },
851
852 // Pluck an attribute from each model in the collection.
853 pluck: function(attr) {
854 return _.invoke(this.models, 'get', attr);
855 },
856
857 // Fetch the default set of models for this collection, resetting the
858 // collection when they arrive. If `reset: true` is passed, the response
859 // data will be passed through the `reset` method instead of `set`.
860 fetch: function(options) {
861 options = options ? _.clone(options) : {};
862 if (options.parse === void 0) options.parse = true;
863 var success = options.success;
864 var collection = this;
865 options.success = function(resp) {
866 var method = options.reset ? 'reset' : 'set';
867 collection[method](resp, options);
868 if (success) success(collection, resp, options);
869 collection.trigger('sync', collection, resp, options);
870 };
871 wrapError(this, options);
872 return this.sync('read', this, options);
873 },
874
875 // Create a new instance of a model in this collection. Add the model to the
876 // collection immediately, unless `wait: true` is passed, in which case we
877 // wait for the server to agree.
878 create: function(model, options) {
879 options = options ? _.clone(options) : {};
880 if (!(model = this._prepareModel(model, options))) return false;
881 if (!options.wait) this.add(model, options);
882 var collection = this;
883 var success = options.success;
884 options.success = function(model, resp) {
885 if (options.wait) collection.add(model, options);
886 if (success) success(model, resp, options);
887 };
888 model.save(null, options);
889 return model;
890 },
891
892 // **parse** converts a response into a list of models to be added to the
893 // collection. The default implementation is just to pass it through.
894 parse: function(resp, options) {
895 return resp;
896 },
897
898 // Create a new collection with an identical list of models as this one.
899 clone: function() {
900 return new this.constructor(this.models);
901 },
902
903 // Private method to reset all internal state. Called when the collection
904 // is first initialized or reset.
905 _reset: function() {
906 this.length = 0;
907 this.models = [];
908 this._byId = {};
909 },
910
911 // Prepare a hash of attributes (or other model) to be added to this
912 // collection.
913 _prepareModel: function(attrs, options) {
914 if (attrs instanceof Model) return attrs;
915 options = options ? _.clone(options) : {};
916 options.collection = this;
917 var model = new this.model(attrs, options);
918 if (!model.validationError) return model;
919 this.trigger('invalid', this, model.validationError, options);
920 return false;
921 },
922
923 // Internal method to create a model's ties to a collection.
924 _addReference: function(model, options) {
925 this._byId[model.cid] = model;
926 if (model.id != null) this._byId[model.id] = model;
927 if (!model.collection) model.collection = this;
928 model.on('all', this._onModelEvent, this);
929 },
930
931 // Internal method to sever a model's ties to a collection.
932 _removeReference: function(model, options) {
933 if (this === model.collection) delete model.collection;
934 model.off('all', this._onModelEvent, this);
935 },
936
937 // Internal method called every time a model in the set fires an event.
938 // Sets need to update their indexes when models change ids. All other
939 // events simply proxy through. "add" and "remove" events that originate
940 // in other collections are ignored.
941 _onModelEvent: function(event, model, collection, options) {
942 if ((event === 'add' || event === 'remove') && collection !== this) return;
943 if (event === 'destroy') this.remove(model, options);
944 if (model && event === 'change:' + model.idAttribute) {
945 delete this._byId[model.previous(model.idAttribute)];
946 if (model.id != null) this._byId[model.id] = model;
947 }
948 this.trigger.apply(this, arguments);
949 }
950
951 });
952
953 // Underscore methods that we want to implement on the Collection.
954 // 90% of the core usefulness of Backbone Collections is actually implemented
955 // right here:
956 var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
957 'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
958 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
959 'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
960 'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle',
961 'lastIndexOf', 'isEmpty', 'chain', 'sample'];
962
963 // Mix in each Underscore method as a proxy to `Collection#models`.
964 _.each(methods, function(method) {
965 Collection.prototype[method] = function() {
966 var args = slice.call(arguments);
967 args.unshift(this.models);
968 return _[method].apply(_, args);
969 };
970 });
971
972 // Underscore methods that take a property name as an argument.
973 var attributeMethods = ['groupBy', 'countBy', 'sortBy', 'indexBy'];
974
975 // Use attributes instead of properties.
976 _.each(attributeMethods, function(method) {
977 Collection.prototype[method] = function(value, context) {
978 var iterator = _.isFunction(value) ? value : function(model) {
979 return model.get(value);
980 };
981 return _[method](this.models, iterator, context);
982 };
983 });
984
985 // Backbone.View
986 // -------------
987
988 // Backbone Views are almost more convention than they are actual code. A View
989 // is simply a JavaScript object that represents a logical chunk of UI in the
990 // DOM. This might be a single item, an entire list, a sidebar or panel, or
991 // even the surrounding frame which wraps your whole app. Defining a chunk of
992 // UI as a **View** allows you to define your DOM events declaratively, without
993 // having to worry about render order ... and makes it easy for the view to
994 // react to specific changes in the state of your models.
995
996 // Creating a Backbone.View creates its initial element outside of the DOM,
997 // if an existing element is not provided...
998 var View = Backbone.View = function(options) {
999 this.cid = _.uniqueId('view');
1000 options || (options = {});
1001 _.extend(this, _.pick(options, viewOptions));
1002 this._ensureElement();
1003 this.initialize.apply(this, arguments);
1004 this.delegateEvents();
1005 };
1006
1007 // Cached regex to split keys for `delegate`.
1008 var delegateEventSplitter = /^(\S+)\s*(.*)$/;
1009
1010 // List of view options to be merged as properties.
1011 var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
1012
1013 // Set up all inheritable **Backbone.View** properties and methods.
1014 _.extend(View.prototype, Events, {
1015
1016 // The default `tagName` of a View's element is `"div"`.
1017 tagName: 'div',
1018
1019 // jQuery delegate for element lookup, scoped to DOM elements within the
1020 // current view. This should be preferred to global lookups where possible.
1021 $: function(selector) {
1022 return this.$el.find(selector);
1023 },
1024
1025 // Initialize is an empty function by default. Override it with your own
1026 // initialization logic.
1027 initialize: function(){},
1028
1029 // **render** is the core function that your view should override, in order
1030 // to populate its element (`this.el`), with the appropriate HTML. The
1031 // convention is for **render** to always return `this`.
1032 render: function() {
1033 return this;
1034 },
1035
1036 // Remove this view by taking the element out of the DOM, and removing any
1037 // applicable Backbone.Events listeners.
1038 remove: function() {
1039 this.$el.remove();
1040 this.stopListening();
1041 return this;
1042 },
1043
1044 // Change the view's element (`this.el` property), including event
1045 // re-delegation.
1046 setElement: function(element, delegate) {
1047 if (this.$el) this.undelegateEvents();
1048 this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
1049 this.el = this.$el[0];
1050 if (delegate !== false) this.delegateEvents();
1051 return this;
1052 },
1053
1054 // Set callbacks, where `this.events` is a hash of
1055 //
1056 // *{"event selector": "callback"}*
1057 //
1058 // {
1059 // 'mousedown .title': 'edit',
1060 // 'click .button': 'save',
1061 // 'click .open': function(e) { ... }
1062 // }
1063 //
1064 // pairs. Callbacks will be bound to the view, with `this` set properly.
1065 // Uses event delegation for efficiency.
1066 // Omitting the selector binds the event to `this.el`.
1067 // This only works for delegate-able events: not `focus`, `blur`, and
1068 // not `change`, `submit`, and `reset` in Internet Explorer.
1069 delegateEvents: function(events) {
1070 if (!(events || (events = _.result(this, 'events')))) return this;
1071 this.undelegateEvents();
1072 for (var key in events) {
1073 var method = events[key];
1074 if (!_.isFunction(method)) method = this[events[key]];
1075 if (!method) continue;
1076
1077 var match = key.match(delegateEventSplitter);
1078 var eventName = match[1], selector = match[2];
1079 method = _.bind(method, this);
1080 eventName += '.delegateEvents' + this.cid;
1081 if (selector === '') {
1082 this.$el.on(eventName, method);
1083 } else {
1084 this.$el.on(eventName, selector, method);
1085 }
1086 }
1087 return this;
1088 },
1089
1090 // Clears all callbacks previously bound to the view with `delegateEvents`.
1091 // You usually don't need to use this, but may wish to if you have multiple
1092 // Backbone views attached to the same DOM element.
1093 undelegateEvents: function() {
1094 this.$el.off('.delegateEvents' + this.cid);
1095 return this;
1096 },
1097
1098 // Ensure that the View has a DOM element to render into.
1099 // If `this.el` is a string, pass it through `$()`, take the first
1100 // matching element, and re-assign it to `el`. Otherwise, create
1101 // an element from the `id`, `className` and `tagName` properties.
1102 _ensureElement: function() {
1103 if (!this.el) {
1104 var attrs = _.extend({}, _.result(this, 'attributes'));
1105 if (this.id) attrs.id = _.result(this, 'id');
1106 if (this.className) attrs['class'] = _.result(this, 'className');
1107 var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
1108 this.setElement($el, false);
1109 } else {
1110 this.setElement(_.result(this, 'el'), false);
1111 }
1112 }
1113
1114 });
1115
1116 // Backbone.sync
1117 // -------------
1118
1119 // Override this function to change the manner in which Backbone persists
1120 // models to the server. You will be passed the type of request, and the
1121 // model in question. By default, makes a RESTful Ajax request
1122 // to the model's `url()`. Some possible customizations could be:
1123 //
1124 // * Use `setTimeout` to batch rapid-fire updates into a single request.
1125 // * Send up the models as XML instead of JSON.
1126 // * Persist models via WebSockets instead of Ajax.
1127 //
1128 // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
1129 // as `POST`, with a `_method` parameter containing the true HTTP method,
1130 // as well as all requests with the body as `application/x-www-form-urlencoded`
1131 // instead of `application/json` with the model in a param named `model`.
1132 // Useful when interfacing with server-side languages like **PHP** that make
1133 // it difficult to read the body of `PUT` requests.
1134 Backbone.sync = function(method, model, options) {
1135 var type = methodMap[method];
1136
1137 // Default options, unless specified.
1138 _.defaults(options || (options = {}), {
1139 emulateHTTP: Backbone.emulateHTTP,
1140 emulateJSON: Backbone.emulateJSON
1141 });
1142
1143 // Default JSON-request options.
1144 var params = {type: type, dataType: 'json'};
1145
1146 // Ensure that we have a URL.
1147 if (!options.url) {
1148 params.url = _.result(model, 'url') || urlError();
1149 }
1150
1151 // Ensure that we have the appropriate request data.
1152 if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
1153 params.contentType = 'application/json';
1154 params.data = JSON.stringify(options.attrs || model.toJSON(options));
1155 }
1156
1157 // For older servers, emulate JSON by encoding the request into an HTML-form.
1158 if (options.emulateJSON) {
1159 params.contentType = 'application/x-www-form-urlencoded';
1160 params.data = params.data ? {model: params.data} : {};
1161 }
1162
1163 // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1164 // And an `X-HTTP-Method-Override` header.
1165 if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
1166 params.type = 'POST';
1167 if (options.emulateJSON) params.data._method = type;
1168 var beforeSend = options.beforeSend;
1169 options.beforeSend = function(xhr) {
1170 xhr.setRequestHeader('X-HTTP-Method-Override', type);
1171 if (beforeSend) return beforeSend.apply(this, arguments);
1172 };
1173 }
1174
1175 // Don't process data on a non-GET request.
1176 if (params.type !== 'GET' && !options.emulateJSON) {
1177 params.processData = false;
1178 }
1179
1180 // If we're sending a `PATCH` request, and we're in an old Internet Explorer
1181 // that still has ActiveX enabled by default, override jQuery to use that
1182 // for XHR instead. Remove this line when jQuery supports `PATCH` on IE8.
1183 if (params.type === 'PATCH' && noXhrPatch) {
1184 params.xhr = function() {
1185 return new ActiveXObject("Microsoft.XMLHTTP");
1186 };
1187 }
1188
1189 // Make the request, allowing the user to override any Ajax options.
1190 var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
1191 model.trigger('request', model, xhr, options);
1192 return xhr;
1193 };
1194
1195 var noXhrPatch =
1196 typeof window !== 'undefined' && !!window.ActiveXObject &&
1197 !(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent);
1198
1199 // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1200 var methodMap = {
1201 'create': 'POST',
1202 'update': 'PUT',
1203 'patch': 'PATCH',
1204 'delete': 'DELETE',
1205 'read': 'GET'
1206 };
1207
1208 // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
1209 // Override this if you'd like to use a different library.
1210 Backbone.ajax = function() {
1211 return Backbone.$.ajax.apply(Backbone.$, arguments);
1212 };
1213
1214 // Backbone.Router
1215 // ---------------
1216
1217 // Routers map faux-URLs to actions, and fire events when routes are
1218 // matched. Creating a new one sets its `routes` hash, if not set statically.
1219 var Router = Backbone.Router = function(options) {
1220 options || (options = {});
1221 if (options.routes) this.routes = options.routes;
1222 this._bindRoutes();
1223 this.initialize.apply(this, arguments);
1224 };
1225
1226 // Cached regular expressions for matching named param parts and splatted
1227 // parts of route strings.
1228 var optionalParam = /\((.*?)\)/g;
1229 var namedParam = /(\(\?)?:\w+/g;
1230 var splatParam = /\*\w+/g;
1231 var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
1232
1233 // Set up all inheritable **Backbone.Router** properties and methods.
1234 _.extend(Router.prototype, Events, {
1235
1236 // Initialize is an empty function by default. Override it with your own
1237 // initialization logic.
1238 initialize: function(){},
1239
1240 // Manually bind a single named route to a callback. For example:
1241 //
1242 // this.route('search/:query/p:num', 'search', function(query, num) {
1243 // ...
1244 // });
1245 //
1246 route: function(route, name, callback) {
1247 if (!_.isRegExp(route)) route = this._routeToRegExp(route);
1248 if (_.isFunction(name)) {
1249 callback = name;
1250 name = '';
1251 }
1252 if (!callback) callback = this[name];
1253 var router = this;
1254 Backbone.history.route(route, function(fragment) {
1255 var args = router._extractParameters(route, fragment);
1256 router.execute(callback, args);
1257 router.trigger.apply(router, ['route:' + name].concat(args));
1258 router.trigger('route', name, args);
1259 Backbone.history.trigger('route', router, name, args);
1260 });
1261 return this;
1262 },
1263
1264 // Execute a route handler with the provided parameters. This is an
1265 // excellent place to do pre-route setup or post-route cleanup.
1266 execute: function(callback, args) {
1267 if (callback) callback.apply(this, args);
1268 },
1269
1270 // Simple proxy to `Backbone.history` to save a fragment into the history.
1271 navigate: function(fragment, options) {
1272 Backbone.history.navigate(fragment, options);
1273 return this;
1274 },
1275
1276 // Bind all defined routes to `Backbone.history`. We have to reverse the
1277 // order of the routes here to support behavior where the most general
1278 // routes can be defined at the bottom of the route map.
1279 _bindRoutes: function() {
1280 if (!this.routes) return;
1281 this.routes = _.result(this, 'routes');
1282 var route, routes = _.keys(this.routes);
1283 while ((route = routes.pop()) != null) {
1284 this.route(route, this.routes[route]);
1285 }
1286 },
1287
1288 // Convert a route string into a regular expression, suitable for matching
1289 // against the current location hash.
1290 _routeToRegExp: function(route) {
1291 route = route.replace(escapeRegExp, '\\$&')
1292 .replace(optionalParam, '(?:$1)?')
1293 .replace(namedParam, function(match, optional) {
1294 return optional ? match : '([^/?]+)';
1295 })
1296 .replace(splatParam, '([^?]*?)');
1297 return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$');
1298 },
1299
1300 // Given a route, and a URL fragment that it matches, return the array of
1301 // extracted decoded parameters. Empty or unmatched parameters will be
1302 // treated as `null` to normalize cross-browser behavior.
1303 _extractParameters: function(route, fragment) {
1304 var params = route.exec(fragment).slice(1);
1305 return _.map(params, function(param, i) {
1306 // Don't decode the search params.
1307 if (i === params.length - 1) return param || null;
1308 return param ? decodeURIComponent(param) : null;
1309 });
1310 }
1311
1312 });
1313
1314 // Backbone.History
1315 // ----------------
1316
1317 // Handles cross-browser history management, based on either
1318 // [pushState](http://diveintohtml5.info/history.html) and real URLs, or
1319 // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
1320 // and URL fragments. If the browser supports neither (old IE, natch),
1321 // falls back to polling.
1322 var History = Backbone.History = function() {
1323 this.handlers = [];
1324 _.bindAll(this, 'checkUrl');
1325
1326 // Ensure that `History` can be used outside of the browser.
1327 if (typeof window !== 'undefined') {
1328 this.location = window.location;
1329 this.history = window.history;
1330 }
1331 };
1332
1333 // Cached regex for stripping a leading hash/slash and trailing space.
1334 var routeStripper = /^[#\/]|\s+$/g;
1335
1336 // Cached regex for stripping leading and trailing slashes.
1337 var rootStripper = /^\/+|\/+$/g;
1338
1339 // Cached regex for detecting MSIE.
1340 var isExplorer = /msie [\w.]+/;
1341
1342 // Cached regex for removing a trailing slash.
1343 var trailingSlash = /\/$/;
1344
1345 // Cached regex for stripping urls of hash.
1346 var pathStripper = /#.*$/;
1347
1348 // Has the history handling already been started?
1349 History.started = false;
1350
1351 // Set up all inheritable **Backbone.History** properties and methods.
1352 _.extend(History.prototype, Events, {
1353
1354 // The default interval to poll for hash changes, if necessary, is
1355 // twenty times a second.
1356 interval: 50,
1357
1358 // Are we at the app root?
1359 atRoot: function() {
1360 return this.location.pathname.replace(/[^\/]$/, '$&/') === this.root;
1361 },
1362
1363 // Gets the true hash value. Cannot use location.hash directly due to bug
1364 // in Firefox where location.hash will always be decoded.
1365 getHash: function(window) {
1366 var match = (window || this).location.href.match(/#(.*)$/);
1367 return match ? match[1] : '';
1368 },
1369
1370 // Get the cross-browser normalized URL fragment, either from the URL,
1371 // the hash, or the override.
1372 getFragment: function(fragment, forcePushState) {
1373 if (fragment == null) {
1374 if (this._hasPushState || !this._wantsHashChange || forcePushState) {
1375 fragment = decodeURI(this.location.pathname + this.location.search);
1376 var root = this.root.replace(trailingSlash, '');
1377 if (!fragment.indexOf(root)) fragment = fragment.slice(root.length);
1378 } else {
1379 fragment = this.getHash();
1380 }
1381 }
1382 return fragment.replace(routeStripper, '');
1383 },
1384
1385 // Start the hash change handling, returning `true` if the current URL matches
1386 // an existing route, and `false` otherwise.
1387 start: function(options) {
1388 if (History.started) throw new Error("Backbone.history has already been started");
1389 History.started = true;
1390
1391 // Figure out the initial configuration. Do we need an iframe?
1392 // Is pushState desired ... is it available?
1393 this.options = _.extend({root: '/'}, this.options, options);
1394 this.root = this.options.root;
1395 this._wantsHashChange = this.options.hashChange !== false;
1396 this._wantsPushState = !!this.options.pushState;
1397 this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState);
1398 var fragment = this.getFragment();
1399 var docMode = document.documentMode;
1400 var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
1401
1402 // Normalize root to always include a leading and trailing slash.
1403 this.root = ('/' + this.root + '/').replace(rootStripper, '/');
1404
1405 if (oldIE && this._wantsHashChange) {
1406 var frame = Backbone.$('<iframe src="javascript:0" tabindex="-1">');
1407 this.iframe = frame.hide().appendTo('body')[0].contentWindow;
1408 this.navigate(fragment);
1409 }
1410
1411 // Depending on whether we're using pushState or hashes, and whether
1412 // 'onhashchange' is supported, determine how we check the URL state.
1413 if (this._hasPushState) {
1414 Backbone.$(window).on('popstate', this.checkUrl);
1415 } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
1416 Backbone.$(window).on('hashchange', this.checkUrl);
1417 } else if (this._wantsHashChange) {
1418 this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
1419 }
1420
1421 // Determine if we need to change the base url, for a pushState link
1422 // opened by a non-pushState browser.
1423 this.fragment = fragment;
1424 var loc = this.location;
1425
1426 // Transition from hashChange to pushState or vice versa if both are
1427 // requested.
1428 if (this._wantsHashChange && this._wantsPushState) {
1429
1430 // If we've started off with a route from a `pushState`-enabled
1431 // browser, but we're currently in a browser that doesn't support it...
1432 if (!this._hasPushState && !this.atRoot()) {
1433 this.fragment = this.getFragment(null, true);
1434 this.location.replace(this.root + '#' + this.fragment);
1435 // Return immediately as browser will do redirect to new url
1436 return true;
1437
1438 // Or if we've started out with a hash-based route, but we're currently
1439 // in a browser where it could be `pushState`-based instead...
1440 } else if (this._hasPushState && this.atRoot() && loc.hash) {
1441 this.fragment = this.getHash().replace(routeStripper, '');
1442 this.history.replaceState({}, document.title, this.root + this.fragment);
1443 }
1444
1445 }
1446
1447 if (!this.options.silent) return this.loadUrl();
1448 },
1449
1450 // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
1451 // but possibly useful for unit testing Routers.
1452 stop: function() {
1453 Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl);
1454 if (this._checkUrlInterval) clearInterval(this._checkUrlInterval);
1455 History.started = false;
1456 },
1457
1458 // Add a route to be tested when the fragment changes. Routes added later
1459 // may override previous routes.
1460 route: function(route, callback) {
1461 this.handlers.unshift({route: route, callback: callback});
1462 },
1463
1464 // Checks the current URL to see if it has changed, and if it has,
1465 // calls `loadUrl`, normalizing across the hidden iframe.
1466 checkUrl: function(e) {
1467 var current = this.getFragment();
1468 if (current === this.fragment && this.iframe) {
1469 current = this.getFragment(this.getHash(this.iframe));
1470 }
1471 if (current === this.fragment) return false;
1472 if (this.iframe) this.navigate(current);
1473 this.loadUrl();
1474 },
1475
1476 // Attempt to load the current URL fragment. If a route succeeds with a
1477 // match, returns `true`. If no defined routes matches the fragment,
1478 // returns `false`.
1479 loadUrl: function(fragment) {
1480 fragment = this.fragment = this.getFragment(fragment);
1481 return _.any(this.handlers, function(handler) {
1482 if (handler.route.test(fragment)) {
1483 handler.callback(fragment);
1484 return true;
1485 }
1486 });
1487 },
1488
1489 // Save a fragment into the hash history, or replace the URL state if the
1490 // 'replace' option is passed. You are responsible for properly URL-encoding
1491 // the fragment in advance.
1492 //
1493 // The options object can contain `trigger: true` if you wish to have the
1494 // route callback be fired (not usually desirable), or `replace: true`, if
1495 // you wish to modify the current URL without adding an entry to the history.
1496 navigate: function(fragment, options) {
1497 if (!History.started) return false;
1498 if (!options || options === true) options = {trigger: !!options};
1499
1500 var url = this.root + (fragment = this.getFragment(fragment || ''));
1501
1502 // Strip the hash for matching.
1503 fragment = fragment.replace(pathStripper, '');
1504
1505 if (this.fragment === fragment) return;
1506 this.fragment = fragment;
1507
1508 // Don't include a trailing slash on the root.
1509 if (fragment === '' && url !== '/') url = url.slice(0, -1);
1510
1511 // If pushState is available, we use it to set the fragment as a real URL.
1512 if (this._hasPushState) {
1513 this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
1514
1515 // If hash changes haven't been explicitly disabled, update the hash
1516 // fragment to store history.
1517 } else if (this._wantsHashChange) {
1518 this._updateHash(this.location, fragment, options.replace);
1519 if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) {
1520 // Opening and closing the iframe tricks IE7 and earlier to push a
1521 // history entry on hash-tag change. When replace is true, we don't
1522 // want this.
1523 if(!options.replace) this.iframe.document.open().close();
1524 this._updateHash(this.iframe.location, fragment, options.replace);
1525 }
1526
1527 // If you've told us that you explicitly don't want fallback hashchange-
1528 // based history, then `navigate` becomes a page refresh.
1529 } else {
1530 return this.location.assign(url);
1531 }
1532 if (options.trigger) return this.loadUrl(fragment);
1533 },
1534
1535 // Update the hash location, either replacing the current entry, or adding
1536 // a new one to the browser history.
1537 _updateHash: function(location, fragment, replace) {
1538 if (replace) {
1539 var href = location.href.replace(/(javascript:|#).*$/, '');
1540 location.replace(href + '#' + fragment);
1541 } else {
1542 // Some browsers require that `hash` contains a leading #.
1543 location.hash = '#' + fragment;
1544 }
1545 }
1546
1547 });
1548
1549 // Create the default Backbone.history.
1550 Backbone.history = new History;
1551
1552 // Helpers
1553 // -------
1554
1555 // Helper function to correctly set up the prototype chain, for subclasses.
1556 // Similar to `goog.inherits`, but uses a hash of prototype properties and
1557 // class properties to be extended.
1558 var extend = function(protoProps, staticProps) {
1559 var parent = this;
1560 var child;
1561
1562 // The constructor function for the new subclass is either defined by you
1563 // (the "constructor" property in your `extend` definition), or defaulted
1564 // by us to simply call the parent's constructor.
1565 if (protoProps && _.has(protoProps, 'constructor')) {
1566 child = protoProps.constructor;
1567 } else {
1568 child = function(){ return parent.apply(this, arguments); };
1569 }
1570
1571 // Add static properties to the constructor function, if supplied.
1572 _.extend(child, parent, staticProps);
1573
1574 // Set the prototype chain to inherit from `parent`, without calling
1575 // `parent`'s constructor function.
1576 var Surrogate = function(){ this.constructor = child; };
1577 Surrogate.prototype = parent.prototype;
1578 child.prototype = new Surrogate;
1579
1580 // Add prototype properties (instance properties) to the subclass,
1581 // if supplied.
1582 if (protoProps) _.extend(child.prototype, protoProps);
1583
1584 // Set a convenience property in case the parent's prototype is needed
1585 // later.
1586 child.__super__ = parent.prototype;
1587
1588 return child;
1589 };
1590
1591 // Set up inheritance for the model, collection, router, view and history.
1592 Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
1593
1594 // Throw an error when a URL is needed, and none is supplied.
1595 var urlError = function() {
1596 throw new Error('A "url" property or function must be specified');
1597 };
1598
1599 // Wrap an optional error callback with a fallback error event.
1600 var wrapError = function(model, options) {
1601 var error = options.error;
1602 options.error = function(resp) {
1603 if (error) error(model, resp, options);
1604 model.trigger('error', model, resp, options);
1605 };
1606 };
1607
1608 return Backbone;
1609
1610 }));