backbone-rails 0.9.10 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
- // Backbone.js 0.9.10
1
+ // Backbone.js 1.0.0
2
2
 
3
- // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
3
+ // (c) 2010-2013 Jeremy Ashkenas, DocumentCloud Inc.
4
4
  // Backbone may be freely distributed under the MIT license.
5
5
  // For all details and documentation:
6
6
  // http://backbonejs.org
@@ -18,14 +18,14 @@
18
18
  // restored later on, if `noConflict` is used.
19
19
  var previousBackbone = root.Backbone;
20
20
 
21
- // Create a local reference to array methods.
21
+ // Create local references to array methods we'll want to use later.
22
22
  var array = [];
23
23
  var push = array.push;
24
24
  var slice = array.slice;
25
25
  var splice = array.splice;
26
26
 
27
27
  // The top-level namespace. All public Backbone classes and modules will
28
- // be attached to this. Exported for both CommonJS and the browser.
28
+ // be attached to this. Exported for both the browser and the server.
29
29
  var Backbone;
30
30
  if (typeof exports !== 'undefined') {
31
31
  Backbone = exports;
@@ -34,14 +34,15 @@
34
34
  }
35
35
 
36
36
  // Current version of the library. Keep in sync with `package.json`.
37
- Backbone.VERSION = '0.9.10';
37
+ Backbone.VERSION = '1.0.0';
38
38
 
39
39
  // Require Underscore, if we're on the server, and it's not already present.
40
40
  var _ = root._;
41
41
  if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
42
42
 
43
- // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
44
- Backbone.$ = root.jQuery || root.Zepto || root.ender;
43
+ // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
44
+ // the `$` variable.
45
+ Backbone.$ = root.jQuery || root.Zepto || root.ender || root.$;
45
46
 
46
47
  // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
47
48
  // to its previous owner. Returns a reference to this Backbone object.
@@ -64,45 +65,6 @@
64
65
  // Backbone.Events
65
66
  // ---------------
66
67
 
67
- // Regular expression used to split event strings.
68
- var eventSplitter = /\s+/;
69
-
70
- // Implement fancy features of the Events API such as multiple event
71
- // names `"change blur"` and jQuery-style event maps `{change: action}`
72
- // in terms of the existing API.
73
- var eventsApi = function(obj, action, name, rest) {
74
- if (!name) return true;
75
- if (typeof name === 'object') {
76
- for (var key in name) {
77
- obj[action].apply(obj, [key, name[key]].concat(rest));
78
- }
79
- } else if (eventSplitter.test(name)) {
80
- var names = name.split(eventSplitter);
81
- for (var i = 0, l = names.length; i < l; i++) {
82
- obj[action].apply(obj, [names[i]].concat(rest));
83
- }
84
- } else {
85
- return true;
86
- }
87
- };
88
-
89
- // Optimized internal dispatch function for triggering events. Tries to
90
- // keep the usual cases speedy (most Backbone events have 3 arguments).
91
- var triggerEvents = function(events, args) {
92
- var ev, i = -1, l = events.length;
93
- switch (args.length) {
94
- case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx);
95
- return;
96
- case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0]);
97
- return;
98
- case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0], args[1]);
99
- return;
100
- case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0], args[1], args[2]);
101
- return;
102
- default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
103
- }
104
- };
105
-
106
68
  // A module that can be mixed in to *any object* in order to provide it with
107
69
  // custom events. You may bind with `on` or remove with `off` callback
108
70
  // functions to an event; `trigger`-ing an event fires all callbacks in
@@ -115,29 +77,27 @@
115
77
  //
