rocky 0.1.2.pre → 0.1.2

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,15 +1,15 @@
1
- // Backbone.js
2
- // ===========
1
+ // Backbone.js 1.0.0
3
2
 
4
- // > (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
5
- // > Backbone may be freely distributed under the MIT license.
6
- // > For all details and documentation: http://backbonejs.org
7
-
8
- // Initial Setup
9
- // -------------
3
+ // (c) 2010-2013 Jeremy Ashkenas, DocumentCloud Inc.
4
+ // Backbone may be freely distributed under the MIT license.
5
+ // For all details and documentation:
6
+ // http://backbonejs.org
10
7
 
11
8
  (function(){
12
9
 
10
+ // Initial Setup
11
+ // -------------
12
+
13
13
  // Save a reference to the global object (`window` in the browser, `exports`
14
14
  // on the server).
15
15
  var root = this;
@@ -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,74 +65,39 @@
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
109
71
  // succession.
72
+ //
73
+ // var object = {};
74
+ // _.extend(object, Backbone.Events);
75
+ // object.on('expand', function(){ alert('expanded'); });
76
+ // object.trigger('expand');
77
+ //
110
78
  var Events = Backbone.Events = {
111
79
 
112
- // Bind one or more space separated events, or an events map,
113
- // to a `callback` function. Passing `"all"` will bind the callback to
114
- // all events fired.
80
+ // Bind an event to a `callback` function. Passing `"all"` will bind
81
+ // the callback to all events fired.
115
82
  on: function(name, callback, context) {
116
- if (!(eventsApi(this, 'on', name, [callback, context]) && callback)) return this;
83
+ if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
117
84
  this._events || (this._events = {});
118
- var list = this._events[name] || (this._events[name] = []);
119
- 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});
120
87
  return this;
121
88
  },
122
89
 
123
- // 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
124
91
  // the callback is invoked, it will be removed.
125
92
  once: function(name, callback, context) {
126
- if (!(eventsApi(this, 'once', name, [callback, context]) && callback)) return this;
93
+ if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
127
94
  var self = this;
128
95
  var once = _.once(function() {
129
96
  self.off(name, once);
130
97
  callback.apply(this, arguments);
131
98
  });
132
99
  once._callback = callback;
133
- this.on(name, once, context);
134
- return this;
100
+ return this.on(name, once, context);
135
101
  },
136
102
 
137
103
  // Remove one or many callbacks. If `context` is null, removes all
@@ -139,7 +105,7 @@
139
105
  // callbacks for the event. If `name` is null, removes all bound
140
106
  // callbacks for all events.
141
107
  off: function(name, callback, context) {
142
- var list, ev, events, names, i, l, j, k;
108
+ var retain, ev, events, names, i, l, j, k;
143
109
  if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
144
110
  if (!name && !callback && !context) {
145
111
  this._events = {};
@@ -149,19 +115,18 @@
149
115
  names = name ? [name] : _.keys(this._events);
150
116
  for (i = 0, l = names.length; i < l; i++) {
151
117
  name = names[i];
152
- if (list = this._events[name]) {
153
- events = [];
118
+ if (events = this._events[name]) {
119
+ this._events[name] = retain = [];
154
120
  if (callback || context) {
155
- for (j = 0, k = list.length; j < k; j++) {
156
- ev = list[j];
157
- if ((callback && callback !== ev.callback &&
158
- 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) ||
159
124
  (context && context !== ev.context)) {
160
- events.push(ev);
125
+ retain.push(ev);
161
126
  }
162
127
  }
163
128
  }
164
- this._events[name] = events;
129
+ if (!retain.length) delete this._events[name];
165
130
  }
166
131
  }
167
132
 
@@ -183,35 +148,82 @@
183
148
  return this;
184
149
  },
185
150
 
186
- // An inversion-of-control version of `on`. Tell *this* object to listen to
187
- // an event in another object ... keeping track of what it's listening to.
188
- listenTo: function(obj, name, callback) {
189
- var listeners = this._listeners || (this._listeners = {});
190
- var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
191
- listeners[id] = obj;
192
- obj.on(name, typeof name === 'object' ? this : callback, this);
193
- return this;
194
- },
195
-
196
151
  // Tell this object to stop listening to either specific events ... or
197
152
  // to every object it's currently listening to.
198
153
  stopListening: function(obj, name, callback) {
199
154
  var listeners = this._listeners;
200
- if (!listeners) return;
201
- if (obj) {
202
- obj.off(name, typeof name === 'object' ? this : callback, this);
203
- if (!name && !callback) delete listeners[obj._listenerId];
204
- } else {
205
- if (typeof name === 'object') callback = this;
206
- for (var id in listeners) {
207
- listeners[id].off(name, callback, this);
208
- }
209
- 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];
210
162
  }
211
163
  return this;
212
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
+ }
213
209
  };
214
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
+
215
227
  // Aliases for backwards compatibility.
216
228
  Events.bind = Events.on;
217
229
  Events.unbind = Events.off;
@@ -223,15 +235,21 @@
223
235
  // Backbone.Model
224
236
  // --------------
225
237
 
226
- // 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`)
227
244
  // is automatically generated and assigned for you.
228
245
  var Model = Backbone.Model = function(attributes, options) {
229
246
  var defaults;
230
247
  var attrs = attributes || {};
248
+ options || (options = {});
231
249
  this.cid = _.uniqueId('c');
232
250
  this.attributes = {};
233
- if (options && options.collection) this.collection = options.collection;
234
- if (options && options.parse) attrs = this.parse(attrs, options) || {};
251
+ _.extend(this, _.pick(options, modelOptions));
252
+ if (options.parse) attrs = this.parse(attrs, options) || {};
235
253
  if (defaults = _.result(this, 'defaults')) {
236
254
  attrs = _.defaults({}, attrs, defaults);
237
255
  }
@@ -240,12 +258,18 @@
240
258
  this.initialize.apply(this, arguments);
241
259
  };
242
260
 
261
+ // A list of options to be attached directly to the model, if provided.
262
+ var modelOptions = ['url', 'urlRoot', 'collection'];
263
+
243
264
  // Attach all inheritable methods to the Model prototype.
244
265
  _.extend(Model.prototype, Events, {
245
266
 
246
267
  // A hash of attributes whose current and previous value differ.
247
268
  changed: null,
248
269
 
270
+ // The value returned during the last failed validation.
271
+ validationError: null,
272
+
249
273
  // The default name for the JSON `id` attribute is `"id"`. MongoDB and
250
274
  // CouchDB users may want to set this to `"_id"`.
251
275
  idAttribute: 'id',
@@ -259,7 +283,8 @@
259
283
  return _.clone(this.attributes);
260
284
  },
261
285
 
262
- // 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.
263
288
  sync: function() {
264
289
  return Backbone.sync.apply(this, arguments);
265
290
  },
@@ -280,10 +305,9 @@
280
305
  return this.get(attr) != null;
281
306
  },
282
307
 
283
- // ----------------------------------------------------------------------
284
-
285
- // Set a hash of model attributes on the object, firing `"change"` unless
286
- // 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.
287
311
  set: function(key, val, options) {
288
312
  var attr, attrs, unset, changes, silent, changing, prev, current;
289
313
  if (key == null) return this;
@@ -337,6 +361,8 @@
337
361
  }
338
362
  }
