// Backbone.js 1.0.0
2
3 // (c) 2010-2013 Jeremy Ashkenas, DocumentCloud Inc.
4 // Backbone may be freely distributed under the MIT license.
5 // For all details and documentation:
6 // http://backbonejs.org
7
8 (function(){
9
10 // Initial Setup
11 // -------------
12
13 // Save a reference to the global object (`window` in the browser, `exports`
14 // on the server).
15 var root = this;
16
17 // Save the previous value of the `Backbone` variable, so that it can be
18 // restored later on, if `noConflict` is used.
19 var previousBackbone = root.Backbone;
20
21 // Create local references to array methods we'll want to use later.
22 var array = [];
23 var push = array.push;
24 var slice = array.slice;
25 var splice = array.splice;
26
27 // The top-level namespace. All public Backbone classes and modules will
28 // be attached to this. Exported for both the browser and the server.
29 var Backbone;
30 if (typeof exports !== 'undefined') {
31 Backbone = exports;
32 } else {
33 Backbone = root.Backbone = {};
34 }
35
36 // Current version of the library. Keep in sync with `package.json`.
37 Backbone.VERSION = '1.0.0';
38
39 // Require Underscore, if we're on the server, and it's not already present.
40 var _ = root._;
41 if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
42
43 // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
44 // the `$` variable.
45 Backbone.$ = root.jQuery || root.Zepto || root.ender || root.$;
46
47 // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
48 // to its previous owner. Returns a reference to this Backbone object.
49 Backbone.noConflict = function() {
50 root.Backbone = previousBackbone;
51 return this;
52 };
53
54 // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
55 // will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
56 // set a `X-Http-Method-Override` header.
57 Backbone.emulateHTTP = false;
58
59 // Turn on `emulateJSON` to support legacy servers that can't deal with direct
60 // `application/json` requests ... will encode the body as
61 // `application/x-www-form-urlencoded` instead and will send the model in a
62 // form param named `model`.
63 Backbone.emulateJSON = false;
64
65 // Backbone.Events
66 // ---------------
67
68 // A module that can be mixed in to *any object* in order to provide it with
69 // custom events. You may bind with `on` or remove with `off` callback
70 // functions to an event; `trigger`-ing an event fires all callbacks in
71 // succession.
72 //
73 // var object = {};
74 // _.extend(object, Backbone.Events);
75 // object.on('expand', function(){ alert('expanded'); });
76 // object.trigger('expand');
77 //
78 var Events = Backbone.Events = {
79
80 // Bind an event to a `callback` function. Passing `"all"` will bind
81 // the callback to all events fired.
82 on: function(name, callback, context) {
83 if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
84 this._events || (this._events = {});
85 var events = this._events[name] || (this._events[name] = []);
86 events.push({callback: callback, context: context, ctx: context || this});
87 return this;
88 },
89
90 // Bind an event to only be triggered a single time. After the first time
91 // the callback is invoked, it will be removed.
92 once: function(name, callback, context) {
93 if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
94 var self = this;
95 var once = _.once(function() {
96 self.off(name, once);
97 callback.apply(this, arguments);
98 });
99 once._callback = callback;
100 return this.on(name, once, context);
101 },
102
103 // Remove one or many callbacks. If `context` is null, removes all
104 // callbacks with that function. If `callback` is null, removes all
105 // callbacks for the event. If `name` is null, removes all bound
106 // callbacks for all events.
107 off: function(name, callback, context) {
108 var retain, ev, events, names, i, l, j, k;
109 if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
110 if (!name && !callback && !context) {
111 this._events = {};
112 return this;
113 }
114
115 names = name ? [name] : _.keys(this._events);
116 for (i = 0, l = names.length; i < l; i++) {
117 name = names[i];
118 if (events = this._events[name]) {
119 this._events[name] = retain = [];
120 if (callback || context) {
121 for (j = 0, k = events.length; j < k; j++) {
122 ev = events[j];
123 if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
124 (context && context !== ev.context)) {
125 retain.push(ev);
126 }
127 }
128 }
129 if (!retain.length) delete this._events[name];
130 }
131 }
132
133 return this;
134 },
135
136 // Trigger one or many events, firing all bound callbacks. Callbacks are
137 // passed the same arguments as `trigger` is, apart from the event name
138 // (unless you're listening on `"all"`, which will cause your callback to
139 // receive the true name of the event as the first argument).
140 trigger: function(name) {
141 if (!this._events) return this;
142 var args = slice.call(arguments, 1);
143 if (!eventsApi(this, 'trigger', name, args)) return this;
144 var events = this._events[name];
145 var allEvents = this._events.all;
146 if (events) triggerEvents(events, args);
147 if (allEvents) triggerEvents(allEvents, arguments);
148 return this;
149 },
150
151 // Tell this object to stop listening to either specific events ... or
152 // to every object it's currently listening to.
153 stopListening: function(obj, name, callback) {
154 var listeners = this._listeners;
155 if (!listeners) return this;
156 var deleteListener = !name && !callback;
157 if (typeof name === 'object') callback = this;
158 if (obj) (listeners = {})[obj._listenerId] = obj;
159 for (var id in listeners) {
160 listeners[id].off(name, callback, this);
161 if (deleteListener) delete this._listeners[id];
162 }
163 return this;
164 }
165
166 };
167
168 // Regular expression used to split event strings.
169 var eventSplitter = /\s+/;
170
171 // Implement fancy features of the Events API such as multiple event
172 // names `"change blur"` and jQuery-style event maps `{change: action}`
173 // in terms of the existing API.
174 var eventsApi = function(obj, action, name, rest) {
175 if (!name) return true;
176
177 // Handle event maps.
178 if (typeof name === 'object') {
179 for (var key in name) {
180 obj[action].apply(obj, [key, name[key]].concat(rest));
181 }
182 return false;
183 }
184
185 // Handle space separated event names.
186 if (eventSplitter.test(name)) {
187 var names = name.split(eventSplitter);
188 for (var i = 0, l = names.length; i < l; i++) {
189 obj[action].apply(obj, [names[i]].concat(rest));
190 }
191 return false;
192 }
193
194 return true;
195 };
196
197 // A difficult-to-believe, but optimized internal dispatch function for
198 // triggering events. Tries to keep the usual cases speedy (most internal
199 // Backbone events have 3 arguments).
200 var triggerEvents = function(events, args) {
201 var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
202 switch (args.length) {
203 case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
204 case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
205 case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
206 case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
207 default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
208 }
209 };
210
211 var listenMethods = {listenTo: 'on', listenToOnce: 'once'};
212
213 // Inversion-of-control versions of `on` and `once`. Tell *this* object to
214 // listen to an event in another object ... keeping track of what it's
215 // listening to.
216 _.each(listenMethods, function(implementation, method) {
217 Events[method] = function(obj, name, callback) {
218 var listeners = this._listeners || (this._listeners = {});
219 var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
220 listeners[id] = obj;
221 if (typeof name === 'object') callback = this;
222 obj[implementation](name, callback, this);
223 return this;
224 };
225 });
226
227 // Aliases for backwards compatibility.
228 Events.bind = Events.on;
229 Events.unbind = Events.off;
230
231 // Allow the `Backbone` object to serve as a global event bus, for folks who
232 // want global "pubsub" in a convenient place.
233 _.extend(Backbone, Events);
234
235 // Backbone.Model
236 // --------------
237
238 // Backbone **Models** are the basic data object in the framework --
239 // frequently representing a row in a table in a database on your server.
240 // A discrete chunk of data and a bunch of useful, related methods for
241 // performing computations and transformations on that data.
242
243 // Create a new model with the specified attributes. A client id (`cid`)
244 // is automatically generated and assigned for you.
245 var Model = Backbone.Model = function(attributes, options) {
246 var defaults;
247 var attrs = attributes || {};
248 options || (options = {});
249 this.cid = _.uniqueId('c');
250 this.attributes = {};
251 _.extend(this, _.pick(options, modelOptions));
252 if (options.parse) attrs = this.parse(attrs, options) || {};
253 if (defaults = _.result(this, 'defaults')) {
254 attrs = _.defaults({}, attrs, defaults);
255 }
256 this.set(attrs, options);
257 this.changed = {};
258 this.initialize.apply(this, arguments);
259 };
260
261 // A list of options to be attached directly to the model, if provided.
262 var modelOptions = ['url', 'urlRoot', 'collection'];
263
264 // Attach all inheritable methods to the Model prototype.
265 _.extend(Model.prototype, Events, {
266
267 // A hash of attributes whose current and previous value differ.
268 changed: null,
269
270 // The value returned during the last failed validation.
271 validationError: null,
272
273 // The default name for the JSON `id` attribute is `"id"`. MongoDB and
274 // CouchDB users may want to set this to `"_id"`.
275 idAttribute: 'id',
276
277 // Initialize is an empty function by default. Override it with your own
278 // initialization logic.
279 initialize: function(){},
280
281 // Return a copy of the model's `attributes` object.
282 toJSON: function(options) {
283 return _.clone(this.attributes);
284 },
285
286 // Proxy `Backbone.sync` by default -- but override this if you need
287 // custom syncing semantics for *this* particular model.
288 sync: function() {
289 return Backbone.sync.apply(this, arguments);
290 },
291
292 // Get the value of an attribute.
293 get: function(attr) {
294 return this.attributes[attr];
295 },
296
297 // Get the HTML-escaped value of an attribute.
298 escape: function(attr) {
299 return _.escape(this.get(attr));
300 },
301
302 // Returns `true` if the attribute contains a value that is not null
303 // or undefined.
304 has: function(attr) {
305 return this.get(attr) != null;
306 },
307
308 // Set a hash of model attributes on the object, firing `"change"`. This is
309 // the core primitive operation of a model, updating the data and notifying
310 // anyone who needs to know about the change in state. The heart of the beast.
311 set: function(key, val, options) {
312 var attr, attrs, unset, changes, silent, changing, prev, current;
313 if (key == null) return this;
314
315 // Handle both `"key", value` and `{key: value}` -style arguments.
316 if (typeof key === 'object') {
317 attrs = key;
318 options = val;
319 } else {
320 (attrs = {})[key] = val;
321 }
322
323 options || (options = {});
324
325 // Run validation.
326 if (!this._validate(attrs, options)) return false;
327
328 // Extract attributes and options.
329 unset = options.unset;
330 silent = options.silent;
331 changes = [];
332 changing = this._changing;
333 this._changing = true;
334
335 if (!changing) {
336 this._previousAttributes = _.clone(this.attributes);
337 this.changed = {};
338 }
339 current = this.attributes, prev = this._previousAttributes;
340
341 // Check for changes of `id`.
342 if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
343
344 // For each `set` attribute, update or delete the current value.
345 for (attr in attrs) {
346 val = attrs[attr];
347 if (!_.isEqual(current[attr], val)) changes.push(attr);
348 if (!_.isEqual(prev[attr], val)) {
349 this.changed[attr] = val;
350 } else {
351 delete this.changed[attr];
352 }
353 unset ? delete current[attr] : current[attr] = val;
354 }
355
356 // Trigger all relevant attribute changes.
357 if (!silent) {
358 if (changes.length) this._pending = true;
359 for (var i = 0, l = changes.length; i < l; i++) {
360 this.trigger('change:' + changes[i], this, current[changes[i]], options);
361 }
362 }
363
364 // You might be wondering why there's a `while` loop here. Changes can
365 // be recursively nested within `"change"` events.
366 if (changing) return this;
367 if (!silent) {
368 while (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 // If we're not waiting and attributes exist, save acts as `set(attr).save(null, opts)`.
460 if (attrs && (!options || !options.wait) && !this.set(attrs, options)) return false;
461
462 options = _.extend({validate: true}, options);
463
464 // Do not persist invalid models.
465 if (!this._validate(attrs, options)) return false;
466
467 // Set temporary attributes if `{wait: true}`.
468 if (attrs && options.wait) {
469 this.attributes = _.extend({}, attributes, attrs);
470 }
471
472 // After a successful server-side save, the client is (optionally)
473 // updated with the server-side state.
474 if (options.parse === void 0) options.parse = true;
475 var model = this;
476 var success = options.success;
477 options.success = function(resp) {
478 // Ensure attributes are restored during synchronous saves.
479 model.attributes = attributes;
480 var serverAttrs = model.parse(resp, options);
481 if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
482 if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
483 return false;
484 }
485 if (success) success(model, resp, options);
486 model.trigger('sync', model, resp, options);
487 };
488 wrapError(this, options);
489
490 method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
491 if (method === 'patch') options.attrs = attrs;
492 xhr = this.sync(method, this, options);
493
494 // Restore attributes.
495 if (attrs && options.wait) this.attributes = attributes;
496
497 return xhr;
498 },
499
500 // Destroy this model on the server if it was already persisted.
501 // Optimistically removes the model from its collection, if it has one.
502 // If `wait: true` is passed, waits for the server to respond before removal.
503 destroy: function(options) {
504 options = options ? _.clone(options) : {};
505 var model = this;
506 var success = options.success;
507
508 var destroy = function() {
509 model.trigger('destroy', model, model.collection, options);
510 };
511
512 options.success = function(resp) {
513 if (options.wait || model.isNew()) destroy();
514 if (success) success(model, resp, options);
515 if (!model.isNew()) model.trigger('sync', model, resp, options);
516 };
517
518 if (this.isNew()) {
519 options.success();
520 return false;
521 }
522 wrapError(this, options);
523
524 var xhr = this.sync('delete', this, options);
525 if (!options.wait) destroy();
526 return xhr;
527 },
528
529 // Default URL for the model's representation on the server -- if you're
530 // using Backbone's restful methods, override this to change the endpoint
531 // that will be called.
532 url: function() {
533 var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError();
534 if (this.isNew()) return base;
535 return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id);
536 },
537
538 // **parse** converts a response into the hash of attributes to be `set` on
539 // the model. The default implementation is just to pass the response along.
540 parse: function(resp, options) {
541 return resp;
542 },
543
544 // Create a new model with identical attributes to this one.
545 clone: function() {
546 return new this.constructor(this.attributes);
547 },
548
549 // A model is new if it has never been saved to the server, and lacks an id.
550 isNew: function() {
551 return this.id == null;
552 },
553
554 // Check if the model is currently in a valid state.
555 isValid: function(options) {
556 return this._validate({}, _.extend(options || {}, { validate: true }));
557 },
558
559 // Run validation against the next complete set of model attributes,
560 // returning `true` if all is well. Otherwise, fire an `"invalid"` event.
561 _validate: function(attrs, options) {
562 if (!options.validate || !this.validate) return true;
563 attrs = _.extend({}, this.attributes, attrs);
564 var error = this.validationError = this.validate(attrs, options) || null;
565 if (!error) return true;
566 this.trigger('invalid', this, error, _.extend(options || {}, {validationError: error}));
567 return false;
568 }
569
570 });
571
572 // Underscore methods that we want to implement on the Model.
573 var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit'];
574
575 // Mix in each Underscore method as a proxy to `Model#attributes`.
576 _.each(modelMethods, function(method) {
577 Model.prototype[method] = function() {
578 var args = slice.call(arguments);
579 args.unshift(this.attributes);
580 return _[method].apply(_, args);
581 };
582 });
583
584 // Backbone.Collection
585 // -------------------
586
587 // If models tend to represent a single row of data, a Backbone Collection is
588 // more analagous to a table full of data ... or a small slice or page of that
589 // table, or a collection of rows that belong together for a particular reason
590 // -- all of the messages in this particular folder, all of the documents
591 // belonging to this particular author, and so on. Collections maintain
592 // indexes of their models, both in order, and for lookup by `id`.
593
594 // Create a new **Collection**, perhaps to contain a specific type of `model`.
595 // If a `comparator` is specified, the Collection will maintain
596 // its models in sort order, as they're added and removed.
597 var Collection = Backbone.Collection = function(models, options) {
598 options || (options = {});
599 if (options.url) this.url = options.url;
600 if (options.model) this.model = options.model;
601 if (options.comparator !== void 0) this.comparator = options.comparator;
602 this._reset();
603 this.initialize.apply(this, arguments);
604 if (models) this.reset(models, _.extend({silent: true}, options));
605 };
606
607 // Default options for `Collection#set`.
608 var setOptions = {add: true, remove: true, merge: true};
609 var addOptions = {add: true, merge: false, remove: false};
610
611 // Define the Collection's inheritable methods.
612 _.extend(Collection.prototype, Events, {
613
614 // The default model for a collection is just a **Backbone.Model**.
615 // This should be overridden in most cases.
616 model: Model,
617
618 // Initialize is an empty function by default. Override it with your own
619 // initialization logic.
620 initialize: function(){},
621
622 // The JSON representation of a Collection is an array of the
623 // models' attributes.
624 toJSON: function(options) {
625 return this.map(function(model){ return model.toJSON(options); });
626 },
627
628 // Proxy `Backbone.sync` by default.
629 sync: function() {
630 return Backbone.sync.apply(this, arguments);
631 },
632
633 // Add a model, or list of models to the set.
634 add: function(models, options) {
635 return this.set(models, _.defaults(options || {}, addOptions));
636 },
637
638 // Remove a model, or a list of models from the set.
639 remove: function(models, options) {
640 models = _.isArray(models) ? models.slice() : [models];
641 options || (options = {});
642 var i, l, index, model;
643 for (i = 0, l = models.length; i < l; i++) {
644 model = this.get(models[i]);
645 if (!model) continue;
646 delete this._byId[model.id];
647 delete this._byId[model.cid];
648 index = this.indexOf(model);
649 this.models.splice(index, 1);
650 this.length--;
651 if (!options.silent) {
652 options.index = index;
653 model.trigger('remove', model, this, options);
654 }
655 this._removeReference(model);
656 }
657 return this;
658 },
659
660 // Update a collection by `set`-ing a new list of models, adding new ones,
661 // removing models that are no longer present, and merging models that
662 // already exist in the collection, as necessary. Similar to **Model#set**,
663 // the core operation for updating the data contained by the collection.
664 set: function(models, options) {
665 options = _.defaults(options || {}, setOptions);
666 if (options.parse) models = this.parse(models, options);
667 if (!_.isArray(models)) models = models ? [models] : [];
668 var i, l, model, attrs, existing, sort;
669 var at = options.at;
670 var sortable = this.comparator && (at == null) && options.sort !== false;
671 var sortAttr = _.isString(this.comparator) ? this.comparator : null;
672 var toAdd = [], toRemove = [], modelMap = {};
673
674 // Turn bare objects into model references, and prevent invalid models
675 // from being added.
676 for (i = 0, l = models.length; i < l; i++) {
677 if (!(model = this._prepareModel(models[i], options))) continue;
678
679 // If a duplicate is found, prevent it from being added and
680 // optionally merge it into the existing model.
681 if (existing = this.get(model)) {
682 if (options.remove) modelMap[existing.cid] = true;
683 if (options.merge) {
684 existing.set(model.attributes, options);
685 if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
686 }
687
688 // This is a new model, push it to the `toAdd` list.
689 } else if (options.add) {
690 toAdd.push(model);
691
692 // Listen to added models' events, and index models for lookup by
693 // `id` and by `cid`.
694 model.on('all', this._onModelEvent, this);
695 this._byId[model.cid] = model;
696 if (model.id != null) this._byId[model.id] = model;
697 }
698 }
699
700 // Remove nonexistent models if appropriate.
701 if (options.remove) {
702 for (i = 0, l = this.length; i < l; ++i) {
703 if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
704 }
705 if (toRemove.length) this.remove(toRemove, options);
706 }
707
708 // See if sorting is needed, update `length` and splice in new models.
709 if (toAdd.length) {
710 if (sortable) sort = true;
711 this.length += toAdd.length;
712 if (at != null) {
713 splice.apply(this.models, [at, 0].concat(toAdd));
714 } else {
715 push.apply(this.models, toAdd);
716 }
717 }
718
719 // Silently sort the collection if appropriate.
720 if (sort) this.sort({silent: true});
721
722 if (options.silent) return this;
723
724 // Trigger `add` events.
725 for (i = 0, l = toAdd.length; i < l; i++) {
726 (model = toAdd[i]).trigger('add', model, this, options);
727 }
728
729 // Trigger `sort` if the collection was sorted.
730 if (sort) this.trigger('sort', this, options);
731 return this;
732 },
733
734 // When you have more items than you want to add or remove individually,
735 // you can reset the entire set with a new list of models, without firing
736 // any granular `add` or `remove` events. Fires `reset` when finished.
737 // Useful for bulk operations and optimizations.
738 reset: function(models, options) {
739 options || (options = {});
740 for (var i = 0, l = this.models.length; i < l; i++) {
741 this._removeReference(this.models[i]);
742 }
743 options.previousModels = this.models;
744 this._reset();
745 this.add(models, _.extend({silent: true}, options));
746 if (!options.silent) this.trigger('reset', this, options);
747 return this;
748 },
749
750 // Add a model to the end of the collection.
751 push: function(model, options) {
752 model = this._prepareModel(model, options);
753 this.add(model, _.extend({at: this.length}, options));
754 return model;
755 },
756
757 // Remove a model from the end of the collection.
758 pop: function(options) {
759 var model = this.at(this.length - 1);
760 this.remove(model, options);
761 return model;
762 },
763
764 // Add a model to the beginning of the collection.
765 unshift: function(model, options) {
766 model = this._prepareModel(model, options);
767 this.add(model, _.extend({at: 0}, options));
768 return model;
769 },
770
771 // Remove a model from the beginning of the collection.
772 shift: function(options) {
773 var model = this.at(0);
774 this.remove(model, options);
775 return model;
776 },
777
778 // Slice out a sub-array of models from the collection.
779 slice: function(begin, end) {
780 return this.models.slice(begin, end);
781 },
782
783 // Get a model from the set by id.
784 get: function(obj) {
785 if (obj == null) return void 0;
786 return this._byId[obj.id != null ? obj.id : obj.cid || obj];
787 },
788
789 // Get the model at the given index.
790 at: function(index) {
791 return this.models[index];
792 },
793
794 // Return models with matching attributes. Useful for simple cases of
795 // `filter`.
796 where: function(attrs, first) {
797 if (_.isEmpty(attrs)) return first ? void 0 : [];
798 return this[first ? 'find' : 'filter'](function(model) {
799 for (var key in attrs) {
800 if (attrs[key] !== model.get(key)) return false;
801 }
802 return true;
803 });
804 },
805
806 // Return the first model with matching attributes. Useful for simple cases
807 // of `find`.
808 findWhere: function(attrs) {
809 return this.where(attrs, true);
810 },
811
812 // Force the collection to re-sort itself. You don't need to call this under
813 // normal circumstances, as the set will maintain sort order as each item
814 // is added.
815 sort: function(options) {
816 if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
817 options || (options = {});
818
819 // Run sort based on type of `comparator`.
820 if (_.isString(this.comparator) || this.comparator.length === 1) {
821 this.models = this.sortBy(this.comparator, this);
822 } else {
823 this.models.sort(_.bind(this.comparator, this));
824 }
825
826 if (!options.silent) this.trigger('sort', this, options);
827 return this;
828 },
829
830 // Figure out the smallest index at which a model should be inserted so as
831 // to maintain order.
832 sortedIndex: function(model, value, context) {
833 value || (value = this.comparator);
834 var iterator = _.isFunction(value) ? value : function(model) {
835 return model.get(value);
836 };
837 return _.sortedIndex(this.models, model, iterator, context);
838 },
839
840 // Pluck an attribute from each model in the collection.
841 pluck: function(attr) {
842 return _.invoke(this.models, 'get', attr);
843 },
844
845 // Fetch the default set of models for this collection, resetting the
846 // collection when they arrive. If `reset: true` is passed, the response
847 // data will be passed through the `reset` method instead of `set`.
848 fetch: function(options) {
849 options = options ? _.clone(options) : {};
850 if (options.parse === void 0) options.parse = true;
851 var success = options.success;
852 var collection = this;
853 options.success = function(resp) {
854 var method = options.reset ? 'reset' : 'set';
855 collection[method](resp, options);
856 if (success) success(collection, resp, options);
857 collection.trigger('sync', collection, resp, options);
858 };
859 wrapError(this, options);
860 return this.sync('read', this, options);
861 },
862
863 // Create a new instance of a model in this collection. Add the model to the
864 // collection immediately, unless `wait: true` is passed, in which case we
865 // wait for the server to agree.
866 create: function(model, options) {
867 options = options ? _.clone(options) : {};
868 if (!(model = this._prepareModel(model, options))) return false;
869 if (!options.wait) this.add(model, options);
870 var collection = this;
871 var success = options.success;
872 options.success = function(resp) {
873 if (options.wait) collection.add(model, options);
874 if (success) success(model, resp, options);
875 };
876 model.save(null, options);
877 return model;
878 },
879
880 // **parse** converts a response into a list of models to be added to the
881 // collection. The default implementation is just to pass it through.
882 parse: function(resp, options) {
883 return resp;
884 },
885
886 // Create a new collection with an identical list of models as this one.
887 clone: function() {
888 return new this.constructor(this.models);
889 },
890
891 // Private method to reset all internal state. Called when the collection
892 // is first initialized or reset.
893 _reset: function() {
894 this.length = 0;
895 this.models = [];
896 this._byId = {};
897 },
898
899 // Prepare a hash of attributes (or other model) to be added to this
900 // collection.
901 _prepareModel: function(attrs, options) {
902 if (attrs instanceof Model) {
903 if (!attrs.collection) attrs.collection = this;
904 return attrs;
905 }
906 options || (options = {});
907 options.collection = this;
908 var model = new this.model(attrs, options);
909 if (!model._validate(attrs, options)) {
910 this.trigger('invalid', this, attrs, options);
911 return false;
912 }
913 return model;
914 },
915
916 // Internal method to sever a model's ties to a collection.
917 _removeReference: function(model) {
918 if (this === model.collection) delete model.collection;
919 model.off('all', this._onModelEvent, this);
920 },
921
922 // Internal method called every time a model in the set fires an event.
923 // Sets need to update their indexes when models change ids. All other
924 // events simply proxy through. "add" and "remove" events that originate
925 // in other collections are ignored.
926 _onModelEvent: function(event, model, collection, options) {
927 if ((event === 'add' || event === 'remove') && collection !== this) return;
928 if (event === 'destroy') this.remove(model, options);
929 if (model && event === 'change:' + model.idAttribute) {
930 delete this._byId[model.previous(model.idAttribute)];
931 if (model.id != null) this._byId[model.id] = model;
932 }
933 this.trigger.apply(this, arguments);
934 }
935
936 });
937
938 // Underscore methods that we want to implement on the Collection.
939 // 90% of the core usefulness of Backbone Collections is actually implemented
940 // right here:
941 var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
942 'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
943 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
944 'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
945 'tail', 'drop', 'last', 'without', 'indexOf', 'shuffle', 'lastIndexOf',
946 'isEmpty', 'chain'];
947
948 // Mix in each Underscore method as a proxy to `Collection#models`.
949 _.each(methods, function(method) {
950 Collection.prototype[method] = function() {
951 var args = slice.call(arguments);
952 args.unshift(this.models);
953 return _[method].apply(_, args);
954 };
955 });
956
957 // Underscore methods that take a property name as an argument.
958 var attributeMethods = ['groupBy', 'countBy', 'sortBy'];
959
960 // Use attributes instead of properties.
961 _.each(attributeMethods, function(method) {
962 Collection.prototype[method] = function(value, context) {
963 var iterator = _.isFunction(value) ? value : function(model) {
964 return model.get(value);
965 };
966 return _[method](this.models, iterator, context);
967 };
968 });
969
970 // Backbone.View
971 // -------------
972
973 // Backbone Views are almost more convention than they are actual code. A View
974 // is simply a JavaScript object that represents a logical chunk of UI in the
975 // DOM. This might be a single item, an entire list, a sidebar or panel, or
976 // even the surrounding frame which wraps your whole app. Defining a chunk of
977 // UI as a **View** allows you to define your DOM events declaratively, without
978 // having to worry about render order ... and makes it easy for the view to
979 // react to specific changes in the state of your models.
980
981 // Creating a Backbone.View creates its initial element outside of the DOM,
982 // if an existing element is not provided...
983 var View = Backbone.View = function(options) {
984 this.cid = _.uniqueId('view');
985 this._configure(options || {});
986 this._ensureElement();
987 this.initialize.apply(this, arguments);
988 this.delegateEvents();
989 };
990
991 // Cached regex to split keys for `delegate`.
992 var delegateEventSplitter = /^(\S+)\s*(.*)$/;
993
994 // List of view options to be merged as properties.
995 var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
996
997 // Set up all inheritable **Backbone.View** properties and methods.
998 _.extend(View.prototype, Events, {
999
1000 // The default `tagName` of a View's element is `"div"`.
1001 tagName: 'div',
1002
1003 // jQuery delegate for element lookup, scoped to DOM elements within the
1004 // current view. This should be prefered to global lookups where possible.
1005 $: function(selector) {
1006 return this.$el.find(selector);
1007 },
1008
1009 // Initialize is an empty function by default. Override it with your own
1010 // initialization logic.
1011 initialize: function(){},
1012
1013 // **render** is the core function that your view should override, in order
1014 // to populate its element (`this.el`), with the appropriate HTML. The
1015 // convention is for **render** to always return `this`.
1016 render: function() {
1017 return this;
1018 },
1019
1020 // Remove this view by taking the element out of the DOM, and removing any
1021 // applicable Backbone.Events listeners.
1022 remove: function() {
1023 this.$el.remove();
1024 this.stopListening();
1025 return this;
1026 },
1027
1028 // Change the view's element (`this.el` property), including event
1029 // re-delegation.
1030 setElement: function(element, delegate) {
1031 if (this.$el) this.undelegateEvents();
1032 this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
1033 this.el = this.$el[0];
1034 if (delegate !== false) this.delegateEvents();
1035 return this;
1036 },
1037
1038 // Set callbacks, where `this.events` is a hash of
1039 //
1040 // *{"event selector": "callback"}*
1041 //
1042 // {
1043 // 'mousedown .title': 'edit',
1044 // 'click .button': 'save'
1045 // 'click .open': function(e) { ... }
1046 // }
1047 //
1048 // pairs. Callbacks will be bound to the view, with `this` set properly.
1049 // Uses event delegation for efficiency.
1050 // Omitting the selector binds the event to `this.el`.
1051 // This only works for delegate-able events: not `focus`, `blur`, and
1052 // not `change`, `submit`, and `reset` in Internet Explorer.
1053 delegateEvents: function(events) {
1054 if (!(events || (events = _.result(this, 'events')))) return this;
1055 this.undelegateEvents();
1056 for (var key in events) {
1057 var method = events[key];
1058 if (!_.isFunction(method)) method = this[events[key]];
1059 if (!method) continue;
1060
1061 var match = key.match(delegateEventSplitter);
1062 var eventName = match[1], selector = match[2];
1063 method = _.bind(method, this);
1064 eventName += '.delegateEvents' + this.cid;
1065 if (selector === '') {
1066 this.$el.on(eventName, method);
1067 } else {
1068 this.$el.on(eventName, selector, method);
1069 }
1070 }
1071 return this;
1072 },
1073
1074 // Clears all callbacks previously bound to the view with `delegateEvents`.
1075 // You usually don't need to use this, but may wish to if you have multiple
1076 // Backbone views attached to the same DOM element.
1077 undelegateEvents: function() {
1078 this.$el.off('.delegateEvents' + this.cid);
1079 return this;
1080 },
1081
1082 // Performs the initial configuration of a View with a set of options.
1083 // Keys with special meaning *(e.g. model, collection, id, className)* are
1084 // attached directly to the view. See `viewOptions` for an exhaustive
1085 // list.
1086 _configure: function(options) {
1087 if (this.options) options = _.extend({}, _.result(this, 'options'), options);
1088 _.extend(this, _.pick(options, viewOptions));
1089 this.options = options;
1090 },
1091
1092 // Ensure that the View has a DOM element to render into.
1093 // If `this.el` is a string, pass it through `$()`, take the first
1094 // matching element, and re-assign it to `el`. Otherwise, create
1095 // an element from the `id`, `className` and `tagName` properties.
1096 _ensureElement: function() {
1097 if (!this.el) {
1098 var attrs = _.extend({}, _.result(this, 'attributes'));
1099 if (this.id) attrs.id = _.result(this, 'id');
1100 if (this.className) attrs['class'] = _.result(this, 'className');
1101 var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
1102 this.setElement($el, false);
1103 } else {
1104 this.setElement(_.result(this, 'el'), false);
1105 }
1106 }
1107
1108 });
1109
1110 // Backbone.sync
1111 // -------------
1112
1113 // Override this function to change the manner in which Backbone persists
1114 // models to the server. You will be passed the type of request, and the
1115 // model in question. By default, makes a RESTful Ajax request
1116 // to the model's `url()`. Some possible customizations could be:
1117 //
1118 // * Use `setTimeout` to batch rapid-fire updates into a single request.
1119 // * Send up the models as XML instead of JSON.
1120 // * Persist models via WebSockets instead of Ajax.
1121 //
1122 // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
1123 // as `POST`, with a `_method` parameter containing the true HTTP method,
1124 // as well as all requests with the body as `application/x-www-form-urlencoded`
1125 // instead of `application/json` with the model in a param named `model`.
1126 // Useful when interfacing with server-side languages like **PHP** that make
1127 // it difficult to read the body of `PUT` requests.
1128 Backbone.sync = function(method, model, options) {
1129 var type = methodMap[method];
1130
1131 // Default options, unless specified.
1132 _.defaults(options || (options = {}), {
1133 emulateHTTP: Backbone.emulateHTTP,
1134 emulateJSON: Backbone.emulateJSON
1135 });
1136
1137 // Default JSON-request options.
1138 var params = {type: type, dataType: 'json'};
1139
1140 // Ensure that we have a URL.
1141 if (!options.url) {
1142 params.url = _.result(model, 'url') || urlError();
1143 }
1144
1145 // Ensure that we have the appropriate request data.
1146 if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
1147 params.contentType = 'application/json';
1148 params.data = JSON.stringify(options.attrs || model.toJSON(options));
1149 }
1150
1151 // For older servers, emulate JSON by encoding the request into an HTML-form.
1152 if (options.emulateJSON) {
1153 params.contentType = 'application/x-www-form-urlencoded';
1154 params.data = params.data ? {model: params.data} : {};
1155 }
1156
1157 // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1158 // And an `X-HTTP-Method-Override` header.
1159 if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
1160 params.type = 'POST';
1161 if (options.emulateJSON) params.data._method = type;
1162 var beforeSend = options.beforeSend;
1163 options.beforeSend = function(xhr) {
1164 xhr.setRequestHeader('X-HTTP-Method-Override', type);
1165 if (beforeSend) return beforeSend.apply(this, arguments);
1166 };
1167 }
1168
1169 // Don't process data on a non-GET request.
1170 if (params.type !== 'GET' && !options.emulateJSON) {
1171 params.processData = false;
1172 }
1173
1174 // If we're sending a `PATCH` request, and we're in an old Internet Explorer
1175 // that still has ActiveX enabled by default, override jQuery to use that
1176 // for XHR instead. Remove this line when jQuery supports `PATCH` on IE8.
1177 if (params.type === 'PATCH' && window.ActiveXObject &&
1178 !(window.external && window.external.msActiveXFilteringEnabled)) {
1179 params.xhr = function() {
1180 return new ActiveXObject("Microsoft.XMLHTTP");
1181 };
1182 }
1183
1184 // Make the request, allowing the user to override any Ajax options.
1185 var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
1186 model.trigger('request', model, xhr, options);
1187 return xhr;
1188 };
1189
1190 // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1191 var methodMap = {
1192 'create': 'POST',
1193 'update': 'PUT',
1194 'patch': 'PATCH',
1195 'delete': 'DELETE',
1196 'read': 'GET'
1197 };
1198
1199 // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
1200 // Override this if you'd like to use a different library.
1201 Backbone.ajax = function() {
1202 return Backbone.$.ajax.apply(Backbone.$, arguments);
1203 };
1204
1205 // Backbone.Router
1206 // ---------------
1207
1208 // Routers map faux-URLs to actions, and fire events when routes are
1209 // matched. Creating a new one sets its `routes` hash, if not set statically.
1210 var Router = Backbone.Router = function(options) {
1211 options || (options = {});
1212 if (options.routes) this.routes = options.routes;
1213 this._bindRoutes();
1214 this.initialize.apply(this, arguments);
1215 };
1216
1217 // Cached regular expressions for matching named param parts and splatted
1218 // parts of route strings.
1219 var optionalParam = /\((.*?)\)/g;
1220 var namedParam = /(\(\?)?:\w+/g;
1221 var splatParam = /\*\w+/g;
1222 var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
1223
1224 // Set up all inheritable **Backbone.Router** properties and methods.
1225 _.extend(Router.prototype, Events, {
1226
1227 // Initialize is an empty function by default. Override it with your own
1228 // initialization logic.
1229 initialize: function(){},
1230
1231 // Manually bind a single named route to a callback. For example:
1232 //
1233 // this.route('search/:query/p:num', 'search', function(query, num) {
1234 // ...
1235 // });
1236 //
1237 route: function(route, name, callback) {
1238 if (!_.isRegExp(route)) route = this._routeToRegExp(route);
1239 if (_.isFunction(name)) {
1240 callback = name;
1241 name = '';
1242 }
1243 if (!callback) callback = this[name];
1244 var router = this;
1245 Backbone.history.route(route, function(fragment) {
1246 var args = router._extractParameters(route, fragment);
1247 callback && callback.apply(router, args);
1248 router.trigger.apply(router, ['route:' + name].concat(args));
1249 router.trigger('route', name, args);
1250 Backbone.history.trigger('route', router, name, args);
1251 });
1252 return this;
1253 },
1254
1255 // Simple proxy to `Backbone.history` to save a fragment into the history.
1256 navigate: function(fragment, options) {
1257 Backbone.history.navigate(fragment, options);
1258 return this;
1259 },
1260
1261 // Bind all defined routes to `Backbone.history`. We have to reverse the
1262 // order of the routes here to support behavior where the most general
1263 // routes can be defined at the bottom of the route map.
1264 _bindRoutes: function() {
1265 if (!this.routes) return;
1266 this.routes = _.result(this, 'routes');
1267 var route, routes = _.keys(this.routes);
1268 while ((route = routes.pop()) != null) {
1269 this.route(route, this.routes[route]);
1270 }
1271 },
1272
1273 // Convert a route string into a regular expression, suitable for matching
1274 // against the current location hash.
1275 _routeToRegExp: function(route) {
1276 route = route.replace(escapeRegExp, '\\$&')
1277 .replace(optionalParam, '(?:$1)?')
1278 .replace(namedParam, function(match, optional){
1279 return optional ? match : '([^\/]+)';
1280 })
1281 .replace(splatParam, '(.*?)');
1282 return new RegExp('^' + route + '$');
1283 },
1284
1285 // Given a route, and a URL fragment that it matches, return the array of
1286 // extracted decoded parameters. Empty or unmatched parameters will be
1287 // treated as `null` to normalize cross-browser behavior.
1288 _extractParameters: function(route, fragment) {
1289 var params = route.exec(fragment).slice(1);
1290 return _.map(params, function(param) {
1291 return param ? decodeURIComponent(param) : null;
1292 });
1293 }
1294
1295 });
1296
1297 // Backbone.History
1298 // ----------------
1299
1300 // Handles cross-browser history management, based on either
1301 // [pushState](http://diveintohtml5.info/history.html) and real URLs, or
1302 // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
1303 // and URL fragments. If the browser supports neither (old IE, natch),
1304 // falls back to polling.
1305 var History = Backbone.History = function() {
1306 this.handlers = [];
1307 _.bindAll(this, 'checkUrl');
1308
1309 // Ensure that `History` can be used outside of the browser.
1310 if (typeof window !== 'undefined') {
1311 this.location = window.location;
1312 this.history = window.history;
1313 }
1314 };
1315
1316 // Cached regex for stripping a leading hash/slash and trailing space.
1317 var routeStripper = /^[#\/]|\s+$/g;
1318
1319 // Cached regex for stripping leading and trailing slashes.
1320 var rootStripper = /^\/+|\/+$/g;
1321
1322 // Cached regex for detecting MSIE.
1323 var isExplorer = /msie [\w.]+/;
1324
1325 // Cached regex for removing a trailing slash.
1326 var trailingSlash = /\/$/;
1327
1328 // Has the history handling already been started?
1329 History.started = false;
1330
1331 // Set up all inheritable **Backbone.History** properties and methods.
1332 _.extend(History.prototype, Events, {
1333
1334 // The default interval to poll for hash changes, if necessary, is
1335 // twenty times a second.
1336 interval: 50,
1337
1338 // Gets the true hash value. Cannot use location.hash directly due to bug
1339 // in Firefox where location.hash will always be decoded.
1340 getHash: function(window) {
1341 var match = (window || this).location.href.match(/#(.*)$/);
1342 return match ? match[1] : '';
1343 },
1344
1345 // Get the cross-browser normalized URL fragment, either from the URL,
1346 // the hash, or the override.
1347 getFragment: function(fragment, forcePushState) {
1348 if (fragment == null) {
1349 if (this._hasPushState || !this._wantsHashChange || forcePushState) {
1350 fragment = this.location.pathname;
1351 var root = this.root.replace(trailingSlash, '');
1352 if (!fragment.indexOf(root)) fragment = fragment.substr(root.length);
1353 } else {
1354 fragment = this.getHash();
1355 }
1356 }
1357 return fragment.replace(routeStripper, '');
1358 },
1359
1360 // Start the hash change handling, returning `true` if the current URL matches
1361 // an existing route, and `false` otherwise.
1362 start: function(options) {
1363 if (History.started) throw new Error("Backbone.history has already been started");
1364 History.started = true;
1365
1366 // Figure out the initial configuration. Do we need an iframe?
1367 // Is pushState desired ... is it available?
1368 this.options = _.extend({}, {root: '/'}, this.options, options);
1369 this.root = this.options.root;
1370 this._wantsHashChange = this.options.hashChange !== false;
1371 this._wantsPushState = !!this.options.pushState;
1372 this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState);
1373 var fragment = this.getFragment();
1374 var docMode = document.documentMode;
1375 var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
1376
1377 // Normalize root to always include a leading and trailing slash.
1378 this.root = ('/' + this.root + '/').replace(rootStripper, '/');
1379
1380 if (oldIE && this._wantsHashChange) {
1381 this.iframe = Backbone.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
1382 this.navigate(fragment);
1383 }
1384
1385 // Depending on whether we're using pushState or hashes, and whether
1386 // 'onhashchange' is supported, determine how we check the URL state.
1387 if (this._hasPushState) {
1388 Backbone.$(window).on('popstate', this.checkUrl);
1389 } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
1390 Backbone.$(window).on('hashchange', this.checkUrl);
1391 } else if (this._wantsHashChange) {
1392 this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
1393 }
1394
1395 // Determine if we need to change the base url, for a pushState link
1396 // opened by a non-pushState browser.
1397 this.fragment = fragment;
1398 var loc = this.location;
1399 var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root;
1400
1401 // If we've started off with a route from a `pushState`-enabled browser,
1402 // but we're currently in a browser that doesn't support it...
1403 if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
1404 this.fragment = this.getFragment(null, true);
1405 this.location.replace(this.root + this.location.search + '#' + this.fragment);
1406 // Return immediately as browser will do redirect to new url
1407 return true;
1408
1409 // Or if we've started out with a hash-based route, but we're currently
1410 // in a browser where it could be `pushState`-based instead...
1411 } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
1412 this.fragment = this.getHash().replace(routeStripper, '');
1413 this.history.replaceState({}, document.title, this.root + this.fragment + loc.search);
1414 }
1415
1416 if (!this.options.silent) return this.loadUrl();
1417 },
1418
1419 // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
1420 // but possibly useful for unit testing Routers.
1421 stop: function() {
1422 Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl);
1423 clearInterval(this._checkUrlInterval);
1424 History.started = false;
1425 },
1426
1427 // Add a route to be tested when the fragment changes. Routes added later
1428 // may override previous routes.
1429 route: function(route, callback) {
1430 this.handlers.unshift({route: route, callback: callback});
1431 },
1432
1433 // Checks the current URL to see if it has changed, and if it has,
1434 // calls `loadUrl`, normalizing across the hidden iframe.
1435 checkUrl: function(e) {
1436 var current = this.getFragment();
1437 if (current === this.fragment && this.iframe) {
1438 current = this.getFragment(this.getHash(this.iframe));
1439 }
1440 if (current === this.fragment) return false;
1441 if (this.iframe) this.navigate(current);
1442 this.loadUrl() || this.loadUrl(this.getHash());
1443 },
1444
1445 // Attempt to load the current URL fragment. If a route succeeds with a
1446 // match, returns `true`. If no defined routes matches the fragment,
1447 // returns `false`.
1448 loadUrl: function(fragmentOverride) {
1449 var fragment = this.fragment = this.getFragment(fragmentOverride);
1450 var matched = _.any(this.handlers, function(handler) {
1451 if (handler.route.test(fragment)) {
1452 handler.callback(fragment);
1453 return true;
1454 }
1455 });
1456 return matched;
1457 },
1458
1459 // Save a fragment into the hash history, or replace the URL state if the
1460 // 'replace' option is passed. You are responsible for properly URL-encoding
1461 // the fragment in advance.
1462 //
1463 // The options object can contain `trigger: true` if you wish to have the
1464 // route callback be fired (not usually desirable), or `replace: true`, if
1465 // you wish to modify the current URL without adding an entry to the history.
1466 navigate: function(fragment, options) {
1467 if (!History.started) return false;
1468 if (!options || options === true) options = {trigger: options};
1469 fragment = this.getFragment(fragment || '');
1470 if (this.fragment === fragment) return;
1471 this.fragment = fragment;
1472 var url = this.root + fragment;
1473
1474 // If pushState is available, we use it to set the fragment as a real URL.
1475 if (this._hasPushState) {
1476 this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
1477
1478 // If hash changes haven't been explicitly disabled, update the hash
1479 // fragment to store history.
1480 } else if (this._wantsHashChange) {
1481 this._updateHash(this.location, fragment, options.replace);
1482 if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) {
1483 // Opening and closing the iframe tricks IE7 and earlier to push a
1484 // history entry on hash-tag change. When replace is true, we don't
1485 // want this.
1486 if(!options.replace) this.iframe.document.open().close();
1487 this._updateHash(this.iframe.location, fragment, options.replace);
1488 }
1489
1490 // If you've told us that you explicitly don't want fallback hashchange-
1491 // based history, then `navigate` becomes a page refresh.
1492 } else {
1493 return this.location.assign(url);
1494 }
1495 if (options.trigger) this.loadUrl(fragment);
1496 },
1497
1498 // Update the hash location, either replacing the current entry, or adding
1499 // a new one to the browser history.
1500 _updateHash: function(location, fragment, replace) {
1501 if (replace) {
1502 var href = location.href.replace(/(javascript:|#).*$/, '');
1503 location.replace(href + '#' + fragment);
1504 } else {
1505 // Some browsers require that `hash` contains a leading #.
1506 location.hash = '#' + fragment;
1507 }
1508 }
1509
1510 });
1511
1512 // Create the default Backbone.history.
1513 Backbone.history = new History;
1514
1515 // Helpers
1516 // -------
1517
1518 // Helper function to correctly set up the prototype chain, for subclasses.
1519 // Similar to `goog.inherits`, but uses a hash of prototype properties and
1520 // class properties to be extended.
1521 var extend = function(protoProps, staticProps) {
1522 var parent = this;
1523 var child;
1524
1525 // The constructor function for the new subclass is either defined by you
1526 // (the "constructor" property in your `extend` definition), or defaulted
1527 // by us to simply call the parent's constructor.
1528 if (protoProps && _.has(protoProps, 'constructor')) {
1529 child = protoProps.constructor;
1530 } else {
1531 child = function(){ return parent.apply(this, arguments); };
1532 }
1533
1534 // Add static properties to the constructor function, if supplied.
1535 _.extend(child, parent, staticProps);
1536
1537 // Set the prototype chain to inherit from `parent`, without calling
1538 // `parent`'s constructor function.
1539 var Surrogate = function(){ this.constructor = child; };
1540 Surrogate.prototype = parent.prototype;
1541 child.prototype = new Surrogate;
1542
1543 // Add prototype properties (instance properties) to the subclass,
1544 // if supplied.
1545 if (protoProps) _.extend(child.prototype, protoProps);
1546
1547 // Set a convenience property in case the parent's prototype is needed
1548 // later.
1549 child.__super__ = parent.prototype;
1550
1551 return child;
1552 };
1553
1554 // Set up inheritance for the model, collection, router, view and history.
1555 Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
1556
1557 // Throw an error when a URL is needed, and none is supplied.
1558 var urlError = function() {
1559 throw new Error('A "url" property or function must be specified');
1560 };
1561
1562 // Wrap an optional error callback with a fallback error event.
1563 var wrapError = function (model, options) {
1564 var error = options.error;
1565 options.error = function(resp) {
1566 if (error) error(model, resp, options);
1567 model.trigger('error', model, resp, options);
1568 };
1569 };
1570
1571 }).call(this);
联系我们 |
---|
文章看不懂?联系我们为您免费解答!免费助力个人,小企站点! |
① 电话:020-2206-9892 |
② QQ咨询:1025174874 |
③ 邮件:info@361sale.com |
④ 工作时间:周一至周五,9:30-18:30,节假日休息 |
发布者:光子波动,转转请注明出处:https://www.361sale.com/11653/