Diff: STRATO-apps/wordpress_03/app/wp-includes/js/backbone.js

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
1 -
1 + // Backbone.js 1.6.0
2 +
3 + // (c) 2010-2024 Jeremy Ashkenas and DocumentCloud
4 + // Backbone may be freely distributed under the MIT license.
5 + // For all details and documentation:
6 + // http://backbonejs.org
7 +
8 + (function(factory) {
9 +
10 + // Establish the root object, `window` (`self`) in the browser, or `global` on the server.
11 + // We use `self` instead of `window` for `WebWorker` support.
12 + var root = typeof self == 'object' && self.self === self && self ||
13 + typeof global == 'object' && global.global === global && global;
14 +
15 + // Set up Backbone appropriately for the environment. Start with AMD.
16 + if (typeof define === 'function' && define.amd) {
17 + define(['underscore', 'jquery', 'exports'], function(_, $, exports) {
18 + // Export global even in AMD case in case this script is loaded with
19 + // others that may still expect a global Backbone.
20 + root.Backbone = factory(root, exports, _, $);
21 + });
22 +
23 + // Next for Node.js or CommonJS. jQuery may not be needed as a module.
24 + } else if (typeof exports !== 'undefined') {
25 + var _ = require('underscore'), $;
26 + try { $ = require('jquery'); } catch (e) {}
27 + factory(root, exports, _, $);
28 +
29 + // Finally, as a browser global.
30 + } else {
31 + root.Backbone = factory(root, {}, root._, root.jQuery || root.Zepto || root.ender || root.$);
32 + }
33 +
34 + })(function(root, Backbone, _, $) {
35 +
36 + // Initial Setup
37 + // -------------
38 +
39 + // Save the previous value of the `Backbone` variable, so that it can be
40 + // restored later on, if `noConflict` is used.
41 + var previousBackbone = root.Backbone;
42 +
43 + // Create a local reference to a common array method we'll want to use later.
44 + var slice = Array.prototype.slice;
45 +
46 + // Current version of the library. Keep in sync with `package.json`.
47 + Backbone.VERSION = '1.6.0';
48 +
49 + // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
50 + // the `$` variable.
51 + Backbone.$ = $;
52 +
53 + // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
54 + // to its previous owner. Returns a reference to this Backbone object.
55 + Backbone.noConflict = function() {
56 + root.Backbone = previousBackbone;
57 + return this;
58 + };
59 +
60 + // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
61 + // will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and
62 + // set a `X-Http-Method-Override` header.
63 + Backbone.emulateHTTP = false;
64 +
65 + // Turn on `emulateJSON` to support legacy servers that can't deal with direct
66 + // `application/json` requests ... this will encode the body as
67 + // `application/x-www-form-urlencoded` instead and will send the model in a
68 + // form param named `model`.
69 + Backbone.emulateJSON = false;
70 +
71 + // Backbone.Events
72 + // ---------------
73 +
74 + // A module that can be mixed in to *any object* in order to provide it with
75 + // a custom event channel. You may bind a callback to an event with `on` or
76 + // remove with `off`; `trigger`-ing an event fires all callbacks in
77 + // succession.
78 + //
79 + // var object = {};
80 + // _.extend(object, Backbone.Events);
81 + // object.on('expand', function(){ alert('expanded'); });
82 + // object.trigger('expand');
83 + //
84 + var Events = Backbone.Events = {};
85 +
86 + // Regular expression used to split event strings.
87 + var eventSplitter = /\s+/;
88 +
89 + // A private global variable to share between listeners and listenees.
90 + var _listening;
91 +
92 + // Iterates over the standard `event, callback` (as well as the fancy multiple
93 + // space-separated events `"change blur", callback` and jQuery-style event
94 + // maps `{event: callback}`).
95 + var eventsApi = function(iteratee, events, name, callback, opts) {
96 + var i = 0, names;
97 + if (name && typeof name === 'object') {
98 + // Handle event maps.
99 + if (callback !== void 0 && 'context' in opts && opts.context === void 0) opts.context = callback;
100 + for (names = _.keys(name); i < names.length ; i++) {
101 + events = eventsApi(iteratee, events, names[i], name[names[i]], opts);
102 + }
103 + } else if (name && eventSplitter.test(name)) {
104 + // Handle space-separated event names by delegating them individually.
105 + for (names = name.split(eventSplitter); i < names.length; i++) {
106 + events = iteratee(events, names[i], callback, opts);
107 + }
108 + } else {
109 + // Finally, standard events.
110 + events = iteratee(events, name, callback, opts);
111 + }
112 + return events;
113 + };
114 +
115 + // Bind an event to a `callback` function. Passing `"all"` will bind
116 + // the callback to all events fired.
117 + Events.on = function(name, callback, context) {
118 + this._events = eventsApi(onApi, this._events || {}, name, callback, {
119 + context: context,
120 + ctx: this,
121 + listening: _listening
122 + });
123 +
124 + if (_listening) {
125 + var listeners = this._listeners || (this._listeners = {});
126 + listeners[_listening.id] = _listening;
127 + // Allow the listening to use a counter, instead of tracking
128 + // callbacks for library interop
129 + _listening.interop = false;
130 + }
131 +
132 + return this;
133 + };
134 +
135 + // Inversion-of-control versions of `on`. Tell *this* object to listen to
136 + // an event in another object... keeping track of what it's listening to
137 + // for easier unbinding later.
138 + Events.listenTo = function(obj, name, callback) {
139 + if (!obj) return this;
140 + var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
141 + var listeningTo = this._listeningTo || (this._listeningTo = {});
142 + var listening = _listening = listeningTo[id];
143 +
144 + // This object is not listening to any other events on `obj` yet.
145 + // Setup the necessary references to track the listening callbacks.
146 + if (!listening) {
147 + this._listenId || (this._listenId = _.uniqueId('l'));
148 + listening = _listening = listeningTo[id] = new Listening(this, obj);
149 + }
150 +
151 + // Bind callbacks on obj.
152 + var error = tryCatchOn(obj, name, callback, this);
153 + _listening = void 0;
154 +
155 + if (error) throw error;
156 + // If the target obj is not Backbone.Events, track events manually.
157 + if (listening.interop) listening.on(name, callback);
158 +
159 + return this;
160 + };
161 +
162 + // The reducing API that adds a callback to the `events` object.
163 + var onApi = function(events, name, callback, options) {
164 + if (callback) {
165 + var handlers = events[name] || (events[name] = []);
166 + var context = options.context, ctx = options.ctx, listening = options.listening;
167 + if (listening) listening.count++;
168 +
169 + handlers.push({callback: callback, context: context, ctx: context || ctx, listening: listening});
170 + }
171 + return events;
172 + };
173 +
174 + // An try-catch guarded #on function, to prevent poisoning the global
175 + // `_listening` variable.
176 + var tryCatchOn = function(obj, name, callback, context) {
177 + try {
178 + obj.on(name, callback, context);
179 + } catch (e) {
180 + return e;
181 + }
182 + };
183 +
184 + // Remove one or many callbacks. If `context` is null, removes all
185 + // callbacks with that function. If `callback` is null, removes all
186 + // callbacks for the event. If `name` is null, removes all bound
187 + // callbacks for all events.
188 + Events.off = function(name, callback, context) {
189 + if (!this._events) return this;
190 + this._events = eventsApi(offApi, this._events, name, callback, {
191 + context: context,
192 + listeners: this._listeners
193 + });
194 +
195 + return this;
196 + };
197 +
198 + // Tell this object to stop listening to either specific events ... or
199 + // to every object it's currently listening to.
200 + Events.stopListening = function(obj, name, callback) {
201 + var listeningTo = this._listeningTo;
202 + if (!listeningTo) return this;
203 +
204 + var ids = obj ? [obj._listenId] : _.keys(listeningTo);
205 + for (var i = 0; i < ids.length; i++) {
206 + var listening = listeningTo[ids[i]];
207 +
208 + // If listening doesn't exist, this object is not currently
209 + // listening to obj. Break out early.
210 + if (!listening) break;
211 +
212 + listening.obj.off(name, callback, this);
213 + if (listening.interop) listening.off(name, callback);
214 + }
215 + if (_.isEmpty(listeningTo)) this._listeningTo = void 0;
216 +
217 + return this;
218 + };
219 +
220 + // The reducing API that removes a callback from the `events` object.
221 + var offApi = function(events, name, callback, options) {
222 + if (!events) return;
223 +
224 + var context = options.context, listeners = options.listeners;
225 + var i = 0, names;
226 +
227 + // Delete all event listeners and "drop" events.
228 + if (!name && !context && !callback) {
229 + for (names = _.keys(listeners); i < names.length; i++) {
230 + listeners[names[i]].cleanup();
231 + }
232 + return;
233 + }
234 +
235 + names = name ? [name] : _.keys(events);
236 + for (; i < names.length; i++) {
237 + name = names[i];
238 + var handlers = events[name];
239 +
240 + // Bail out if there are no events stored.
241 + if (!handlers) break;
242 +
243 + // Find any remaining events.
244 + var remaining = [];
245 + for (var j = 0; j < handlers.length; j++) {
246 + var handler = handlers[j];
247 + if (
248 + callback && callback !== handler.callback &&
249 + callback !== handler.callback._callback ||
250 + context && context !== handler.context
251 + ) {
252 + remaining.push(handler);
253 + } else {
254 + var listening = handler.listening;
255 + if (listening) listening.off(name, callback);
256 + }
257 + }
258 +
259 + // Replace events if there are any remaining. Otherwise, clean up.
260 + if (remaining.length) {
261 + events[name] = remaining;
262 + } else {
263 + delete events[name];
264 + }
265 + }
266 +
267 + return events;
268 + };
269 +
270 + // Bind an event to only be triggered a single time. After the first time
271 + // the callback is invoked, its listener will be removed. If multiple events
272 + // are passed in using the space-separated syntax, the handler will fire
273 + // once for each event, not once for a combination of all events.
274 + Events.once = function(name, callback, context) {
275 + // Map the event into a `{event: once}` object.
276 + var events = eventsApi(onceMap, {}, name, callback, this.off.bind(this));
277 + if (typeof name === 'string' && context == null) callback = void 0;
278 + return this.on(events, callback, context);
279 + };
280 +
281 + // Inversion-of-control versions of `once`.
282 + Events.listenToOnce = function(obj, name, callback) {
283 + // Map the event into a `{event: once}` object.
284 + var events = eventsApi(onceMap, {}, name, callback, this.stopListening.bind(this, obj));
285 + return this.listenTo(obj, events);
286 + };
287 +
288 + // Reduces the event callbacks into a map of `{event: onceWrapper}`.
289 + // `offer` unbinds the `onceWrapper` after it has been called.
290 + var onceMap = function(map, name, callback, offer) {
291 + if (callback) {
292 + var once = map[name] = _.once(function() {
293 + offer(name, once);
294 + callback.apply(this, arguments);
295 + });
296 + once._callback = callback;
297 + }
298 + return map;
299 + };
300 +
301 + // Trigger one or many events, firing all bound callbacks. Callbacks are
302 + // passed the same arguments as `trigger` is, apart from the event name
303 + // (unless you're listening on `"all"`, which will cause your callback to
304 + // receive the true name of the event as the first argument).
305 + Events.trigger = function(name) {
306 + if (!this._events) return this;
307 +
308 + var length = Math.max(0, arguments.length - 1);
309 + var args = Array(length);
310 + for (var i = 0; i < length; i++) args[i] = arguments[i + 1];
311 +
312 + eventsApi(triggerApi, this._events, name, void 0, args);
313 + return this;
314 + };
315 +
316 + // Handles triggering the appropriate event callbacks.
317 + var triggerApi = function(objEvents, name, callback, args) {
318 + if (objEvents) {
319 + var events = objEvents[name];
320 + var allEvents = objEvents.all;
321 + if (events && allEvents) allEvents = allEvents.slice();
322 + if (events) triggerEvents(events, args);
323 + if (allEvents) triggerEvents(allEvents, [name].concat(args));
324 + }
325 + return objEvents;
326 + };
327 +
328 + // A difficult-to-believe, but optimized internal dispatch function for
329 + // triggering events. Tries to keep the usual cases speedy (most internal
330 + // Backbone events have 3 arguments).
331 + var triggerEvents = function(events, args) {
332 + var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
333 + switch (args.length) {
334 + case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
335 + case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
336 + case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
337 + case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
338 + default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return;
339 + }
340 + };
341 +
342 + // A listening class that tracks and cleans up memory bindings
343 + // when all callbacks have been offed.
344 + var Listening = function(listener, obj) {
345 + this.id = listener._listenId;
346 + this.listener = listener;
347 + this.obj = obj;
348 + this.interop = true;
349 + this.count = 0;
350 + this._events = void 0;
351 + };
352 +
353 + Listening.prototype.on = Events.on;
354 +
355 + // Offs a callback (or several).
356 + // Uses an optimized counter if the listenee uses Backbone.Events.
357 + // Otherwise, falls back to manual tracking to support events
358 + // library interop.
359 + Listening.prototype.off = function(name, callback) {
360 + var cleanup;
361 + if (this.interop) {
362 + this._events = eventsApi(offApi, this._events, name, callback, {
363 + context: void 0,
364 + listeners: void 0
365 + });
366 + cleanup = !this._events;
367 + } else {
368 + this.count--;
369 + cleanup = this.count === 0;
370 + }
371 + if (cleanup) this.cleanup();
372 + };
373 +
374 + // Cleans up memory bindings between the listener and the listenee.
375 + Listening.prototype.cleanup = function() {
376 + delete this.listener._listeningTo[this.obj._listenId];
377 + if (!this.interop) delete this.obj._listeners[this.id];
378 + };
379 +
380 + // Aliases for backwards compatibility.
381 + Events.bind = Events.on;
382 + Events.unbind = Events.off;
383 +
384 + // Allow the `Backbone` object to serve as a global event bus, for folks who
385 + // want global "pubsub" in a convenient place.
386 + _.extend(Backbone, Events);
387 +
388 + // Backbone.Model
389 + // --------------
390 +
391 + // Backbone **Models** are the basic data object in the framework --
392 + // frequently representing a row in a table in a database on your server.
393 + // A discrete chunk of data and a bunch of useful, related methods for
394 + // performing computations and transformations on that data.
395 +
396 + // Create a new model with the specified attributes. A client id (`cid`)
397 + // is automatically generated and assigned for you.
398 + var Model = Backbone.Model = function(attributes, options) {
399 + var attrs = attributes || {};
400 + options || (options = {});
401 + this.preinitialize.apply(this, arguments);
402 + this.cid = _.uniqueId(this.cidPrefix);
403 + this.attributes = {};
404 + if (options.collection) this.collection = options.collection;
405 + if (options.parse) attrs = this.parse(attrs, options) || {};
406 + var defaults = _.result(this, 'defaults');
407 +
408 + // Just _.defaults would work fine, but the additional _.extends
409 + // is in there for historical reasons. See #3843.
410 + attrs = _.defaults(_.extend({}, defaults, attrs), defaults);
411 +
412 + this.set(attrs, options);
413 + this.changed = {};
414 + this.initialize.apply(this, arguments);
415 + };
416 +
417 + // Attach all inheritable methods to the Model prototype.
418 + _.extend(Model.prototype, Events, {
419 +
420 + // A hash of attributes whose current and previous value differ.
421 + changed: null,
422 +
423 + // The value returned during the last failed validation.
424 + validationError: null,
425 +
426 + // The default name for the JSON `id` attribute is `"id"`. MongoDB and
427 + // CouchDB users may want to set this to `"_id"`.
428 + idAttribute: 'id',
429 +
430 + // The prefix is used to create the client id which is used to identify models locally.
431 + // You may want to override this if you're experiencing name clashes with model ids.
432 + cidPrefix: 'c',
433 +
434 + // preinitialize is an empty function by default. You can override it with a function
435 + // or object. preinitialize will run before any instantiation logic is run in the Model.
436 + preinitialize: function(){},
437 +
438 + // Initialize is an empty function by default. Override it with your own
439 + // initialization logic.
440 + initialize: function(){},
441 +
442 + // Return a copy of the model's `attributes` object.
443 + toJSON: function(options) {
444 + return _.clone(this.attributes);
445 + },
446 +
447 + // Proxy `Backbone.sync` by default -- but override this if you need
448 + // custom syncing semantics for *this* particular model.
449 + sync: function() {
450 + return Backbone.sync.apply(this, arguments);
451 + },
452 +
453 + // Get the value of an attribute.
454 + get: function(attr) {
455 + return this.attributes[attr];
456 + },
457 +
458 + // Get the HTML-escaped value of an attribute.
459 + escape: function(attr) {
460 + return _.escape(this.get(attr));
461 + },
462 +
463 + // Returns `true` if the attribute contains a value that is not null
464 + // or undefined.
465 + has: function(attr) {
466 + return this.get(attr) != null;
467 + },
468 +
469 + // Special-cased proxy to underscore's `_.matches` method.
470 + matches: function(attrs) {
471 + return !!_.iteratee(attrs, this)(this.attributes);
472 + },
473 +
474 + // Set a hash of model attributes on the object, firing `"change"`. This is
475 + // the core primitive operation of a model, updating the data and notifying
476 + // anyone who needs to know about the change in state. The heart of the beast.
477 + set: function(key, val, options) {
478 + if (key == null) return this;
479 +
480 + // Handle both `"key", value` and `{key: value}` -style arguments.
481 + var attrs;
482 + if (typeof key === 'object') {
483 + attrs = key;
484 + options = val;
485 + } else {
486 + (attrs = {})[key] = val;
487 + }
488 +
489 + options || (options = {});
490 +
491 + // Run validation.
492 + if (!this._validate(attrs, options)) return false;
493 +
494 + // Extract attributes and options.
495 + var unset = options.unset;
496 + var silent = options.silent;
497 + var changes = [];
498 + var changing = this._changing;
499 + this._changing = true;
500 +
501 + if (!changing) {
502 + this._previousAttributes = _.clone(this.attributes);
503 + this.changed = {};
504 + }
505 +
506 + var current = this.attributes;
507 + var changed = this.changed;
508 + var prev = this._previousAttributes;
509 +
510 + // For each `set` attribute, update or delete the current value.
511 + for (var attr in attrs) {
512 + val = attrs[attr];
513 + if (!_.isEqual(current[attr], val)) changes.push(attr);
514 + if (!_.isEqual(prev[attr], val)) {
515 + changed[attr] = val;
516 + } else {
517 + delete changed[attr];
518 + }
519 + unset ? delete current[attr] : current[attr] = val;
520 + }
521 +
522 + // Update the `id`.
523 + if (this.idAttribute in attrs) {
524 + var prevId = this.id;
525 + this.id = this.get(this.idAttribute);
526 + this.trigger('changeId', this, prevId, options);
527 + }
528 +
529 + // Trigger all relevant attribute changes.
530 + if (!silent) {
531 + if (changes.length) this._pending = options;
532 + for (var i = 0; i < changes.length; i++) {
533 + this.trigger('change:' + changes[i], this, current[changes[i]], options);
534 + }
535 + }
536 +
537 + // You might be wondering why there's a `while` loop here. Changes can
538 + // be recursively nested within `"change"` events.
539 + if (changing) return this;
540 + if (!silent) {
541 + while (this._pending) {
542 + options = this._pending;
543 + this._pending = false;
544 + this.trigger('change', this, options);
545 + }
546 + }
547 + this._pending = false;
548 + this._changing = false;
549 + return this;
550 + },
551 +
552 + // Remove an attribute from the model, firing `"change"`. `unset` is a noop
553 + // if the attribute doesn't exist.
554 + unset: function(attr, options) {
555 + return this.set(attr, void 0, _.extend({}, options, {unset: true}));
556 + },
557 +
558 + // Clear all attributes on the model, firing `"change"`.
559 + clear: function(options) {
560 + var attrs = {};
561 + for (var key in this.attributes) attrs[key] = void 0;
562 + return this.set(attrs, _.extend({}, options, {unset: true}));
563 + },
564 +
565 + // Determine if the model has changed since the last `"change"` event.
566 + // If you specify an attribute name, determine if that attribute has changed.
567 + hasChanged: function(attr) {
568 + if (attr == null) return !_.isEmpty(this.changed);
569 + return _.has(this.changed, attr);
570 + },
571 +
572 + // Return an object containing all the attributes that have changed, or
573 + // false if there are no changed attributes. Useful for determining what
574 + // parts of a view need to be updated and/or what attributes need to be
575 + // persisted to the server. Unset attributes will be set to undefined.
576 + // You can also pass an attributes object to diff against the model,
577 + // determining if there *would be* a change.
578 + changedAttributes: function(diff) {
579 + if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
580 + var old = this._changing ? this._previousAttributes : this.attributes;
581 + var changed = {};
582 + var hasChanged;
583 + for (var attr in diff) {
584 + var val = diff[attr];
585 + if (_.isEqual(old[attr], val)) continue;
586 + changed[attr] = val;
587 + hasChanged = true;
588 + }
589 + return hasChanged ? changed : false;
590 + },
591 +
592 + // Get the previous value of an attribute, recorded at the time the last
593 + // `"change"` event was fired.
594 + previous: function(attr) {
595 + if (attr == null || !this._previousAttributes) return null;
596 + return this._previousAttributes[attr];
597 + },
598 +
599 + // Get all of the attributes of the model at the time of the previous
600 + // `"change"` event.
601 + previousAttributes: function() {
602 + return _.clone(this._previousAttributes);
603 + },
604 +
605 + // Fetch the model from the server, merging the response with the model's
606 + // local attributes. Any changed attributes will trigger a "change" event.
607 + fetch: function(options) {
608 + options = _.extend({parse: true}, options);
609 + var model = this;
610 + var success = options.success;
611 + options.success = function(resp) {
612 + var serverAttrs = options.parse ? model.parse(resp, options) : resp;
613 + if (!model.set(serverAttrs, options)) return false;
614 + if (success) success.call(options.context, model, resp, options);
615 + model.trigger('sync', model, resp, options);
616 + };
617 + wrapError(this, options);
618 + return this.sync('read', this, options);
619 + },
620 +
621 + // Set a hash of model attributes, and sync the model to the server.
622 + // If the server returns an attributes hash that differs, the model's
623 + // state will be `set` again.
624 + save: function(key, val, options) {
625 + // Handle both `"key", value` and `{key: value}` -style arguments.
626 + var attrs;
627 + if (key == null || typeof key === 'object') {
628 + attrs = key;
629 + options = val;
630 + } else {
631 + (attrs = {})[key] = val;
632 + }
633 +
634 + options = _.extend({validate: true, parse: true}, options);
635 + var wait = options.wait;
636 +
637 + // If we're not waiting and attributes exist, save acts as
638 + // `set(attr).save(null, opts)` with validation. Otherwise, check if
639 + // the model will be valid when the attributes, if any, are set.
640 + if (attrs && !wait) {
641 + if (!this.set(attrs, options)) return false;
642 + } else if (!this._validate(attrs, options)) {
643 + return false;
644 + }
645 +
646 + // After a successful server-side save, the client is (optionally)
647 + // updated with the server-side state.
648 + var model = this;
649 + var success = options.success;
650 + var attributes = this.attributes;
651 + options.success = function(resp) {
652 + // Ensure attributes are restored during synchronous saves.
653 + model.attributes = attributes;
654 + var serverAttrs = options.parse ? model.parse(resp, options) : resp;
655 + if (wait) serverAttrs = _.extend({}, attrs, serverAttrs);
656 + if (serverAttrs && !model.set(serverAttrs, options)) return false;
657 + if (success) success.call(options.context, model, resp, options);
658 + model.trigger('sync', model, resp, options);
659 + };
660 + wrapError(this, options);
661 +
662 + // Set temporary attributes if `{wait: true}` to properly find new ids.
663 + if (attrs && wait) this.attributes = _.extend({}, attributes, attrs);
664 +
665 + var method = this.isNew() ? 'create' : options.patch ? 'patch' : 'update';
666 + if (method === 'patch' && !options.attrs) options.attrs = attrs;
667 + var xhr = this.sync(method, this, options);
668 +
669 + // Restore attributes.
670 + this.attributes = attributes;
671 +
672 + return xhr;
673 + },
674 +
675 + // Destroy this model on the server if it was already persisted.
676 + // Optimistically removes the model from its collection, if it has one.
677 + // If `wait: true` is passed, waits for the server to respond before removal.
678 + destroy: function(options) {
679 + options = options ? _.clone(options) : {};
680 + var model = this;
681 + var success = options.success;
682 + var wait = options.wait;
683 +
684 + var destroy = function() {
685 + model.stopListening();
686 + model.trigger('destroy', model, model.collection, options);
687 + };
688 +
689 + options.success = function(resp) {
690 + if (wait) destroy();
691 + if (success) success.call(options.context, model, resp, options);
692 + if (!model.isNew()) model.trigger('sync', model, resp, options);
693 + };
694 +
695 + var xhr = false;
696 + if (this.isNew()) {
697 + _.defer(options.success);
698 + } else {
699 + wrapError(this, options);
700 + xhr = this.sync('delete', this, options);
701 + }
702 + if (!wait) destroy();
703 + return xhr;
704 + },
705 +
706 + // Default URL for the model's representation on the server -- if you're
707 + // using Backbone's restful methods, override this to change the endpoint
708 + // that will be called.
709 + url: function() {
710 + var base =
711 + _.result(this, 'urlRoot') ||
712 + _.result(this.collection, 'url') ||
713 + urlError();
714 + if (this.isNew()) return base;
715 + var id = this.get(this.idAttribute);
716 + return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(id);
717 + },
718 +
719 + // **parse** converts a response into the hash of attributes to be `set` on
720 + // the model. The default implementation is just to pass the response along.
721 + parse: function(resp, options) {
722 + return resp;
723 + },
724 +
725 + // Create a new model with identical attributes to this one.
726 + clone: function() {
727 + return new this.constructor(this.attributes);
728 + },
729 +
730 + // A model is new if it has never been saved to the server, and lacks an id.
731 + isNew: function() {
732 + return !this.has(this.idAttribute);
733 + },
734 +
735 + // Check if the model is currently in a valid state.
736 + isValid: function(options) {
737 + return this._validate({}, _.extend({}, options, {validate: true}));
738 + },
739 +
740 + // Run validation against the next complete set of model attributes,
741 + // returning `true` if all is well. Otherwise, fire an `"invalid"` event.
742 + _validate: function(attrs, options) {
743 + if (!options.validate || !this.validate) return true;
744 + attrs = _.extend({}, this.attributes, attrs);
745 + var error = this.validationError = this.validate(attrs, options) || null;
746 + if (!error) return true;
747 + this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
748 + return false;
749 + }
750 +
751 + });
752 +
753 + // Backbone.Collection
754 + // -------------------
755 +
756 + // If models tend to represent a single row of data, a Backbone Collection is
757 + // more analogous to a table full of data ... or a small slice or page of that
758 + // table, or a collection of rows that belong together for a particular reason
759 + // -- all of the messages in this particular folder, all of the documents
760 + // belonging to this particular author, and so on. Collections maintain
761 + // indexes of their models, both in order, and for lookup by `id`.
762 +
763 + // Create a new **Collection**, perhaps to contain a specific type of `model`.
764 + // If a `comparator` is specified, the Collection will maintain
765 + // its models in sort order, as they're added and removed.
766 + var Collection = Backbone.Collection = function(models, options) {
767 + options || (options = {});
768 + this.preinitialize.apply(this, arguments);
769 + if (options.model) this.model = options.model;
770 + if (options.comparator !== void 0) this.comparator = options.comparator;
771 + this._reset();
772 + this.initialize.apply(this, arguments);
773 + if (models) this.reset(models, _.extend({silent: true}, options));
774 + };
775 +
776 + // Default options for `Collection#set`.
777 + var setOptions = {add: true, remove: true, merge: true};
778 + var addOptions = {add: true, remove: false};
779 +
780 + // Splices `insert` into `array` at index `at`.
781 + var splice = function(array, insert, at) {
782 + at = Math.min(Math.max(at, 0), array.length);
783 + var tail = Array(array.length - at);
784 + var length = insert.length;
785 + var i;
786 + for (i = 0; i < tail.length; i++) tail[i] = array[i + at];
787 + for (i = 0; i < length; i++) array[i + at] = insert[i];
788 + for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i];
789 + };
790 +
791 + // Define the Collection's inheritable methods.
792 + _.extend(Collection.prototype, Events, {
793 +
794 + // The default model for a collection is just a **Backbone.Model**.
795 + // This should be overridden in most cases.
796 + model: Model,
797 +
798 +
799 + // preinitialize is an empty function by default. You can override it with a function
800 + // or object. preinitialize will run before any instantiation logic is run in the Collection.
801 + preinitialize: function(){},
802 +
803 + // Initialize is an empty function by default. Override it with your own
804 + // initialization logic.
805 + initialize: function(){},
806 +
807 + // The JSON representation of a Collection is an array of the
808 + // models' attributes.
809 + toJSON: function(options) {
810 + return this.map(function(model) { return model.toJSON(options); });
811 + },
812 +
813 + // Proxy `Backbone.sync` by default.
814 + sync: function() {
815 + return Backbone.sync.apply(this, arguments);
816 + },
817 +
818 + // Add a model, or list of models to the set. `models` may be Backbone
819 + // Models or raw JavaScript objects to be converted to Models, or any
820 + // combination of the two.
821 + add: function(models, options) {
822 + return this.set(models, _.extend({merge: false}, options, addOptions));
823 + },
824 +
825 + // Remove a model, or a list of models from the set.
826 + remove: function(models, options) {
827 + options = _.extend({}, options);
828 + var singular = !_.isArray(models);
829 + models = singular ? [models] : models.slice();
830 + var removed = this._removeModels(models, options);
831 + if (!options.silent && removed.length) {
832 + options.changes = {added: [], merged: [], removed: removed};
833 + this.trigger('update', this, options);
834 + }
835 + return singular ? removed[0] : removed;
836 + },
837 +
838 + // Update a collection by `set`-ing a new list of models, adding new ones,
839 + // removing models that are no longer present, and merging models that
840 + // already exist in the collection, as necessary. Similar to **Model#set**,
841 + // the core operation for updating the data contained by the collection.
842 + set: function(models, options) {
843 + if (models == null) return;
844 +
845 + options = _.extend({}, setOptions, options);
846 + if (options.parse && !this._isModel(models)) {
847 + models = this.parse(models, options) || [];
848 + }
849 +
850 + var singular = !_.isArray(models);
851 + models = singular ? [models] : models.slice();
852 +
853 + var at = options.at;
854 + if (at != null) at = +at;
855 + if (at > this.length) at = this.length;
856 + if (at < 0) at += this.length + 1;
857 +
858 + var set = [];
859 + var toAdd = [];
860 + var toMerge = [];
861 + var toRemove = [];
862 + var modelMap = {};
863 +
864 + var add = options.add;
865 + var merge = options.merge;
866 + var remove = options.remove;
867 +
868 + var sort = false;
869 + var sortable = this.comparator && at == null && options.sort !== false;
870 + var sortAttr = _.isString(this.comparator) ? this.comparator : null;
871 +
872 + // Turn bare objects into model references, and prevent invalid models
873 + // from being added.
874 + var model, i;
875 + for (i = 0; i < models.length; i++) {
876 + model = models[i];
877 +
878 + // If a duplicate is found, prevent it from being added and
879 + // optionally merge it into the existing model.
880 + var existing = this.get(model);
881 + if (existing) {
882 + if (merge && model !== existing) {
883 + var attrs = this._isModel(model) ? model.attributes : model;
884 + if (options.parse) attrs = existing.parse(attrs, options);
885 + existing.set(attrs, options);
886 + toMerge.push(existing);
887 + if (sortable && !sort) sort = existing.hasChanged(sortAttr);
888 + }
889 + if (!modelMap[existing.cid]) {
890 + modelMap[existing.cid] = true;
891 + set.push(existing);
892 + }
893 + models[i] = existing;
894 +
895 + // If this is a new, valid model, push it to the `toAdd` list.
896 + } else if (add) {
897 + model = models[i] = this._prepareModel(model, options);
898 + if (model) {
899 + toAdd.push(model);
900 + this._addReference(model, options);
901 + modelMap[model.cid] = true;
902 + set.push(model);
903 + }
904 + }
905 + }
906 +
907 + // Remove stale models.
908 + if (remove) {
909 + for (i = 0; i < this.length; i++) {
910 + model = this.models[i];
911 + if (!modelMap[model.cid]) toRemove.push(model);
912 + }
913 + if (toRemove.length) this._removeModels(toRemove, options);
914 + }
915 +
916 + // See if sorting is needed, update `length` and splice in new models.
917 + var orderChanged = false;
918 + var replace = !sortable && add && remove;
919 + if (set.length && replace) {
920 + orderChanged = this.length !== set.length || _.some(this.models, function(m, index) {
921 + return m !== set[index];
922 + });
923 + this.models.length = 0;
924 + splice(this.models, set, 0);
925 + this.length = this.models.length;
926 + } else if (toAdd.length) {
927 + if (sortable) sort = true;
928 + splice(this.models, toAdd, at == null ? this.length : at);
929 + this.length = this.models.length;
930 + }
931 +
932 + // Silently sort the collection if appropriate.
933 + if (sort) this.sort({silent: true});
934 +
935 + // Unless silenced, it's time to fire all appropriate add/sort/update events.
936 + if (!options.silent) {
937 + for (i = 0; i < toAdd.length; i++) {
938 + if (at != null) options.index = at + i;
939 + model = toAdd[i];
940 + model.trigger('add', model, this, options);
941 + }
942 + if (sort || orderChanged) this.trigger('sort', this, options);
943 + if (toAdd.length || toRemove.length || toMerge.length) {
944 + options.changes = {
945 + added: toAdd,
946 + removed: toRemove,
947 + merged: toMerge
948 + };
949 + this.trigger('update', this, options);
950 + }
951 + }
952 +
953 + // Return the added (or merged) model (or models).
954 + return singular ? models[0] : models;
955 + },
956 +
957 + // When you have more items than you want to add or remove individually,
958 + // you can reset the entire set with a new list of models, without firing
959 + // any granular `add` or `remove` events. Fires `reset` when finished.
960 + // Useful for bulk operations and optimizations.
961 + reset: function(models, options) {
962 + options = options ? _.clone(options) : {};
963 + for (var i = 0; i < this.models.length; i++) {
964 + this._removeReference(this.models[i], options);
965 + }
966 + options.previousModels = this.models;
967 + this._reset();
968 + models = this.add(models, _.extend({silent: true}, options));
969 + if (!options.silent) this.trigger('reset', this, options);
970 + return models;
971 + },
972 +
973 + // Add a model to the end of the collection.
974 + push: function(model, options) {
975 + return this.add(model, _.extend({at: this.length}, options));
976 + },
977 +
978 + // Remove a model from the end of the collection.
979 + pop: function(options) {
980 + var model = this.at(this.length - 1);
981 + return this.remove(model, options);
982 + },
983 +
984 + // Add a model to the beginning of the collection.
985 + unshift: function(model, options) {
986 + return this.add(model, _.extend({at: 0}, options));
987 + },
988 +
989 + // Remove a model from the beginning of the collection.
990 + shift: function(options) {
991 + var model = this.at(0);
992 + return this.remove(model, options);
993 + },
994 +
995 + // Slice out a sub-array of models from the collection.
996 + slice: function() {
997 + return slice.apply(this.models, arguments);
998 + },
999 +
1000 + // Get a model from the set by id, cid, model object with id or cid
1001 + // properties, or an attributes object that is transformed through modelId.
1002 + get: function(obj) {
1003 + if (obj == null) return void 0;
1004 + return this._byId[obj] ||
1005 + this._byId[this.modelId(this._isModel(obj) ? obj.attributes : obj, obj.idAttribute)] ||
1006 + obj.cid && this._byId[obj.cid];
1007 + },
1008 +
1009 + // Returns `true` if the model is in the collection.
1010 + has: function(obj) {
1011 + return this.get(obj) != null;
1012 + },
1013 +
1014 + // Get the model at the given index.
1015 + at: function(index) {
1016 + if (index < 0) index += this.length;
1017 + return this.models[index];
1018 + },
1019 +
1020 + // Return models with matching attributes. Useful for simple cases of
1021 + // `filter`.
1022 + where: function(attrs, first) {
1023 + return this[first ? 'find' : 'filter'](attrs);
1024 + },
1025 +
1026 + // Return the first model with matching attributes. Useful for simple cases
1027 + // of `find`.
1028 + findWhere: function(attrs) {
1029 + return this.where(attrs, true);
1030 + },
1031 +
1032 + // Force the collection to re-sort itself. You don't need to call this under
1033 + // normal circumstances, as the set will maintain sort order as each item
1034 + // is added.
1035 + sort: function(options) {
1036 + var comparator = this.comparator;
1037 + if (!comparator) throw new Error('Cannot sort a set without a comparator');
1038 + options || (options = {});
1039 +
1040 + var length = comparator.length;
1041 + if (_.isFunction(comparator)) comparator = comparator.bind(this);
1042 +
1043 + // Run sort based on type of `comparator`.
1044 + if (length === 1 || _.isString(comparator)) {
1045 + this.models = this.sortBy(comparator);
1046 + } else {
1047 + this.models.sort(comparator);
1048 + }
1049 + if (!options.silent) this.trigger('sort', this, options);
1050 + return this;
1051 + },
1052 +
1053 + // Pluck an attribute from each model in the collection.
1054 + pluck: function(attr) {
1055 + return this.map(attr + '');
1056 + },
1057 +
1058 + // Fetch the default set of models for this collection, resetting the
1059 + // collection when they arrive. If `reset: true` is passed, the response
1060 + // data will be passed through the `reset` method instead of `set`.
1061 + fetch: function(options) {
1062 + options = _.extend({parse: true}, options);
1063 + var success = options.success;
1064 + var collection = this;
1065 + options.success = function(resp) {
1066 + var method = options.reset ? 'reset' : 'set';
1067 + collection[method](resp, options);
1068 + if (success) success.call(options.context, collection, resp, options);
1069 + collection.trigger('sync', collection, resp, options);
1070 + };
1071 + wrapError(this, options);
1072 + return this.sync('read', this, options);
1073 + },
1074 +
1075 + // Create a new instance of a model in this collection. Add the model to the
1076 + // collection immediately, unless `wait: true` is passed, in which case we
1077 + // wait for the server to agree.
1078 + create: function(model, options) {
1079 + options = options ? _.clone(options) : {};
1080 + var wait = options.wait;
1081 + model = this._prepareModel(model, options);
1082 + if (!model) return false;
1083 + if (!wait) this.add(model, options);
1084 + var collection = this;
1085 + var success = options.success;
1086 + options.success = function(m, resp, callbackOpts) {
1087 + if (wait) {
1088 + m.off('error', collection._forwardPristineError, collection);
1089 + collection.add(m, callbackOpts);
1090 + }
1091 + if (success) success.call(callbackOpts.context, m, resp, callbackOpts);
1092 + };
1093 + // In case of wait:true, our collection is not listening to any
1094 + // of the model's events yet, so it will not forward the error
1095 + // event. In this special case, we need to listen for it
1096 + // separately and handle the event just once.
1097 + // (The reason we don't need to do this for the sync event is
1098 + // in the success handler above: we add the model first, which
1099 + // causes the collection to listen, and then invoke the callback
1100 + // that triggers the event.)
1101 + if (wait) {
1102 + model.once('error', this._forwardPristineError, this);
1103 + }
1104 + model.save(null, options);
1105 + return model;
1106 + },
1107 +
1108 + // **parse** converts a response into a list of models to be added to the
1109 + // collection. The default implementation is just to pass it through.
1110 + parse: function(resp, options) {
1111 + return resp;
1112 + },
1113 +
1114 + // Create a new collection with an identical list of models as this one.
1115 + clone: function() {
1116 + return new this.constructor(this.models, {
1117 + model: this.model,
1118 + comparator: this.comparator
1119 + });
1120 + },
1121 +
1122 + // Define how to uniquely identify models in the collection.
1123 + modelId: function(attrs, idAttribute) {
1124 + return attrs[idAttribute || this.model.prototype.idAttribute || 'id'];
1125 + },
1126 +
1127 + // Get an iterator of all models in this collection.
1128 + values: function() {
1129 + return new CollectionIterator(this, ITERATOR_VALUES);
1130 + },
1131 +
1132 + // Get an iterator of all model IDs in this collection.
1133 + keys: function() {
1134 + return new CollectionIterator(this, ITERATOR_KEYS);
1135 + },
1136 +
1137 + // Get an iterator of all [ID, model] tuples in this collection.
1138 + entries: function() {
1139 + return new CollectionIterator(this, ITERATOR_KEYSVALUES);
1140 + },
1141 +
1142 + // Private method to reset all internal state. Called when the collection
1143 + // is first initialized or reset.
1144 + _reset: function() {
1145 + this.length = 0;
1146 + this.models = [];
1147 + this._byId = {};
1148 + },
1149 +
1150 + // Prepare a hash of attributes (or other model) to be added to this
1151 + // collection.
1152 + _prepareModel: function(attrs, options) {
1153 + if (this._isModel(attrs)) {
1154 + if (!attrs.collection) attrs.collection = this;
1155 + return attrs;
1156 + }
1157 + options = options ? _.clone(options) : {};
1158 + options.collection = this;
1159 +
1160 + var model;
1161 + if (this.model.prototype) {
1162 + model = new this.model(attrs, options);
1163 + } else {
1164 + // ES class methods didn't have prototype
1165 + model = this.model(attrs, options);
1166 + }
1167 +
1168 + if (!model.validationError) return model;
1169 + this.trigger('invalid', this, model.validationError, options);
1170 + return false;
1171 + },
1172 +
1173 + // Internal method called by both remove and set.
1174 + _removeModels: function(models, options) {
1175 + var removed = [];
1176 + for (var i = 0; i < models.length; i++) {
1177 + var model = this.get(models[i]);
1178 + if (!model) continue;
1179 +
1180 + var index = this.indexOf(model);
1181 + this.models.splice(index, 1);
1182 + this.length--;
1183 +
1184 + // Remove references before triggering 'remove' event to prevent an
1185 + // infinite loop. #3693
1186 + delete this._byId[model.cid];
1187 + var id = this.modelId(model.attributes, model.idAttribute);
1188 + if (id != null) delete this._byId[id];
1189 +
1190 + if (!options.silent) {
1191 + options.index = index;
1192 + model.trigger('remove', model, this, options);
1193 + }
1194 +
1195 + removed.push(model);
1196 + this._removeReference(model, options);
1197 + }
1198 + if (models.length > 0 && !options.silent) delete options.index;
1199 + return removed;
1200 + },
1201 +
1202 + // Method for checking whether an object should be considered a model for
1203 + // the purposes of adding to the collection.
1204 + _isModel: function(model) {
1205 + return model instanceof Model;
1206 + },
1207 +
1208 + // Internal method to create a model's ties to a collection.
1209 + _addReference: function(model, options) {
1210 + this._byId[model.cid] = model;
1211 + var id = this.modelId(model.attributes, model.idAttribute);
1212 + if (id != null) this._byId[id] = model;
1213 + model.on('all', this._onModelEvent, this);
1214 + },
1215 +
1216 + // Internal method to sever a model's ties to a collection.
1217 + _removeReference: function(model, options) {
1218 + delete this._byId[model.cid];
1219 + var id = this.modelId(model.attributes, model.idAttribute);
1220 + if (id != null) delete this._byId[id];
1221 + if (this === model.collection) delete model.collection;
1222 + model.off('all', this._onModelEvent, this);
1223 + },
1224 +
1225 + // Internal method called every time a model in the set fires an event.
1226 + // Sets need to update their indexes when models change ids. All other
1227 + // events simply proxy through. "add" and "remove" events that originate
1228 + // in other collections are ignored.
1229 + _onModelEvent: function(event, model, collection, options) {
1230 + if (model) {
1231 + if ((event === 'add' || event === 'remove') && collection !== this) return;
1232 + if (event === 'destroy') this.remove(model, options);
1233 + if (event === 'changeId') {
1234 + var prevId = this.modelId(model.previousAttributes(), model.idAttribute);
1235 + var id = this.modelId(model.attributes, model.idAttribute);
1236 + if (prevId != null) delete this._byId[prevId];
1237 + if (id != null) this._byId[id] = model;
1238 + }
1239 + }
1240 + this.trigger.apply(this, arguments);
1241 + },
1242 +
1243 + // Internal callback method used in `create`. It serves as a
1244 + // stand-in for the `_onModelEvent` method, which is not yet bound
1245 + // during the `wait` period of the `create` call. We still want to
1246 + // forward any `'error'` event at the end of the `wait` period,
1247 + // hence a customized callback.
1248 + _forwardPristineError: function(model, collection, options) {
1249 + // Prevent double forward if the model was already in the
1250 + // collection before the call to `create`.
1251 + if (this.has(model)) return;
1252 + this._onModelEvent('error', model, collection, options);
1253 + }
1254 + });
1255 +
1256 + // Defining an @@iterator method implements JavaScript's Iterable protocol.
1257 + // In modern ES2015 browsers, this value is found at Symbol.iterator.
1258 + /* global Symbol */
1259 + var $$iterator = typeof Symbol === 'function' && Symbol.iterator;
1260 + if ($$iterator) {
1261 + Collection.prototype[$$iterator] = Collection.prototype.values;
1262 + }
1263 +
1264 + // CollectionIterator
1265 + // ------------------
1266 +
1267 + // A CollectionIterator implements JavaScript's Iterator protocol, allowing the
1268 + // use of `for of` loops in modern browsers and interoperation between
1269 + // Backbone.Collection and other JavaScript functions and third-party libraries
1270 + // which can operate on Iterables.
1271 + var CollectionIterator = function(collection, kind) {
1272 + this._collection = collection;
1273 + this._kind = kind;
1274 + this._index = 0;
1275 + };
1276 +
1277 + // This "enum" defines the three possible kinds of values which can be emitted
1278 + // by a CollectionIterator that correspond to the values(), keys() and entries()
1279 + // methods on Collection, respectively.
1280 + var ITERATOR_VALUES = 1;
1281 + var ITERATOR_KEYS = 2;
1282 + var ITERATOR_KEYSVALUES = 3;
1283 +
1284 + // All Iterators should themselves be Iterable.
1285 + if ($$iterator) {
1286 + CollectionIterator.prototype[$$iterator] = function() {
1287 + return this;
1288 + };
1289 + }
1290 +
1291 + CollectionIterator.prototype.next = function() {
1292 + if (this._collection) {
1293 +
1294 + // Only continue iterating if the iterated collection is long enough.
1295 + if (this._index < this._collection.length) {
1296 + var model = this._collection.at(this._index);
1297 + this._index++;
1298 +
1299 + // Construct a value depending on what kind of values should be iterated.
1300 + var value;
1301 + if (this._kind === ITERATOR_VALUES) {
1302 + value = model;
1303 + } else {
1304 + var id = this._collection.modelId(model.attributes, model.idAttribute);
1305 + if (this._kind === ITERATOR_KEYS) {
1306 + value = id;
1307 + } else { // ITERATOR_KEYSVALUES
1308 + value = [id, model];
1309 + }
1310 + }
1311 + return {value: value, done: false};
1312 + }
1313 +
1314 + // Once exhausted, remove the reference to the collection so future
1315 + // calls to the next method always return done.
1316 + this._collection = void 0;
1317 + }
1318 +
1319 + return {value: void 0, done: true};
1320 + };
1321 +
1322 + // Backbone.View
1323 + // -------------
1324 +
1325 + // Backbone Views are almost more convention than they are actual code. A View
1326 + // is simply a JavaScript object that represents a logical chunk of UI in the
1327 + // DOM. This might be a single item, an entire list, a sidebar or panel, or
1328 + // even the surrounding frame which wraps your whole app. Defining a chunk of
1329 + // UI as a **View** allows you to define your DOM events declaratively, without
1330 + // having to worry about render order ... and makes it easy for the view to
1331 + // react to specific changes in the state of your models.
1332 +
1333 + // Creating a Backbone.View creates its initial element outside of the DOM,
1334 + // if an existing element is not provided...
1335 + var View = Backbone.View = function(options) {
1336 + this.cid = _.uniqueId('view');
1337 + this.preinitialize.apply(this, arguments);
1338 + _.extend(this, _.pick(options, viewOptions));
1339 + this._ensureElement();
1340 + this.initialize.apply(this, arguments);
1341 + };
1342 +
1343 + // Cached regex to split keys for `delegate`.
1344 + var delegateEventSplitter = /^(\S+)\s*(.*)$/;
1345 +
1346 + // List of view options to be set as properties.
1347 + var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
1348 +
1349 + // Set up all inheritable **Backbone.View** properties and methods.
1350 + _.extend(View.prototype, Events, {
1351 +
1352 + // The default `tagName` of a View's element is `"div"`.
1353 + tagName: 'div',
1354 +
1355 + // jQuery delegate for element lookup, scoped to DOM elements within the
1356 + // current view. This should be preferred to global lookups where possible.
1357 + $: function(selector) {
1358 + return this.$el.find(selector);
1359 + },
1360 +
1361 + // preinitialize is an empty function by default. You can override it with a function
1362 + // or object. preinitialize will run before any instantiation logic is run in the View
1363 + preinitialize: function(){},
1364 +
1365 + // Initialize is an empty function by default. Override it with your own
1366 + // initialization logic.
1367 + initialize: function(){},
1368 +
1369 + // **render** is the core function that your view should override, in order
1370 + // to populate its element (`this.el`), with the appropriate HTML. The
1371 + // convention is for **render** to always return `this`.
1372 + render: function() {
1373 + return this;
1374 + },
1375 +
1376 + // Remove this view by taking the element out of the DOM, and removing any
1377 + // applicable Backbone.Events listeners.
1378 + remove: function() {
1379 + this._removeElement();
1380 + this.stopListening();
1381 + return this;
1382 + },
1383 +
1384 + // Remove this view's element from the document and all event listeners
1385 + // attached to it. Exposed for subclasses using an alternative DOM
1386 + // manipulation API.
1387 + _removeElement: function() {
1388 + this.$el.remove();
1389 + },
1390 +
1391 + // Change the view's element (`this.el` property) and re-delegate the
1392 + // view's events on the new element.
1393 + setElement: function(element) {
1394 + this.undelegateEvents();
1395 + this._setElement(element);
1396 + this.delegateEvents();
1397 + return this;
1398 + },
1399 +
1400 + // Creates the `this.el` and `this.$el` references for this view using the
1401 + // given `el`. `el` can be a CSS selector or an HTML string, a jQuery
1402 + // context or an element. Subclasses can override this to utilize an
1403 + // alternative DOM manipulation API and are only required to set the
1404 + // `this.el` property.
1405 + _setElement: function(el) {
1406 + this.$el = el instanceof Backbone.$ ? el : Backbone.$(el);
1407 + this.el = this.$el[0];
1408 + },
1409 +
1410 + // Set callbacks, where `this.events` is a hash of
1411 + //
1412 + // *{"event selector": "callback"}*
1413 + //
1414 + // {
1415 + // 'mousedown .title': 'edit',
1416 + // 'click .button': 'save',
1417 + // 'click .open': function(e) { ... }
1418 + // }
1419 + //
1420 + // pairs. Callbacks will be bound to the view, with `this` set properly.
1421 + // Uses event delegation for efficiency.
1422 + // Omitting the selector binds the event to `this.el`.
1423 + delegateEvents: function(events) {
1424 + events || (events = _.result(this, 'events'));
1425 + if (!events) return this;
1426 + this.undelegateEvents();
1427 + for (var key in events) {
1428 + var method = events[key];
1429 + if (!_.isFunction(method)) method = this[method];
1430 + if (!method) continue;
1431 + var match = key.match(delegateEventSplitter);
1432 + this.delegate(match[1], match[2], method.bind(this));
1433 + }
1434 + return this;
1435 + },
1436 +
1437 + // Add a single event listener to the view's element (or a child element
1438 + // using `selector`). This only works for delegate-able events: not `focus`,
1439 + // `blur`, and not `change`, `submit`, and `reset` in Internet Explorer.
1440 + delegate: function(eventName, selector, listener) {
1441 + this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener);
1442 + return this;
1443 + },
1444 +
1445 + // Clears all callbacks previously bound to the view by `delegateEvents`.
1446 + // You usually don't need to use this, but may wish to if you have multiple
1447 + // Backbone views attached to the same DOM element.
1448 + undelegateEvents: function() {
1449 + if (this.$el) this.$el.off('.delegateEvents' + this.cid);
1450 + return this;
1451 + },
1452 +
1453 + // A finer-grained `undelegateEvents` for removing a single delegated event.
1454 + // `selector` and `listener` are both optional.
1455 + undelegate: function(eventName, selector, listener) {
1456 + this.$el.off(eventName + '.delegateEvents' + this.cid, selector, listener);
1457 + return this;
1458 + },
1459 +
1460 + // Produces a DOM element to be assigned to your view. Exposed for
1461 + // subclasses using an alternative DOM manipulation API.
1462 + _createElement: function(tagName) {
1463 + return document.createElement(tagName);
1464 + },
1465 +
1466 + // Ensure that the View has a DOM element to render into.
1467 + // If `this.el` is a string, pass it through `$()`, take the first
1468 + // matching element, and re-assign it to `el`. Otherwise, create
1469 + // an element from the `id`, `className` and `tagName` properties.
1470 + _ensureElement: function() {
1471 + if (!this.el) {
1472 + var attrs = _.extend({}, _.result(this, 'attributes'));
1473 + if (this.id) attrs.id = _.result(this, 'id');
1474 + if (this.className) attrs['class'] = _.result(this, 'className');
1475 + this.setElement(this._createElement(_.result(this, 'tagName')));
1476 + this._setAttributes(attrs);
1477 + } else {
1478 + this.setElement(_.result(this, 'el'));
1479 + }
1480 + },
1481 +
1482 + // Set attributes from a hash on this view's element. Exposed for
1483 + // subclasses using an alternative DOM manipulation API.
1484 + _setAttributes: function(attributes) {
1485 + this.$el.attr(attributes);
1486 + }
1487 +
1488 + });
1489 +
1490 + // Proxy Backbone class methods to Underscore functions, wrapping the model's
1491 + // `attributes` object or collection's `models` array behind the scenes.
1492 + //
1493 + // collection.filter(function(model) { return model.get('age') > 10 });
1494 + // collection.each(this.addView);
1495 + //
1496 + // `Function#apply` can be slow so we use the method's arg count, if we know it.
1497 + var addMethod = function(base, length, method, attribute) {
1498 + switch (length) {
1499 + case 1: return function() {
1500 + return base[method](this[attribute]);
1501 + };
1502 + case 2: return function(value) {
1503 + return base[method](this[attribute], value);
1504 + };
1505 + case 3: return function(iteratee, context) {
1506 + return base[method](this[attribute], cb(iteratee, this), context);
1507 + };
1508 + case 4: return function(iteratee, defaultVal, context) {
1509 + return base[method](this[attribute], cb(iteratee, this), defaultVal, context);
1510 + };
1511 + default: return function() {
1512 + var args = slice.call(arguments);
1513 + args.unshift(this[attribute]);
1514 + return base[method].apply(base, args);
1515 + };
1516 + }
1517 + };
1518 +
1519 + var addUnderscoreMethods = function(Class, base, methods, attribute) {
1520 + _.each(methods, function(length, method) {
1521 + if (base[method]) Class.prototype[method] = addMethod(base, length, method, attribute);
1522 + });
1523 + };
1524 +
1525 + // Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`.
1526 + var cb = function(iteratee, instance) {
1527 + if (_.isFunction(iteratee)) return iteratee;
1528 + if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee);
1529 + if (_.isString(iteratee)) return function(model) { return model.get(iteratee); };
1530 + return iteratee;
1531 + };
1532 + var modelMatcher = function(attrs) {
1533 + var matcher = _.matches(attrs);
1534 + return function(model) {
1535 + return matcher(model.attributes);
1536 + };
1537 + };
1538 +
1539 + // Underscore methods that we want to implement on the Collection.
1540 + // 90% of the core usefulness of Backbone Collections is actually implemented
1541 + // right here:
1542 + var collectionMethods = {forEach: 3, each: 3, map: 3, collect: 3, reduce: 0,
1543 + foldl: 0, inject: 0, reduceRight: 0, foldr: 0, find: 3, detect: 3, filter: 3,
1544 + select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3,
1545 + contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3,
1546 + head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3,
1547 + without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3,
1548 + isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3,
1549 + sortBy: 3, indexBy: 3, findIndex: 3, findLastIndex: 3};
1550 +
1551 +
1552 + // Underscore methods that we want to implement on the Model, mapped to the
1553 + // number of arguments they take.
1554 + var modelMethods = {keys: 1, values: 1, pairs: 1, invert: 1, pick: 0,
1555 + omit: 0, chain: 1, isEmpty: 1};
1556 +
1557 + // Mix in each Underscore method as a proxy to `Collection#models`.
1558 +
1559 + _.each([
1560 + [Collection, collectionMethods, 'models'],
1561 + [Model, modelMethods, 'attributes']
1562 + ], function(config) {
1563 + var Base = config[0],
1564 + methods = config[1],
1565 + attribute = config[2];
1566 +
1567 + Base.mixin = function(obj) {
1568 + var mappings = _.reduce(_.functions(obj), function(memo, name) {
1569 + memo[name] = 0;
1570 + return memo;
1571 + }, {});
1572 + addUnderscoreMethods(Base, obj, mappings, attribute);
1573 + };
1574 +
1575 + addUnderscoreMethods(Base, _, methods, attribute);
1576 + });
1577 +
1578 + // Backbone.sync
1579 + // -------------
1580 +
1581 + // Override this function to change the manner in which Backbone persists
1582 + // models to the server. You will be passed the type of request, and the
1583 + // model in question. By default, makes a RESTful Ajax request
1584 + // to the model's `url()`. Some possible customizations could be:
1585 + //
1586 + // * Use `setTimeout` to batch rapid-fire updates into a single request.
1587 + // * Send up the models as XML instead of JSON.
1588 + // * Persist models via WebSockets instead of Ajax.
1589 + //
1590 + // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
1591 + // as `POST`, with a `_method` parameter containing the true HTTP method,
1592 + // as well as all requests with the body as `application/x-www-form-urlencoded`
1593 + // instead of `application/json` with the model in a param named `model`.
1594 + // Useful when interfacing with server-side languages like **PHP** that make
1595 + // it difficult to read the body of `PUT` requests.
1596 + Backbone.sync = function(method, model, options) {
1597 + var type = methodMap[method];
1598 +
1599 + // Default options, unless specified.
1600 + _.defaults(options || (options = {}), {
1601 + emulateHTTP: Backbone.emulateHTTP,
1602 + emulateJSON: Backbone.emulateJSON
1603 + });
1604 +
1605 + // Default JSON-request options.
1606 + var params = {type: type, dataType: 'json'};
1607 +
1608 + // Ensure that we have a URL.
1609 + if (!options.url) {
1610 + params.url = _.result(model, 'url') || urlError();
1611 + }
1612 +
1613 + // Ensure that we have the appropriate request data.
1614 + if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
1615 + params.contentType = 'application/json';
1616 + params.data = JSON.stringify(options.attrs || model.toJSON(options));
1617 + }
1618 +
1619 + // For older servers, emulate JSON by encoding the request into an HTML-form.
1620 + if (options.emulateJSON) {
1621 + params.contentType = 'application/x-www-form-urlencoded';
1622 + params.data = params.data ? {model: params.data} : {};
1623 + }
1624 +
1625 + // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1626 + // And an `X-HTTP-Method-Override` header.
1627 + if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
1628 + params.type = 'POST';
1629 + if (options.emulateJSON) params.data._method = type;
1630 + var beforeSend = options.beforeSend;
1631 + options.beforeSend = function(xhr) {
1632 + xhr.setRequestHeader('X-HTTP-Method-Override', type);
1633 + if (beforeSend) return beforeSend.apply(this, arguments);
1634 + };
1635 + }
1636 +
1637 + // Don't process data on a non-GET request.
1638 + if (params.type !== 'GET' && !options.emulateJSON) {
1639 + params.processData = false;
1640 + }
1641 +
1642 + // Pass along `textStatus` and `errorThrown` from jQuery.
1643 + var error = options.error;
1644 + options.error = function(xhr, textStatus, errorThrown) {
1645 + options.textStatus = textStatus;
1646 + options.errorThrown = errorThrown;
1647 + if (error) error.call(options.context, xhr, textStatus, errorThrown);
1648 + };
1649 +
1650 + // Make the request, allowing the user to override any Ajax options.
1651 + var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
1652 + model.trigger('request', model, xhr, options);
1653 + return xhr;
1654 + };
1655 +
1656 + // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1657 + var methodMap = {
1658 + 'create': 'POST',
1659 + 'update': 'PUT',
1660 + 'patch': 'PATCH',
1661 + 'delete': 'DELETE',
1662 + 'read': 'GET'
1663 + };
1664 +
1665 + // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
1666 + // Override this if you'd like to use a different library.
1667 + Backbone.ajax = function() {
1668 + return Backbone.$.ajax.apply(Backbone.$, arguments);
1669 + };
1670 +
1671 + // Backbone.Router
1672 + // ---------------
1673 +
1674 + // Routers map faux-URLs to actions, and fire events when routes are
1675 + // matched. Creating a new one sets its `routes` hash, if not set statically.
1676 + var Router = Backbone.Router = function(options) {
1677 + options || (options = {});
1678 + this.preinitialize.apply(this, arguments);
1679 + if (options.routes) this.routes = options.routes;
1680 + this._bindRoutes();
1681 + this.initialize.apply(this, arguments);
1682 + };
1683 +
1684 + // Cached regular expressions for matching named param parts and splatted
1685 + // parts of route strings.
1686 + var optionalParam = /\((.*?)\)/g;
1687 + var namedParam = /(\(\?)?:\w+/g;
1688 + var splatParam = /\*\w+/g;
1689 + var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
1690 +
1691 + // Set up all inheritable **Backbone.Router** properties and methods.
1692 + _.extend(Router.prototype, Events, {
1693 +
1694 + // preinitialize is an empty function by default. You can override it with a function
1695 + // or object. preinitialize will run before any instantiation logic is run in the Router.
1696 + preinitialize: function(){},
1697 +
1698 + // Initialize is an empty function by default. Override it with your own
1699 + // initialization logic.
1700 + initialize: function(){},
1701 +
1702 + // Manually bind a single named route to a callback. For example:
1703 + //
1704 + // this.route('search/:query/p:num', 'search', function(query, num) {
1705 + // ...
1706 + // });
1707 + //
1708 + route: function(route, name, callback) {
1709 + if (!_.isRegExp(route)) route = this._routeToRegExp(route);
1710 + if (_.isFunction(name)) {
1711 + callback = name;
1712 + name = '';
1713 + }
1714 + if (!callback) callback = this[name];
1715 + var router = this;
1716 + Backbone.history.route(route, function(fragment) {
1717 + var args = router._extractParameters(route, fragment);
1718 + if (router.execute(callback, args, name) !== false) {
1719 + router.trigger.apply(router, ['route:' + name].concat(args));
1720 + router.trigger('route', name, args);
1721 + Backbone.history.trigger('route', router, name, args);
1722 + }
1723 + });
1724 + return this;
1725 + },
1726 +
1727 + // Execute a route handler with the provided parameters. This is an
1728 + // excellent place to do pre-route setup or post-route cleanup.
1729 + execute: function(callback, args, name) {
1730 + if (callback) callback.apply(this, args);
1731 + },
1732 +
1733 + // Simple proxy to `Backbone.history` to save a fragment into the history.
1734 + navigate: function(fragment, options) {
1735 + Backbone.history.navigate(fragment, options);
1736 + return this;
1737 + },
1738 +
1739 + // Bind all defined routes to `Backbone.history`. We have to reverse the
1740 + // order of the routes here to support behavior where the most general
1741 + // routes can be defined at the bottom of the route map.
1742 + _bindRoutes: function() {
1743 + if (!this.routes) return;
1744 + this.routes = _.result(this, 'routes');
1745 + var route, routes = _.keys(this.routes);
1746 + while ((route = routes.pop()) != null) {
1747 + this.route(route, this.routes[route]);
1748 + }
1749 + },
1750 +
1751 + // Convert a route string into a regular expression, suitable for matching
1752 + // against the current location hash.
1753 + _routeToRegExp: function(route) {
1754 + route = route.replace(escapeRegExp, '\\$&')
1755 + .replace(optionalParam, '(?:$1)?')
1756 + .replace(namedParam, function(match, optional) {
1757 + return optional ? match : '([^/?]+)';
1758 + })
1759 + .replace(splatParam, '([^?]*?)');
1760 + return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$');
1761 + },
1762 +
1763 + // Given a route, and a URL fragment that it matches, return the array of
1764 + // extracted decoded parameters. Empty or unmatched parameters will be
1765 + // treated as `null` to normalize cross-browser behavior.
1766 + _extractParameters: function(route, fragment) {
1767 + var params = route.exec(fragment).slice(1);
1768 + return _.map(params, function(param, i) {
1769 + // Don't decode the search params.
1770 + if (i === params.length - 1) return param || null;
1771 + return param ? decodeURIComponent(param) : null;
1772 + });
1773 + }
1774 +
1775 + });
1776 +
1777 + // Backbone.History
1778 + // ----------------
1779 +
1780 + // Handles cross-browser history management, based on either
1781 + // [pushState](http://diveintohtml5.info/history.html) and real URLs, or
1782 + // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
1783 + // and URL fragments. If the browser supports neither (old IE, natch),
1784 + // falls back to polling.
1785 + var History = Backbone.History = function() {
1786 + this.handlers = [];
1787 + this.checkUrl = this.checkUrl.bind(this);
1788 +
1789 + // Ensure that `History` can be used outside of the browser.
1790 + if (typeof window !== 'undefined') {
1791 + this.location = window.location;
1792 + this.history = window.history;
1793 + }
1794 + };
1795 +
1796 + // Cached regex for stripping a leading hash/slash and trailing space.
1797 + var routeStripper = /^[#\/]|\s+$/g;
1798 +
1799 + // Cached regex for stripping leading and trailing slashes.
1800 + var rootStripper = /^\/+|\/+$/g;
1801 +
1802 + // Cached regex for stripping urls of hash.
1803 + var pathStripper = /#.*$/;
1804 +
1805 + // Has the history handling already been started?
1806 + History.started = false;
1807 +
1808 + // Set up all inheritable **Backbone.History** properties and methods.
1809 + _.extend(History.prototype, Events, {
1810 +
1811 + // The default interval to poll for hash changes, if necessary, is
1812 + // twenty times a second.
1813 + interval: 50,
1814 +
1815 + // Are we at the app root?
1816 + atRoot: function() {
1817 + var path = this.location.pathname.replace(/[^\/]$/, '$&/');
1818 + return path === this.root && !this.getSearch();
1819 + },
1820 +
1821 + // Does the pathname match the root?
1822 + matchRoot: function() {
1823 + var path = this.decodeFragment(this.location.pathname);
1824 + var rootPath = path.slice(0, this.root.length - 1) + '/';
1825 + return rootPath === this.root;
1826 + },
1827 +
1828 + // Unicode characters in `location.pathname` are percent encoded so they're
1829 + // decoded for comparison. `%25` should not be decoded since it may be part
1830 + // of an encoded parameter.
1831 + decodeFragment: function(fragment) {
1832 + return decodeURI(fragment.replace(/%25/g, '%2525'));
1833 + },
1834 +
1835 + // In IE6, the hash fragment and search params are incorrect if the
1836 + // fragment contains `?`.
1837 + getSearch: function() {
1838 + var match = this.location.href.replace(/#.*/, '').match(/\?.+/);
1839 + return match ? match[0] : '';
1840 + },
1841 +
1842 + // Gets the true hash value. Cannot use location.hash directly due to bug
1843 + // in Firefox where location.hash will always be decoded.
1844 + getHash: function(window) {
1845 + var match = (window || this).location.href.match(/#(.*)$/);
1846 + return match ? match[1] : '';
1847 + },
1848 +
1849 + // Get the pathname and search params, without the root.
1850 + getPath: function() {
1851 + var path = this.decodeFragment(
1852 + this.location.pathname + this.getSearch()
1853 + ).slice(this.root.length - 1);
1854 + return path.charAt(0) === '/' ? path.slice(1) : path;
1855 + },
1856 +
1857 + // Get the cross-browser normalized URL fragment from the path or hash.
1858 + getFragment: function(fragment) {
1859 + if (fragment == null) {
1860 + if (this._usePushState || !this._wantsHashChange) {
1861 + fragment = this.getPath();
1862 + } else {
1863 + fragment = this.getHash();
1864 + }
1865 + }
1866 + return fragment.replace(routeStripper, '');
1867 + },
1868 +
1869 + // Start the hash change handling, returning `true` if the current URL matches
1870 + // an existing route, and `false` otherwise.
1871 + start: function(options) {
1872 + if (History.started) throw new Error('Backbone.history has already been started');
1873 + History.started = true;
1874 +
1875 + // Figure out the initial configuration. Do we need an iframe?
1876 + // Is pushState desired ... is it available?
1877 + this.options = _.extend({root: '/'}, this.options, options);
1878 + this.root = this.options.root;
1879 + this._trailingSlash = this.options.trailingSlash;
1880 + this._wantsHashChange = this.options.hashChange !== false;
1881 + this._hasHashChange = 'onhashchange' in window && (document.documentMode === void 0 || document.documentMode > 7);
1882 + this._useHashChange = this._wantsHashChange && this._hasHashChange;
1883 + this._wantsPushState = !!this.options.pushState;
1884 + this._hasPushState = !!(this.history && this.history.pushState);
1885 + this._usePushState = this._wantsPushState && this._hasPushState;
1886 + this.fragment = this.getFragment();
1887 +
1888 + // Normalize root to always include a leading and trailing slash.
1889 + this.root = ('/' + this.root + '/').replace(rootStripper, '/');
1890 +
1891 + // Transition from hashChange to pushState or vice versa if both are
1892 + // requested.
1893 + if (this._wantsHashChange && this._wantsPushState) {
1894 +
1895 + // If we've started off with a route from a `pushState`-enabled
1896 + // browser, but we're currently in a browser that doesn't support it...
1897 + if (!this._hasPushState && !this.atRoot()) {
1898 + var rootPath = this.root.slice(0, -1) || '/';
1899 + this.location.replace(rootPath + '#' + this.getPath());
1900 + // Return immediately as browser will do redirect to new url
1901 + return true;
1902 +
1903 + // Or if we've started out with a hash-based route, but we're currently
1904 + // in a browser where it could be `pushState`-based instead...
1905 + } else if (this._hasPushState && this.atRoot()) {
1906 + this.navigate(this.getHash(), {replace: true});
1907 + }
1908 +
1909 + }
1910 +
1911 + // Proxy an iframe to handle location events if the browser doesn't
1912 + // support the `hashchange` event, HTML5 history, or the user wants
1913 + // `hashChange` but not `pushState`.
1914 + if (!this._hasHashChange && this._wantsHashChange && !this._usePushState) {
1915 + this.iframe = document.createElement('iframe');
1916 + this.iframe.src = 'javascript:0';
1917 + this.iframe.style.display = 'none';
1918 + this.iframe.tabIndex = -1;
1919 + var body = document.body;
1920 + // Using `appendChild` will throw on IE < 9 if the document is not ready.
1921 + var iWindow = body.insertBefore(this.iframe, body.firstChild).contentWindow;
1922 + iWindow.document.open();
1923 + iWindow.document.close();
1924 + iWindow.location.hash = '#' + this.fragment;
1925 + }
1926 +
1927 + // Add a cross-platform `addEventListener` shim for older browsers.
1928 + var addEventListener = window.addEventListener || function(eventName, listener) {
1929 + return attachEvent('on' + eventName, listener);
1930 + };
1931 +
1932 + // Depending on whether we're using pushState or hashes, and whether
1933 + // 'onhashchange' is supported, determine how we check the URL state.
1934 + if (this._usePushState) {
1935 + addEventListener('popstate', this.checkUrl, false);
1936 + } else if (this._useHashChange && !this.iframe) {
1937 + addEventListener('hashchange', this.checkUrl, false);
1938 + } else if (this._wantsHashChange) {
1939 + this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
1940 + }
1941 +
1942 + if (!this.options.silent) return this.loadUrl();
1943 + },
1944 +
1945 + // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
1946 + // but possibly useful for unit testing Routers.
1947 + stop: function() {
1948 + // Add a cross-platform `removeEventListener` shim for older browsers.
1949 + var removeEventListener = window.removeEventListener || function(eventName, listener) {
1950 + return detachEvent('on' + eventName, listener);
1951 + };
1952 +
1953 + // Remove window listeners.
1954 + if (this._usePushState) {
1955 + removeEventListener('popstate', this.checkUrl, false);
1956 + } else if (this._useHashChange && !this.iframe) {
1957 + removeEventListener('hashchange', this.checkUrl, false);
1958 + }
1959 +
1960 + // Clean up the iframe if necessary.
1961 + if (this.iframe) {
1962 + document.body.removeChild(this.iframe);
1963 + this.iframe = null;
1964 + }
1965 +
1966 + // Some environments will throw when clearing an undefined interval.
1967 + if (this._checkUrlInterval) clearInterval(this._checkUrlInterval);
1968 + History.started = false;
1969 + },
1970 +
1971 + // Add a route to be tested when the fragment changes. Routes added later
1972 + // may override previous routes.
1973 + route: function(route, callback) {
1974 + this.handlers.unshift({route: route, callback: callback});
1975 + },
1976 +
1977 + // Checks the current URL to see if it has changed, and if it has,
1978 + // calls `loadUrl`, normalizing across the hidden iframe.
1979 + checkUrl: function(e) {
1980 + var current = this.getFragment();
1981 +
1982 + // If the user pressed the back button, the iframe's hash will have
1983 + // changed and we should use that for comparison.
1984 + if (current === this.fragment && this.iframe) {
1985 + current = this.getHash(this.iframe.contentWindow);
1986 + }
1987 +
1988 + if (current === this.fragment) {
1989 + if (!this.matchRoot()) return this.notfound();
1990 + return false;
1991 + }
1992 + if (this.iframe) this.navigate(current);
1993 + this.loadUrl();
1994 + },
1995 +
1996 + // Attempt to load the current URL fragment. If a route succeeds with a
1997 + // match, returns `true`. If no defined routes matches the fragment,
1998 + // returns `false`.
1999 + loadUrl: function(fragment) {
2000 + // If the root doesn't match, no routes can match either.
2001 + if (!this.matchRoot()) return this.notfound();
2002 + fragment = this.fragment = this.getFragment(fragment);
2003 + return _.some(this.handlers, function(handler) {
2004 + if (handler.route.test(fragment)) {
2005 + handler.callback(fragment);
2006 + return true;
2007 + }
2008 + }) || this.notfound();
2009 + },
2010 +
2011 + // When no route could be matched, this method is called internally to
2012 + // trigger the `'notfound'` event. It returns `false` so that it can be used
2013 + // in tail position.
2014 + notfound: function() {
2015 + this.trigger('notfound');
2016 + return false;
2017 + },
2018 +
2019 + // Save a fragment into the hash history, or replace the URL state if the
2020 + // 'replace' option is passed. You are responsible for properly URL-encoding
2021 + // the fragment in advance.
2022 + //
2023 + // The options object can contain `trigger: true` if you wish to have the
2024 + // route callback be fired (not usually desirable), or `replace: true`, if
2025 + // you wish to modify the current URL without adding an entry to the history.
2026 + navigate: function(fragment, options) {
2027 + if (!History.started) return false;
2028 + if (!options || options === true) options = {trigger: !!options};
2029 +
2030 + // Normalize the fragment.
2031 + fragment = this.getFragment(fragment || '');
2032 +
2033 + // Strip trailing slash on the root unless _trailingSlash is true
2034 + var rootPath = this.root;
2035 + if (!this._trailingSlash && (fragment === '' || fragment.charAt(0) === '?')) {
2036 + rootPath = rootPath.slice(0, -1) || '/';
2037 + }
2038 + var url = rootPath + fragment;
2039 +
2040 + // Strip the fragment of the query and hash for matching.
2041 + fragment = fragment.replace(pathStripper, '');
2042 +
2043 + // Decode for matching.
2044 + var decodedFragment = this.decodeFragment(fragment);
2045 +
2046 + if (this.fragment === decodedFragment) return;
2047 + this.fragment = decodedFragment;
2048 +
2049 + // If pushState is available, we use it to set the fragment as a real URL.
2050 + if (this._usePushState) {
2051 + this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
2052 +
2053 + // If hash changes haven't been explicitly disabled, update the hash
2054 + // fragment to store history.
2055 + } else if (this._wantsHashChange) {
2056 + this._updateHash(this.location, fragment, options.replace);
2057 + if (this.iframe && fragment !== this.getHash(this.iframe.contentWindow)) {
2058 + var iWindow = this.iframe.contentWindow;
2059 +
2060 + // Opening and closing the iframe tricks IE7 and earlier to push a
2061 + // history entry on hash-tag change. When replace is true, we don't
2062 + // want this.
2063 + if (!options.replace) {
2064 + iWindow.document.open();
2065 + iWindow.document.close();
2066 + }
2067 +
2068 + this._updateHash(iWindow.location, fragment, options.replace);
2069 + }
2070 +
2071 + // If you've told us that you explicitly don't want fallback hashchange-
2072 + // based history, then `navigate` becomes a page refresh.
2073 + } else {
2074 + return this.location.assign(url);
2075 + }
2076 + if (options.trigger) return this.loadUrl(fragment);
2077 + },
2078 +
2079 + // Update the hash location, either replacing the current entry, or adding
2080 + // a new one to the browser history.
2081 + _updateHash: function(location, fragment, replace) {
2082 + if (replace) {
2083 + var href = location.href.replace(/(javascript:|#).*$/, '');
2084 + location.replace(href + '#' + fragment);
2085 + } else {
2086 + // Some browsers require that `hash` contains a leading #.
2087 + location.hash = '#' + fragment;
2088 + }
2089 + }
2090 +
2091 + });
2092 +
2093 + // Create the default Backbone.history.
2094 + Backbone.history = new History;
2095 +
2096 + // Helpers
2097 + // -------
2098 +
2099 + // Helper function to correctly set up the prototype chain for subclasses.
2100 + // Similar to `goog.inherits`, but uses a hash of prototype properties and
2101 + // class properties to be extended.
2102 + var extend = function(protoProps, staticProps) {
2103 + var parent = this;
2104 + var child;
2105 +
2106 + // The constructor function for the new subclass is either defined by you
2107 + // (the "constructor" property in your `extend` definition), or defaulted
2108 + // by us to simply call the parent constructor.
2109 + if (protoProps && _.has(protoProps, 'constructor')) {
2110 + child = protoProps.constructor;
2111 + } else {
2112 + child = function(){ return parent.apply(this, arguments); };
2113 + }
2114 +
2115 + // Add static properties to the constructor function, if supplied.
2116 + _.extend(child, parent, staticProps);
2117 +
2118 + // Set the prototype chain to inherit from `parent`, without calling
2119 + // `parent`'s constructor function and add the prototype properties.
2120 + child.prototype = _.create(parent.prototype, protoProps);
2121 + child.prototype.constructor = child;
2122 +
2123 + // Set a convenience property in case the parent's prototype is needed
2124 + // later.
2125 + child.__super__ = parent.prototype;
2126 +
2127 + return child;
2128 + };
2129 +
2130 + // Set up inheritance for the model, collection, router, view and history.
2131 + Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
2132 +
2133 + // Throw an error when a URL is needed, and none is supplied.
2134 + var urlError = function() {
2135 + throw new Error('A "url" property or function must be specified');
2136 + };
2137 +
2138 + // Wrap an optional error callback with a fallback error event.
2139 + var wrapError = function(model, options) {
2140 + var error = options.error;
2141 + options.error = function(resp) {
2142 + if (error) error.call(options.context, model, resp, options);
2143 + model.trigger('error', model, resp, options);
2144 + };
2145 + };
2146 +
2147 + // Provide useful information when things go wrong. This method is not meant
2148 + // to be used directly; it merely provides the necessary introspection for the
2149 + // external `debugInfo` function.
2150 + Backbone._debug = function() {
2151 + return {root: root, _: _};
2152 + };
2153 +
2154 + return Backbone;
2155 + });
2156 +