339
363
 
364
+ // You might be wondering why there's a `while` loop here. Changes can
365
+ // be recursively nested within `"change"` events.
340
366
  if (changing) return this;
341
367
  if (!silent) {
342
368
  while (this._pending) {
@@ -349,14 +375,13 @@
349
375
  return this;
350
376
  },
351
377
 
352
- // Remove an attribute from the model, firing `"change"` unless you choose
353
- // 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.
354
380
  unset: function(attr, options) {
355
381
  return this.set(attr, void 0, _.extend({}, options, {unset: true}));
356
382
  },
357
383
 
358
- // Clear all attributes on the model, firing `"change"` unless you choose
359
- // to silence it.
384
+ // Clear all attributes on the model, firing `"change"`.
360
385
  clear: function(options) {
361
386
  var attrs = {};
362
387
  for (var key in this.attributes) attrs[key] = void 0;
@@ -400,19 +425,20 @@
400
425
  return _.clone(this._previousAttributes);
401
426
  },
402
427
 
403
- // ---------------------------------------------------------------------
404
-
405
428
  // Fetch the model from the server. If the server's representation of the
406
- // model differs from its current attributes, they will be overriden,
429
+ // model differs from its current attributes, they will be overridden,
407
430
  // triggering a `"change"` event.
408
431
  fetch: function(options) {
409
432
  options = options ? _.clone(options) : {};
410
433
  if (options.parse === void 0) options.parse = true;
434
+ var model = this;
411
435
  var success = options.success;
412
- options.success = function(model, resp, options) {
436
+ options.success = function(resp) {
413
437
  if (!model.set(model.parse(resp, options), options)) return false;
414
438
  if (success) success(model, resp, options);
439
+ model.trigger('sync', model, resp, options);
415
440
  };
441
+ wrapError(this, options);
416
442
  return this.sync('read', this, options);
417
443
  },
418
444
 
@@ -420,7 +446,7 @@
420
446
  // If the server returns an attributes hash that differs, the model's
421
447
  // state will be `set` again.
422
448
  save: function(key, val, options) {
423
- var attrs, success, method, xhr, attributes = this.attributes;
449
+ var attrs, method, xhr, attributes = this.attributes;
424
450
 
425
451
  // Handle both `"key", value` and `{key: value}` -style arguments.
426
452
  if (key == null || typeof key === 'object') {
@@ -446,8 +472,9 @@
446
472
  // After a successful server-side save, the client is (optionally)
447
473
  // updated with the server-side state.
448
474
  if (options.parse === void 0) options.parse = true;
449
- success = options.success;
450
- options.success = function(model, resp, options) {
475
+ var model = this;
476
+ var success = options.success;
477
+ options.success = function(resp) {
451
478
  // Ensure attributes are restored during synchronous saves.
452
479
  model.attributes = attributes;
453
480
  var serverAttrs = model.parse(resp, options);
@@ -456,9 +483,10 @@
456
483
  return false;
457
484
  }
458
485
  if (success) success(model, resp, options);
486
+ model.trigger('sync', model, resp, options);
459
487
  };
488
+ wrapError(this, options);
460
489
 
461
- // Finish configuring and sending the Ajax request.
462
490
  method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
463
491
  if (method === 'patch') options.attrs = attrs;
464
492
  xhr = this.sync(method, this, options);
@@ -481,15 +509,17 @@
481
509
  model.trigger('destroy', model, model.collection, options);
482
510
  };
483
511
 
484
- options.success = function(model, resp, options) {
512
+ options.success = function(resp) {
485
513
  if (options.wait || model.isNew()) destroy();
486
514
  if (success) success(model, resp, options);
515
+ if (!model.isNew()) model.trigger('sync', model, resp, options);
487
516
  };
488
517
 
489
518
  if (this.isNew()) {
490
- options.success(this, null, options);
519
+ options.success();
491
520
  return false;
492
521
  }
522
+ wrapError(this, options);
493
523
 
494
524
  var xhr = this.sync('delete', this, options);
495
525
  if (!options.wait) destroy();
@@ -523,39 +553,61 @@
523
553
 
524
554
  // Check if the model is currently in a valid state.
525
555
  isValid: function(options) {
526
- return !this.validate || !this.validate(this.attributes, options);
556
+ return this._validate({}, _.extend(options || {}, { validate: true }));
527
557
  },
528
558
 
529
559
  // Run validation against the next complete set of model attributes,
530
- // returning `true` if all is well. Otherwise, fire a general
531
- // `"error"` event and call the error callback, if specified.
560
+ // returning `true` if all is well. Otherwise, fire an `"invalid"` event.
532
561
  _validate: function(attrs, options) {
533
562
  if (!options.validate || !this.validate) return true;
534
563
  attrs = _.extend({}, this.attributes, attrs);
535
564
  var error = this.validationError = this.validate(attrs, options) || null;
536
565
  if (!error) return true;
537
- this.trigger('invalid', this, error, options || {});
566
+ this.trigger('invalid', this, error, _.extend(options || {}, {validationError: error}));
538
567
  return false;
539
568
  }
540
569
 
541
570
  });
542
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
+
543
584
  // Backbone.Collection
544
585
  // -------------------
545
586
 
546
- // Provides a standard collection class for our sets of models, ordered
547
- // 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
548
596
  // its models in sort order, as they're added and removed.
549
597
  var Collection = Backbone.Collection = function(models, options) {
550
598
  options || (options = {});
599
+ if (options.url) this.url = options.url;
551
600
  if (options.model) this.model = options.model;
552
601
  if (options.comparator !== void 0) this.comparator = options.comparator;
553
- this.models = [];
554
602
  this._reset();
555
603
  this.initialize.apply(this, arguments);
556
604
  if (models) this.reset(models, _.extend({silent: true}, options));
557
605
  };
558
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
+
559
611
  // Define the Collection's inheritable methods.
560
612
  _.extend(Collection.prototype, Events, {
561
613
 
@@ -580,88 +632,118 @@
580
632
 
581
633
  // Add a model, or list of models to the set.
582
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) {
583
640
  models = _.isArray(models) ? models.slice() : [models];
584
641
  options || (options = {});
585
- var i, l, model, attrs, existing, doSort, add, at, sort, sortAttr;
586
- add = [];
587
- at = options.at;
588
- sort = this.comparator && (at == null) && options.sort != false;
589
- 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 = {};
590
673
 
591
674
  // Turn bare objects into model references, and prevent invalid models
592
675
  // from being added.
593
676
  for (i = 0, l = models.length; i < l; i++) {
594
- if (!(model = this._prepareModel(attrs = models[i], options))) {
595
- this.trigger('invalid', this, attrs, options);
596
- continue;
597
- }
677
+ if (!(model = this._prepareModel(models[i], options))) continue;
598
678
 
599
679
  // If a duplicate is found, prevent it from being added and
600
680
  // optionally merge it into the existing model.
601
681
  if (existing = this.get(model)) {
682
+ if (options.remove) modelMap[existing.cid] = true;
602
683
  if (options.merge) {
603
- existing.set(attrs === model ? model.attributes : attrs, options);
604
- if (sort && !doSort && existing.hasChanged(sortAttr)) doSort = true;
684
+ existing.set(model.attributes, options);
685
+ if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
605
686
  }
606
- continue;
607
- }
608
687
 
609
- // This is a new model, push it to the `add` list.
610
- add.push(model);
688
+ // This is a new model, push it to the `toAdd` list.
689
+ } else if (options.add) {
690
+ toAdd.push(model);
611
691
 
612
- // Listen to added models' events, and index models for lookup by
613
- // `id` and by `cid`.
614
- model.on('all', this._onModelEvent, this);
615
- this._byId[model.cid] = model;
616
- 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);
617
706
  }
618
707
 
619
708
  // See if sorting is needed, update `length` and splice in new models.
620
- if (add.length) {
621
- if (sort) doSort = true;
622
- this.length += add.length;
709
+ if (toAdd.length) {
710
+ if (sortable) sort = true;
711
+ this.length += toAdd.length;
623
712
  if (at != null) {
624
- splice.apply(this.models, [at, 0].concat(add));
713
+ splice.apply(this.models, [at, 0].concat(toAdd));
625
714
  } else {
626
- push.apply(this.models, add);
715
+ push.apply(this.models, toAdd);
627
716
  }
628
717
  }
629
718
 
630
719
  // Silently sort the collection if appropriate.
631
- if (doSort) this.sort({silent: true});
720
+ if (sort) this.sort({silent: true});
632
721
 
633
722
  if (options.silent) return this;
634
723
 
635
724
  // Trigger `add` events.
636
- for (i = 0, l = add.length; i < l; i++) {
637
- (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);
638
727
  }
639
728
 
640
729
  // Trigger `sort` if the collection was sorted.
641
- if (doSort) this.trigger('sort', this, options);
642
-
730
+ if (sort) this.trigger('sort', this, options);
643
731
  return this;
644
732
  },
645
733
 
646
- // Remove a model, or a list of models from the set.
647
- remove: function(models, options) {
648
- 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) {
649
739
  options || (options = {});
650
- var i, l, index, model;
651
- for (i = 0, l = models.length; i < l; i++) {
652
- model = this.get(models[i]);
653
- if (!model) continue;
654
- delete this._byId[model.id];
655
- delete this._byId[model.cid];
656
- index = this.indexOf(model);
657
- this.models.splice(index, 1);
658
- this.length--;
659
- if (!options.silent) {
660
- options.index = index;
661
- model.trigger('remove', model, this, options);
662
- }
663
- this._removeReference(model);
740
+ for (var i = 0, l = this.models.length; i < l; i++) {
741
+ this._removeReference(this.models[i]);
664
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);
665
747
  return this;
666
748
  },
667
749
 
@@ -701,8 +783,7 @@
701
783
  // Get a model from the set by id.
702
784
  get: function(obj) {
703
785
  if (obj == null) return void 0;
704
- this._idAttr || (this._idAttr = this.model.prototype.idAttribute);
705
- return this._byId[obj.id || obj.cid || obj[this._idAttr] || obj];
786
+ return this._byId[obj.id != null ? obj.id : obj.cid || obj];
706
787
  },
707
788
 
708
789
  // Get the model at the given index.
@@ -710,10 +791,11 @@
710
791
  return this.models[index];
711
792
  },
712
793
 
713
- // Return models with matching attributes. Useful for simple cases of `filter`.
714
- where: function(attrs) {
715
- if (_.isEmpty(attrs)) return [];
716
- 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) {
717
799
  for (var key in attrs) {
718
800
  if (attrs[key] !== model.get(key)) return false;
719
801
  }
@@ -721,13 +803,17 @@
721
803
  });
722
804
  },
723
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
+
724
812
  // Force the collection to re-sort itself. You don't need to call this under
725
813
  // normal circumstances, as the set will maintain sort order as each item
726
814
  // is added.
727
815
  sort: function(options) {
728
- if (!this.comparator) {
729
- throw new Error('Cannot sort a set without a comparator');
730
- }
816
+ if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
731
817
  options || (options = {});
732
818
 
733
819
  // Run sort based on type of `comparator`.
@@ -741,75 +827,36 @@
741
827
  return this;
742
828
  },
743
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
+
744
840
  // Pluck an attribute from each model in the collection.
745
841
  pluck: function(attr) {
746
842
  return _.invoke(this.models, 'get', attr);
747
843
  },
748
844
 
749
- // Smartly update a collection with a change set of models, adding,
750
- // removing, and merging as necessary.
751
- update: function(models, options) {
752
- options = _.extend({add: true, merge: true, remove: true}, options);
753
- if (options.parse) models = this.parse(models, options);
754
- var model, i, l, existing;
755
- var add = [], remove = [], modelMap = {};
756
-
757
- // Allow a single model (or no argument) to be passed.
758
- if (!_.isArray(models)) models = models ? [models] : [];
759
-
760
- // Proxy to `add` for this case, no need to iterate...
761
- if (options.add && !options.remove) return this.add(models, options);
762
-
763
- // Determine which models to add and merge, and which to remove.
764
- for (i = 0, l = models.length; i < l; i++) {
765
- model = models[i];
766
- existing = this.get(model);
767
- if (options.remove && existing) modelMap[existing.cid] = true;
768
- if ((options.add && !existing) || (options.merge && existing)) {
769
- add.push(model);
770
- }
771
- }
772
- if (options.remove) {
773
- for (i = 0, l = this.models.length; i < l; i++) {
774
- model = this.models[i];
775
- if (!modelMap[model.cid]) remove.push(model);
776
- }
777
- }
778
-
779
- // Remove models (if applicable) before we add and merge the rest.
780
- if (remove.length) this.remove(remove, options);
781
- if (add.length) this.add(add, options);
782
- return this;
783
- },
784
-
785
- // When you have more items than you want to add or remove individually,
786
- // you can reset the entire set with a new list of models, without firing
787
- // any `add` or `remove` events. Fires `reset` when finished.
788
- reset: function(models, options) {
789
- options || (options = {});
790
- if (options.parse) models = this.parse(models, options);
791
- for (var i = 0, l = this.models.length; i < l; i++) {
792
- this._removeReference(this.models[i]);
793
- }
794
- options.previousModels = this.models.slice();
795
- this._reset();
796
- if (models) this.add(models, _.extend({silent: true}, options));
797
- if (!options.silent) this.trigger('reset', this, options);
798
- return this;
799
- },
800
-
801
845
  // Fetch the default set of models for this collection, resetting the
802
- // collection when they arrive. If `update: true` is passed, the response
803
- // 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`.
804
848
  fetch: function(options) {
805
849
  options = options ? _.clone(options) : {};
806
850
  if (options.parse === void 0) options.parse = true;
807
851
  var success = options.success;
808
- options.success = function(collection, resp, options) {
809
- var method = options.update ? 'update' : 'reset';
852
+ var collection = this;
853
+ options.success = function(resp) {
854
+ var method = options.reset ? 'reset' : 'set';
810
855
  collection[method](resp, options);
811
856
  if (success) success(collection, resp, options);
857
+ collection.trigger('sync', collection, resp, options);
812
858
  };
859
+ wrapError(this, options);
813
860
  return this.sync('read', this, options);
814
861
  },
815
862
 
@@ -822,7 +869,7 @@
822
869
  if (!options.wait) this.add(model, options);
823
870
  var collection = this;
824
871
  var success = options.success;
825
- options.success = function(model, resp, options) {
872
+ options.success = function(resp) {
826
873
  if (options.wait) collection.add(model, options);
827
874
  if (success) success(model, resp, options);
828
875
  };
@@ -841,14 +888,16 @@
841
888
  return new this.constructor(this.models);
842
889
  },
843
890
 
844
- // 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.
845
893
  _reset: function() {
846
894
  this.length = 0;
847
- this.models.length = 0;
895
+ this.models = [];
848
896
  this._byId = {};
849
897
  },
850
898
 
851
- // 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.
852
901
  _prepareModel: function(attrs, options) {
853
902
  if (attrs instanceof Model) {
854
903
  if (!attrs.collection) attrs.collection = this;
@@ -857,11 +906,14 @@
857
906
  options || (options = {});
858
907
  options.collection = this;
859
908
  var model = new this.model(attrs, options);
860
- if (!model._validate(attrs, options)) return false;
909
+ if (!model._validate(attrs, options)) {
910
+ this.trigger('invalid', this, attrs, options);
911
+ return false;
912
+ }
861
913
  return model;
862
914
  },
863
915
 
864
- // Internal method to remove a model's ties to a collection.
916
+ // Internal method to sever a model's ties to a collection.
865
917
  _removeReference: function(model) {
866
918
  if (this === model.collection) delete model.collection;
867
919
  model.off('all', this._onModelEvent, this);
@@ -879,19 +931,13 @@
879
931
  if (model.id != null) this._byId[model.id] = model;
880
932
  }
881
933
  this.trigger.apply(this, arguments);
882
- },
883
-
884
- sortedIndex: function (model, value, context) {
885
- value || (value = this.comparator);
886
- var iterator = _.isFunction(value) ? value : function(model) {
887
- return model.get(value);
888
- };
889
- return _.sortedIndex(this.models, model, iterator, context);
890
934
  }
891
935
 
892
936
  });
