backbone-rails 0.9.1 → 0.9.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.
Files changed (2) hide show
  1. data/vendor/assets/javascripts/backbone.js +280 -139
  2. metadata +4 -4
@@ -1,4 +1,4 @@
1
- // Backbone.js 0.9.1
1
+ // Backbone.js 0.9.2
2
2
 
3
3
  // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
4
4
  // Backbone may be freely distributed under the MIT license.
@@ -32,7 +32,7 @@
32
32
  }
33
33
 
34
34
  // Current version of the library. Keep in sync with `package.json`.
35
- Backbone.VERSION = '0.9.1';
35
+ Backbone.VERSION = '0.9.2';
36
36
 
37
37
  // Require Underscore, if we're on the server, and it's not already present.
38
38
  var _ = root._;
@@ -71,6 +71,9 @@
71
71
  // Backbone.Events
72
72
  // -----------------
73
73
 
74
+ // Regular expression used to split event strings
75
+ var eventSplitter = /\s+/;
76
+
74
77
  // A module that can be mixed in to *any object* in order to provide it with
75
78
  // custom events. You may bind with `on` or remove with `off` callback functions
76
79
  // to an event; trigger`-ing an event fires all callbacks in succession.
@@ -80,89 +83,110 @@
80
83
  // object.on('expand', function(){ alert('expanded'); });
81
84
  // object.trigger('expand');
82
85
  //
