Diff: STRATO-apps/wordpress_03/app/wp-includes/js/backbone.js
Keine Baseline-Datei – Diff nur gegen leer.
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
+