893
937
 
894
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:
895
941
  var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
896
942
  'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
897
943
  'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
@@ -921,50 +967,295 @@
921
967
  };
922
968
  });
923
969
 
924
- // Backbone.Router
925
- // ---------------
970
+ // Backbone.View
971
+ // -------------
926
972
 
927
- // Routers map faux-URLs to actions, and fire events when routes are
928
- // matched. Creating a new one sets its `routes` hash, if not set statically.
929
- var Router = Backbone.Router = function(options) {
930
- options || (options = {});
931
- if (options.routes) this.routes = options.routes;
932
- 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();
933
987
  this.initialize.apply(this, arguments);
988
+ this.delegateEvents();
934
989
  };
935
990
 
936
- // Cached regular expressions for matching named param parts and splatted
937
- // parts of route strings.
938
- var optionalParam = /\((.*?)\)/g;
939
- var namedParam = /(\(\?)?:\w+/g;
940
- var splatParam = /\*\w+/g;
941
- var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
991
+ // Cached regex to split keys for `delegate`.
992
+ var delegateEventSplitter = /^(\S+)\s*(.*)$/;
942
993
 
943
- // Set up all inheritable **Backbone.Router** properties and methods.
944
- _.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
+ },
945
1008
 
946
1009
  // Initialize is an empty function by default. Override it with your own
