rails-backbone 0.9.0 → 0.9.10

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.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Backbone-Rails [![Build Status](https://secure.travis-ci.org/codebrew/backbone-rails.png)](http://travis-ci.org/codebrew/backbone-rails)
2
2
 
3
- Easily setup and use backbone.js (0.9.9) with rails 3.1 and greater
3
+ Easily setup and use backbone.js (0.9.10) with Rails 3.1 and greater
4
4
 
5
5
  Follow [@TheRyanFitz on Twitter](http://twitter.com/#!/TheRyanFitz). Tweet any questions or suggestions you have about the project.
6
6
 
@@ -4,31 +4,31 @@ module Backbone
4
4
  module Generators
5
5
  class InstallGenerator < Rails::Generators::Base
6
6
  include Backbone::Generators::ResourceHelpers
7
-
7
+
8
8
  source_root File.expand_path("../templates", __FILE__)
9
-
9
+
10
10
  desc "This generator installs backbone.js with a default folder layout in app/assets/javascripts/backbone"
11
-
11
+
12
12
  class_option :skip_git, :type => :boolean, :aliases => "-G", :default => false,
13
13
  :desc => "Skip Git ignores and keeps"
14
-
14
+
15
15
  def inject_backbone
16
16
  inject_into_file "app/assets/javascripts/application.js", :before => "//= require_tree" do
17
17
  "//= require underscore\n//= require backbone\n//= require backbone_rails_sync\n//= require backbone_datalink\n//= require backbone/#{application_name.underscore}\n"
18
18
  end
19
19
  end
20
-
20
+
21
21
  def create_dir_layout
22
22
  %W{routers models views templates}.each do |dir|
23
23
  empty_directory "app/assets/javascripts/backbone/#{dir}"
24
24
  create_file "app/assets/javascripts/backbone/#{dir}/.gitkeep" unless options[:skip_git]
25
25
  end
26
26
  end
27
-
27
+
28
28
  def create_app_file
29
29
  template "app.coffee", "app/assets/javascripts/backbone/#{application_name.underscore}.js.coffee"
30
30
  end
31
-
31
+
32
32
  end
33
33
  end
34
- end
34
+ end
@@ -1,4 +1,4 @@
1
- // Backbone.js 0.9.9
1
+ // Backbone.js 0.9.10
2
2
 
3
3
  // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
4
4
  // Backbone may be freely distributed under the MIT license.
@@ -34,7 +34,7 @@
34
34
  }
35
35
 
36
36
  // Current version of the library. Keep in sync with `package.json`.
37
- Backbone.VERSION = '0.9.9';
37
+ Backbone.VERSION = '0.9.10';
38
38
 
39
39
  // Require Underscore, if we're on the server, and it's not already present.
40
40
  var _ = root._;
@@ -88,7 +88,7 @@
88
88
 
89
89
  // Optimized internal dispatch function for triggering events. Tries to
90
90
  // keep the usual cases speedy (most Backbone events have 3 arguments).
91
- var triggerEvents = function(obj, events, args) {
91
+ var triggerEvents = function(events, args) {
92
92
  var ev, i = -1, l = events.length;
93
93
  switch (args.length) {
94
94
  case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx);
@@ -142,7 +142,7 @@
142
142
 
143
143
  // Remove one or many callbacks. If `context` is null, removes all
144
144
  // callbacks with that function. If `callback` is null, removes all
145
- // callbacks for the event. If `events` is null, removes all bound
145
+ // callbacks for the event. If `name` is null, removes all bound
146
146
  // callbacks for all events.
147
147
  off: function(name, callback, context) {
148
148
  var list, ev, events, names, i, l, j, k;
@@ -160,7 +160,8 @@
160
160
  if (callback || context) {
161
161
  for (j = 0, k = list.length; j < k; j++) {
162
162
  ev = list[j];
163
- if ((callback && callback !== (ev.callback._callback || ev.callback)) ||
163
+ if ((callback && callback !== ev.callback &&
164
+ callback !== ev.callback._callback) ||
164
165
  (context && context !== ev.context)) {
165
166
  events.push(ev);
166
167
  }
@@ -183,32 +184,33 @@
183
184
  if (!eventsApi(this, 'trigger', name, args)) return this;
184
185
  var events = this._events[name];
185
186
  var allEvents = this._events.all;
186
- if (events) triggerEvents(this, events, args);
187
- if (allEvents) triggerEvents(this, allEvents, arguments);
187
+ if (events) triggerEvents(events, args);
188
+ if (allEvents) triggerEvents(allEvents, arguments);
188
189
  return this;
189
190
  },
190
191
 
191
192
  // An inversion-of-control version of `on`. Tell *this* object to listen to
192
193
  // an event in another object ... keeping track of what it's listening to.
193
- listenTo: function(object, events, callback) {
194
+ listenTo: function(obj, name, callback) {
194
195
  var listeners = this._listeners || (this._listeners = {});
195
- var id = object._listenerId || (object._listenerId = _.uniqueId('l'));
196
- listeners[id] = object;
197
- object.on(events, callback || this, this);
196
+ var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
197
+ listeners[id] = obj;
198
+ obj.on(name, typeof name === 'object' ? this : callback, this);
198
199
  return this;
199
200
  },
200
201
 
201
202
  // Tell this object to stop listening to either specific events ... or
202
203
  // to every object it's currently listening to.
203
- stopListening: function(object, events, callback) {
204
+ stopListening: function(obj, name, callback) {
204
205
  var listeners = this._listeners;
205
206
  if (!listeners) return;
206
- if (object) {
207
- object.off(events, callback, this);
208
- if (!events && !callback) delete listeners[object._listenerId];
207
+ if (obj) {
208
+ obj.off(name, typeof name === 'object' ? this : callback, this);
209
+ if (!name && !callback) delete listeners[obj._listenerId];
209
210
  } else {
211
+ if (typeof name === 'object') callback = this;
210
212
  for (var id in listeners) {
211
- listeners[id].off(null, null, this);
213
+ listeners[id].off(name, callback, this);
212
214
  }
213
215
  this._listeners = {};
214
216
  }
@@ -233,15 +235,14 @@
233
235
  var defaults;
234
236
  var attrs = attributes || {};
235
237
  this.cid = _.uniqueId('c');
236
- this.changed = {};
237
238
  this.attributes = {};
238
- this._changes = [];
239
239
  if (options && options.collection) this.collection = options.collection;
240
- if (options && options.parse) attrs = this.parse(attrs);
241
- if (defaults = _.result(this, 'defaults')) _.defaults(attrs, defaults);
242
- this.set(attrs, {silent: true});
243
- this._currentAttributes = _.clone(this.attributes);
244
- this._previousAttributes = _.clone(this.attributes);
240
+ if (options && options.parse) attrs = this.parse(attrs, options) || {};
241
+ if (defaults = _.result(this, 'defaults')) {
242
+ attrs = _.defaults({}, attrs, defaults);
243
+ }
244
+ this.set(attrs, options);
245
+ this.changed = {};
245
246
  this.initialize.apply(this, arguments);
246
247
  };
247
248
 
@@ -285,47 +286,72 @@
285
286
  return this.get(attr) != null;
286
287
  },
287
288
 
289
+ // ----------------------------------------------------------------------
290
+
288
291
  // Set a hash of model attributes on the object, firing `"change"` unless
289
292
  // you choose to silence it.
290
293
  set: function(key, val, options) {
291
- var attr, attrs;
294
+ var attr, attrs, unset, changes, silent, changing, prev, current;
292
295
  if (key == null) return this;
293
296
 
294
297
  // Handle both `"key", value` and `{key: value}` -style arguments.
295
- if (_.isObject(key)) {
298
+ if (typeof key === 'object') {
296
299
  attrs = key;
297
300
  options = val;
298
301
  } else {
299
302
  (attrs = {})[key] = val;
300
303
  }
301
304
 
302
- // Extract attributes and options.
303
- var silent = options && options.silent;
304
- var unset = options && options.unset;
305
+ options || (options = {});
305
306
 
306
307
  // Run validation.
307
308
  if (!this._validate(attrs, options)) return false;
308
309
 
310
+ // Extract attributes and options.
311
+ unset = options.unset;
312
+ silent = options.silent;
313
+ changes = [];
314
+ changing = this._changing;
315
+ this._changing = true;
316
+
317
+ if (!changing) {
318
+ this._previousAttributes = _.clone(this.attributes);
319
+ this.changed = {};
320
+ }
321
+ current = this.attributes, prev = this._previousAttributes;
322
+
309
323
  // Check for changes of `id`.
310
324
  if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
311
325
 
312
- var now = this.attributes;
313
-
314
- // For each `set` attribute...
326
+ // For each `set` attribute, update or delete the current value.
315
327
  for (attr in attrs) {
316
328
  val = attrs[attr];
317
-
318
- // Update or delete the current value, and track the change.
319
- unset ? delete now[attr] : now[attr] = val;
320
- this._changes.push(attr, val);
329
+ if (!_.isEqual(current[attr], val)) changes.push(attr);
330
+ if (!_.isEqual(prev[attr], val)) {
331
+ this.changed[attr] = val;
332
+ } else {
333
+ delete this.changed[attr];
334
+ }
335
+ unset ? delete current[attr] : current[attr] = val;
321
336
  }
322
337
 
323
- // Signal that the model's state has potentially changed, and we need
324
- // to recompute the actual changes.
325
- this._hasComputed = false;
338
+ // Trigger all relevant attribute changes.
339
+ if (!silent) {
340
+ if (changes.length) this._pending = true;
341
+ for (var i = 0, l = changes.length; i < l; i++) {
342
+ this.trigger('change:' + changes[i], this, current[changes[i]], options);
343
+ }
344
+ }
326
345
 
327
- // Fire the `"change"` events.
328
- if (!silent) this.change(options);
346
+ if (changing) return this;
347
+ if (!silent) {
348
+ while (this._pending) {
349
+ this._pending = false;
350
+ this.trigger('change', this, options);
351
+ }
352
+ }
353
+ this._pending = false;
354
+ this._changing = false;
329
355
  return this;
330
356
  },
331
357
 
@@ -343,16 +369,54 @@
343
369
  return this.set(attrs, _.extend({}, options, {unset: true}));
344
370
  },
345
371
 
372
+ // Determine if the model has changed since the last `"change"` event.
373
+ // If you specify an attribute name, determine if that attribute has changed.
374
+ hasChanged: function(attr) {
375
+ if (attr == null) return !_.isEmpty(this.changed);
376
+ return _.has(this.changed, attr);
377
+ },
378
+
379
+ // Return an object containing all the attributes that have changed, or
380
+ // false if there are no changed attributes. Useful for determining what
381
+ // parts of a view need to be updated and/or what attributes need to be
382
+ // persisted to the server. Unset attributes will be set to undefined.
383
+ // You can also pass an attributes object to diff against the model,
384
+ // determining if there *would be* a change.
385
+ changedAttributes: function(diff) {
386
+ if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
387
+ var val, changed = false;
388
+ var old = this._changing ? this._previousAttributes : this.attributes;
389
+ for (var attr in diff) {
390
+ if (_.isEqual(old[attr], (val = diff[attr]))) continue;
391
+ (changed || (changed = {}))[attr] = val;
392
+ }
393
+ return changed;
394
+ },
395
+
396
+ // Get the previous value of an attribute, recorded at the time the last
397
+ // `"change"` event was fired.
398
+ previous: function(attr) {
399
+ if (attr == null || !this._previousAttributes) return null;
400
+ return this._previousAttributes[attr];
401
+ },
402
+
403
+ // Get all of the attributes of the model at the time of the previous
404
+ // `"change"` event.
405
+ previousAttributes: function() {
406
+ return _.clone(this._previousAttributes);
407
+ },
408
+
409
+ // ---------------------------------------------------------------------
410
+
346
411
  // Fetch the model from the server. If the server's representation of the
347
412
  // model differs from its current attributes, they will be overriden,
348
413
  // triggering a `"change"` event.
349
414
  fetch: function(options) {
350
415
  options = options ? _.clone(options) : {};
351
416
  if (options.parse === void 0) options.parse = true;
352
- var model = this;
353
417
  var success = options.success;
354
- options.success = function(resp, status, xhr) {
355
- if (!model.set(model.parse(resp), options)) return false;
418
+ options.success = function(model, resp, options) {
419
+ if (!model.set(model.parse(resp, options), options)) return false;
356
420
  if (success) success(model, resp, options);
357
421
  };
358
422
  return this.sync('read', this, options);
@@ -362,55 +426,51 @@
362
426
  // If the server returns an attributes hash that differs, the model's
363
427
  // state will be `set` again.
364
428
  save: function(key, val, options) {
365
- var attrs, current, done;
429
+ var attrs, success, method, xhr, attributes = this.attributes;
366
430
 
367
431
  // Handle both `"key", value` and `{key: value}` -style arguments.
368
- if (key == null || _.isObject(key)) {
432
+ if (key == null || typeof key === 'object') {
369
433
  attrs = key;
370
434
  options = val;
371
- } else if (key != null) {
435
+ } else {
372
436
  (attrs = {})[key] = val;
373
437
  }
374
- options = options ? _.clone(options) : {};
375
438
 
376
- // If we're "wait"-ing to set changed attributes, validate early.
377
- if (options.wait) {
378
- if (attrs && !this._validate(attrs, options)) return false;
379
- current = _.clone(this.attributes);
380
- }
439
+ // If we're not waiting and attributes exist, save acts as `set(attr).save(null, opts)`.
440
+ if (attrs && (!options || !options.wait) && !this.set(attrs, options)) return false;
381
441
 
382
- // Regular saves `set` attributes before persisting to the server.
383
- var silentOptions = _.extend({}, options, {silent: true});
384
- if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
385
- return false;
386
- }
442
+ options = _.extend({validate: true}, options);
387
443
 
388
444
  // Do not persist invalid models.
389
- if (!attrs && !this._validate(null, options)) return false;
445
+ if (!this._validate(attrs, options)) return false;
446
+
447
+ // Set temporary attributes if `{wait: true}`.
448
+ if (attrs && options.wait) {
449
+ this.attributes = _.extend({}, attributes, attrs);
450
+ }
390
451
 
391
452
  // After a successful server-side save, the client is (optionally)
392
453
  // updated with the server-side state.
393
- var model = this;
394
- var success = options.success;
395
- options.success = function(resp, status, xhr) {
396
- done = true;
397
- var serverAttrs = model.parse(resp);
454
+ if (options.parse === void 0) options.parse = true;
455
+ success = options.success;
456
+ options.success = function(model, resp, options) {
457
+ // Ensure attributes are restored during synchronous saves.
458
+ model.attributes = attributes;
459
+ var serverAttrs = model.parse(resp, options);
398
460
  if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
399
- if (!model.set(serverAttrs, options)) return false;
461
+ if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
462
+ return false;
463
+ }
400
464
  if (success) success(model, resp, options);
401
465
  };
402
466
 
403
467
  // Finish configuring and sending the Ajax request.
404
- var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
405
- if (method == 'patch') options.attrs = attrs;
406
- var xhr = this.sync(method, this, options);
407
-
408
- // When using `wait`, reset attributes to original values unless
409
- // `success` has been called already.
410
- if (!done && options.wait) {
411
- this.clear(silentOptions);
412
- this.set(current, silentOptions);
413
- }
468
+ method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
469
+ if (method === 'patch') options.attrs = attrs;
470
+ xhr = this.sync(method, this, options);
471
+
472
+ // Restore attributes.
473
+ if (attrs && options.wait) this.attributes = attributes;
414
474
 
415
475
  return xhr;
416
476
  },
@@ -427,13 +487,13 @@
427
487
  model.trigger('destroy', model, model.collection, options);
428
488
  };
429
489
 
430
- options.success = function(resp) {
490
+ options.success = function(model, resp, options) {
431
491
  if (options.wait || model.isNew()) destroy();
432
492
  if (success) success(model, resp, options);
433
493
  };
434
494
 
435
495
  if (this.isNew()) {
436
- options.success();
496
+ options.success(this, null, options);
437
497
  return false;
438
498
  }
439
499
 
@@ -453,7 +513,7 @@
453
513
 
454
514
  // **parse** converts a response into the hash of attributes to be `set` on
455
515
  // the model. The default implementation is just to pass the response along.
456
- parse: function(resp) {
516
+ parse: function(resp, options) {
457
517
  return resp;
458
518
  },
459
519
 
@@ -467,115 +527,20 @@
467
527
  return this.id == null;
468
528
  },
469
529
 
470
- // Call this method to manually fire a `"change"` event for this model and
471
- // a `"change:attribute"` event for each changed attribute.
472
- // Calling this will cause all objects observing the model to update.
473
- change: function(options) {
474
- var changing = this._changing;
475
- this._changing = true;
476
-
477
- // Generate the changes to be triggered on the model.
478
- var triggers = this._computeChanges(true);
479
-
480
- this._pending = !!triggers.length;
481
-
482
- for (var i = triggers.length - 2; i >= 0; i -= 2) {
483
- this.trigger('change:' + triggers[i], this, triggers[i + 1], options);
484
- }
485
-
486
- if (changing) return this;
487
-
488
- // Trigger a `change` while there have been changes.
489
- while (this._pending) {
490
- this._pending = false;
491
- this.trigger('change', this, options);
492
- this._previousAttributes = _.clone(this.attributes);
493
- }
494
-
495
- this._changing = false;
496
- return this;
497
- },
498
-
499
- // Determine if the model has changed since the last `"change"` event.
500
- // If you specify an attribute name, determine if that attribute has changed.
501
- hasChanged: function(attr) {
502
- if (!this._hasComputed) this._computeChanges();
503
- if (attr == null) return !_.isEmpty(this.changed);
504
- return _.has(this.changed, attr);
505
- },
506
-
507
- // Return an object containing all the attributes that have changed, or
508
- // false if there are no changed attributes. Useful for determining what
509
- // parts of a view need to be updated and/or what attributes need to be
510
- // persisted to the server. Unset attributes will be set to undefined.
511
- // You can also pass an attributes object to diff against the model,
512
- // determining if there *would be* a change.
513
- changedAttributes: function(diff) {
514
- if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
515
- var val, changed = false, old = this._previousAttributes;
516
- for (var attr in diff) {
517
- if (_.isEqual(old[attr], (val = diff[attr]))) continue;
518
- (changed || (changed = {}))[attr] = val;
519
- }
520
- return changed;
521
- },
522
-
523
- // Looking at the built up list of `set` attribute changes, compute how
524
- // many of the attributes have actually changed. If `loud`, return a
525
- // boiled-down list of only the real changes.
526
- _computeChanges: function(loud) {
527
- this.changed = {};
528
- var already = {};
529
- var triggers = [];
530
- var current = this._currentAttributes;
531
- var changes = this._changes;
532
-
533
- // Loop through the current queue of potential model changes.
534
- for (var i = changes.length - 2; i >= 0; i -= 2) {
535
- var key = changes[i], val = changes[i + 1];
536
- if (already[key]) continue;
537
- already[key] = true;
538
-
539
- // Check if the attribute has been modified since the last change,
540
- // and update `this.changed` accordingly. If we're inside of a `change`
541
- // call, also add a trigger to the list.
542
- if (current[key] !== val) {
543
- this.changed[key] = val;
544
- if (!loud) continue;
545
- triggers.push(key, val);
546
- current[key] = val;
547
- }
548
- }
549
- if (loud) this._changes = [];
550
-
551
- // Signals `this.changed` is current to prevent duplicate calls from `this.hasChanged`.
552
- this._hasComputed = true;
553
- return triggers;
554
- },
555
-
556
- // Get the previous value of an attribute, recorded at the time the last
557
- // `"change"` event was fired.
558
- previous: function(attr) {
559
- if (attr == null || !this._previousAttributes) return null;
560
- return this._previousAttributes[attr];
561
- },
562
-
563
- // Get all of the attributes of the model at the time of the previous
564
- // `"change"` event.
565
- previousAttributes: function() {
566
- return _.clone(this._previousAttributes);
530
+ // Check if the model is currently in a valid state.
531
+ isValid: function(options) {
532
+ return !this.validate || !this.validate(this.attributes, options);
567
533
  },
568
534
 
569
535
  // Run validation against the next complete set of model attributes,
570
- // returning `true` if all is well. If a specific `error` callback has
571
- // been passed, call that instead of firing the general `"error"` event.
536
+ // returning `true` if all is well. Otherwise, fire a general
537
+ // `"error"` event and call the error callback, if specified.
572
538
  _validate: function(attrs, options) {
573
- if (!this.validate) return true;
539
+ if (!options.validate || !this.validate) return true;
574
540
  attrs = _.extend({}, this.attributes, attrs);
575
- var error = this.validate(attrs, options);
541
+ var error = this.validationError = this.validate(attrs, options) || null;
576
542
  if (!error) return true;
577
- if (options && options.error) options.error(this, error, options);
578
- this.trigger('error', this, error, options);
543
+ this.trigger('invalid', this, error, options || {});
579
544
  return false;
580
545
  }
581
546
 
@@ -591,6 +556,7 @@
591
556
  options || (options = {});
592
557
  if (options.model) this.model = options.model;
593
558
  if (options.comparator !== void 0) this.comparator = options.comparator;
559
+ this.models = [];
594
560
  this._reset();
595
561
  this.initialize.apply(this, arguments);
596
562
  if (models) this.reset(models, _.extend({silent: true}, options));
@@ -618,74 +584,81 @@
618
584
  return Backbone.sync.apply(this, arguments);
619
585
  },
620
586
 
621
- // Add a model, or list of models to the set. Pass **silent** to avoid
622
- // firing the `add` event for every new model.
587
+ // Add a model, or list of models to the set.
623
588
  add: function(models, options) {
624
- var i, args, length, model, existing, needsSort;
625
- var at = options && options.at;
626
- var sort = ((options && options.sort) == null ? true : options.sort);
627
589
  models = _.isArray(models) ? models.slice() : [models];
590
+ options || (options = {});
591
+ var i, l, model, attrs, existing, doSort, add, at, sort, sortAttr;
592
+ add = [];
593
+ at = options.at;
594
+ sort = this.comparator && (at == null) && options.sort != false;
595
+ sortAttr = _.isString(this.comparator) ? this.comparator : null;
628
596
 
629
597
  // Turn bare objects into model references, and prevent invalid models
630
598
  // from being added.
631
- for (i = models.length - 1; i >= 0; i--) {
632
- if(!(model = this._prepareModel(models[i], options))) {
633
- this.trigger("error", this, models[i], options);
634
- models.splice(i, 1);
599
+ for (i = 0, l = models.length; i < l; i++) {
600
+ if (!(model = this._prepareModel(attrs = models[i], options))) {
601
+ this.trigger('invalid', this, attrs, options);
635
602
  continue;
636
603
  }
637
- models[i] = model;
638
604
 
639
- existing = model.id != null && this._byId[model.id];
640
605
  // If a duplicate is found, prevent it from being added and
641
606
  // optionally merge it into the existing model.
642
- if (existing || this._byCid[model.cid]) {
643
- if (options && options.merge && existing) {
644
- existing.set(model.attributes, options);
645
- needsSort = sort;
607
+ if (existing = this.get(model)) {
608
+ if (options.merge) {
609
+ existing.set(attrs === model ? model.attributes : attrs, options);
610
+ if (sort && !doSort && existing.hasChanged(sortAttr)) doSort = true;
646
611
  }
647
- models.splice(i, 1);
648
612
  continue;
649
613
  }
650
614
 
615
+ // This is a new model, push it to the `add` list.
616
+ add.push(model);
617
+
651
618
  // Listen to added models' events, and index models for lookup by
652
619
  // `id` and by `cid`.
653
620
  model.on('all', this._onModelEvent, this);
654
- this._byCid[model.cid] = model;
621
+ this._byId[model.cid] = model;
655
622
  if (model.id != null) this._byId[model.id] = model;
656
623
  }
657
624
 
658
625
  // See if sorting is needed, update `length` and splice in new models.
659
- if (models.length) needsSort = sort;
660
- this.length += models.length;
661
- args = [at != null ? at : this.models.length, 0];
662
- push.apply(args, models);
663
- splice.apply(this.models, args);
626
+ if (add.length) {
627
+ if (sort) doSort = true;
628
+ this.length += add.length;
629
+ if (at != null) {
630
+ splice.apply(this.models, [at, 0].concat(add));
631
+ } else {
632
+ push.apply(this.models, add);
633
+ }
634
+ }
664
635
 
665
- // Sort the collection if appropriate.
666
- if (needsSort && this.comparator && at == null) this.sort({silent: true});
636
+ // Silently sort the collection if appropriate.
637
+ if (doSort) this.sort({silent: true});
667
638
 
668
- if (options && options.silent) return this;
639
+ if (options.silent) return this;
669
640
 
670
641
  // Trigger `add` events.
671
- while (model = models.shift()) {
672
- model.trigger('add', model, this, options);
642
+ for (i = 0, l = add.length; i < l; i++) {
643
+ (model = add[i]).trigger('add', model, this, options);
673
644
  }
674
645
 
646
+ // Trigger `sort` if the collection was sorted.
647
+ if (doSort) this.trigger('sort', this, options);
648
+
675
649
  return this;
676
650
  },
677
651
 
678
- // Remove a model, or a list of models from the set. Pass silent to avoid
679
- // firing the `remove` event for every model removed.
652
+ // Remove a model, or a list of models from the set.
680
653
  remove: function(models, options) {
681
- var i, l, index, model;
682
- options || (options = {});
683
654
  models = _.isArray(models) ? models.slice() : [models];
655
+ options || (options = {});
656
+ var i, l, index, model;
684
657
  for (i = 0, l = models.length; i < l; i++) {
685
658
  model = this.get(models[i]);
686
659
  if (!model) continue;
687
660
  delete this._byId[model.id];
688
- delete this._byCid[model.cid];
661
+ delete this._byId[model.cid];
689
662
  index = this.indexOf(model);
690
663
  this.models.splice(index, 1);
691
664
  this.length--;
@@ -734,7 +707,8 @@
734
707
  // Get a model from the set by id.
735
708
  get: function(obj) {
736
709
  if (obj == null) return void 0;
737
- return this._byId[obj.id != null ? obj.id : obj] || this._byCid[obj.cid || obj];
710
+ this._idAttr || (this._idAttr = this.model.prototype.idAttribute);
711
+ return this._byId[obj.id || obj.cid || obj[this._idAttr] || obj];
738
712
  },
739
713
 
740
714
  // Get the model at the given index.
@@ -760,14 +734,16 @@
760
734
  if (!this.comparator) {
761
735
  throw new Error('Cannot sort a set without a comparator');
762
736
  }
737
+ options || (options = {});
763
738
 
739
+ // Run sort based on type of `comparator`.
764
740
  if (_.isString(this.comparator) || this.comparator.length === 1) {
765
741
  this.models = this.sortBy(this.comparator, this);
766
742
  } else {
767
743
  this.models.sort(_.bind(this.comparator, this));
768
744
  }
769
745
 
770
- if (!options || !options.silent) this.trigger('sort', this, options);
746
+ if (!options.silent) this.trigger('sort', this, options);
771
747
  return this;
772
748
  },
773
749
 
@@ -779,11 +755,10 @@
779
755
  // Smartly update a collection with a change set of models, adding,
780
756
  // removing, and merging as necessary.
781
757
  update: function(models, options) {
758
+ options = _.extend({add: true, merge: true, remove: true}, options);
759
+ if (options.parse) models = this.parse(models, options);
782
760
  var model, i, l, existing;
783
761
  var add = [], remove = [], modelMap = {};
784
- var idAttr = this.model.prototype.idAttribute;
785
- options = _.extend({add: true, merge: true, remove: true}, options);
786
- if (options.parse) models = this.parse(models);
787
762
 
788
763
  // Allow a single model (or no argument) to be passed.
789
764
  if (!_.isArray(models)) models = models ? [models] : [];
@@ -794,7 +769,7 @@
794
769
  // Determine which models to add and merge, and which to remove.
795
770
  for (i = 0, l = models.length; i < l; i++) {
796
771
  model = models[i];
797
- existing = this.get(model.id || model.cid || model[idAttr]);
772
+ existing = this.get(model);
798
773
  if (options.remove && existing) modelMap[existing.cid] = true;
799
774
  if ((options.add && !existing) || (options.merge && existing)) {
800
775
  add.push(model);
@@ -818,11 +793,11 @@
818
793
  // any `add` or `remove` events. Fires `reset` when finished.
819
794
  reset: function(models, options) {
820
795
  options || (options = {});
821
- if (options.parse) models = this.parse(models);
796
+ if (options.parse) models = this.parse(models, options);
822
797
  for (var i = 0, l = this.models.length; i < l; i++) {
823
798
  this._removeReference(this.models[i]);
824
799
  }
825
- options.previousModels = this.models;
800
+ options.previousModels = this.models.slice();
826
801
  this._reset();
827
802
  if (models) this.add(models, _.extend({silent: true}, options));
828
803
  if (!options.silent) this.trigger('reset', this, options);
@@ -830,14 +805,13 @@
830
805
  },
831
806
 
832
807
  // Fetch the default set of models for this collection, resetting the
833
- // collection when they arrive. If `add: true` is passed, appends the
834
- // models to the collection instead of resetting.
808
+ // collection when they arrive. If `update: true` is passed, the response
809
+ // data will be passed through the `update` method instead of `reset`.
835
810
  fetch: function(options) {
836
811
  options = options ? _.clone(options) : {};
837
812
  if (options.parse === void 0) options.parse = true;
838
- var collection = this;
839
813
  var success = options.success;
840
- options.success = function(resp, status, xhr) {
814
+ options.success = function(collection, resp, options) {
841
815
  var method = options.update ? 'update' : 'reset';
842
816
  collection[method](resp, options);
843
817
  if (success) success(collection, resp, options);
@@ -849,11 +823,10 @@
849
823
  // collection immediately, unless `wait: true` is passed, in which case we
850
824
  // wait for the server to agree.
851
825
  create: function(model, options) {
852
- var collection = this;
853
826
  options = options ? _.clone(options) : {};
854
- model = this._prepareModel(model, options);
855
- if (!model) return false;
856
- if (!options.wait) collection.add(model, options);
827
+ if (!(model = this._prepareModel(model, options))) return false;
828
+ if (!options.wait) this.add(model, options);
829
+ var collection = this;
857
830
  var success = options.success;
858
831
  options.success = function(model, resp, options) {
859
832
  if (options.wait) collection.add(model, options);
@@ -865,7 +838,7 @@
865
838
 
866
839
  // **parse** converts a response into a list of models to be added to the
867
840
  // collection. The default implementation is just to pass it through.
868
- parse: function(resp) {
841
+ parse: function(resp, options) {
869
842
  return resp;
870
843
  },
871
844
 
@@ -874,19 +847,11 @@
874
847
  return new this.constructor(this.models);
875
848
  },
876
849
 
877
- // Proxy to _'s chain. Can't be proxied the same way the rest of the
878
- // underscore methods are proxied because it relies on the underscore
879
- // constructor.
880
- chain: function() {
881
- return _(this.models).chain();
882
- },
883
-
884
850
  // Reset all internal state. Called when the collection is reset.
885
851
  _reset: function() {
886
852
  this.length = 0;
887
- this.models = [];
853
+ this.models.length = 0;
888
854
  this._byId = {};
889
- this._byCid = {};
890
855
  },
891
856
 
892
857
  // Prepare a model or hash of attributes to be added to this collection.
@@ -920,6 +885,14 @@
920
885
  if (model.id != null) this._byId[model.id] = model;
921
886
  }
922
887
  this.trigger.apply(this, arguments);
888
+ },
889
+
890
+ sortedIndex: function (model, value, context) {
891
+ value || (value = this.comparator);
892
+ var iterator = _.isFunction(value) ? value : function(model) {
893
+ return model.get(value);
894
+ };
895
+ return _.sortedIndex(this.models, model, iterator, context);
923
896
  }
924
897
 
925
898
  });
@@ -928,9 +901,9 @@
928
901
  var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
929
902
  'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
930
903
  'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
931
- 'max', 'min', 'sortedIndex', 'toArray', 'size', 'first', 'head', 'take',
932
- 'initial', 'rest', 'tail', 'last', 'without', 'indexOf', 'shuffle',
933
- 'lastIndexOf', 'isEmpty'];
904
+ 'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
905
+ 'tail', 'drop', 'last', 'without', 'indexOf', 'shuffle', 'lastIndexOf',
906
+ 'isEmpty', 'chain'];
934
907
 
935
908
  // Mix in each Underscore method as a proxy to `Collection#models`.
936
909
  _.each(methods, function(method) {
@@ -969,7 +942,7 @@
969
942
  // Cached regular expressions for matching named param parts and splatted
970
943
  // parts of route strings.
971
944
  var optionalParam = /\((.*?)\)/g;
972
- var namedParam = /:\w+/g;
945
+ var namedParam = /(\(\?)?:\w+/g;
973
946
  var splatParam = /\*\w+/g;
974
947
  var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
975
948
 
@@ -993,6 +966,7 @@
993
966
  var args = this._extractParameters(route, fragment);
994
967
  callback && callback.apply(this, args);
995
968
  this.trigger.apply(this, ['route:' + name].concat(args));
969
+ this.trigger('route', name, args);
996
970
  Backbone.history.trigger('route', this, name, args);
997
971
  }, this));
998
972
  return this;
@@ -1020,7 +994,9 @@
1020
994
  _routeToRegExp: function(route) {
1021
995
  route = route.replace(escapeRegExp, '\\$&')
1022
996
  .replace(optionalParam, '(?:$1)?')
1023
- .replace(namedParam, '([^\/]+)')
997
+ .replace(namedParam, function(match, optional){
998
+ return optional ? match : '([^\/]+)';
999
+ })
1024
1000
  .replace(splatParam, '(.*?)');
1025
1001
  return new RegExp('^' + route + '$');
1026
1002
  },
@@ -1042,7 +1018,7 @@
1042
1018
  this.handlers = [];
1043
1019
  _.bindAll(this, 'checkUrl');
1044
1020
 
1045
- // #1653 - Ensure that `History` can be used outside of the browser.
1021
+ // Ensure that `History` can be used outside of the browser.
1046
1022
  if (typeof window !== 'undefined') {
1047
1023
  this.location = window.location;
1048
1024
  this.history = window.history;
@@ -1121,9 +1097,9 @@
1121
1097
  // Depending on whether we're using pushState or hashes, and whether
1122
1098
  // 'onhashchange' is supported, determine how we check the URL state.
1123
1099
  if (this._hasPushState) {
1124
- Backbone.$(window).bind('popstate', this.checkUrl);
1100
+ Backbone.$(window).on('popstate', this.checkUrl);
1125
1101
  } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
1126
- Backbone.$(window).bind('hashchange', this.checkUrl);
1102
+ Backbone.$(window).on('hashchange', this.checkUrl);
1127
1103
  } else if (this._wantsHashChange) {
1128
1104
  this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
1129
1105
  }
@@ -1155,7 +1131,7 @@
1155
1131
  // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
1156
1132
  // but possibly useful for unit testing Routers.
1157
1133
  stop: function() {
1158
- Backbone.$(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
1134
+ Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl);
1159
1135
  clearInterval(this._checkUrlInterval);
1160
1136
  History.started = false;
1161
1137
  },
@@ -1238,7 +1214,7 @@
1238
1214
  var href = location.href.replace(/(javascript:|#).*$/, '');
1239
1215
  location.replace(href + '#' + fragment);
1240
1216
  } else {
1241
- // #1649 - Some browsers require that `hash` contains a leading #.
1217
+ // Some browsers require that `hash` contains a leading #.
1242
1218
  location.hash = '#' + fragment;
1243
1219
  }
1244
1220
  }
@@ -1298,18 +1274,6 @@
1298
1274
  return this;
1299
1275
  },
1300
1276
 
1301
- // For small amounts of DOM Elements, where a full-blown template isn't
1302
- // needed, use **make** to manufacture elements, one at a time.
1303
- //
1304
- // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
1305
- //
1306
- make: function(tagName, attributes, content) {
1307
- var el = document.createElement(tagName);
1308
- if (attributes) Backbone.$(el).attr(attributes);
1309
- if (content != null) Backbone.$(el).html(content);
1310
- return el;
1311
- },
1312
-
1313
1277
  // Change the view's element (`this.el` property), including event
1314
1278
  // re-delegation.
1315
1279
  setElement: function(element, delegate) {
@@ -1347,9 +1311,9 @@
1347
1311
  method = _.bind(method, this);
1348
1312
  eventName += '.delegateEvents' + this.cid;
1349
1313
  if (selector === '') {
1350
- this.$el.bind(eventName, method);
1314
+ this.$el.on(eventName, method);
1351
1315
  } else {
1352
- this.$el.delegate(selector, eventName, method);
1316
+ this.$el.on(eventName, selector, method);
1353
1317
  }
1354
1318
  }
1355
1319
  },
@@ -1358,7 +1322,7 @@
1358
1322
  // You usually don't need to use this, but may wish to if you have multiple
1359
1323
  // Backbone views attached to the same DOM element.
1360
1324
  undelegateEvents: function() {
1361
- this.$el.unbind('.delegateEvents' + this.cid);
1325
+ this.$el.off('.delegateEvents' + this.cid);
1362
1326
  },
1363
1327
 
1364
1328
  // Performs the initial configuration of a View with a set of options.
@@ -1379,7 +1343,8 @@
1379
1343
  var attrs = _.extend({}, _.result(this, 'attributes'));
1380
1344
  if (this.id) attrs.id = _.result(this, 'id');
1381
1345
  if (this.className) attrs['class'] = _.result(this, 'className');
1382
- this.setElement(this.make(_.result(this, 'tagName'), attrs), false);
1346
+ var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
1347
+ this.setElement($el, false);
1383
1348
  } else {
1384
1349
  this.setElement(_.result(this, 'el'), false);
1385
1350
  }
@@ -1461,19 +1426,19 @@
1461
1426
  }
1462
1427
 
1463
1428
  var success = options.success;
1464
- options.success = function(resp, status, xhr) {
1465
- if (success) success(resp, status, xhr);
1429
+ options.success = function(resp) {
1430
+ if (success) success(model, resp, options);
1466
1431
  model.trigger('sync', model, resp, options);
1467
1432
  };
1468
1433
 
1469
1434
  var error = options.error;
1470
- options.error = function(xhr, status, thrown) {
1435
+ options.error = function(xhr) {
1471
1436
  if (error) error(model, xhr, options);
1472
1437
  model.trigger('error', model, xhr, options);
1473
1438
  };
1474
1439
 
1475
1440
  // Make the request, allowing the user to override any Ajax options.
1476
- var xhr = Backbone.ajax(_.extend(params, options));
1441
+ var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
1477
1442
  model.trigger('request', model, xhr, options);
1478
1443
  return xhr;
1479
1444
  };
@@ -1499,7 +1464,7 @@
1499
1464
  if (protoProps && _.has(protoProps, 'constructor')) {
1500
1465
  child = protoProps.constructor;
1501
1466
  } else {
1502
- child = function(){ parent.apply(this, arguments); };
1467
+ child = function(){ return parent.apply(this, arguments); };
1503
1468
  }
1504
1469
 
1505
1470
  // Add static properties to the constructor function, if supplied.
@@ -62,6 +62,18 @@
62
62
  if (complete) complete(jqXHR, textStatus);
63
63
  };
64
64
 
65
+ var success = options.success;
66
+ params.success = function(resp) {
67
+ if (success) success(model, resp, options);
68
+ model.trigger('sync', model, resp, options);
69
+ };
70
+
71
+ var error = options.error;
72
+ params.error = function(xhr) {
73
+ if (error) error(model, xhr, options);
74
+ model.trigger('error', model, xhr, options);
75
+ };
76
+
65
77
  // Make the request.
66
78
  return $.ajax(params);
67
79
  }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-backbone
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.9.10
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-12-17 00:00:00.000000000 Z
13
+ date: 2013-02-02 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: railties
@@ -242,7 +242,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
242
242
  version: '0'
243
243
  segments:
244
244
  - 0
245
- hash: 1886977708027563026
245
+ hash: 1693904627653384440
246
246
  required_rubygems_version: !ruby/object:Gem::Requirement
247
247
  none: false
248
248
  requirements:
@@ -251,7 +251,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
251
251
  version: '0'
252
252
  segments:
253
253
  - 0
254
- hash: 1886977708027563026
254
+ hash: 1693904627653384440
255
255
  requirements: []
256
256
  rubyforge_project:
257
257
  rubygems_version: 1.8.23