116
78
  var Events = Backbone.Events = {
117
79
 
118
- // Bind one or more space separated events, or an events map,
119
- // to a `callback` function. Passing `"all"` will bind the callback to
120
- // all events fired.
80
+ // Bind an event to a `callback` function. Passing `"all"` will bind
81
+ // the callback to all events fired.
121
82
  on: function(name, callback, context) {
122
- if (!(eventsApi(this, 'on', name, [callback, context]) && callback)) return this;
83
+ if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
123
84
  this._events || (this._events = {});
124
- var list = this._events[name] || (this._events[name] = []);
125
- list.push({callback: callback, context: context, ctx: context || this});
85
+ var events = this._events[name] || (this._events[name] = []);
86
+ events.push({callback: callback, context: context, ctx: context || this});
126
87
  return this;
127
88
  },
128
89
 
129
- // Bind events to only be triggered a single time. After the first time
90
+ // Bind an event to only be triggered a single time. After the first time
130
91
  // the callback is invoked, it will be removed.
131
92
  once: function(name, callback, context) {
132
- if (!(eventsApi(this, 'once', name, [callback, context]) && callback)) return this;
93
+ if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
133
94
  var self = this;
134
95
  var once = _.once(function() {
135
96
  self.off(name, once);
136
97
  callback.apply(this, arguments);
137
98
  });
138
99
  once._callback = callback;
139
- this.on(name, once, context);
140
- return this;
100
+ return this.on(name, once, context);
141
101
  },
142
102
 
143
103
  // Remove one or many callbacks. If `context` is null, removes all
@@ -145,7 +105,7 @@
145
105
  // callbacks for the event. If `name` is null, removes all bound
146
106
  // callbacks for all events.
147
107
  off: function(name, callback, context) {
148
- var list, ev, events, names, i, l, j, k;
108
+ var retain, ev, events, names, i, l, j, k;
149
109
  if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
150
110
  if (!name && !callback && !context) {
151
111
  this._events = {};
@@ -155,19 +115,18 @@
155
115
  names = name ? [name] : _.keys(this._events);
156
116
  for (i = 0, l = names.length; i < l; i++) {
157
117
  name = names[i];
158
- if (list = this._events[name]) {
159
- events = [];
118
+ if (events = this._events[name]) {
119
+ this._events[name] = retain = [];
160
120
  if (callback || context) {
161
- for (j = 0, k = list.length; j < k; j++) {
162
- ev = list[j];
163
- if ((callback && callback !== ev.callback &&
164
- callback !== ev.callback._callback) ||
121
+ for (j = 0, k = events.length; j < k; j++) {
122
+ ev = events[j];
123
+ if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
165
124
  (context && context !== ev.context)) {
166
- events.push(ev);
125
+ retain.push(ev);
167
126
  }
168
127
  }
169
128
  }
170
- this._events[name] = events;
129
+ if (!retain.length) delete this._events[name];
171
130
  }
172
131
  }
173
132
 
@@ -189,35 +148,82 @@
189
148
  return this;
190
149
  },
191
150
 
192
- // An inversion-of-control version of `on`. Tell *this* object to listen to
193
- // an event in another object ... keeping track of what it's listening to.
194
- listenTo: function(obj, name, callback) {
195
- var listeners = this._listeners || (this._listeners = {});
196
- var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
197
- listeners[id] = obj;
198
- obj.on(name, typeof name === 'object' ? this : callback, this);
199
- return this;
200
- },
201
-
202
151
  // Tell this object to stop listening to either specific events ... or
203
152
  // to every object it's currently listening to.
204
153
  stopListening: function(obj, name, callback) {
205
154
  var listeners = this._listeners;
206
- if (!listeners) return;
207
- if (obj) {
208
- obj.off(name, typeof name === 'object' ? this : callback, this);
209
- if (!name && !callback) delete listeners[obj._listenerId];
210
- } else {
211
- if (typeof name === 'object') callback = this;
212
- for (var id in listeners) {
213
- listeners[id].off(name, callback, this);
214
- }
215
- this._listeners = {};
155
+ if (!listeners) return this;
156
+ var deleteListener = !name && !callback;
157
+ if (typeof name === 'object') callback = this;
158
+ if (obj) (listeners = {})[obj._listenerId] = obj;
159
+ for (var id in listeners) {
160
+ listeners[id].off(name, callback, this);
161
+ if (deleteListener) delete this._listeners[id];
216
162
  }
217
163
  return this;
218
164
  }
165
+
166
+ };
167
+
168
+ // Regular expression used to split event strings.
169
+ var eventSplitter = /\s+/;
170
+
171
+ // Implement fancy features of the Events API such as multiple event
172
+ // names `"change blur"` and jQuery-style event maps `{change: action}`
173
+ // in terms of the existing API.
174
+ var eventsApi = function(obj, action, name, rest) {
175
+ if (!name) return true;
176
+
177
+ // Handle event maps.
178
+ if (typeof name === 'object') {
179
+ for (var key in name) {
180
+ obj[action].apply(obj, [key, name[key]].concat(rest));
181
+ }
182
+ return false;
183
+ }
184
+
185
+ // Handle space separated event names.
186
+ if (eventSplitter.test(name)) {
187
+ var names = name.split(eventSplitter);
188
+ for (var i = 0, l = names.length; i < l; i++) {
189
+ obj[action].apply(obj, [names[i]].concat(rest));
190
+ }
191
+ return false;
192
+ }
193
+
194
+ return true;
195
+ };
196
+
197
+ // A difficult-to-believe, but optimized internal dispatch function for
198
+ // triggering events. Tries to keep the usual cases speedy (most internal
199
+ // Backbone events have 3 arguments).
200
+ var triggerEvents = function(events, args) {
201
+ var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
202
+ switch (args.length) {
203
+ case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
204
+ case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
205
+ case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
206
+ case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
207
+ default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
208
+ }
219
209
  };
220
210
 
211
+ var listenMethods = {listenTo: 'on', listenToOnce: 'once'};
212
+
213
+ // Inversion-of-control versions of `on` and `once`. Tell *this* object to
214
+ // listen to an event in another object ... keeping track of what it's
215
+ // listening to.
216
+ _.each(listenMethods, function(implementation, method) {
217
+ Events[method] = function(obj, name, callback) {
218
+ var listeners = this._listeners || (this._listeners = {});
219
+ var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
220
+ listeners[id] = obj;
221
+ if (typeof name === 'object') callback = this;
222
+ obj[implementation](name, callback, this);
223
+ return this;
224
+ };
225
+ });
226
+
221
227
  // Aliases for backwards compatibility.
222
228
  Events.bind = Events.on;
223
229
  Events.unbind = Events.off;
@@ -229,15 +235,21 @@
229
235
  // Backbone.Model
230
236
  // --------------
231
237
 
232
- // Create a new model, with defined attributes. A client id (`cid`)
238
+ // Backbone **Models** are the basic data object in the framework --
239
+ // frequently representing a row in a table in a database on your server.
240
+ // A discrete chunk of data and a bunch of useful, related methods for
241
+ // performing computations and transformations on that data.
242
+
243
+ // Create a new model with the specified attributes. A client id (`cid`)
233
244
  // is automatically generated and assigned for you.
234
245
  var Model = Backbone.Model = function(attributes, options) {
235
246
  var defaults;
236
247
  var attrs = attributes || {};
248
+ options || (options = {});
237
249
  this.cid = _.uniqueId('c');
238
250
  this.attributes = {};
239
- if (options && options.collection) this.collection = options.collection;
240
- if (options && options.parse) attrs = this.parse(attrs, options) || {};
251
+ _.extend(this, _.pick(options, modelOptions));
252
+ if (options.parse) attrs = this.parse(attrs, options) || {};
241
253
  if (defaults = _.result(this, 'defaults')) {
242
254
  attrs = _.defaults({}, attrs, defaults);
243
255
  }
@@ -246,12 +258,18 @@
246
258
  this.initialize.apply(this, arguments);
247
259
  };
248
260
 
261
+ // A list of options to be attached directly to the model, if provided.
262
+ var modelOptions = ['url', 'urlRoot', 'collection'];
263
+
249
264
  // Attach all inheritable methods to the Model prototype.
250
265
  _.extend(Model.prototype, Events, {
251
266
 
252
267
  // A hash of attributes whose current and previous value differ.
253
268
  changed: null,
254
269
 
270
+ // The value returned during the last failed validation.
271
+ validationError: null,
272
+
255
273
  // The default name for the JSON `id` attribute is `"id"`. MongoDB and
256
274
  // CouchDB users may want to set this to `"_id"`.
257
275
  idAttribute: 'id',
@@ -265,7 +283,8 @@
265
283
  return _.clone(this.attributes);
266
284
  },
267
285
 
268
- // Proxy `Backbone.sync` by default.
286
+ // Proxy `Backbone.sync` by default -- but override this if you need
287
+ // custom syncing semantics for *this* particular model.
269
288
  sync: function() {
270
289
  return Backbone.sync.apply(this, arguments);
271
290
  },
@@ -286,10 +305,9 @@
286
305
  return this.get(attr) != null;
287
306
  },
288
307
 
289
- // ----------------------------------------------------------------------
290
-
291
- // Set a hash of model attributes on the object, firing `"change"` unless
292
- // you choose to silence it.
308
+ // Set a hash of model attributes on the object, firing `"change"`. This is
309
+ // the core primitive operation of a model, updating the data and notifying
310
+ // anyone who needs to know about the change in state. The heart of the beast.
293
311
  set: function(key, val, options) {
294
312
  var attr, attrs, unset, changes, silent, changing, prev, current;
295
313
  if (key == null) return this;
@@ -343,6 +361,8 @@
343
361
  }
344
362
  }
345
363
 
364
+ // You might be wondering why there's a `while` loop here. Changes can
365
+ // be recursively nested within `"change"` events.
346
366
  if (changing) return this;
347
367
  if (!silent) {
348
368
  while (this._pending) {
@@ -355,14 +375,13 @@
355
375
  return this;
356
376
  },
357
377
 
358
- // Remove an attribute from the model, firing `"change"` unless you choose
359
- // to silence it. `unset` is a noop if the attribute doesn't exist.
378
+ // Remove an attribute from the model, firing `"change"`. `unset` is a noop
379
+ // if the attribute doesn't exist.
360
380
  unset: function(attr, options) {
361
381
  return this.set(attr, void 0, _.extend({}, options, {unset: true}));
362
382
  },
363
383
 
364
- // Clear all attributes on the model, firing `"change"` unless you choose
365
- // to silence it.
384
+ // Clear all attributes on the model, firing `"change"`.
366
385
  clear: function(options) {
367
386
  var attrs = {};
368
387
  for (var key in this.attributes) attrs[key] = void 0;
@@ -406,19 +425,20 @@
406
425
  return _.clone(this._previousAttributes);
407
426
  },
408
427
 
409
- // ---------------------------------------------------------------------
410
-
411
428
  // Fetch the model from the server. If the server's representation of the
412
- // model differs from its current attributes, they will be overriden,
429
+ // model differs from its current attributes, they will be overridden,
413
430
  // triggering a `"change"` event.
414
431
  fetch: function(options) {
415
432
  options = options ? _.clone(options) : {};
416
433
  if (options.parse === void 0) options.parse = true;
434
+ var model = this;
417
435
  var success = options.success;
418
- options.success = function(model, resp, options) {
436
+ options.success = function(resp) {
419
437
  if (!model.set(model.parse(resp, options), options)) return false;
420
438
  if (success) success(model, resp, options);
439
+ model.trigger('sync', model, resp, options);
421
440
  };
441
+ wrapError(this, options);
422
442
  return this.sync('read', this, options);
423
443
  },
424
444
 
@@ -426,7 +446,7 @@
426
446
  // If the server returns an attributes hash that differs, the model's
427
447
  // state will be `set` again.
428
448
  save: function(key, val, options) {
429
- var attrs, success, method, xhr, attributes = this.attributes;
449
+ var attrs, method, xhr, attributes = this.attributes;
430
450
 
431
451
  // Handle both `"key", value` and `{key: value}` -style arguments.
432
452
  if (key == null || typeof key === 'object') {
@@ -452,8 +472,9 @@
452
472
  // After a successful server-side save, the client is (optionally)
453
473
  // updated with the server-side state.
454
474
  if (options.parse === void 0) options.parse = true;
455
- success = options.success;
456
- options.success = function(model, resp, options) {
475
+ var model = this;
476
+ var success = options.success;
477
+ options.success = function(resp) {
457
478
  // Ensure attributes are restored during synchronous saves.
458
479
  model.attributes = attributes;
459
480
  var serverAttrs = model.parse(resp, options);
@@ -462,9 +483,10 @@
462
483
  return false;
463
484
  }
464
485
  if (success) success(model, resp, options);
486
+ model.trigger('sync', model, resp, options);
465
487
  };
488
+ wrapError(this, options);
466
489
 
467
- // Finish configuring and sending the Ajax request.
468
490
  method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
469
491
  if (method === 'patch') options.attrs = attrs;
470
492
  xhr = this.sync(method, this, options);
@@ -487,15 +509,17 @@
487
509
  model.trigger('destroy', model, model.collection, options);
488
510
  };
489
511
 
490
- options.success = function(model, resp, options) {
512
+ options.success = function(resp) {
491
513
  if (options.wait || model.isNew()) destroy();
492
514
  if (success) success(model, resp, options);
515
+ if (!model.isNew()) model.trigger('sync', model, resp, options);
493
516
  };
494
517
 
495
518
  if (this.isNew()) {
496
- options.success(this, null, options);
519
+ options.success();
497
520
  return false;
498
521
  }
522
+ wrapError(this, options);
499
523
 
500
524
  var xhr = this.sync('delete', this, options);
501
525
  if (!options.wait) destroy();
@@ -529,39 +553,61 @@
529
553
 
530
554
  // Check if the model is currently in a valid state.
531
555
  isValid: function(options) {
532
- return !this.validate || !this.validate(this.attributes, options);
556
+ return this._validate({}, _.extend(options || {}, { validate: true }));
533
557
  },
534
558
 
535
559
  // Run validation against the next complete set of model attributes,
536
- // returning `true` if all is well. Otherwise, fire a general
537
- // `"error"` event and call the error callback, if specified.
560
+ // returning `true` if all is well. Otherwise, fire an `"invalid"` event.
538
561
  _validate: function(attrs, options) {
539
562
  if (!options.validate || !this.validate) return true;
540
563
  attrs = _.extend({}, this.attributes, attrs);
541
564
  var error = this.validationError = this.validate(attrs, options) || null;
542
565
  if (!error) return true;
543
- this.trigger('invalid', this, error, options || {});
566
+ this.trigger('invalid', this, error, _.extend(options || {}, {validationError: error}));
544
567
  return false;
545
568
  }
546
569
 
547
570
  });
548
571
 
572
+ // Underscore methods that we want to implement on the Model.
573
+ var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit'];
574
+
575
+ // Mix in each Underscore method as a proxy to `Model#attributes`.
576
+ _.each(modelMethods, function(method) {
577
+ Model.prototype[method] = function() {
578
+ var args = slice.call(arguments);
579
+ args.unshift(this.attributes);
580
+ return _[method].apply(_, args);
581
+ };
582
+ });
583
+
549
584
  // Backbone.Collection
550
585
  // -------------------
551
586
 
552
- // Provides a standard collection class for our sets of models, ordered
553
- // or unordered. If a `comparator` is specified, the Collection will maintain
587
+ // If models tend to represent a single row of data, a Backbone Collection is
588
+ // more analagous to a table full of data ... or a small slice or page of that
589
+ // table, or a collection of rows that belong together for a particular reason
590
+ // -- all of the messages in this particular folder, all of the documents
591
+ // belonging to this particular author, and so on. Collections maintain
592
+ // indexes of their models, both in order, and for lookup by `id`.
593
+
594
+ // Create a new **Collection**, perhaps to contain a specific type of `model`.
595
+ // If a `comparator` is specified, the Collection will maintain
554
596
  // its models in sort order, as they're added and removed.
555
597
  var Collection = Backbone.Collection = function(models, options) {
556
598
  options || (options = {});
599
+ if (options.url) this.url = options.url;
557
600
  if (options.model) this.model = options.model;
558
601
  if (options.comparator !== void 0) this.comparator = options.comparator;
559
- this.models = [];
560
602
  this._reset();
561
603
  this.initialize.apply(this, arguments);
562
604
  if (models) this.reset(models, _.extend({silent: true}, options));
563
605
  };
564
606
 
607
+ // Default options for `Collection#set`.
608
+ var setOptions = {add: true, remove: true, merge: true};
609
+ var addOptions = {add: true, merge: false, remove: false};
610
+
565
611
  // Define the Collection's inheritable methods.
566
612
  _.extend(Collection.prototype, Events, {
567
613
 
@@ -586,88 +632,118 @@
586
632
 
587
633
  // Add a model, or list of models to the set.
588
634
  add: function(models, options) {
635
+ return this.set(models, _.defaults(options || {}, addOptions));
636
+ },
637
+
638
+ // Remove a model, or a list of models from the set.
639
+ remove: function(models, options) {
589
640
  models = _.isArray(models) ? models.slice() : [models];
590
641
  options || (options = {});
591
- var i, l, model, attrs, existing, doSort, add, at, sort, sortAttr;
592
- add = [];
593
- at = options.at;
594
- sort = this.comparator && (at == null) && options.sort != false;
595
- sortAttr = _.isString(this.comparator) ? this.comparator : null;
642
+ var i, l, index, model;
643
+ for (i = 0, l = models.length; i < l; i++) {
644
+ model = this.get(models[i]);
645
+ if (!model) continue;
646
+ delete this._byId[model.id];
647
+ delete this._byId[model.cid];
648
+ index = this.indexOf(model);
649
+ this.models.splice(index, 1);
650
+ this.length--;
651
+ if (!options.silent) {
652
+ options.index = index;
653
+ model.trigger('remove', model, this, options);
654
+ }
655
+ this._removeReference(model);
656
+ }
657
+ return this;
658
+ },
659
+
660
+ // Update a collection by `set`-ing a new list of models, adding new ones,
661
+ // removing models that are no longer present, and merging models that
662
+ // already exist in the collection, as necessary. Similar to **Model#set**,
663
+ // the core operation for updating the data contained by the collection.
664
+ set: function(models, options) {
665
+ options = _.defaults(options || {}, setOptions);
666
+ if (options.parse) models = this.parse(models, options);
667
+ if (!_.isArray(models)) models = models ? [models] : [];
668
+ var i, l, model, attrs, existing, sort;
669
+ var at = options.at;
670
+ var sortable = this.comparator && (at == null) && options.sort !== false;
671
+ var sortAttr = _.isString(this.comparator) ? this.comparator : null;
672
+ var toAdd = [], toRemove = [], modelMap = {};
596
673
 
597
674
  // Turn bare objects into model references, and prevent invalid models
598
675
  // from being added.
599
676
  for (i = 0, l = models.length; i < l; i++) {
600
- if (!(model = this._prepareModel(attrs = models[i], options))) {
601
- this.trigger('invalid', this, attrs, options);
602
- continue;
603
- }
677
+ if (!(model = this._prepareModel(models[i], options))) continue;
604
678
 
605
679
  // If a duplicate is found, prevent it from being added and
606
680
  // optionally merge it into the existing model.
607
681
  if (existing = this.get(model)) {
682
+ if (options.remove) modelMap[existing.cid] = true;
608
683
  if (options.merge) {
609
- existing.set(attrs === model ? model.attributes : attrs, options);
610
- if (sort && !doSort && existing.hasChanged(sortAttr)) doSort = true;
684
+ existing.set(model.attributes, options);
685
+ if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
611
686
  }
612
- continue;
613
- }
614
687
 
615
- // This is a new model, push it to the `add` list.
616
- add.push(model);
688
+ // This is a new model, push it to the `toAdd` list.
689
+ } else if (options.add) {
690
+ toAdd.push(model);
617
691
 
618
- // Listen to added models' events, and index models for lookup by
619
- // `id` and by `cid`.
620
- model.on('all', this._onModelEvent, this);
621
- this._byId[model.cid] = model;
622
- if (model.id != null) this._byId[model.id] = model;
692
+ // Listen to added models' events, and index models for lookup by
693
+ // `id` and by `cid`.
694
+ model.on('all', this._onModelEvent, this);
695
+ this._byId[model.cid] = model;
696
+ if (model.id != null) this._byId[model.id] = model;
697
+ }
698
+ }
699
+
700
+ // Remove nonexistent models if appropriate.
701
+ if (options.remove) {
702
+ for (i = 0, l = this.length; i < l; ++i) {
703
+ if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
704
+ }
705
+ if (toRemove.length) this.remove(toRemove, options);
623
706
  }
624
707
 
625
708
  // See if sorting is needed, update `length` and splice in new models.
626
- if (add.length) {
627
- if (sort) doSort = true;
628
- this.length += add.length;
709
+ if (toAdd.length) {
710
+ if (sortable) sort = true;
711
+ this.length += toAdd.length;
629
712
  if (at != null) {
630
- splice.apply(this.models, [at, 0].concat(add));
713
+ splice.apply(this.models, [at, 0].concat(toAdd));
631
714
  } else {
632
- push.apply(this.models, add);
715
+ push.apply(this.models, toAdd);
633
716
  }
634
717
  }
635
718
 
636
719
  // Silently sort the collection if appropriate.
637
- if (doSort) this.sort({silent: true});
720
+ if (sort) this.sort({silent: true});
638
721
 
639
722
  if (options.silent) return this;
640
723
 
641
724
  // Trigger `add` events.
642
- for (i = 0, l = add.length; i < l; i++) {
643
- (model = add[i]).trigger('add', model, this, options);
725
+ for (i = 0, l = toAdd.length; i < l; i++) {
726
+ (model = toAdd[i]).trigger('add', model, this, options);
644
727
  }
645
728
 
646
729
  // Trigger `sort` if the collection was sorted.
647
- if (doSort) this.trigger('sort', this, options);
648
-
730
+ if (sort) this.trigger('sort', this, options);
649
731
  return this;
650
732
  },
651
733
 
652
- // Remove a model, or a list of models from the set.
653
- remove: function(models, options) {
654
- models = _.isArray(models) ? models.slice() : [models];
734
+ // When you have more items than you want to add or remove individually,
735
+ // you can reset the entire set with a new list of models, without firing
736
+ // any granular `add` or `remove` events. Fires `reset` when finished.
737
+ // Useful for bulk operations and optimizations.
738
+ reset: function(models, options) {
655
739
  options || (options = {});
656
- var i, l, index, model;
657
- for (i = 0, l = models.length; i < l; i++) {
658
- model = this.get(models[i]);
659
- if (!model) continue;
660
- delete this._byId[model.id];
661
- delete this._byId[model.cid];
662
- index = this.indexOf(model);
663
- this.models.splice(index, 1);
664
- this.length--;
665
- if (!options.silent) {
666
- options.index = index;
667
- model.trigger('remove', model, this, options);
668
- }
669
- this._removeReference(model);
740
+ for (var i = 0, l = this.models.length; i < l; i++) {
741
+ this._removeReference(this.models[i]);
670
742
  }
743
+ options.previousModels = this.models;
744
+ this._reset();
745
+ this.add(models, _.extend({silent: true}, options));
746
+ if (!options.silent) this.trigger('reset', this, options);
671
747
  return this;
672
748
  },
673
749
 
@@ -707,8 +783,7 @@
707
783
  // Get a model from the set by id.
708
784
  get: function(obj) {
709
785
  if (obj == null) return void 0;
710
- this._idAttr || (this._idAttr = this.model.prototype.idAttribute);
711
- return this._byId[obj.id || obj.cid || obj[this._idAttr] || obj];
786
+ return this._byId[obj.id != null ? obj.id : obj.cid || obj];
712
787
  },
713
788
 
714
789
  // Get the model at the given index.
@@ -716,10 +791,11 @@
716
791
  return this.models[index];
717
792
  },
718
793
 
719
- // Return models with matching attributes. Useful for simple cases of `filter`.
720
- where: function(attrs) {
721
- if (_.isEmpty(attrs)) return [];
722
- return this.filter(function(model) {
794
+ // Return models with matching attributes. Useful for simple cases of
795
+ // `filter`.
796
+ where: function(attrs, first) {
797
+ if (_.isEmpty(attrs)) return first ? void 0 : [];
798
+ return this[first ? 'find' : 'filter'](function(model) {
723
799
  for (var key in attrs) {
724
800
  if (attrs[key] !== model.get(key)) return false;
725
801
  }
@@ -727,13 +803,17 @@
727
803
  });
728
804
  },
729
805
 
806
+ // Return the first model with matching attributes. Useful for simple cases
807
+ // of `find`.
808
+ findWhere: function(attrs) {
809
+ return this.where(attrs, true);
810
+ },
811
+
730
812
  // Force the collection to re-sort itself. You don't need to call this under
731
813
  // normal circumstances, as the set will maintain sort order as each item
732
814
  // is added.
733
815
  sort: function(options) {
734
- if (!this.comparator) {
735
- throw new Error('Cannot sort a set without a comparator');
736
- }
816
+ if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
737
817
  options || (options = {});
738
818
 
739
819
  // Run sort based on type of `comparator`.
@@ -747,75 +827,36 @@
747
827
  return this;
748
828
  },
749
829
 
830
+ // Figure out the smallest index at which a model should be inserted so as
831
+ // to maintain order.
832
+ sortedIndex: function(model, value, context) {
833
+ value || (value = this.comparator);
834
+ var iterator = _.isFunction(value) ? value : function(model) {
835
+ return model.get(value);
836
+ };
837
+ return _.sortedIndex(this.models, model, iterator, context);
838
+ },
839
+
750
840
  // Pluck an attribute from each model in the collection.
751
841
  pluck: function(attr) {
752
842
  return _.invoke(this.models, 'get', attr);
753
843
  },
754
844
 
755
- // Smartly update a collection with a change set of models, adding,
756
- // removing, and merging as necessary.
757
- update: function(models, options) {
758
- options = _.extend({add: true, merge: true, remove: true}, options);
759
- if (options.parse) models = this.parse(models, options);
760
- var model, i, l, existing;
761
- var add = [], remove = [], modelMap = {};
762
-
763
- // Allow a single model (or no argument) to be passed.
764
- if (!_.isArray(models)) models = models ? [models] : [];
765
-
766
- // Proxy to `add` for this case, no need to iterate...
767
- if (options.add && !options.remove) return this.add(models, options);
768
-
769
- // Determine which models to add and merge, and which to remove.
770
- for (i = 0, l = models.length; i < l; i++) {
771
- model = models[i];
772
- existing = this.get(model);
773
- if (options.remove && existing) modelMap[existing.cid] = true;
774
- if ((options.add && !existing) || (options.merge && existing)) {
775
- add.push(model);
776
- }
777
- }
778
- if (options.remove) {
779
- for (i = 0, l = this.models.length; i < l; i++) {
780
- model = this.models[i];
781
- if (!modelMap[model.cid]) remove.push(model);
782
- }
783
- }
784
-
785
- // Remove models (if applicable) before we add and merge the rest.
786
- if (remove.length) this.remove(remove, options);
787
- if (add.length) this.add(add, options);
788
- return this;
789
- },
790
-
791
- // When you have more items than you want to add or remove individually,
792
- // you can reset the entire set with a new list of models, without firing
793
- // any `add` or `remove` events. Fires `reset` when finished.
794
- reset: function(models, options) {
795
- options || (options = {});
796
- if (options.parse) models = this.parse(models, options);
797
- for (var i = 0, l = this.models.length; i < l; i++) {
798
- this._removeReference(this.models[i]);
799
- }
800
- options.previousModels = this.models.slice();
801
- this._reset();
802
- if (models) this.add(models, _.extend({silent: true}, options));
803
- if (!options.silent) this.trigger('reset', this, options);
804
- return this;
805
- },
806
-
807
845
  // Fetch the default set of models for this collection, resetting the
808
- // collection when they arrive. If `update: true` is passed, the response
809
- // data will be passed through the `update` method instead of `reset`.
846
+ // collection when they arrive. If `reset: true` is passed, the response
847
+ // data will be passed through the `reset` method instead of `set`.
810
848
  fetch: function(options) {
811
849
  options = options ? _.clone(options) : {};
812
850
  if (options.parse === void 0) options.parse = true;
813
851
  var success = options.success;
814
- options.success = function(collection, resp, options) {
815
- var method = options.update ? 'update' : 'reset';
852
+ var collection = this;
853
+ options.success = function(resp) {
854
+ var method = options.reset ? 'reset' : 'set';
816
855
  collection[method](resp, options);
817
856
  if (success) success(collection, resp, options);
857
+ collection.trigger('sync', collection, resp, options);
818
858
  };
859
+ wrapError(this, options);
819
860
  return this.sync('read', this, options);
820
861
  },
821
862
 
@@ -828,7 +869,7 @@
828
869
  if (!options.wait) this.add(model, options);
829
870
  var collection = this;
830
871
  var success = options.success;
831
- options.success = function(model, resp, options) {
872
+ options.success = function(resp) {
832
873
  if (options.wait) collection.add(model, options);
833
874
  if (success) success(model, resp, options);
834
875
  };
@@ -847,14 +888,16 @@
847
888
  return new this.constructor(this.models);
848
889
  },
849
890
 
850
- // Reset all internal state. Called when the collection is reset.
891
+ // Private method to reset all internal state. Called when the collection
892
+ // is first initialized or reset.
851
893
  _reset: function() {
852
894
  this.length = 0;
853
- this.models.length = 0;
895
+ this.models = [];
854
896
  this._byId = {};
855
897
  },
856
898
 
857
- // Prepare a model or hash of attributes to be added to this collection.
899
+ // Prepare a hash of attributes (or other model) to be added to this
900
+ // collection.
858
901
  _prepareModel: function(attrs, options) {
859
902
  if (attrs instanceof Model) {
860
903
  if (!attrs.collection) attrs.collection = this;
@@ -863,11 +906,14 @@
863
906
  options || (options = {});
864
907
  options.collection = this;
865
908
  var model = new this.model(attrs, options);
866
- if (!model._validate(attrs, options)) return false;
909
+ if (!model._validate(attrs, options)) {
910
+ this.trigger('invalid', this, attrs, options);
911
+ return false;
912
+ }
867
913
  return model;
868
914
  },
869
915
 
870
- // Internal method to remove a model's ties to a collection.
916
+ // Internal method to sever a model's ties to a collection.
871
917
  _removeReference: function(model) {
872
918
  if (this === model.collection) delete model.collection;
873
919
  model.off('all', this._onModelEvent, this);
@@ -885,19 +931,13 @@
885
931
  if (model.id != null) this._byId[model.id] = model;
886
932
  }
887
933
  this.trigger.apply(this, arguments);
888
- },
889
-
890
- sortedIndex: function (model, value, context) {
891
- value || (value = this.comparator);
892
- var iterator = _.isFunction(value) ? value : function(model) {
893
- return model.get(value);
894
- };
895
- return _.sortedIndex(this.models, model, iterator, context);
896
934
  }
897
935
 
898
936
  });
899
937
 
900
938
  // Underscore methods that we want to implement on the Collection.
939
+ // 90% of the core usefulness of Backbone Collections is actually implemented
940
+ // right here:
901
941
  var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
902
942
  'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
903
943
  'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
@@ -927,62 +967,303 @@
927
967
  };
928
968
  });
929
969
 
930
- // Backbone.Router
931
- // ---------------
970
+ // Backbone.View
971
+ // -------------
932
972
 
933
- // Routers map faux-URLs to actions, and fire events when routes are
934
- // matched. Creating a new one sets its `routes` hash, if not set statically.
935
- var Router = Backbone.Router = function(options) {
936
- options || (options = {});
937
- if (options.routes) this.routes = options.routes;
938
- this._bindRoutes();
973
+ // Backbone Views are almost more convention than they are actual code. A View
974
+ // is simply a JavaScript object that represents a logical chunk of UI in the
975
+ // DOM. This might be a single item, an entire list, a sidebar or panel, or
976
+ // even the surrounding frame which wraps your whole app. Defining a chunk of
977
+ // UI as a **View** allows you to define your DOM events declaratively, without
978
+ // having to worry about render order ... and makes it easy for the view to
979
+ // react to specific changes in the state of your models.
980
+
981
+ // Creating a Backbone.View creates its initial element outside of the DOM,
982
+ // if an existing element is not provided...
983
+ var View = Backbone.View = function(options) {
984
+ this.cid = _.uniqueId('view');
985
+ this._configure(options || {});
986
+ this._ensureElement();
939
987
  this.initialize.apply(this, arguments);
988
+ this.delegateEvents();
940
989
  };
941
990
 
942
- // Cached regular expressions for matching named param parts and splatted
943
- // parts of route strings.
944
- var optionalParam = /\((.*?)\)/g;
945
- var namedParam = /(\(\?)?:\w+/g;
946
- var splatParam = /\*\w+/g;
947
- var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
991
+ // Cached regex to split keys for `delegate`.
992
+ var delegateEventSplitter = /^(\S+)\s*(.*)$/;
948
993
 
949
- // Set up all inheritable **Backbone.Router** properties and methods.
950
- _.extend(Router.prototype, Events, {
994
+ // List of view options to be merged as properties.
995
+ var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
996
+
997
+ // Set up all inheritable **Backbone.View** properties and methods.
998
+ _.extend(View.prototype, Events, {
999
+
1000
+ // The default `tagName` of a View's element is `"div"`.
1001
+ tagName: 'div',
1002
+
1003
+ // jQuery delegate for element lookup, scoped to DOM elements within the
1004
+ // current view. This should be prefered to global lookups where possible.
1005
+ $: function(selector) {
1006
+ return this.$el.find(selector);
1007
+ },
951
1008
 
952
1009
  // Initialize is an empty function by default. Override it with your own
953
1010
  // initialization logic.
954
1011
  initialize: function(){},
955
1012
 
956
- // Manually bind a single named route to a callback. For example:
957
- //
958
- // this.route('search/:query/p:num', 'search', function(query, num) {
959
- // ...
960
- // });
961
- //
962
- route: function(route, name, callback) {
963
- if (!_.isRegExp(route)) route = this._routeToRegExp(route);
964
- if (!callback) callback = this[name];
965
- Backbone.history.route(route, _.bind(function(fragment) {
966
- var args = this._extractParameters(route, fragment);
967
- callback && callback.apply(this, args);
968
- this.trigger.apply(this, ['route:' + name].concat(args));
969
- this.trigger('route', name, args);
970
- Backbone.history.trigger('route', this, name, args);
971
- }, this));
1013
+ // **render** is the core function that your view should override, in order
1014
+ // to populate its element (`this.el`), with the appropriate HTML. The
1015
+ // convention is for **render** to always return `this`.
1016
+ render: function() {
972
1017
  return this;
973
1018
  },
974
1019
 
975
- // Simple proxy to `Backbone.history` to save a fragment into the history.
976
- navigate: function(fragment, options) {
977
- Backbone.history.navigate(fragment, options);
978
- return this;
979
- },
1020
+ // Remove this view by taking the element out of the DOM, and removing any
1021
+ // applicable Backbone.Events listeners.
1022
+ remove: function() {
1023
+ this.$el.remove();
1024
+ this.stopListening();
1025
+ return this;
1026
+ },
1027
+
1028
+ // Change the view's element (`this.el` property), including event
1029
+ // re-delegation.
1030
+ setElement: function(element, delegate) {
1031
+ if (this.$el) this.undelegateEvents();
1032
+ this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
1033
+ this.el = this.$el[0];
1034
+ if (delegate !== false) this.delegateEvents();
1035
+ return this;
1036
+ },
1037
+
1038
+ // Set callbacks, where `this.events` is a hash of
1039
+ //
1040
+ // *{"event selector": "callback"}*
1041
+ //
1042
+ // {
1043
+ // 'mousedown .title': 'edit',
1044
+ // 'click .button': 'save'
1045
+ // 'click .open': function(e) { ... }
1046
+ // }
1047
+ //
1048
+ // pairs. Callbacks will be bound to the view, with `this` set properly.
1049
+ // Uses event delegation for efficiency.
1050
+ // Omitting the selector binds the event to `this.el`.
1051
+ // This only works for delegate-able events: not `focus`, `blur`, and
1052
+ // not `change`, `submit`, and `reset` in Internet Explorer.
1053
+ delegateEvents: function(events) {
1054
+ if (!(events || (events = _.result(this, 'events')))) return this;
1055
+ this.undelegateEvents();
1056
+ for (var key in events) {
1057
+ var method = events[key];
1058
+ if (!_.isFunction(method)) method = this[events[key]];
1059
+ if (!method) continue;
1060
+
1061
+ var match = key.match(delegateEventSplitter);
1062
+ var eventName = match[1], selector = match[2];
1063
+ method = _.bind(method, this);
1064
+ eventName += '.delegateEvents' + this.cid;
1065
+ if (selector === '') {
1066
+ this.$el.on(eventName, method);
1067
+ } else {
1068
+ this.$el.on(eventName, selector, method);
1069
+ }
1070
+ }
1071
+ return this;
1072
+ },
1073
+
1074
+ // Clears all callbacks previously bound to the view with `delegateEvents`.
1075
+ // You usually don't need to use this, but may wish to if you have multiple
1076
+ // Backbone views attached to the same DOM element.
1077
+ undelegateEvents: function() {
1078
+ this.$el.off('.delegateEvents' + this.cid);
1079
+ return this;
1080
+ },
1081
+
1082
+ // Performs the initial configuration of a View with a set of options.
1083
+ // Keys with special meaning *(e.g. model, collection, id, className)* are
1084
+ // attached directly to the view. See `viewOptions` for an exhaustive
1085
+ // list.
1086
+ _configure: function(options) {
1087
+ if (this.options) options = _.extend({}, _.result(this, 'options'), options);
1088
+ _.extend(this, _.pick(options, viewOptions));
1089
+ this.options = options;
1090
+ },
1091
+
1092
+ // Ensure that the View has a DOM element to render into.
1093
+ // If `this.el` is a string, pass it through `$()`, take the first
1094
+ // matching element, and re-assign it to `el`. Otherwise, create
1095
+ // an element from the `id`, `className` and `tagName` properties.
1096
+ _ensureElement: function() {
1097
+ if (!this.el) {
1098
+ var attrs = _.extend({}, _.result(this, 'attributes'));
1099
+ if (this.id) attrs.id = _.result(this, 'id');
1100
+ if (this.className) attrs['class'] = _.result(this, 'className');
1101
+ var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
1102
+ this.setElement($el, false);
1103
+ } else {
1104
+ this.setElement(_.result(this, 'el'), false);
1105
+ }
1106
+ }
1107
+
1108
+ });
1109
+
1110
+ // Backbone.sync
1111
+ // -------------
1112
+
1113
+ // Override this function to change the manner in which Backbone persists
1114
+ // models to the server. You will be passed the type of request, and the
1115
+ // model in question. By default, makes a RESTful Ajax request
1116
+ // to the model's `url()`. Some possible customizations could be:
1117
+ //
1118
+ // * Use `setTimeout` to batch rapid-fire updates into a single request.
1119
+ // * Send up the models as XML instead of JSON.
1120
+ // * Persist models via WebSockets instead of Ajax.
1121
+ //
1122
+ // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
1123
+ // as `POST`, with a `_method` parameter containing the true HTTP method,
1124
+ // as well as all requests with the body as `application/x-www-form-urlencoded`
1125
+ // instead of `application/json` with the model in a param named `model`.
1126
+ // Useful when interfacing with server-side languages like **PHP** that make
1127
+ // it difficult to read the body of `PUT` requests.
1128
+ Backbone.sync = function(method, model, options) {
1129
+ var type = methodMap[method];
1130
+
1131
+ // Default options, unless specified.
1132
+ _.defaults(options || (options = {}), {
1133
+ emulateHTTP: Backbone.emulateHTTP,
1134
+ emulateJSON: Backbone.emulateJSON
1135
+ });
1136
+
1137
+ // Default JSON-request options.
1138
+ var params = {type: type, dataType: 'json'};
1139
+
1140
+ // Ensure that we have a URL.
1141
+ if (!options.url) {
1142
+ params.url = _.result(model, 'url') || urlError();
1143
+ }
1144
+
1145
+ // Ensure that we have the appropriate request data.
1146
+ if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
1147
+ params.contentType = 'application/json';
1148
+ params.data = JSON.stringify(options.attrs || model.toJSON(options));
1149
+ }
1150
+
1151
+ // For older servers, emulate JSON by encoding the request into an HTML-form.
1152
+ if (options.emulateJSON) {
1153
+ params.contentType = 'application/x-www-form-urlencoded';
1154
+ params.data = params.data ? {model: params.data} : {};
1155
+ }
1156
+
1157
+ // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1158
+ // And an `X-HTTP-Method-Override` header.
1159
+ if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
1160
+ params.type = 'POST';
1161
+ if (options.emulateJSON) params.data._method = type;
1162
+ var beforeSend = options.beforeSend;
1163
+ options.beforeSend = function(xhr) {
1164
+ xhr.setRequestHeader('X-HTTP-Method-Override', type);
1165
+ if (beforeSend) return beforeSend.apply(this, arguments);
1166
+ };
1167
+ }
1168
+
1169
+ // Don't process data on a non-GET request.
1170
+ if (params.type !== 'GET' && !options.emulateJSON) {
1171
+ params.processData = false;
1172
+ }
1173
+
1174
+ // If we're sending a `PATCH` request, and we're in an old Internet Explorer
1175
+ // that still has ActiveX enabled by default, override jQuery to use that
1176
+ // for XHR instead. Remove this line when jQuery supports `PATCH` on IE8.
1177
+ if (params.type === 'PATCH' && window.ActiveXObject &&
1178
+ !(window.external && window.external.msActiveXFilteringEnabled)) {
1179
+ params.xhr = function() {
1180
+ return new ActiveXObject("Microsoft.XMLHTTP");
1181
+ };
1182
+ }
1183
+
1184
+ // Make the request, allowing the user to override any Ajax options.
1185
+ var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
1186
+ model.trigger('request', model, xhr, options);
1187
+ return xhr;
1188
+ };
1189
+
1190
+ // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1191
+ var methodMap = {
1192
+ 'create': 'POST',
1193
+ 'update': 'PUT',
1194
+ 'patch': 'PATCH',
1195
+ 'delete': 'DELETE',
1196
+ 'read': 'GET'
1197
+ };
1198
+
1199
+ // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
1200
+ // Override this if you'd like to use a different library.
1201
+ Backbone.ajax = function() {
1202
+ return Backbone.$.ajax.apply(Backbone.$, arguments);
1203
+ };
1204
+
1205
+ // Backbone.Router
1206
+ // ---------------
1207
+
1208
+ // Routers map faux-URLs to actions, and fire events when routes are
1209
+ // matched. Creating a new one sets its `routes` hash, if not set statically.
1210
+ var Router = Backbone.Router = function(options) {
1211
+ options || (options = {});
1212
+ if (options.routes) this.routes = options.routes;
1213
+ this._bindRoutes();
1214
+ this.initialize.apply(this, arguments);
1215
+ };
1216
+
1217
+ // Cached regular expressions for matching named param parts and splatted
1218
+ // parts of route strings.
1219
+ var optionalParam = /\((.*?)\)/g;
1220
+ var namedParam = /(\(\?)?:\w+/g;
1221
+ var splatParam = /\*\w+/g;
1222
+ var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
1223
+
1224
+ // Set up all inheritable **Backbone.Router** properties and methods.
1225
+ _.extend(Router.prototype, Events, {
1226
+
1227
+ // Initialize is an empty function by default. Override it with your own
1228
+ // initialization logic.
1229
+ initialize: function(){},
1230
+
1231
+ // Manually bind a single named route to a callback. For example:
1232
+ //
1233
+ // this.route('search/:query/p:num', 'search', function(query, num) {
1234
+ // ...
1235
+ // });
1236
+ //
1237
+ route: function(route, name, callback) {
1238
+ if (!_.isRegExp(route)) route = this._routeToRegExp(route);
1239
+ if (_.isFunction(name)) {
1240
+ callback = name;
1241
+ name = '';
1242
+ }
1243
+ if (!callback) callback = this[name];
1244
+ var router = this;
1245
+ Backbone.history.route(route, function(fragment) {
1246
+ var args = router._extractParameters(route, fragment);
1247
+ callback && callback.apply(router, args);
1248
+ router.trigger.apply(router, ['route:' + name].concat(args));
1249
+ router.trigger('route', name, args);
1250
+ Backbone.history.trigger('route', router, name, args);
1251
+ });
1252
+ return this;
1253
+ },
1254
+
1255
+ // Simple proxy to `Backbone.history` to save a fragment into the history.
1256
+ navigate: function(fragment, options) {
1257
+ Backbone.history.navigate(fragment, options);
1258
+ return this;
1259
+ },
980
1260
 
981
1261
  // Bind all defined routes to `Backbone.history`. We have to reverse the
982
1262
  // order of the routes here to support behavior where the most general
983
1263
  // routes can be defined at the bottom of the route map.
984
1264
  _bindRoutes: function() {
985
1265
  if (!this.routes) return;
1266
+ this.routes = _.result(this, 'routes');
986
1267
  var route, routes = _.keys(this.routes);
987
1268
  while ((route = routes.pop()) != null) {
988
1269
  this.route(route, this.routes[route]);
@@ -1002,9 +1283,13 @@
1002
1283
  },
1003
1284
 
1004
1285
  // Given a route, and a URL fragment that it matches, return the array of
1005
- // extracted parameters.
1286
+ // extracted decoded parameters. Empty or unmatched parameters will be
1287
+ // treated as `null` to normalize cross-browser behavior.
1006
1288
  _extractParameters: function(route, fragment) {
1007
- return route.exec(fragment).slice(1);
1289
+ var params = route.exec(fragment).slice(1);
1290
+ return _.map(params, function(param) {
1291
+ return param ? decodeURIComponent(param) : null;
1292
+ });
1008
1293
  }
1009
1294
 
1010
1295
  });
@@ -1012,8 +1297,11 @@
1012
1297
  // Backbone.History
1013
1298
  // ----------------
1014
1299
 
1015
- // Handles cross-browser history management, based on URL fragments. If the
1016
- // browser does not support `onhashchange`, falls back to polling.
1300
+ // Handles cross-browser history management, based on either
1301
+ // [pushState](http://diveintohtml5.info/history.html) and real URLs, or
1302
+ // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
1303
+ // and URL fragments. If the browser supports neither (old IE, natch),
1304
+ // falls back to polling.
1017
1305
  var History = Backbone.History = function() {
1018
1306
  this.handlers = [];
1019
1307
  _.bindAll(this, 'checkUrl');
@@ -1224,230 +1512,6 @@
1224
1512
  // Create the default Backbone.history.
1225
1513
  Backbone.history = new History;
1226
1514
 
1227
- // Backbone.View
1228
- // -------------
1229
-
1230
- // Creating a Backbone.View creates its initial element outside of the DOM,
1231
- // if an existing element is not provided...
1232
- var View = Backbone.View = function(options) {
1233
- this.cid = _.uniqueId('view');
1234
- this._configure(options || {});
1235
- this._ensureElement();
1236
- this.initialize.apply(this, arguments);
1237
- this.delegateEvents();
1238
- };
1239
-
1240
- // Cached regex to split keys for `delegate`.
1241
- var delegateEventSplitter = /^(\S+)\s*(.*)$/;
1242
-
1243
- // List of view options to be merged as properties.
1244
- var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
1245
-
1246
- // Set up all inheritable **Backbone.View** properties and methods.
1247
- _.extend(View.prototype, Events, {
1248
-
1249
- // The default `tagName` of a View's element is `"div"`.
1250
- tagName: 'div',
1251
-
1252
- // jQuery delegate for element lookup, scoped to DOM elements within the
1253
- // current view. This should be prefered to global lookups where possible.
1254
- $: function(selector) {
1255
- return this.$el.find(selector);
1256
- },
1257
-
1258
- // Initialize is an empty function by default. Override it with your own
1259
- // initialization logic.
1260
- initialize: function(){},
1261
-
1262
- // **render** is the core function that your view should override, in order
1263
- // to populate its element (`this.el`), with the appropriate HTML. The
1264
- // convention is for **render** to always return `this`.
1265
- render: function() {
1266
- return this;
1267
- },
1268
-
1269
- // Remove this view by taking the element out of the DOM, and removing any
1270
- // applicable Backbone.Events listeners.
1271
- remove: function() {
1272
- this.$el.remove();
1273
- this.stopListening();
1274
- return this;
1275
- },
1276
-
1277
- // Change the view's element (`this.el` property), including event
1278
- // re-delegation.
1279
- setElement: function(element, delegate) {
1280
- if (this.$el) this.undelegateEvents();
1281
- this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
1282
- this.el = this.$el[0];
1283
- if (delegate !== false) this.delegateEvents();
1284
- return this;
1285
- },
1286
-
1287
- // Set callbacks, where `this.events` is a hash of
1288
- //
1289
- // *{"event selector": "callback"}*
1290
- //
1291
- // {
1292
- // 'mousedown .title': 'edit',
1293
- // 'click .button': 'save'
1294
- // 'click .open': function(e) { ... }
1295
- // }
1296
- //
1297
- // pairs. Callbacks will be bound to the view, with `this` set properly.
1298
- // Uses event delegation for efficiency.
1299
- // Omitting the selector binds the event to `this.el`.
1300
- // This only works for delegate-able events: not `focus`, `blur`, and
1301
- // not `change`, `submit`, and `reset` in Internet Explorer.
1302
- delegateEvents: function(events) {
1303
- if (!(events || (events = _.result(this, 'events')))) return;
1304
- this.undelegateEvents();
1305
- for (var key in events) {
1306
- var method = events[key];
1307
- if (!_.isFunction(method)) method = this[events[key]];
1308
- if (!method) throw new Error('Method "' + events[key] + '" does not exist');
1309
- var match = key.match(delegateEventSplitter);
1310
- var eventName = match[1], selector = match[2];
1311
- method = _.bind(method, this);
1312
- eventName += '.delegateEvents' + this.cid;
1313
- if (selector === '') {
1314
- this.$el.on(eventName, method);
1315
- } else {
1316
- this.$el.on(eventName, selector, method);
1317
- }
1318
- }
1319
- },
1320
-
1321
- // Clears all callbacks previously bound to the view with `delegateEvents`.
1322
- // You usually don't need to use this, but may wish to if you have multiple
1323
- // Backbone views attached to the same DOM element.
1324
- undelegateEvents: function() {
1325
- this.$el.off('.delegateEvents' + this.cid);
1326
- },
1327
-
1328
- // Performs the initial configuration of a View with a set of options.
1329
- // Keys with special meaning *(model, collection, id, className)*, are
1330
- // attached directly to the view.
1331
- _configure: function(options) {
1332
- if (this.options) options = _.extend({}, _.result(this, 'options'), options);
1333
- _.extend(this, _.pick(options, viewOptions));
1334
- this.options = options;
1335
- },
1336
-
1337
- // Ensure that the View has a DOM element to render into.
1338
- // If `this.el` is a string, pass it through `$()`, take the first
1339
- // matching element, and re-assign it to `el`. Otherwise, create
1340
- // an element from the `id`, `className` and `tagName` properties.
1341
- _ensureElement: function() {
1342
- if (!this.el) {
1343
- var attrs = _.extend({}, _.result(this, 'attributes'));
1344
- if (this.id) attrs.id = _.result(this, 'id');
1345
- if (this.className) attrs['class'] = _.result(this, 'className');
1346
- var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
1347
- this.setElement($el, false);
1348
- } else {
1349
- this.setElement(_.result(this, 'el'), false);
1350
- }
1351
- }
1352
-
1353
- });
1354
-
1355
- // Backbone.sync
1356
- // -------------
1357
-
1358
- // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1359
- var methodMap = {
1360
- 'create': 'POST',
1361
- 'update': 'PUT',
1362
- 'patch': 'PATCH',
1363
- 'delete': 'DELETE',
1364
- 'read': 'GET'
1365
- };
1366
-
1367
- // Override this function to change the manner in which Backbone persists
1368
- // models to the server. You will be passed the type of request, and the
1369
- // model in question. By default, makes a RESTful Ajax request
1370
- // to the model's `url()`. Some possible customizations could be:
1371
- //
1372
- // * Use `setTimeout` to batch rapid-fire updates into a single request.
1373
- // * Send up the models as XML instead of JSON.
1374
- // * Persist models via WebSockets instead of Ajax.
1375
- //
1376
- // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
1377
- // as `POST`, with a `_method` parameter containing the true HTTP method,
1378
- // as well as all requests with the body as `application/x-www-form-urlencoded`
1379
- // instead of `application/json` with the model in a param named `model`.
1380
- // Useful when interfacing with server-side languages like **PHP** that make
1381
- // it difficult to read the body of `PUT` requests.
1382
- Backbone.sync = function(method, model, options) {
1383
- var type = methodMap[method];
1384
-
1385
- // Default options, unless specified.
1386
- _.defaults(options || (options = {}), {
1387
- emulateHTTP: Backbone.emulateHTTP,
1388
- emulateJSON: Backbone.emulateJSON
1389
- });
1390
-
1391
- // Default JSON-request options.
1392
- var params = {type: type, dataType: 'json'};
1393
-
1394
- // Ensure that we have a URL.
1395
- if (!options.url) {
1396
- params.url = _.result(model, 'url') || urlError();
1397
- }
1398
-
1399
- // Ensure that we have the appropriate request data.
1400
- if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
1401
- params.contentType = 'application/json';
1402
- params.data = JSON.stringify(options.attrs || model.toJSON(options));
1403
- }
1404
-
1405
- // For older servers, emulate JSON by encoding the request into an HTML-form.
1406
- if (options.emulateJSON) {
1407
- params.contentType = 'application/x-www-form-urlencoded';
1408
- params.data = params.data ? {model: params.data} : {};
1409
- }
1410
-
1411
- // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1412
- // And an `X-HTTP-Method-Override` header.
1413
- if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
1414
- params.type = 'POST';
1415
- if (options.emulateJSON) params.data._method = type;
1416
- var beforeSend = options.beforeSend;
1417
- options.beforeSend = function(xhr) {
1418
- xhr.setRequestHeader('X-HTTP-Method-Override', type);
1419
- if (beforeSend) return beforeSend.apply(this, arguments);
1420
- };
1421
- }
1422
-
1423
- // Don't process data on a non-GET request.
1424
- if (params.type !== 'GET' && !options.emulateJSON) {
1425
- params.processData = false;
1426
- }
1427
-
1428
- var success = options.success;
1429
- options.success = function(resp) {
1430
- if (success) success(model, resp, options);
1431
- model.trigger('sync', model, resp, options);
1432
- };
1433
-
1434
- var error = options.error;
1435
- options.error = function(xhr) {
1436
- if (error) error(model, xhr, options);
1437
- model.trigger('error', model, xhr, options);
1438
- };
1439
-
1440
- // Make the request, allowing the user to override any Ajax options.
1441
- var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
1442
- model.trigger('request', model, xhr, options);
1443
- return xhr;
1444
- };
1445
-
1446
- // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
1447
- Backbone.ajax = function() {
1448
- return Backbone.$.ajax.apply(Backbone.$, arguments);
1449
- };
1450
-
1451
1515
  // Helpers
1452
1516
  // -------
1453
1517
 
@@ -1495,4 +1559,13 @@
1495
1559
  throw new Error('A "url" property or function must be specified');
1496
1560
  };
1497
1561
 
1562
+ // Wrap an optional error callback with a fallback error event.
1563
+ var wrapError = function (model, options) {
1564
+ var error = options.error;
1565
+ options.error = function(resp) {
1566
+ if (error) error(model, resp, options);
1567
+ model.trigger('error', model, resp, options);
1568
+ };
1569
+ };
1570
+
1498
1571
  }).call(this);