947
1010
  // initialization logic.
948
1011
  initialize: function(){},
949
1012
 
950
- // Manually bind a single named route to a callback function.
951
- route: function(route, name, callback) {
952
- if (!_.isRegExp(route)) route = this._routeToRegExp(route);
953
- if (!callback) callback = this[name];
954
- Backbone.history.route(route, _.bind(function(fragment) {
955
- var args = this._extractParameters(route, fragment);
956
- callback && callback.apply(this, args);
957
- this.trigger.apply(this, ['route:' + name].concat(args));
958
- this.trigger('route', name, args);
959
- Backbone.history.trigger('route', this, name, args);
960
- }, 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() {
961
1017
  return this;
962
1018
  },
963
1019
 
964
- // Simple proxy to `Backbone.history` to save a fragment into the history.
965
- navigate: function(fragment, options) {
966
- Backbone.history.navigate(fragment, options);
967
- return this;
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;
968
1259
  },
969
1260
 
970
1261
  // Bind all defined routes to `Backbone.history`. We have to reverse the
@@ -972,6 +1263,7 @@
972
1263
  // routes can be defined at the bottom of the route map.
973
1264
  _bindRoutes: function() {
974
1265
  if (!this.routes) return;
1266
+ this.routes = _.result(this, 'routes');
975
1267
  var route, routes = _.keys(this.routes);
976
1268
  while ((route = routes.pop()) != null) {
977
1269
  this.route(route, this.routes[route]);
@@ -991,9 +1283,13 @@
991
1283
  },
992
1284
 
993
1285
  // Given a route, and a URL fragment that it matches, return the array of
994
- // extracted parameters.
1286
+ // extracted decoded parameters. Empty or unmatched parameters will be
1287
+ // treated as `null` to normalize cross-browser behavior.
995
1288
  _extractParameters: function(route, fragment) {
996
- 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
+ });
997
1293
  }
998
1294
 
999
1295
  });