83
- Backbone.Events = {
86
+ var Events = Backbone.Events = {
84
87
 
85
- // Bind an event, specified by a string name, `ev`, to a `callback`
88
+ // Bind one or more space separated events, `events`, to a `callback`
86
89
  // function. Passing `"all"` will bind the callback to all events fired.
87
90
  on: function(events, callback, context) {
88
- var ev;
89
- events = events.split(/\s+/);
90
- var calls = this._callbacks || (this._callbacks = {});
91
- while (ev = events.shift()) {
92
- // Create an immutable callback list, allowing traversal during
93
- // modification. The tail is an empty object that will always be used
94
- // as the next node.
95
- var list = calls[ev] || (calls[ev] = {});
96
- var tail = list.tail || (list.tail = list.next = {});
97
- tail.callback = callback;
98
- tail.context = context;
99
- list.tail = tail.next = {};
91
+
92
+ var calls, event, node, tail, list;
93
+ if (!callback) return this;
94
+ events = events.split(eventSplitter);
95
+ calls = this._callbacks || (this._callbacks = {});
96
+
97
+ // Create an immutable callback list, allowing traversal during
98
+ // modification. The tail is an empty object that will always be used
99
+ // as the next node.
100
+ while (event = events.shift()) {
101
+ list = calls[event];
102
+ node = list ? list.tail : {};
103
+ node.next = tail = {};
104
+ node.context = context;
105
+ node.callback = callback;
106
+ calls[event] = {tail: tail, next: list ? list.next : node};
100
107
  }
108
+
101
109
  return this;
102
110
  },
103
111
 
104
112
  // Remove one or many callbacks. If `context` is null, removes all callbacks
105
113
  // with that function. If `callback` is null, removes all callbacks for the
106
- // event. If `ev` is null, removes all bound callbacks for all events.
114
+ // event. If `events` is null, removes all bound callbacks for all events.
107
115
  off: function(events, callback, context) {
108
- var ev, calls, node;
109
- if (!events) {
116
+ var event, calls, node, tail, cb, ctx;
117
+
118
+ // No events, or removing *all* events.
119
+ if (!(calls = this._callbacks)) return;
120
+ if (!(events || callback || context)) {
110
121
  delete this._callbacks;
111
- } else if (calls = this._callbacks) {
112
- events = events.split(/\s+/);
113
- while (ev = events.shift()) {
114
- node = calls[ev];
115
- delete calls[ev];
116
- if (!callback || !node) continue;
117
- // Create a new list, omitting the indicated event/context pairs.
118
- while ((node = node.next) && node.next) {
119
- if (node.callback === callback &&
120
- (!context || node.context === context)) continue;
121
- this.on(ev, node.callback, node.context);
122
+ return this;
123
+ }
124
+
125
+ // Loop through the listed events and contexts, splicing them out of the
126
+ // linked list of callbacks if appropriate.
127
+ events = events ? events.split(eventSplitter) : _.keys(calls);
128
+ while (event = events.shift()) {
129
+ node = calls[event];
130
+ delete calls[event];
131
+ if (!node || !(callback || context)) continue;
132
+ // Create a new list, omitting the indicated callbacks.
133
+ tail = node.tail;
134
+ while ((node = node.next) !== tail) {
135
+ cb = node.callback;
136
+ ctx = node.context;
137
+ if ((callback && cb !== callback) || (context && ctx !== context)) {
138
+ this.on(event, cb, ctx);
122
139
  }
123
140
  }
124
141
  }
142
+
125
143
  return this;
126
144
  },
127
145
 
128
- // Trigger an event, firing all bound callbacks. Callbacks are passed the
129
- // same arguments as `trigger` is, apart from the event name.
130
- // Listening for `"all"` passes the true event name as the first argument.
146
+ // Trigger one or many events, firing all bound callbacks. Callbacks are
147
+ // passed the same arguments as `trigger` is, apart from the event name
148
+ // (unless you're listening on `"all"`, which will cause your callback to
149
+ // receive the true name of the event as the first argument).
131
150
  trigger: function(events) {
132
151
  var event, node, calls, tail, args, all, rest;
133
152
  if (!(calls = this._callbacks)) return this;
134
- all = calls['all'];
135
- (events = events.split(/\s+/)).push(null);
136
- // Save references to the current heads & tails.
137
- while (event = events.shift()) {
138
- if (all) events.push({next: all.next, tail: all.tail, event: event});
139
- if (!(node = calls[event])) continue;
140
- events.push({next: node.next, tail: node.tail});
141
- }
142
- // Traverse each list, stopping when the saved tail is reached.
153
+ all = calls.all;
154
+ events = events.split(eventSplitter);
143
155
  rest = slice.call(arguments, 1);
144
- while (node = events.pop()) {
145
- tail = node.tail;
146
- args = node.event ? [node.event].concat(rest) : rest;
147
- while ((node = node.next) !== tail) {
148
- node.callback.apply(node.context || this, args);
156
+
157
+ // For each event, walk through the linked list of callbacks twice,
158
+ // first to trigger the event, then to trigger any `"all"` callbacks.
159
+ while (event = events.shift()) {
160
+ if (node = calls[event]) {
161
+ tail = node.tail;
162
+ while ((node = node.next) !== tail) {
163
+ node.callback.apply(node.context || this, rest);
164
+ }
165
+ }
166
+ if (node = all) {
167
+ tail = node.tail;
168
+ args = [event].concat(rest);
169
+ while ((node = node.next) !== tail) {
170
+ node.callback.apply(node.context || this, args);
171
+ }
149
172
  }
150
173
  }
174
+
151
175
  return this;
152
176
  }
153
177
 
154
178
  };
155
179
 
156
180
  // Aliases for backwards compatibility.
157
- Backbone.Events.bind = Backbone.Events.on;
158
- Backbone.Events.unbind = Backbone.Events.off;
181
+ Events.bind = Events.on;
182
+ Events.unbind = Events.off;
159
183
 
160
184
  // Backbone.Model
161
185
  // --------------
162
186
 
163
187
  // Create a new model, with defined attributes. A client id (`cid`)
164
188
  // is automatically generated and assigned for you.
165
- Backbone.Model = function(attributes, options) {
189
+ var Model = Backbone.Model = function(attributes, options) {
166
190
  var defaults;
167
191
  attributes || (attributes = {});
168
192
  if (options && options.parse) attributes = this.parse(attributes);
@@ -173,16 +197,31 @@
173
197
  this.attributes = {};
174
198
  this._escapedAttributes = {};
175
199
  this.cid = _.uniqueId('c');
176
- if (!this.set(attributes, {silent: true})) {
177
- throw new Error("Can't create an invalid model");
178
- }
179
- delete this._changed;
200
+ this.changed = {};
201
+ this._silent = {};
202
+ this._pending = {};
203
+ this.set(attributes, {silent: true});
204
+ // Reset change tracking.
205
+ this.changed = {};
206
+ this._silent = {};
207
+ this._pending = {};
180
208
  this._previousAttributes = _.clone(this.attributes);
181
209
  this.initialize.apply(this, arguments);
182
210
  };
183
211
 
184
212
  // Attach all inheritable methods to the Model prototype.
185
- _.extend(Backbone.Model.prototype, Backbone.Events, {
213
+ _.extend(Model.prototype, Events, {
214
+
215
+ // A hash of attributes whose current and previous value differ.
216
+ changed: null,
217
+
218
+ // A hash of attributes that have silently changed since the last time
219
+ // `change` was called. Will become pending attributes on the next call.
220
+ _silent: null,
221
+
222
+ // A hash of attributes that have changed since the last `'change'` event
223
+ // began.
224
+ _pending: null,
186
225
 
187
226
  // The default name for the JSON `id` attribute is `"id"`. MongoDB and
188
227
  // CouchDB users may want to set this to `"_id"`.
@@ -193,7 +232,7 @@
193
232
  initialize: function(){},
194
233
 
195
234
  // Return a copy of the model's `attributes` object.
196
- toJSON: function() {
235
+ toJSON: function(options) {
197
236
  return _.clone(this.attributes);
198
237
  },
199
238
 
@@ -206,20 +245,22 @@
206
245
  escape: function(attr) {
207
246
  var html;
208
247
  if (html = this._escapedAttributes[attr]) return html;
209
- var val = this.attributes[attr];
248
+ var val = this.get(attr);
210
249
  return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
211
250
  },
212
251
 
213
252
  // Returns `true` if the attribute contains a value that is not null
214
253
  // or undefined.
215
254
  has: function(attr) {
216
- return this.attributes[attr] != null;
255
+ return this.get(attr) != null;
217
256
  },
218
257
 
219
258
  // Set a hash of model attributes on the object, firing `"change"` unless
220
259
  // you choose to silence it.
221
260
  set: function(key, value, options) {
222
261
  var attrs, attr, val;
262
+
263
+ // Handle both `"key", value` and `{key: value}` -style arguments.
223
264
  if (_.isObject(key) || key == null) {
224
265
  attrs = key;
225
266
  options = value;
@@ -231,7 +272,7 @@
231
272
  // Extract attributes and options.
232
273
  options || (options = {});
233
274
  if (!attrs) return this;
234
- if (attrs instanceof Backbone.Model) attrs = attrs.attributes;
275
+ if (attrs instanceof Model) attrs = attrs.attributes;
235
276
  if (options.unset) for (attr in attrs) attrs[attr] = void 0;
236
277
 
237
278
  // Run validation.
@@ -240,33 +281,37 @@
240
281
  // Check for changes of `id`.
241
282
  if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
242
283
 
284
+ var changes = options.changes = {};
243
285
  var now = this.attributes;
244
286
  var escaped = this._escapedAttributes;
245
287
  var prev = this._previousAttributes || {};
246
- var alreadySetting = this._setting;
247
- this._changed || (this._changed = {});
248
- this._setting = true;
249
288
 
250
- // Update attributes.
289
+ // For each `set` attribute...
251
290
  for (attr in attrs) {
252
291
  val = attrs[attr];
253
- if (!_.isEqual(now[attr], val)) delete escaped[attr];
254
- options.unset ? delete now[attr] : now[attr] = val;
255
- if (this._changing && !_.isEqual(this._changed[attr], val)) {
256
- this.trigger('change:' + attr, this, val, options);
257
- this._moreChanges = true;
292
+
293
+ // If the new and current value differ, record the change.
294
+ if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
295
+ delete escaped[attr];
296
+ (options.silent ? this._silent : changes)[attr] = true;
258
297
  }
259
- delete this._changed[attr];
298
+
299
+ // Update or delete the current value.
300
+ options.unset ? delete now[attr] : now[attr] = val;
301
+
302
+ // If the new and previous value differ, record the change. If not,
303
+ // then remove changes for this attribute.
260
304
  if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
261
- this._changed[attr] = val;
305
+ this.changed[attr] = val;
306
+ if (!options.silent) this._pending[attr] = true;
307
+ } else {
308
+ delete this.changed[attr];
309
+ delete this._pending[attr];
262
310
  }
263
311
  }
264
312
 
265
- // Fire the `"change"` events, if the model has been changed.
266
- if (!alreadySetting) {
267
- if (!options.silent && this.hasChanged()) this.change(options);
268
- this._setting = false;
269
- }
313
+ // Fire the `"change"` events.
314
+ if (!options.silent) this.change(options);
270
315
  return this;
271
316
  },
272
317
 
@@ -304,6 +349,8 @@
304
349
  // state will be `set` again.
305
350
  save: function(key, value, options) {
306
351
  var attrs, current;
352
+
353
+ // Handle both `("key", value)` and `({key: value})` -style calls.
307
354
  if (_.isObject(key) || key == null) {
308
355
  attrs = key;
309
356
  options = value;
@@ -311,18 +358,30 @@
311
358
  attrs = {};
312
359
  attrs[key] = value;
313
360
  }
314
-
315
361
  options = options ? _.clone(options) : {};
316
- if (options.wait) current = _.clone(this.attributes);
362
+
363
+ // If we're "wait"-ing to set changed attributes, validate early.
364
+ if (options.wait) {
365
+ if (!this._validate(attrs, options)) return false;
366
+ current = _.clone(this.attributes);
367
+ }
368
+
369
+ // Regular saves `set` attributes before persisting to the server.
317
370
  var silentOptions = _.extend({}, options, {silent: true});
318
371
  if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
319
372
  return false;
320
373
  }
374
+
375
+ // After a successful server-side save, the client is (optionally)
376
+ // updated with the server-side state.
321
377
  var model = this;
322
378
  var success = options.success;
323
379
  options.success = function(resp, status, xhr) {
324
380
  var serverAttrs = model.parse(resp, xhr);
325
- if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
381
+ if (options.wait) {
382
+ delete options.wait;
383
+ serverAttrs = _.extend(attrs || {}, serverAttrs);
384
+ }
326
385
  if (!model.set(serverAttrs, options)) return false;
327
386
  if (success) {
328
387
  success(model, resp);
@@ -330,6 +389,8 @@
330
389
  model.trigger('sync', model, resp, options);
331
390
  }
332
391
  };
392
+
393
+ // Finish configuring and sending the Ajax request.
333
394
  options.error = Backbone.wrapError(options.error, model, options);
334
395
  var method = this.isNew() ? 'create' : 'update';
335
396
  var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
@@ -349,7 +410,11 @@
349
410
  model.trigger('destroy', model, model.collection, options);
350
411
  };
351
412
 
352
- if (this.isNew()) return triggerDestroy();
413
+ if (this.isNew()) {
414
+ triggerDestroy();
415
+ return false;
416
+ }
417
+
353
418
  options.success = function(resp) {
354
419
  if (options.wait) triggerDestroy();
355
420
  if (success) {
@@ -358,6 +423,7 @@
358
423
  model.trigger('sync', model, resp, options);
359
424
  }
360
425
  };
426
+
361
427
  options.error = Backbone.wrapError(options.error, model, options);
362
428
  var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);
363
429
  if (!options.wait) triggerDestroy();
@@ -368,7 +434,7 @@
368
434
  // using Backbone's restful methods, override this to change the endpoint
369
435
  // that will be called.
370
436
  url: function() {
371
- var base = getValue(this.collection, 'url') || getValue(this, 'urlRoot') || urlError();
437
+ var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
372
438
  if (this.isNew()) return base;
373
439
  return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
374
440
  },
@@ -393,18 +459,33 @@
393
459
  // a `"change:attribute"` event for each changed attribute.
394
460
  // Calling this will cause all objects observing the model to update.
395
461
  change: function(options) {
396
- if (this._changing || !this.hasChanged()) return this;
462
+ options || (options = {});
463
+ var changing = this._changing;
397
464
  this._changing = true;
398
- this._moreChanges = true;
399
- for (var attr in this._changed) {
400
- this.trigger('change:' + attr, this, this._changed[attr], options);
465
+
466
+ // Silent changes become pending changes.
467
+ for (var attr in this._silent) this._pending[attr] = true;
468
+
469
+ // Silent changes are triggered.
470
+ var changes = _.extend({}, options.changes, this._silent);
471
+ this._silent = {};
472
+ for (var attr in changes) {
473
+ this.trigger('change:' + attr, this, this.get(attr), options);
401
474
  }
402
- while (this._moreChanges) {
403
- this._moreChanges = false;
475
+ if (changing) return this;
476
+
477
+ // Continue firing `"change"` events while there are pending changes.
478
+ while (!_.isEmpty(this._pending)) {
479
+ this._pending = {};
404
480
  this.trigger('change', this, options);
481
+ // Pending and silent changes still remain.
482
+ for (var attr in this.changed) {
483
+ if (this._pending[attr] || this._silent[attr]) continue;
484
+ delete this.changed[attr];
485
+ }
486
+ this._previousAttributes = _.clone(this.attributes);
405
487
  }
406
- this._previousAttributes = _.clone(this.attributes);
407
- delete this._changed;
488
+
408
489
  this._changing = false;
409
490
  return this;
410
491
  },
@@ -412,8 +493,8 @@
412
493
  // Determine if the model has changed since the last `"change"` event.
413
494
  // If you specify an attribute name, determine if that attribute has changed.
414
495
  hasChanged: function(attr) {
415
- if (!arguments.length) return !_.isEmpty(this._changed);
416
- return this._changed && _.has(this._changed, attr);
496
+ if (!arguments.length) return !_.isEmpty(this.changed);
497
+ return _.has(this.changed, attr);
417
498
  },
418
499
 
419
500
  // Return an object containing all the attributes that have changed, or
@@ -423,7 +504,7 @@
423
504
  // You can also pass an attributes object to diff against the model,
424
505
  // determining if there *would be* a change.
425
506
  changedAttributes: function(diff) {
426
- if (!diff) return this.hasChanged() ? _.clone(this._changed) : false;
507
+ if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
427
508
  var val, changed = false, old = this._previousAttributes;
428
509
  for (var attr in diff) {
429
510
  if (_.isEqual(old[attr], (val = diff[attr]))) continue;
@@ -451,9 +532,9 @@
451
532
  return !this.validate(this.attributes);
452
533
  },
453
534
 
454
- // Run validation against a set of incoming attributes, returning `true`
455
- // if all is well. If a specific `error` callback has been passed,
456
- // call that instead of firing the general `"error"` event.
535
+ // Run validation against the next complete set of model attributes,
536
+ // returning `true` if all is well. If a specific `error` callback has
537
+ // been passed, call that instead of firing the general `"error"` event.
457
538
  _validate: function(attrs, options) {
458
539
  if (options.silent || !this.validate) return true;
459
540
  attrs = _.extend({}, this.attributes, attrs);
@@ -475,8 +556,9 @@
475
556
  // Provides a standard collection class for our sets of models, ordered
476
557
  // or unordered. If a `comparator` is specified, the Collection will maintain
477
558
  // its models in sort order, as they're added and removed.
478
- Backbone.Collection = function(models, options) {
559
+ var Collection = Backbone.Collection = function(models, options) {
479
560
  options || (options = {});
561
+ if (options.model) this.model = options.model;
480
562
  if (options.comparator) this.comparator = options.comparator;
481
563
  this._reset();
482
564
  this.initialize.apply(this, arguments);
@@ -484,11 +566,11 @@
484
566
  };
485
567
 
486
568
  // Define the Collection's inheritable methods.
487
- _.extend(Backbone.Collection.prototype, Backbone.Events, {
569
+ _.extend(Collection.prototype, Events, {
488
570
 
489
571
  // The default model for a collection is just a **Backbone.Model**.
490
572
  // This should be overridden in most cases.
491
- model: Backbone.Model,
573
+ model: Model,
492
574
 
493
575
  // Initialize is an empty function by default. Override it with your own
494
576
  // initialization logic.
@@ -496,14 +578,14 @@
496
578
 
497
579
  // The JSON representation of a Collection is an array of the
498
580
  // models' attributes.
499
- toJSON: function() {
500
- return this.map(function(model){ return model.toJSON(); });
581
+ toJSON: function(options) {
582
+ return this.map(function(model){ return model.toJSON(options); });
501
583
  },
502
584
 
503
585
  // Add a model, or list of models to the set. Pass **silent** to avoid
504
586
  // firing the `add` event for every new model.
505
587
  add: function(models, options) {
506
- var i, index, length, model, cid, id, cids = {}, ids = {};
588
+ var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
507
589
  options || (options = {});
508
590
  models = _.isArray(models) ? models.slice() : [models];
509
591
 
@@ -513,16 +595,24 @@
513
595
  if (!(model = models[i] = this._prepareModel(models[i], options))) {
514
596
  throw new Error("Can't add an invalid model to a collection");
515
597
  }
516
- if (cids[cid = model.cid] || this._byCid[cid] ||
517
- (((id = model.id) != null) && (ids[id] || this._byId[id]))) {
518
- throw new Error("Can't add the same model to a collection twice");
598
+ cid = model.cid;
599
+ id = model.id;
600
+ if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
601
+ dups.push(i);
602
+ continue;
519
603
  }
520
604
  cids[cid] = ids[id] = model;
521
605
  }
522
606
 
607
+ // Remove duplicates.
608
+ i = dups.length;
609
+ while (i--) {
610
+ models.splice(dups[i], 1);
611
+ }
612
+
523
613
  // Listen to added models' events, and index models for lookup by
524
614
  // `id` and by `cid`.
525
- for (i = 0; i < length; i++) {
615
+ for (i = 0, length = models.length; i < length; i++) {
526
616
  (model = models[i]).on('all', this._onModelEvent, this);
527
617
  this._byCid[model.cid] = model;
528
618
  if (model.id != null) this._byId[model.id] = model;
@@ -566,9 +656,37 @@
566
656
  return this;
567
657
  },
568
658
 
659
+ // Add a model to the end of the collection.
660
+ push: function(model, options) {
661
+ model = this._prepareModel(model, options);
662
+ this.add(model, options);
663
+ return model;
664
+ },
665
+
666
+ // Remove a model from the end of the collection.
667
+ pop: function(options) {
668
+ var model = this.at(this.length - 1);
669
+ this.remove(model, options);
670
+ return model;
671
+ },
672
+
673
+ // Add a model to the beginning of the collection.
674
+ unshift: function(model, options) {
675
+ model = this._prepareModel(model, options);
676
+ this.add(model, _.extend({at: 0}, options));
677
+ return model;
678
+ },
679
+
680
+ // Remove a model from the beginning of the collection.
681
+ shift: function(options) {
682
+ var model = this.at(0);
683
+ this.remove(model, options);
684
+ return model;
685
+ },
686
+
569
687
  // Get a model from the set by id.
570
688
  get: function(id) {
571
- if (id == null) return null;
689
+ if (id == null) return void 0;
572
690
  return this._byId[id.id != null ? id.id : id];
573
691
  },
574
692
 
@@ -582,6 +700,17 @@
582
700
  return this.models[index];
583
701
  },
584
702
 
703
+ // Return models with matching attributes. Useful for simple cases of `filter`.
704
+ where: function(attrs) {
705
+ if (_.isEmpty(attrs)) return [];
706
+ return this.filter(function(model) {
707
+ for (var key in attrs) {
708
+ if (attrs[key] !== model.get(key)) return false;
709
+ }
710
+ return true;
711
+ });
712
+ },
713
+
585
714
  // Force the collection to re-sort itself. You don't need to call this under
586
715
  // normal circumstances, as the set will maintain sort order as each item
587
716
  // is added.
@@ -613,7 +742,7 @@
613
742
  this._removeReference(this.models[i]);
614
743
  }
615
744
  this._reset();
616
- this.add(models, {silent: true, parse: options.parse});
745
+ this.add(models, _.extend({silent: true}, options));
617
746
  if (!options.silent) this.trigger('reset', this, options);
618
747
  return this;
619
748
  },
@@ -679,7 +808,8 @@
679
808
 
680
809
  // Prepare a model or hash of attributes to be added to this collection.
681
810
  _prepareModel: function(model, options) {
682
- if (!(model instanceof Backbone.Model)) {
811
+ options || (options = {});
812
+ if (!(model instanceof Model)) {
683
813
  var attrs = model;
684
814
  options.collection = this;
685
815
  model = new this.model(attrs, options);
@@ -702,12 +832,12 @@
702
832
  // Sets need to update their indexes when models change ids. All other
703
833
  // events simply proxy through. "add" and "remove" events that originate
704
834
  // in other collections are ignored.
705
- _onModelEvent: function(ev, model, collection, options) {
706
- if ((ev == 'add' || ev == 'remove') && collection != this) return;
707
- if (ev == 'destroy') {
835
+ _onModelEvent: function(event, model, collection, options) {
836
+ if ((event == 'add' || event == 'remove') && collection != this) return;
837
+ if (event == 'destroy') {
708
838
  this.remove(model, options);
709
839
  }
710
- if (model && ev === 'change:' + model.idAttribute) {
840
+ if (model && event === 'change:' + model.idAttribute) {
711
841
  delete this._byId[model.previous(model.idAttribute)];
712
842
  this._byId[model.id] = model;
713
843
  }
@@ -725,7 +855,7 @@
725
855
 
726
856
  // Mix in each Underscore method as a proxy to `Collection#models`.
727
857
  _.each(methods, function(method) {
728
- Backbone.Collection.prototype[method] = function() {
858
+ Collection.prototype[method] = function() {
729
859
  return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
730
860
  };
731
861
  });
@@ -735,7 +865,7 @@
735
865
 
736
866
  // Routers map faux-URLs to actions, and fire events when routes are
737
867
  // matched. Creating a new one sets its `routes` hash, if not set statically.
738
- Backbone.Router = function(options) {
868
+ var Router = Backbone.Router = function(options) {
739
869
  options || (options = {});
740
870
  if (options.routes) this.routes = options.routes;
741
871
  this._bindRoutes();
@@ -749,7 +879,7 @@
749
879
  var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
750
880
 
751
881
  // Set up all inheritable **Backbone.Router** properties and methods.
752
- _.extend(Backbone.Router.prototype, Backbone.Events, {
882
+ _.extend(Router.prototype, Events, {
753
883
 
754
884
  // Initialize is an empty function by default. Override it with your own
755
885
  // initialization logic.
@@ -762,7 +892,7 @@
762
892
  // });
763
893
  //
764
894
  route: function(route, name, callback) {
765
- Backbone.history || (Backbone.history = new Backbone.History);
895
+ Backbone.history || (Backbone.history = new History);
766
896
  if (!_.isRegExp(route)) route = this._routeToRegExp(route);
767
897
  if (!callback) callback = this[name];
768
898
  Backbone.history.route(route, _.bind(function(fragment) {
@@ -815,7 +945,7 @@
815
945
 
816
946
  // Handles cross-browser history management, based on URL fragments. If the
817
947
  // browser does not support `onhashchange`, falls back to polling.
818
- Backbone.History = function() {
948
+ var History = Backbone.History = function() {
819
949
  this.handlers = [];
820
950
  _.bindAll(this, 'checkUrl');
821
951
  };
@@ -827,15 +957,23 @@
827
957
  var isExplorer = /msie [\w.]+/;
828
958
 
829
959
  // Has the history handling already been started?
830
- var historyStarted = false;
960
+ History.started = false;
831
961
 
832
962
  // Set up all inheritable **Backbone.History** properties and methods.
833
- _.extend(Backbone.History.prototype, Backbone.Events, {
963
+ _.extend(History.prototype, Events, {
834
964
 
835
965
  // The default interval to poll for hash changes, if necessary, is
836
966
  // twenty times a second.
837
967
  interval: 50,
838
968
 
969
+ // Gets the true hash value. Cannot use location.hash directly due to bug
970
+ // in Firefox where location.hash will always be decoded.
971
+ getHash: function(windowOverride) {
972
+ var loc = windowOverride ? windowOverride.location : window.location;
973
+ var match = loc.href.match(/#(.*)$/);
974
+ return match ? match[1] : '';
975
+ },
976
+
839
977
  // Get the cross-browser normalized URL fragment, either from the URL,
840
978
  // the hash, or the override.
841
979
  getFragment: function(fragment, forcePushState) {
@@ -845,10 +983,9 @@
845
983
  var search = window.location.search;
846
984
  if (search) fragment += search;
847
985
  } else {
848
- fragment = window.location.hash;
986
+ fragment = this.getHash();
849
987
  }
850
988
  }
851
- fragment = decodeURIComponent(fragment);
852
989
  if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
853
990
  return fragment.replace(routeStripper, '');
854
991
  },
@@ -856,10 +993,11 @@
856
993
  // Start the hash change handling, returning `true` if the current URL matches
857
994
  // an existing route, and `false` otherwise.
858
995
  start: function(options) {
996
+ if (History.started) throw new Error("Backbone.history has already been started");
997
+ History.started = true;
859
998
 
860
999
  // Figure out the initial configuration. Do we need an iframe?
861
1000
  // Is pushState desired ... is it available?
862
- if (historyStarted) throw new Error("Backbone.history has already been started");
863
1001
  this.options = _.extend({}, {root: '/'}, this.options, options);
864
1002
  this._wantsHashChange = this.options.hashChange !== false;
865
1003
  this._wantsPushState = !!this.options.pushState;
@@ -867,6 +1005,7 @@
867
1005
  var fragment = this.getFragment();
868
1006
  var docMode = document.documentMode;
869
1007
  var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
1008
+
870
1009
  if (oldIE) {
871
1010
  this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
872
1011
  this.navigate(fragment);
@@ -885,7 +1024,6 @@
885
1024
  // Determine if we need to change the base url, for a pushState link
886
1025
  // opened by a non-pushState browser.
887
1026
  this.fragment = fragment;
888
- historyStarted = true;
889
1027
  var loc = window.location;
890
1028
  var atRoot = loc.pathname == this.options.root;
891
1029
 
@@ -900,7 +1038,7 @@
900
1038
  // Or if we've started out with a hash-based route, but we're currently
901
1039
  // in a browser where it could be `pushState`-based instead...
902
1040
  } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
903
- this.fragment = loc.hash.replace(routeStripper, '');
1041
+ this.fragment = this.getHash().replace(routeStripper, '');
904
1042
  window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
905
1043
  }
906
1044
 
@@ -914,7 +1052,7 @@
914
1052
  stop: function() {
915
1053
  $(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
916
1054
  clearInterval(this._checkUrlInterval);
917
- historyStarted = false;
1055
+ History.started = false;
918
1056
  },
919
1057
 
920
1058
  // Add a route to be tested when the fragment changes. Routes added later
@@ -927,10 +1065,10 @@
927
1065
  // calls `loadUrl`, normalizing across the hidden iframe.
928
1066
  checkUrl: function(e) {
929
1067
  var current = this.getFragment();
930
- if (current == this.fragment && this.iframe) current = this.getFragment(this.iframe.location.hash);
931
- if (current == this.fragment || current == decodeURIComponent(this.fragment)) return false;
1068
+ if (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe));
1069
+ if (current == this.fragment) return false;
932
1070
  if (this.iframe) this.navigate(current);
933
- this.loadUrl() || this.loadUrl(window.location.hash);
1071
+ this.loadUrl() || this.loadUrl(this.getHash());
934
1072
  },
935
1073
 
936
1074
  // Attempt to load the current URL fragment. If a route succeeds with a
@@ -953,12 +1091,12 @@
953
1091
  //
954
1092
  // The options object can contain `trigger: true` if you wish to have the
955
1093
  // route callback be fired (not usually desirable), or `replace: true`, if
956
- // you which to modify the current URL without adding an entry to the history.
1094
+ // you wish to modify the current URL without adding an entry to the history.
957
1095
  navigate: function(fragment, options) {
958
- if (!historyStarted) return false;
1096
+ if (!History.started) return false;
959
1097
  if (!options || options === true) options = {trigger: options};
960
1098
  var frag = (fragment || '').replace(routeStripper, '');
961
- if (this.fragment == frag || this.fragment == decodeURIComponent(frag)) return;
1099
+ if (this.fragment == frag) return;
962
1100
 
963
1101
  // If pushState is available, we use it to set the fragment as a real URL.
964
1102
  if (this._hasPushState) {
@@ -971,7 +1109,7 @@
971
1109
  } else if (this._wantsHashChange) {
972
1110
  this.fragment = frag;
973
1111
  this._updateHash(window.location, frag, options.replace);
974
- if (this.iframe && (frag != this.getFragment(this.iframe.location.hash))) {
1112
+ if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
975
1113
  // Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
976
1114
  // When replace is true, we don't want this.
977
1115
  if(!options.replace) this.iframe.document.open().close();
@@ -1002,7 +1140,7 @@
1002
1140
 
1003
1141
  // Creating a Backbone.View creates its initial element outside of the DOM,
1004
1142
  // if an existing element is not provided...
1005
- Backbone.View = function(options) {
1143
+ var View = Backbone.View = function(options) {
1006
1144
  this.cid = _.uniqueId('view');
1007
1145
  this._configure(options || {});
1008
1146
  this._ensureElement();
@@ -1011,13 +1149,13 @@
1011
1149
  };
1012
1150
 
1013
1151
  // Cached regex to split keys for `delegate`.
1014
- var eventSplitter = /^(\S+)\s*(.*)$/;
1152
+ var delegateEventSplitter = /^(\S+)\s*(.*)$/;
1015
1153
 
1016
1154
  // List of view options to be merged as properties.
1017
1155
  var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
1018
1156
 
1019
1157
  // Set up all inheritable **Backbone.View** properties and methods.
1020
- _.extend(Backbone.View.prototype, Backbone.Events, {
1158
+ _.extend(View.prototype, Events, {
1021
1159
 
1022
1160
  // The default `tagName` of a View's element is `"div"`.
1023
1161
  tagName: 'div',
@@ -1061,7 +1199,8 @@
1061
1199
  // Change the view's element (`this.el` property), including event
1062
1200
  // re-delegation.
1063
1201
  setElement: function(element, delegate) {
1064
- this.$el = $(element);
1202
+ if (this.$el) this.undelegateEvents();
1203
+ this.$el = (element instanceof $) ? element : $(element);
1065
1204
  this.el = this.$el[0];
1066
1205
  if (delegate !== false) this.delegateEvents();
1067
1206
  return this;
@@ -1088,8 +1227,8 @@
1088
1227
  for (var key in events) {
1089
1228
  var method = events[key];
1090
1229
  if (!_.isFunction(method)) method = this[events[key]];
1091
- if (!method) throw new Error('Event "' + events[key] + '" does not exist');
1092
- var match = key.match(eventSplitter);
1230
+ if (!method) throw new Error('Method "' + events[key] + '" does not exist');
1231
+ var match = key.match(delegateEventSplitter);
1093
1232
  var eventName = match[1], selector = match[2];
1094
1233
  method = _.bind(method, this);
1095
1234
  eventName += '.delegateEvents' + this.cid;
@@ -1145,8 +1284,7 @@
1145
1284
  };
1146
1285
 
1147
1286
  // Set up inheritance for the model, collection, and view.
1148
- Backbone.Model.extend = Backbone.Collection.extend =
1149
- Backbone.Router.extend = Backbone.View.extend = extend;
1287
+ Model.extend = Collection.extend = Router.extend = View.extend = extend;
1150
1288
 
1151
1289
  // Backbone.sync
1152
1290
  // -------------
@@ -1177,6 +1315,9 @@
1177
1315
  Backbone.sync = function(method, model, options) {
1178
1316
  var type = methodMap[method];
1179
1317
 
1318
+ // Default options, unless specified.
1319
+ options || (options = {});
1320
+
1180
1321
  // Default JSON-request options.
1181
1322
  var params = {type: type, dataType: 'json'};
1182
1323
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: backbone-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.9.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-13 00:00:00.000000000Z
12
+ date: 2012-04-06 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
16
- requirement: &2156951720 !ruby/object:Gem::Requirement
16
+ requirement: &2154095100 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,7 +21,7 @@ dependencies:
21
21
  version: 3.0.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2156951720
24
+ version_requirements: *2154095100
25
25
  description: Ships backbone and underscore to your Rails 3.1 application through the
26
26
  new asset pipeline. Rails 3.0 is supported via generators.
27
27
  email: