86
79
var Events = Backbone.Events = {
88
// Bind one or more space separated events, `events`, to a `callback`
89
// function. Passing `"all"` will bind the callback to all events fired.
90
on: function(events, callback, context) {
92
var calls, event, node, tail, list;
93
if (!callback) return this;
94
events = events.split(eventSplitter);
95
calls = this._callbacks || (this._callbacks = {});
97
// Create an immutable callback list, allowing traversal during
98
// modification. The tail is an empty object that will always be used
100
while (event = events.shift()) {
102
node = list ? list.tail : {};
103
node.next = tail = {};
104
node.context = context;
105
node.callback = callback;
106
calls[event] = {tail: tail, next: list ? list.next : node};
81
// Bind an event to a `callback` function. Passing `"all"` will bind
82
// the callback to all events fired.
83
on: function(name, callback, context) {
84
if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
85
this._events || (this._events = {});
86
var events = this._events[name] || (this._events[name] = []);
87
events.push({callback: callback, context: context, ctx: context || this});
112
// Remove one or many callbacks. If `context` is null, removes all callbacks
113
// with that function. If `callback` is null, removes all callbacks for the
114
// event. If `events` is null, removes all bound callbacks for all events.
115
off: function(events, callback, context) {
116
var event, calls, node, tail, cb, ctx;
91
// Bind an event to only be triggered a single time. After the first time
92
// the callback is invoked, it will be removed.
93
once: function(name, callback, context) {
94
if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
96
var once = _.once(function() {
98
callback.apply(this, arguments);
100
once._callback = callback;
101
return this.on(name, once, context);
118
// No events, or removing *all* events.
119
if (!(calls = this._callbacks)) return;
120
if (!(events || callback || context)) {
121
delete this._callbacks;
104
// Remove one or many callbacks. If `context` is null, removes all
105
// callbacks with that function. If `callback` is null, removes all
106
// callbacks for the event. If `name` is null, removes all bound
107
// callbacks for all events.
108
off: function(name, callback, context) {
109
var retain, ev, events, names, i, l, j, k;
110
if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
111
if (!name && !callback && !context) {
125
// Loop through the listed events and contexts, splicing them out of the
126
// linked list of callbacks if appropriate.
127
events = events ? events.split(eventSplitter) : _.keys(calls);
128
while (event = events.shift()) {
131
if (!node || !(callback || context)) continue;
132
// Create a new list, omitting the indicated callbacks.
134
while ((node = node.next) !== tail) {
137
if ((callback && cb !== callback) || (context && ctx !== context)) {
138
this.on(event, cb, ctx);
115
names = name ? [name] : _.keys(this._events);
116
for (i = 0, l = names.length; i < l; i++) {
118
if (events = this._events[name]) {
119
this._events[name] = retain = [];
120
if (callback || context) {
121
for (j = 0, k = events.length; j < k; j++) {
123
if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
124
(context && context !== ev.context)) {
129
if (!retain.length) delete this._events[name];
147
137
// passed the same arguments as `trigger` is, apart from the event name
148
138
// (unless you're listening on `"all"`, which will cause your callback to
149
139
// receive the true name of the event as the first argument).
150
trigger: function(events) {
151
var event, node, calls, tail, args, all, rest;
152
if (!(calls = this._callbacks)) return this;
154
events = events.split(eventSplitter);
155
rest = slice.call(arguments, 1);
157
// For each event, walk through the linked list of callbacks twice,
158
// first to trigger the event, then to trigger any `"all"` callbacks.
159
while (event = events.shift()) {
160
if (node = calls[event]) {
162
while ((node = node.next) !== tail) {
163
node.callback.apply(node.context || this, rest);
168
args = [event].concat(rest);
169
while ((node = node.next) !== tail) {
170
node.callback.apply(node.context || this, args);
140
trigger: function(name) {
141
if (!this._events) return this;
142
var args = slice.call(arguments, 1);
143
if (!eventsApi(this, 'trigger', name, args)) return this;
144
var events = this._events[name];
145
var allEvents = this._events.all;
146
if (events) triggerEvents(events, args);
147
if (allEvents) triggerEvents(allEvents, arguments);
151
// Tell this object to stop listening to either specific events ... or
152
// to every object it's currently listening to.
153
stopListening: function(obj, name, callback) {
154
var listeningTo = this._listeningTo;
155
if (!listeningTo) return this;
156
var remove = !name && !callback;
157
if (!callback && typeof name === 'object') callback = this;
158
if (obj) (listeningTo = {})[obj._listenId] = obj;
159
for (var id in listeningTo) {
160
obj = listeningTo[id];
161
obj.off(name, callback, this);
162
if (remove || _.isEmpty(obj._events)) delete this._listeningTo[id];
169
// Regular expression used to split event strings.
170
var eventSplitter = /\s+/;
172
// Implement fancy features of the Events API such as multiple event
173
// names `"change blur"` and jQuery-style event maps `{change: action}`
174
// in terms of the existing API.
175
var eventsApi = function(obj, action, name, rest) {
176
if (!name) return true;
178
// Handle event maps.
179
if (typeof name === 'object') {
180
for (var key in name) {
181
obj[action].apply(obj, [key, name[key]].concat(rest));
186
// Handle space separated event names.
187
if (eventSplitter.test(name)) {
188
var names = name.split(eventSplitter);
189
for (var i = 0, l = names.length; i < l; i++) {
190
obj[action].apply(obj, [names[i]].concat(rest));
198
// A difficult-to-believe, but optimized internal dispatch function for
199
// triggering events. Tries to keep the usual cases speedy (most internal
200
// Backbone events have 3 arguments).
201
var triggerEvents = function(events, args) {
202
var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
203
switch (args.length) {
204
case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
205
case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
206
case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
207
case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
208
default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
212
var listenMethods = {listenTo: 'on', listenToOnce: 'once'};
214
// Inversion-of-control versions of `on` and `once`. Tell *this* object to
215
// listen to an event in another object ... keeping track of what it's
217
_.each(listenMethods, function(implementation, method) {
218
Events[method] = function(obj, name, callback) {
219
var listeningTo = this._listeningTo || (this._listeningTo = {});
220
var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
221
listeningTo[id] = obj;
222
if (!callback && typeof name === 'object') callback = this;
223
obj[implementation](name, callback, this);
180
228
// Aliases for backwards compatibility.
181
229
Events.bind = Events.on;
182
230
Events.unbind = Events.off;
232
// Allow the `Backbone` object to serve as a global event bus, for folks who
233
// want global "pubsub" in a convenient place.
234
_.extend(Backbone, Events);
184
236
// Backbone.Model
185
237
// --------------
187
// Create a new model, with defined attributes. A client id (`cid`)
239
// Backbone **Models** are the basic data object in the framework --
240
// frequently representing a row in a table in a database on your server.
241
// A discrete chunk of data and a bunch of useful, related methods for
242
// performing computations and transformations on that data.
244
// Create a new model with the specified attributes. A client id (`cid`)
188
245
// is automatically generated and assigned for you.
189
246
var Model = Backbone.Model = function(attributes, options) {
191
attributes || (attributes = {});
192
if (options && options.parse) attributes = this.parse(attributes);
193
if (defaults = getValue(this, 'defaults')) {
194
attributes = _.extend({}, defaults, attributes);
196
if (options && options.collection) this.collection = options.collection;
247
var attrs = attributes || {};
248
options || (options = {});
249
this.cid = _.uniqueId('c');
197
250
this.attributes = {};
198
this._escapedAttributes = {};
199
this.cid = _.uniqueId('c');
203
this.set(attributes, {silent: true});
204
// Reset change tracking.
208
this._previousAttributes = _.clone(this.attributes);
251
if (options.collection) this.collection = options.collection;
252
if (options.parse) attrs = this.parse(attrs, options) || {};
253
attrs = _.defaults({}, attrs, _.result(this, 'defaults'));
254
this.set(attrs, options);
209
256
this.initialize.apply(this, arguments);
255
300
return this.get(attr) != null;
258
// Set a hash of model attributes on the object, firing `"change"` unless
259
// you choose to silence it.
260
set: function(key, value, options) {
261
var attrs, attr, val;
303
// Set a hash of model attributes on the object, firing `"change"`. This is
304
// the core primitive operation of a model, updating the data and notifying
305
// anyone who needs to know about the change in state. The heart of the beast.
306
set: function(key, val, options) {
307
var attr, attrs, unset, changes, silent, changing, prev, current;
308
if (key == null) return this;
263
310
// Handle both `"key", value` and `{key: value}` -style arguments.
264
if (_.isObject(key) || key == null) {
311
if (typeof key === 'object') {
315
(attrs = {})[key] = val;
272
// Extract attributes and options.
273
318
options || (options = {});
274
if (!attrs) return this;
275
if (attrs instanceof Model) attrs = attrs.attributes;
276
if (options.unset) for (attr in attrs) attrs[attr] = void 0;
278
320
// Run validation.
279
321
if (!this._validate(attrs, options)) return false;
323
// Extract attributes and options.
324
unset = options.unset;
325
silent = options.silent;
327
changing = this._changing;
328
this._changing = true;
331
this._previousAttributes = _.clone(this.attributes);
334
current = this.attributes, prev = this._previousAttributes;
281
336
// Check for changes of `id`.
282
337
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
284
var changes = options.changes = {};
285
var now = this.attributes;
286
var escaped = this._escapedAttributes;
287
var prev = this._previousAttributes || {};
289
// For each `set` attribute...
339
// For each `set` attribute, update or delete the current value.
290
340
for (attr in attrs) {
291
341
val = attrs[attr];
293
// If the new and current value differ, record the change.
294
if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
295
delete escaped[attr];
296
(options.silent ? this._silent : changes)[attr] = true;
299
// Update or delete the current value.
300
options.unset ? delete now[attr] : now[attr] = val;
302
// If the new and previous value differ, record the change. If not,
303
// then remove changes for this attribute.
304
if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
342
if (!_.isEqual(current[attr], val)) changes.push(attr);
343
if (!_.isEqual(prev[attr], val)) {
305
344
this.changed[attr] = val;
306
if (!options.silent) this._pending[attr] = true;
308
346
delete this.changed[attr];
309
delete this._pending[attr];
313
// Fire the `"change"` events.
314
if (!options.silent) this.change(options);
348
unset ? delete current[attr] : current[attr] = val;
351
// Trigger all relevant attribute changes.
353
if (changes.length) this._pending = true;
354
for (var i = 0, l = changes.length; i < l; i++) {
355
this.trigger('change:' + changes[i], this, current[changes[i]], options);
359
// You might be wondering why there's a `while` loop here. Changes can
360
// be recursively nested within `"change"` events.
361
if (changing) return this;
363
while (this._pending) {
364
this._pending = false;
365
this.trigger('change', this, options);
368
this._pending = false;
369
this._changing = false;
318
// Remove an attribute from the model, firing `"change"` unless you choose
319
// to silence it. `unset` is a noop if the attribute doesn't exist.
373
// Remove an attribute from the model, firing `"change"`. `unset` is a noop
374
// if the attribute doesn't exist.
320
375
unset: function(attr, options) {
321
(options || (options = {})).unset = true;
322
return this.set(attr, null, options);
376
return this.set(attr, void 0, _.extend({}, options, {unset: true}));
325
// Clear all attributes on the model, firing `"change"` unless you choose
379
// Clear all attributes on the model, firing `"change"`.
327
380
clear: function(options) {
328
(options || (options = {})).unset = true;
329
return this.set(_.clone(this.attributes), options);
382
for (var key in this.attributes) attrs[key] = void 0;
383
return this.set(attrs, _.extend({}, options, {unset: true}));
386
// Determine if the model has changed since the last `"change"` event.
387
// If you specify an attribute name, determine if that attribute has changed.
388
hasChanged: function(attr) {
389
if (attr == null) return !_.isEmpty(this.changed);
390
return _.has(this.changed, attr);
393
// Return an object containing all the attributes that have changed, or
394
// false if there are no changed attributes. Useful for determining what
395
// parts of a view need to be updated and/or what attributes need to be
396
// persisted to the server. Unset attributes will be set to undefined.
397
// You can also pass an attributes object to diff against the model,
398
// determining if there *would be* a change.
399
changedAttributes: function(diff) {
400
if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
401
var val, changed = false;
402
var old = this._changing ? this._previousAttributes : this.attributes;
403
for (var attr in diff) {
404
if (_.isEqual(old[attr], (val = diff[attr]))) continue;
405
(changed || (changed = {}))[attr] = val;
410
// Get the previous value of an attribute, recorded at the time the last
411
// `"change"` event was fired.
412
previous: function(attr) {
413
if (attr == null || !this._previousAttributes) return null;
414
return this._previousAttributes[attr];
417
// Get all of the attributes of the model at the time of the previous
419
previousAttributes: function() {
420
return _.clone(this._previousAttributes);
332
423
// Fetch the model from the server. If the server's representation of the
333
// model differs from its current attributes, they will be overriden,
424
// model differs from its current attributes, they will be overridden,
334
425
// triggering a `"change"` event.
335
426
fetch: function(options) {
336
427
options = options ? _.clone(options) : {};
428
if (options.parse === void 0) options.parse = true;
337
429
var model = this;
338
430
var success = options.success;
339
options.success = function(resp, status, xhr) {
340
if (!model.set(model.parse(resp, xhr), options)) return false;
341
if (success) success(model, resp);
431
options.success = function(resp) {
432
if (!model.set(model.parse(resp, options), options)) return false;
433
if (success) success(model, resp, options);
434
model.trigger('sync', model, resp, options);
343
options.error = Backbone.wrapError(options.error, model, options);
344
return (this.sync || Backbone.sync).call(this, 'read', this, options);
436
wrapError(this, options);
437
return this.sync('read', this, options);
347
440
// Set a hash of model attributes, and sync the model to the server.
348
441
// If the server returns an attributes hash that differs, the model's
349
442
// state will be `set` again.
350
save: function(key, value, options) {
443
save: function(key, val, options) {
444
var attrs, method, xhr, attributes = this.attributes;
353
// Handle both `("key", value)` and `({key: value})` -style calls.
354
if (_.isObject(key) || key == null) {
446
// Handle both `"key", value` and `{key: value}` -style arguments.
447
if (key == null || typeof key === 'object') {
451
(attrs = {})[key] = val;
361
options = options ? _.clone(options) : {};
363
// If we're "wait"-ing to set changed attributes, validate early.
454
options = _.extend({validate: true}, options);
456
// If we're not waiting and attributes exist, save acts as
457
// `set(attr).save(null, opts)` with validation. Otherwise, check if
458
// the model will be valid when the attributes, if any, are set.
459
if (attrs && !options.wait) {
460
if (!this.set(attrs, options)) return false;
365
462
if (!this._validate(attrs, options)) return false;
366
current = _.clone(this.attributes);
369
// Regular saves `set` attributes before persisting to the server.
370
var silentOptions = _.extend({}, options, {silent: true});
371
if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
465
// Set temporary attributes if `{wait: true}`.
466
if (attrs && options.wait) {
467
this.attributes = _.extend({}, attributes, attrs);
375
470
// After a successful server-side save, the client is (optionally)
376
471
// updated with the server-side state.
472
if (options.parse === void 0) options.parse = true;
377
473
var model = this;
378
474
var success = options.success;
379
options.success = function(resp, status, xhr) {
380
var serverAttrs = model.parse(resp, xhr);
383
serverAttrs = _.extend(attrs || {}, serverAttrs);
385
if (!model.set(serverAttrs, options)) return false;
387
success(model, resp);
389
model.trigger('sync', model, resp, options);
475
options.success = function(resp) {
476
// Ensure attributes are restored during synchronous saves.
477
model.attributes = attributes;
478
var serverAttrs = model.parse(resp, options);
479
if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
480
if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
483
if (success) success(model, resp, options);
484
model.trigger('sync', model, resp, options);
393
// Finish configuring and sending the Ajax request.
394
options.error = Backbone.wrapError(options.error, model, options);
395
var method = this.isNew() ? 'create' : 'update';
396
var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
397
if (options.wait) this.set(current, silentOptions);
486
wrapError(this, options);
488
method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
489
if (method === 'patch') options.attrs = attrs;
490
xhr = this.sync(method, this, options);
492
// Restore attributes.
493
if (attrs && options.wait) this.attributes = attributes;
455
549
return this.id == null;
458
// Call this method to manually fire a `"change"` event for this model and
459
// a `"change:attribute"` event for each changed attribute.
460
// Calling this will cause all objects observing the model to update.
461
change: function(options) {
462
options || (options = {});
463
var changing = this._changing;
464
this._changing = true;
466
// Silent changes become pending changes.
467
for (var attr in this._silent) this._pending[attr] = true;
469
// Silent changes are triggered.
470
var changes = _.extend({}, options.changes, this._silent);
472
for (var attr in changes) {
473
this.trigger('change:' + attr, this, this.get(attr), options);
475
if (changing) return this;
477
// Continue firing `"change"` events while there are pending changes.
478
while (!_.isEmpty(this._pending)) {
480
this.trigger('change', this, options);
481
// Pending and silent changes still remain.
482
for (var attr in this.changed) {
483
if (this._pending[attr] || this._silent[attr]) continue;
484
delete this.changed[attr];
486
this._previousAttributes = _.clone(this.attributes);
489
this._changing = false;
493
// Determine if the model has changed since the last `"change"` event.
494
// If you specify an attribute name, determine if that attribute has changed.
495
hasChanged: function(attr) {
496
if (!arguments.length) return !_.isEmpty(this.changed);
497
return _.has(this.changed, attr);
500
// Return an object containing all the attributes that have changed, or
501
// false if there are no changed attributes. Useful for determining what
502
// parts of a view need to be updated and/or what attributes need to be
503
// persisted to the server. Unset attributes will be set to undefined.
504
// You can also pass an attributes object to diff against the model,
505
// determining if there *would be* a change.
506
changedAttributes: function(diff) {
507
if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
508
var val, changed = false, old = this._previousAttributes;
509
for (var attr in diff) {
510
if (_.isEqual(old[attr], (val = diff[attr]))) continue;
511
(changed || (changed = {}))[attr] = val;
516
// Get the previous value of an attribute, recorded at the time the last
517
// `"change"` event was fired.
518
previous: function(attr) {
519
if (!arguments.length || !this._previousAttributes) return null;
520
return this._previousAttributes[attr];
523
// Get all of the attributes of the model at the time of the previous
525
previousAttributes: function() {
526
return _.clone(this._previousAttributes);
529
// Check if the model is currently in a valid state. It's only possible to
530
// get into an *invalid* state if you're using silent changes.
531
isValid: function() {
532
return !this.validate(this.attributes);
552
// Check if the model is currently in a valid state.
553
isValid: function(options) {
554
return this._validate({}, _.extend(options || {}, { validate: true }));
535
557
// Run validation against the next complete set of model attributes,
536
// returning `true` if all is well. If a specific `error` callback has
537
// been passed, call that instead of firing the general `"error"` event.
558
// returning `true` if all is well. Otherwise, fire an `"invalid"` event.
538
559
_validate: function(attrs, options) {
539
if (options.silent || !this.validate) return true;
560
if (!options.validate || !this.validate) return true;
540
561
attrs = _.extend({}, this.attributes, attrs);
541
var error = this.validate(attrs, options);
562
var error = this.validationError = this.validate(attrs, options) || null;
542
563
if (!error) return true;
543
if (options && options.error) {
544
options.error(this, error, options);
546
this.trigger('error', this, error, options);
564
this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
570
// Underscore methods that we want to implement on the Model.
571
var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit'];
573
// Mix in each Underscore method as a proxy to `Model#attributes`.
574
_.each(modelMethods, function(method) {
575
Model.prototype[method] = function() {
576
var args = slice.call(arguments);
577
args.unshift(this.attributes);
578
return _[method].apply(_, args);
553
582
// Backbone.Collection
554
583
// -------------------
556
// Provides a standard collection class for our sets of models, ordered
557
// or unordered. If a `comparator` is specified, the Collection will maintain
585
// If models tend to represent a single row of data, a Backbone Collection is
586
// more analagous to a table full of data ... or a small slice or page of that
587
// table, or a collection of rows that belong together for a particular reason
588
// -- all of the messages in this particular folder, all of the documents
589
// belonging to this particular author, and so on. Collections maintain
590
// indexes of their models, both in order, and for lookup by `id`.
592
// Create a new **Collection**, perhaps to contain a specific type of `model`.
593
// If a `comparator` is specified, the Collection will maintain
558
594
// its models in sort order, as they're added and removed.
559
595
var Collection = Backbone.Collection = function(models, options) {
560
596
options || (options = {});
561
597
if (options.model) this.model = options.model;
562
if (options.comparator) this.comparator = options.comparator;
598
if (options.comparator !== void 0) this.comparator = options.comparator;
564
600
this.initialize.apply(this, arguments);
565
if (models) this.reset(models, {silent: true, parse: options.parse});
601
if (models) this.reset(models, _.extend({silent: true}, options));
604
// Default options for `Collection#set`.
605
var setOptions = {add: true, remove: true, merge: true};
606
var addOptions = {add: true, remove: false};
568
608
// Define the Collection's inheritable methods.
569
609
_.extend(Collection.prototype, Events, {
582
622
return this.map(function(model){ return model.toJSON(options); });
585
// Add a model, or list of models to the set. Pass **silent** to avoid
586
// firing the `add` event for every new model.
625
// Proxy `Backbone.sync` by default.
627
return Backbone.sync.apply(this, arguments);
630
// Add a model, or list of models to the set.
587
631
add: function(models, options) {
588
var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
589
options || (options = {});
590
models = _.isArray(models) ? models.slice() : [models];
592
// Begin by turning bare objects into model references, and preventing
593
// invalid models or duplicate models from being added.
594
for (i = 0, length = models.length; i < length; i++) {
595
if (!(model = models[i] = this._prepareModel(models[i], options))) {
596
throw new Error("Can't add an invalid model to a collection");
600
if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
604
cids[cid] = ids[id] = model;
607
// Remove duplicates.
610
models.splice(dups[i], 1);
613
// Listen to added models' events, and index models for lookup by
614
// `id` and by `cid`.
615
for (i = 0, length = models.length; i < length; i++) {
616
(model = models[i]).on('all', this._onModelEvent, this);
617
this._byCid[model.cid] = model;
618
if (model.id != null) this._byId[model.id] = model;
621
// Insert models into the collection, re-sorting if needed, and triggering
622
// `add` events unless silenced.
623
this.length += length;
624
index = options.at != null ? options.at : this.models.length;
625
splice.apply(this.models, [index, 0].concat(models));
626
if (this.comparator) this.sort({silent: true});
627
if (options.silent) return this;
628
for (i = 0, length = this.models.length; i < length; i++) {
629
if (!cids[(model = this.models[i]).cid]) continue;
631
model.trigger('add', model, this, options);
632
return this.set(models, _.extend({merge: false}, options, addOptions));
636
// Remove a model, or a list of models from the set. Pass silent to avoid
637
// firing the `remove` event for every model removed.
635
// Remove a model, or a list of models from the set.
638
636
remove: function(models, options) {
637
var singular = !_.isArray(models);
638
models = singular ? [models] : _.clone(models);
639
options || (options = {});
639
640
var i, l, index, model;
640
options || (options = {});
641
models = _.isArray(models) ? models.slice() : [models];
642
641
for (i = 0, l = models.length; i < l; i++) {
643
model = this.getByCid(models[i]) || this.get(models[i]);
642
model = models[i] = this.get(models[i]);
644
643
if (!model) continue;
645
644
delete this._byId[model.id];
646
delete this._byCid[model.cid];
645
delete this._byId[model.cid];
647
646
index = this.indexOf(model);
648
647
this.models.splice(index, 1);
654
653
this._removeReference(model);
655
return singular ? models[0] : models;
658
// Update a collection by `set`-ing a new list of models, adding new ones,
659
// removing models that are no longer present, and merging models that
660
// already exist in the collection, as necessary. Similar to **Model#set**,
661
// the core operation for updating the data contained by the collection.
662
set: function(models, options) {
663
options = _.defaults({}, options, setOptions);
664
if (options.parse) models = this.parse(models, options);
665
var singular = !_.isArray(models);
666
models = singular ? (models ? [models] : []) : _.clone(models);
667
var i, l, id, model, attrs, existing, sort;
669
var targetModel = this.model;
670
var sortable = this.comparator && (at == null) && options.sort !== false;
671
var sortAttr = _.isString(this.comparator) ? this.comparator : null;
672
var toAdd = [], toRemove = [], modelMap = {};
673
var add = options.add, merge = options.merge, remove = options.remove;
674
var order = !sortable && add && remove ? [] : false;
676
// Turn bare objects into model references, and prevent invalid models
678
for (i = 0, l = models.length; i < l; i++) {
680
if (attrs instanceof Model) {
683
id = attrs[targetModel.prototype.idAttribute];
686
// If a duplicate is found, prevent it from being added and
687
// optionally merge it into the existing model.
688
if (existing = this.get(id)) {
689
if (remove) modelMap[existing.cid] = true;
691
attrs = attrs === model ? model.attributes : attrs;
692
if (options.parse) attrs = existing.parse(attrs, options);
693
existing.set(attrs, options);
694
if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
696
models[i] = existing;
698
// If this is a new, valid model, push it to the `toAdd` list.
700
model = models[i] = this._prepareModel(attrs, options);
701
if (!model) continue;
704
// Listen to added models' events, and index models for lookup by
705
// `id` and by `cid`.
706
model.on('all', this._onModelEvent, this);
707
this._byId[model.cid] = model;
708
if (model.id != null) this._byId[model.id] = model;
710
if (order) order.push(existing || model);
713
// Remove nonexistent models if appropriate.
715
for (i = 0, l = this.length; i < l; ++i) {
716
if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
718
if (toRemove.length) this.remove(toRemove, options);
721
// See if sorting is needed, update `length` and splice in new models.
722
if (toAdd.length || (order && order.length)) {
723
if (sortable) sort = true;
724
this.length += toAdd.length;
726
for (i = 0, l = toAdd.length; i < l; i++) {
727
this.models.splice(at + i, 0, toAdd[i]);
730
if (order) this.models.length = 0;
731
var orderedModels = order || toAdd;
732
for (i = 0, l = orderedModels.length; i < l; i++) {
733
this.models.push(orderedModels[i]);
738
// Silently sort the collection if appropriate.
739
if (sort) this.sort({silent: true});
741
// Unless silenced, it's time to fire all appropriate add/sort events.
742
if (!options.silent) {
743
for (i = 0, l = toAdd.length; i < l; i++) {
744
(model = toAdd[i]).trigger('add', model, this, options);
746
if (sort || (order && order.length)) this.trigger('sort', this, options);
749
// Return the added (or merged) model (or models).
750
return singular ? models[0] : models;
753
// When you have more items than you want to add or remove individually,
754
// you can reset the entire set with a new list of models, without firing
755
// any granular `add` or `remove` events. Fires `reset` when finished.
756
// Useful for bulk operations and optimizations.
757
reset: function(models, options) {
758
options || (options = {});
759
for (var i = 0, l = this.models.length; i < l; i++) {
760
this._removeReference(this.models[i]);
762
options.previousModels = this.models;
764
models = this.add(models, _.extend({silent: true}, options));
765
if (!options.silent) this.trigger('reset', this, options);
659
769
// Add a model to the end of the collection.
660
770
push: function(model, options) {
661
model = this._prepareModel(model, options);
662
this.add(model, options);
771
return this.add(model, _.extend({at: this.length}, options));
666
774
// Remove a model from the end of the collection.
821
// Return the first model with matching attributes. Useful for simple cases
823
findWhere: function(attrs) {
824
return this.where(attrs, true);
714
827
// Force the collection to re-sort itself. You don't need to call this under
715
828
// normal circumstances, as the set will maintain sort order as each item
717
830
sort: function(options) {
718
options || (options = {});
719
831
if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
720
var boundComparator = _.bind(this.comparator, this);
721
if (this.comparator.length == 1) {
722
this.models = this.sortBy(boundComparator);
832
options || (options = {});
834
// Run sort based on type of `comparator`.
835
if (_.isString(this.comparator) || this.comparator.length === 1) {
836
this.models = this.sortBy(this.comparator, this);
724
this.models.sort(boundComparator);
838
this.models.sort(_.bind(this.comparator, this));
726
if (!options.silent) this.trigger('reset', this, options);
841
if (!options.silent) this.trigger('sort', this, options);
730
845
// Pluck an attribute from each model in the collection.
731
846
pluck: function(attr) {
732
return _.map(this.models, function(model){ return model.get(attr); });
735
// When you have more items than you want to add or remove individually,
736
// you can reset the entire set with a new list of models, without firing
737
// any `add` or `remove` events. Fires `reset` when finished.
738
reset: function(models, options) {
739
models || (models = []);
740
options || (options = {});
741
for (var i = 0, l = this.models.length; i < l; i++) {
742
this._removeReference(this.models[i]);
745
this.add(models, _.extend({silent: true}, options));
746
if (!options.silent) this.trigger('reset', this, options);
847
return _.invoke(this.models, 'get', attr);
750
850
// Fetch the default set of models for this collection, resetting the
751
// collection when they arrive. If `add: true` is passed, appends the
752
// models to the collection instead of resetting.
851
// collection when they arrive. If `reset: true` is passed, the response
852
// data will be passed through the `reset` method instead of `set`.
753
853
fetch: function(options) {
754
854
options = options ? _.clone(options) : {};
755
if (options.parse === undefined) options.parse = true;
855
if (options.parse === void 0) options.parse = true;
856
var success = options.success;
756
857
var collection = this;
757
var success = options.success;
758
options.success = function(resp, status, xhr) {
759
collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
760
if (success) success(collection, resp);
858
options.success = function(resp) {
859
var method = options.reset ? 'reset' : 'set';
860
collection[method](resp, options);
861
if (success) success(collection, resp, options);
862
collection.trigger('sync', collection, resp, options);
762
options.error = Backbone.wrapError(options.error, collection, options);
763
return (this.sync || Backbone.sync).call(this, 'read', this, options);
864
wrapError(this, options);
865
return this.sync('read', this, options);
766
868
// Create a new instance of a model in this collection. Add the model to the
767
869
// collection immediately, unless `wait: true` is passed, in which case we
768
870
// wait for the server to agree.
769
871
create: function(model, options) {
771
872
options = options ? _.clone(options) : {};
772
model = this._prepareModel(model, options);
773
if (!model) return false;
774
if (!options.wait) coll.add(model, options);
873
if (!(model = this._prepareModel(model, options))) return false;
874
if (!options.wait) this.add(model, options);
875
var collection = this;
775
876
var success = options.success;
776
options.success = function(nextModel, resp, xhr) {
777
if (options.wait) coll.add(nextModel, options);
779
success(nextModel, resp);
781
nextModel.trigger('sync', model, resp, options);
877
options.success = function(model, resp, options) {
878
if (options.wait) collection.add(model, options);
879
if (success) success(model, resp, options);
784
881
model.save(null, options);
849
941
// Underscore methods that we want to implement on the Collection.
850
var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
851
'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
852
'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
853
'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
854
'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
942
// 90% of the core usefulness of Backbone Collections is actually implemented
944
var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
945
'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
946
'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
947
'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
948
'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle',
949
'lastIndexOf', 'isEmpty', 'chain'];
856
951
// Mix in each Underscore method as a proxy to `Collection#models`.
857
952
_.each(methods, function(method) {
858
953
Collection.prototype[method] = function() {
859
return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
954
var args = slice.call(arguments);
955
args.unshift(this.models);
956
return _[method].apply(_, args);
960
// Underscore methods that take a property name as an argument.
961
var attributeMethods = ['groupBy', 'countBy', 'sortBy'];
963
// Use attributes instead of properties.
964
_.each(attributeMethods, function(method) {
965
Collection.prototype[method] = function(value, context) {
966
var iterator = _.isFunction(value) ? value : function(model) {
967
return model.get(value);
969
return _[method](this.models, iterator, context);
976
// Backbone Views are almost more convention than they are actual code. A View
977
// is simply a JavaScript object that represents a logical chunk of UI in the
978
// DOM. This might be a single item, an entire list, a sidebar or panel, or
979
// even the surrounding frame which wraps your whole app. Defining a chunk of
980
// UI as a **View** allows you to define your DOM events declaratively, without
981
// having to worry about render order ... and makes it easy for the view to
982
// react to specific changes in the state of your models.
984
// Creating a Backbone.View creates its initial element outside of the DOM,
985
// if an existing element is not provided...
986
var View = Backbone.View = function(options) {
987
this.cid = _.uniqueId('view');
988
options || (options = {});
989
_.extend(this, _.pick(options, viewOptions));
990
this._ensureElement();
991
this.initialize.apply(this, arguments);
992
this.delegateEvents();
995
// Cached regex to split keys for `delegate`.
996
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
998
// List of view options to be merged as properties.
999
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
1001
// Set up all inheritable **Backbone.View** properties and methods.
1002
_.extend(View.prototype, Events, {
1004
// The default `tagName` of a View's element is `"div"`.
1007
// jQuery delegate for element lookup, scoped to DOM elements within the
1008
// current view. This should be preferred to global lookups where possible.
1009
$: function(selector) {
1010
return this.$el.find(selector);
1013
// Initialize is an empty function by default. Override it with your own
1014
// initialization logic.
1015
initialize: function(){},
1017
// **render** is the core function that your view should override, in order
1018
// to populate its element (`this.el`), with the appropriate HTML. The
1019
// convention is for **render** to always return `this`.
1020
render: function() {
1024
// Remove this view by taking the element out of the DOM, and removing any
1025
// applicable Backbone.Events listeners.
1026
remove: function() {
1028
this.stopListening();
1032
// Change the view's element (`this.el` property), including event
1034
setElement: function(element, delegate) {
1035
if (this.$el) this.undelegateEvents();
1036
this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
1037
this.el = this.$el[0];
1038
if (delegate !== false) this.delegateEvents();
1042
// Set callbacks, where `this.events` is a hash of
1044
// *{"event selector": "callback"}*
1047
// 'mousedown .title': 'edit',
1048
// 'click .button': 'save',
1049
// 'click .open': function(e) { ... }
1052
// pairs. Callbacks will be bound to the view, with `this` set properly.
1053
// Uses event delegation for efficiency.
1054
// Omitting the selector binds the event to `this.el`.
1055
// This only works for delegate-able events: not `focus`, `blur`, and
1056
// not `change`, `submit`, and `reset` in Internet Explorer.
1057
delegateEvents: function(events) {
1058
if (!(events || (events = _.result(this, 'events')))) return this;
1059
this.undelegateEvents();
1060
for (var key in events) {
1061
var method = events[key];
1062
if (!_.isFunction(method)) method = this[events[key]];
1063
if (!method) continue;
1065
var match = key.match(delegateEventSplitter);
1066
var eventName = match[1], selector = match[2];
1067
method = _.bind(method, this);
1068
eventName += '.delegateEvents' + this.cid;
1069
if (selector === '') {
1070
this.$el.on(eventName, method);
1072
this.$el.on(eventName, selector, method);
1078
// Clears all callbacks previously bound to the view with `delegateEvents`.
1079
// You usually don't need to use this, but may wish to if you have multiple
1080
// Backbone views attached to the same DOM element.
1081
undelegateEvents: function() {
1082
this.$el.off('.delegateEvents' + this.cid);
1086
// Ensure that the View has a DOM element to render into.
1087
// If `this.el` is a string, pass it through `$()`, take the first
1088
// matching element, and re-assign it to `el`. Otherwise, create
1089
// an element from the `id`, `className` and `tagName` properties.
1090
_ensureElement: function() {
1092
var attrs = _.extend({}, _.result(this, 'attributes'));
1093
if (this.id) attrs.id = _.result(this, 'id');
1094
if (this.className) attrs['class'] = _.result(this, 'className');
1095
var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
1096
this.setElement($el, false);
1098
this.setElement(_.result(this, 'el'), false);
1107
// Override this function to change the manner in which Backbone persists
1108
// models to the server. You will be passed the type of request, and the
1109
// model in question. By default, makes a RESTful Ajax request
1110
// to the model's `url()`. Some possible customizations could be:
1112
// * Use `setTimeout` to batch rapid-fire updates into a single request.
1113
// * Send up the models as XML instead of JSON.
1114
// * Persist models via WebSockets instead of Ajax.
1116
// Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
1117
// as `POST`, with a `_method` parameter containing the true HTTP method,
1118
// as well as all requests with the body as `application/x-www-form-urlencoded`
1119
// instead of `application/json` with the model in a param named `model`.
1120
// Useful when interfacing with server-side languages like **PHP** that make
1121
// it difficult to read the body of `PUT` requests.
1122
Backbone.sync = function(method, model, options) {
1123
var type = methodMap[method];
1125
// Default options, unless specified.
1126
_.defaults(options || (options = {}), {
1127
emulateHTTP: Backbone.emulateHTTP,
1128
emulateJSON: Backbone.emulateJSON
1131
// Default JSON-request options.
1132
var params = {type: type, dataType: 'json'};
1134
// Ensure that we have a URL.
1136
params.url = _.result(model, 'url') || urlError();
1139
// Ensure that we have the appropriate request data.
1140
if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
1141
params.contentType = 'application/json';
1142
params.data = JSON.stringify(options.attrs || model.toJSON(options));
1145
// For older servers, emulate JSON by encoding the request into an HTML-form.
1146
if (options.emulateJSON) {
1147
params.contentType = 'application/x-www-form-urlencoded';
1148
params.data = params.data ? {model: params.data} : {};
1151
// For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1152
// And an `X-HTTP-Method-Override` header.
1153
if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
1154
params.type = 'POST';
1155
if (options.emulateJSON) params.data._method = type;
1156
var beforeSend = options.beforeSend;
1157
options.beforeSend = function(xhr) {
1158
xhr.setRequestHeader('X-HTTP-Method-Override', type);
1159
if (beforeSend) return beforeSend.apply(this, arguments);
1163
// Don't process data on a non-GET request.
1164
if (params.type !== 'GET' && !options.emulateJSON) {
1165
params.processData = false;
1168
// If we're sending a `PATCH` request, and we're in an old Internet Explorer
1169
// that still has ActiveX enabled by default, override jQuery to use that
1170
// for XHR instead. Remove this line when jQuery supports `PATCH` on IE8.
1171
if (params.type === 'PATCH' && noXhrPatch) {
1172
params.xhr = function() {
1173
return new ActiveXObject("Microsoft.XMLHTTP");
1177
// Make the request, allowing the user to override any Ajax options.
1178
var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
1179
model.trigger('request', model, xhr, options);
1183
var noXhrPatch = typeof window !== 'undefined' && !!window.ActiveXObject && !(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent);
1185
// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1194
// Set the default implementation of `Backbone.ajax` to proxy through to `$`.
1195
// Override this if you'd like to use a different library.
1196
Backbone.ajax = function() {
1197
return Backbone.$.ajax.apply(Backbone.$, arguments);
863
1200
// Backbone.Router
864
// -------------------
866
1203
// Routers map faux-URLs to actions, and fire events when routes are
867
1204
// matched. Creating a new one sets its `routes` hash, if not set statically.
1094
1468
// you wish to modify the current URL without adding an entry to the history.
1095
1469
navigate: function(fragment, options) {
1096
1470
if (!History.started) return false;
1097
if (!options || options === true) options = {trigger: options};
1098
var frag = (fragment || '').replace(routeStripper, '');
1099
if (this.fragment == frag) return;
1471
if (!options || options === true) options = {trigger: !!options};
1473
var url = this.root + (fragment = this.getFragment(fragment || ''));
1475
// Strip the fragment of the query and hash for matching.
1476
fragment = fragment.replace(pathStripper, '');
1478
if (this.fragment === fragment) return;
1479
this.fragment = fragment;
1481
// Don't include a trailing slash on the root.
1482
if (fragment === '' && url !== '/') url = url.slice(0, -1);
1101
1484
// If pushState is available, we use it to set the fragment as a real URL.
1102
1485
if (this._hasPushState) {
1103
if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
1104
this.fragment = frag;
1105
window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag);
1486
this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
1107
1488
// If hash changes haven't been explicitly disabled, update the hash
1108
1489
// fragment to store history.
1109
1490
} else if (this._wantsHashChange) {
1110
this.fragment = frag;
1111
this._updateHash(window.location, frag, options.replace);
1112
if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
1113
// Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
1114
// When replace is true, we don't want this.
1491
this._updateHash(this.location, fragment, options.replace);
1492
if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) {
1493
// Opening and closing the iframe tricks IE7 and earlier to push a
1494
// history entry on hash-tag change. When replace is true, we don't
1115
1496
if(!options.replace) this.iframe.document.open().close();
1116
this._updateHash(this.iframe.location, frag, options.replace);
1497
this._updateHash(this.iframe.location, fragment, options.replace);
1119
1500
// If you've told us that you explicitly don't want fallback hashchange-
1120
1501
// based history, then `navigate` becomes a page refresh.
1122
window.location.assign(this.options.root + fragment);
1503
return this.location.assign(url);
1124
if (options.trigger) this.loadUrl(fragment);
1505
if (options.trigger) return this.loadUrl(fragment);
1127
1508
// Update the hash location, either replacing the current entry, or adding
1128
1509
// a new one to the browser history.
1129
1510
_updateHash: function(location, fragment, replace) {
1131
location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment);
1133
location.hash = fragment;
1141
// Creating a Backbone.View creates its initial element outside of the DOM,
1142
// if an existing element is not provided...
1143
var View = Backbone.View = function(options) {
1144
this.cid = _.uniqueId('view');
1145
this._configure(options || {});
1146
this._ensureElement();
1147
this.initialize.apply(this, arguments);
1148
this.delegateEvents();
1151
// Cached regex to split keys for `delegate`.
1152
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
1154
// List of view options to be merged as properties.
1155
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
1157
// Set up all inheritable **Backbone.View** properties and methods.
1158
_.extend(View.prototype, Events, {
1160
// The default `tagName` of a View's element is `"div"`.
1163
// jQuery delegate for element lookup, scoped to DOM elements within the
1164
// current view. This should be prefered to global lookups where possible.
1165
$: function(selector) {
1166
return this.$el.find(selector);
1169
// Initialize is an empty function by default. Override it with your own
1170
// initialization logic.
1171
initialize: function(){},
1173
// **render** is the core function that your view should override, in order
1174
// to populate its element (`this.el`), with the appropriate HTML. The
1175
// convention is for **render** to always return `this`.
1176
render: function() {
1180
// Remove this view from the DOM. Note that the view isn't present in the
1181
// DOM by default, so calling this method may be a no-op.
1182
remove: function() {
1187
// For small amounts of DOM Elements, where a full-blown template isn't
1188
// needed, use **make** to manufacture elements, one at a time.
1190
// var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
1192
make: function(tagName, attributes, content) {
1193
var el = document.createElement(tagName);
1194
if (attributes) $(el).attr(attributes);
1195
if (content) $(el).html(content);
1199
// Change the view's element (`this.el` property), including event
1201
setElement: function(element, delegate) {
1202
if (this.$el) this.undelegateEvents();
1203
this.$el = (element instanceof $) ? element : $(element);
1204
this.el = this.$el[0];
1205
if (delegate !== false) this.delegateEvents();
1209
// Set callbacks, where `this.events` is a hash of
1211
// *{"event selector": "callback"}*
1214
// 'mousedown .title': 'edit',
1215
// 'click .button': 'save'
1216
// 'click .open': function(e) { ... }
1219
// pairs. Callbacks will be bound to the view, with `this` set properly.
1220
// Uses event delegation for efficiency.
1221
// Omitting the selector binds the event to `this.el`.
1222
// This only works for delegate-able events: not `focus`, `blur`, and
1223
// not `change`, `submit`, and `reset` in Internet Explorer.
1224
delegateEvents: function(events) {
1225
if (!(events || (events = getValue(this, 'events')))) return;
1226
this.undelegateEvents();
1227
for (var key in events) {
1228
var method = events[key];
1229
if (!_.isFunction(method)) method = this[events[key]];
1230
if (!method) throw new Error('Method "' + events[key] + '" does not exist');
1231
var match = key.match(delegateEventSplitter);
1232
var eventName = match[1], selector = match[2];
1233
method = _.bind(method, this);
1234
eventName += '.delegateEvents' + this.cid;
1235
if (selector === '') {
1236
this.$el.bind(eventName, method);
1238
this.$el.delegate(selector, eventName, method);
1243
// Clears all callbacks previously bound to the view with `delegateEvents`.
1244
// You usually don't need to use this, but may wish to if you have multiple
1245
// Backbone views attached to the same DOM element.
1246
undelegateEvents: function() {
1247
this.$el.unbind('.delegateEvents' + this.cid);
1250
// Performs the initial configuration of a View with a set of options.
1251
// Keys with special meaning *(model, collection, id, className)*, are
1252
// attached directly to the view.
1253
_configure: function(options) {
1254
if (this.options) options = _.extend({}, this.options, options);
1255
for (var i = 0, l = viewOptions.length; i < l; i++) {
1256
var attr = viewOptions[i];
1257
if (options[attr]) this[attr] = options[attr];
1259
this.options = options;
1262
// Ensure that the View has a DOM element to render into.
1263
// If `this.el` is a string, pass it through `$()`, take the first
1264
// matching element, and re-assign it to `el`. Otherwise, create
1265
// an element from the `id`, `className` and `tagName` properties.
1266
_ensureElement: function() {
1268
var attrs = getValue(this, 'attributes') || {};
1269
if (this.id) attrs.id = this.id;
1270
if (this.className) attrs['class'] = this.className;
1271
this.setElement(this.make(this.tagName, attrs), false);
1273
this.setElement(this.el, false);
1279
// The self-propagating extend function that Backbone classes use.
1280
var extend = function (protoProps, classProps) {
1281
var child = inherits(this, protoProps, classProps);
1282
child.extend = this.extend;
1286
// Set up inheritance for the model, collection, and view.
1287
Model.extend = Collection.extend = Router.extend = View.extend = extend;
1292
// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1300
// Override this function to change the manner in which Backbone persists
1301
// models to the server. You will be passed the type of request, and the
1302
// model in question. By default, makes a RESTful Ajax request
1303
// to the model's `url()`. Some possible customizations could be:
1305
// * Use `setTimeout` to batch rapid-fire updates into a single request.
1306
// * Send up the models as XML instead of JSON.
1307
// * Persist models via WebSockets instead of Ajax.
1309
// Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
1310
// as `POST`, with a `_method` parameter containing the true HTTP method,
1311
// as well as all requests with the body as `application/x-www-form-urlencoded`
1312
// instead of `application/json` with the model in a param named `model`.
1313
// Useful when interfacing with server-side languages like **PHP** that make
1314
// it difficult to read the body of `PUT` requests.
1315
Backbone.sync = function(method, model, options) {
1316
var type = methodMap[method];
1318
// Default options, unless specified.
1319
options || (options = {});
1321
// Default JSON-request options.
1322
var params = {type: type, dataType: 'json'};
1324
// Ensure that we have a URL.
1326
params.url = getValue(model, 'url') || urlError();
1329
// Ensure that we have the appropriate request data.
1330
if (!options.data && model && (method == 'create' || method == 'update')) {
1331
params.contentType = 'application/json';
1332
params.data = JSON.stringify(model.toJSON());
1335
// For older servers, emulate JSON by encoding the request into an HTML-form.
1336
if (Backbone.emulateJSON) {
1337
params.contentType = 'application/x-www-form-urlencoded';
1338
params.data = params.data ? {model: params.data} : {};
1341
// For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1342
// And an `X-HTTP-Method-Override` header.
1343
if (Backbone.emulateHTTP) {
1344
if (type === 'PUT' || type === 'DELETE') {
1345
if (Backbone.emulateJSON) params.data._method = type;
1346
params.type = 'POST';
1347
params.beforeSend = function(xhr) {
1348
xhr.setRequestHeader('X-HTTP-Method-Override', type);
1353
// Don't process data on a non-GET request.
1354
if (params.type !== 'GET' && !Backbone.emulateJSON) {
1355
params.processData = false;
1358
// Make the request, allowing the user to override any Ajax options.
1359
return $.ajax(_.extend(params, options));
1362
// Wrap an optional error callback with a fallback error event.
1363
Backbone.wrapError = function(onError, originalModel, options) {
1364
return function(model, resp) {
1365
resp = model === originalModel ? resp : model;
1367
onError(originalModel, resp, options);
1369
originalModel.trigger('error', originalModel, resp, options);
1512
var href = location.href.replace(/(javascript:|#).*$/, '');
1513
location.replace(href + '#' + fragment);
1515
// Some browsers require that `hash` contains a leading #.
1516
location.hash = '#' + fragment;
1522
// Create the default Backbone.history.
1523
Backbone.history = new History;
1377
// Shared empty constructor function to aid in prototype-chain creation.
1378
var ctor = function(){};
1380
1528
// Helper function to correctly set up the prototype chain, for subclasses.
1381
1529
// Similar to `goog.inherits`, but uses a hash of prototype properties and
1382
1530
// class properties to be extended.
1383
var inherits = function(parent, protoProps, staticProps) {
1531
var extend = function(protoProps, staticProps) {
1386
1535
// The constructor function for the new subclass is either defined by you
1387
1536
// (the "constructor" property in your `extend` definition), or defaulted
1388
1537
// by us to simply call the parent's constructor.
1389
if (protoProps && protoProps.hasOwnProperty('constructor')) {
1538
if (protoProps && _.has(protoProps, 'constructor')) {
1390
1539
child = protoProps.constructor;
1392
child = function(){ parent.apply(this, arguments); };
1541
child = function(){ return parent.apply(this, arguments); };
1395
// Inherit class (static) properties from parent.
1396
_.extend(child, parent);
1544
// Add static properties to the constructor function, if supplied.
1545
_.extend(child, parent, staticProps);
1398
1547
// Set the prototype chain to inherit from `parent`, without calling
1399
1548
// `parent`'s constructor function.
1400
ctor.prototype = parent.prototype;
1401
child.prototype = new ctor();
1549
var Surrogate = function(){ this.constructor = child; };
1550
Surrogate.prototype = parent.prototype;
1551
child.prototype = new Surrogate;
1403
1553
// Add prototype properties (instance properties) to the subclass,
1404
1554
// if supplied.
1405
1555
if (protoProps) _.extend(child.prototype, protoProps);
1407
// Add static properties to the constructor function, if supplied.
1408
if (staticProps) _.extend(child, staticProps);
1410
// Correctly set child's `prototype.constructor`.
1411
child.prototype.constructor = child;
1413
// Set a convenience property in case the parent's prototype is needed later.
1557
// Set a convenience property in case the parent's prototype is needed
1414
1559
child.__super__ = parent.prototype;
1419
// Helper function to get a value from a Backbone object as a property
1420
// or as a function.
1421
var getValue = function(object, prop) {
1422
if (!(object && object[prop])) return null;
1423
return _.isFunction(object[prop]) ? object[prop]() : object[prop];
1564
// Set up inheritance for the model, collection, router, view and history.
1565
Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
1426
1567
// Throw an error when a URL is needed, and none is supplied.
1427
1568
var urlError = function() {
1428
1569
throw new Error('A "url" property or function must be specified');
1572
// Wrap an optional error callback with a fallback error event.
1573
var wrapError = function(model, options) {
1574
var error = options.error;
1575
options.error = function(resp) {
1576
if (error) error(model, resp, options);
1577
model.trigger('error', model, resp, options);