@@ -1001,8 +1297,11 @@
1001
1297
  // Backbone.History
1002
1298
  // ----------------
1003
1299
 
1004
- // Handles cross-browser history management, based on URL fragments. If the
1005
- // 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.
1006
1305
  var History = Backbone.History = function() {
1007
1306
  this.handlers = [];
1008
1307
  _.bindAll(this, 'checkUrl');
@@ -1213,230 +1512,6 @@
1213
1512
  // Create the default Backbone.history.
1214
1513
  Backbone.history = new History;
1215
1514
 
1216
- // Backbone.View
1217
- // -------------
1218
-
1219
- // Creating a Backbone.View creates its initial element outside of the DOM,
1220
- // if an existing element is not provided...
1221
- var View = Backbone.View = function(options) {
1222
- this.cid = _.uniqueId('view');
1223
- this._configure(options || {});
1224
- this._ensureElement();
1225
- this.initialize.apply(this, arguments);
1226
- this.delegateEvents();
1227
- };
1228
-
1229
- // Cached regex to split keys for `delegate`.
1230
- var delegateEventSplitter = /^(\S+)\s*(.*)$/;
1231
-
1232
- // List of view options to be merged as properties.
1233
- var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
1234
-
1235
- // Set up all inheritable **Backbone.View** properties and methods.
1236
- _.extend(View.prototype, Events, {
1237
-
1238
- // The default `tagName` of a View's element is `"div"`.
1239
- tagName: 'div',
1240
-
1241
- // jQuery delegate for element lookup, scoped to DOM elements within the
1242
- // current view. This should be prefered to global lookups where possible.
1243
- $: function(selector) {
1244
- return this.$el.find(selector);
1245
- },
1246
-
1247
- // Initialize is an empty function by default. Override it with your own
1248
- // initialization logic.
1249
- initialize: function(){},
1250
-
1251
- // **render** is the core function that your view should override, in order
1252
- // to populate its element (`this.el`), with the appropriate HTML. The
1253
- // convention is for **render** to always return `this`.
1254
- render: function() {
1255
- return this;
1256
- },
1257
-
1258
- // Remove this view by taking the element out of the DOM, and removing any
1259
- // applicable Backbone.Events listeners.
1260
- remove: function() {
1261
- this.$el.remove();
1262
- this.stopListening();
1263
- return this;
1264
- },
1265
-
1266
- // Change the view's element (`this.el` property), including event
1267
- // re-delegation.
1268
- setElement: function(element, delegate) {
1269
- if (this.$el) this.undelegateEvents();
1270
- this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
1271
- this.el = this.$el[0];
1272
- if (delegate !== false) this.delegateEvents();
1273
- return this;
1274
- },
1275
-
1276
- // Set callbacks, where `this.events` is a hash of
1277
- //
1278
- // *{"event selector": "callback"}*
1279
- //
1280
- // {
1281
- // 'mousedown .title': 'edit',
1282
- // 'click .button': 'save'
1283
- // 'click .open': function(e) { ... }
1284
- // }
1285
- //
1286
- // pairs. Callbacks will be bound to the view, with `this` set properly.
1287
- // Uses event delegation for efficiency.
1288
- // Omitting the selector binds the event to `this.el`.
1289
- // This only works for delegate-able events: not `focus`, `blur`, and
1290
- // not `change`, `submit`, and `reset` in Internet Explorer.
1291
- delegateEvents: function(events) {
1292
- if (!(events || (events = _.result(this, 'events')))) return;
1293
- this.undelegateEvents();
1294
- for (var key in events) {
1295
- var method = events[key];
1296
- if (!_.isFunction(method)) method = this[events[key]];
1297
- if (!method) throw new Error('Method "' + events[key] + '" does not exist');
1298
- var match = key.match(delegateEventSplitter);
1299
- var eventName = match[1], selector = match[2];
1300
- method = _.bind(method, this);
1301
- eventName += '.delegateEvents' + this.cid;
1302
- if (selector === '') {
1303
- this.$el.on(eventName, method);
1304
- } else {
1305
- this.$el.on(eventName, selector, method);
1306
- }
1307
- }
1308
- },
1309
-
1310
- // Clears all callbacks previously bound to the view with `delegateEvents`.
1311
- // You usually don't need to use this, but may wish to if you have multiple
1312
- // Backbone views attached to the same DOM element.
1313
- undelegateEvents: function() {
1314
- this.$el.off('.delegateEvents' + this.cid);
1315
- },
1316
-
1317
- // Performs the initial configuration of a View with a set of options.
1318
- // Keys with special meaning *(model, collection, id, className)*, are
1319
- // attached directly to the view.
1320
- _configure: function(options) {
1321
- if (this.options) options = _.extend({}, _.result(this, 'options'), options);
1322
- _.extend(this, _.pick(options, viewOptions));
1323
- this.options = options;
1324
- },
1325
-
1326
- // Ensure that the View has a DOM element to render into.
1327
- // If `this.el` is a string, pass it through `$()`, take the first
1328
- // matching element, and re-assign it to `el`. Otherwise, create
1329
- // an element from the `id`, `className` and `tagName` properties.
1330
- _ensureElement: function() {
1331
- if (!this.el) {
1332
- var attrs = _.extend({}, _.result(this, 'attributes'));
1333
- if (this.id) attrs.id = _.result(this, 'id');
1334
- if (this.className) attrs['class'] = _.result(this, 'className');
1335
- var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
1336
- this.setElement($el, false);
1337
- } else {
1338
- this.setElement(_.result(this, 'el'), false);
1339
- }
1340
- }
1341
-
1342
- });
1343
-
1344
- // Backbone.sync
1345
- // -------------
1346
-
1347
- // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1348
- var methodMap = {
1349
- 'create': 'POST',
1350
- 'update': 'PUT',
1351
- 'patch': 'PATCH',
1352
- 'delete': 'DELETE',
1353
- 'read': 'GET'
1354
- };
1355
-
1356
- // Override this function to change the manner in which Backbone persists
1357
- // models to the server. You will be passed the type of request, and the
1358
- // model in question. By default, makes a RESTful Ajax request
1359
- // to the model's `url()`. Some possible customizations could be:
1360
- //
1361
- // * Use `setTimeout` to batch rapid-fire updates into a single request.
1362
- // * Send up the models as XML instead of JSON.
1363
- // * Persist models via WebSockets instead of Ajax.
1364
- //
1365
- // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
1366
- // as `POST`, with a `_method` parameter containing the true HTTP method,
1367
- // as well as all requests with the body as `application/x-www-form-urlencoded`
1368
- // instead of `application/json` with the model in a param named `model`.
1369
- // Useful when interfacing with server-side languages like **PHP** that make
1370
- // it difficult to read the body of `PUT` requests.
1371
- Backbone.sync = function(method, model, options) {
1372
- var type = methodMap[method];
1373
-
1374
- // Default options, unless specified.
1375
- _.defaults(options || (options = {}), {
1376
- emulateHTTP: Backbone.emulateHTTP,
1377
- emulateJSON: Backbone.emulateJSON
1378
- });
1379
-
1380
- // Default JSON-request options.
1381
- var params = {type: type, dataType: 'json'};
1382
-
1383
- // Ensure that we have a URL.
1384
- if (!options.url) {
1385
- params.url = _.result(model, 'url') || urlError();
1386
- }
1387
-
1388
- // Ensure that we have the appropriate request data.
1389
- if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
1390
- params.contentType = 'application/json';
1391
- params.data = JSON.stringify(options.attrs || model.toJSON(options));
1392
- }
1393
-
1394
- // For older servers, emulate JSON by encoding the request into an HTML-form.
1395
- if (options.emulateJSON) {
1396
- params.contentType = 'application/x-www-form-urlencoded';
1397
- params.data = params.data ? {model: params.data} : {};
1398
- }
1399
-
1400
- // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1401
- // And an `X-HTTP-Method-Override` header.
1402
- if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
1403
- params.type = 'POST';
1404
- if (options.emulateJSON) params.data._method = type;
1405
- var beforeSend = options.beforeSend;
1406
- options.beforeSend = function(xhr) {
1407
- xhr.setRequestHeader('X-HTTP-Method-Override', type);
1408
- if (beforeSend) return beforeSend.apply(this, arguments);
1409
- };
1410
- }
1411
-
1412
- // Don't process data on a non-GET request.
1413
- if (params.type !== 'GET' && !options.emulateJSON) {
1414
- params.processData = false;
1415
- }
1416
-
1417
- var success = options.success;
1418
- options.success = function(resp) {
1419
- if (success) success(model, resp, options);
1420
- model.trigger('sync', model, resp, options);
1421
- };
1422
-
1423
- var error = options.error;
1424
- options.error = function(xhr) {
1425
- if (error) error(model, xhr, options);
1426
- model.trigger('error', model, xhr, options);
1427
- };
1428
-
1429
- // Make the request, allowing the user to override any Ajax options.
1430
- var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
1431
- model.trigger('request', model, xhr, options);
1432
- return xhr;
1433
- };
1434
-
1435
- // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
1436
- Backbone.ajax = function() {
1437
- return Backbone.$.ajax.apply(Backbone.$, arguments);
1438
- };
1439
-
1440
1515
  // Helpers
1441
1516
  // -------
1442
1517
 
@@ -1484,4 +1559,13 @@
1484
1559
  throw new Error('A "url" property or function must be specified');
1485
1560
  };
1486
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
+
1487
1571
  }).call(this);