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 [](http://travis-ci.org/codebrew/backbone-rails)
|
2
2
|
|
3
|
-
Easily setup and use backbone.js (0.9.
|
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.
|
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.
|
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(
|
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 `
|
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 !==
|
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(
|
187
|
-
if (allEvents) triggerEvents(
|
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(
|
194
|
+
listenTo: function(obj, name, callback) {
|
194
195
|
var listeners = this._listeners || (this._listeners = {});
|
195
|
-
var id =
|
196
|
-
listeners[id] =
|
197
|
-
|
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(
|
204
|
+
stopListening: function(obj, name, callback) {
|
204
205
|
var listeners = this._listeners;
|
205
206
|
if (!listeners) return;
|
206
|
-
if (
|
207
|
-
|
208
|
-
if (!
|
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(
|
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'))
|
242
|
-
|
243
|
-
|
244
|
-
this.
|
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 (
|
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
|
-
|
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
|
-
|
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
|
-
|
319
|
-
|
320
|
-
|
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
|
-
//
|
324
|
-
|
325
|
-
|
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
|
-
|
328
|
-
if (!silent)
|
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(
|
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,
|
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 ||
|
432
|
+
if (key == null || typeof key === 'object') {
|
369
433
|
attrs = key;
|
370
434
|
options = val;
|
371
|
-
} else
|
435
|
+
} else {
|
372
436
|
(attrs = {})[key] = val;
|
373
437
|
}
|
374
|
-
options = options ? _.clone(options) : {};
|
375
438
|
|
376
|
-
// If we're
|
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
|
-
|
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 (!
|
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
|
-
|
394
|
-
|
395
|
-
options.success = function(
|
396
|
-
|
397
|
-
|
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))
|
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
|
-
|
405
|
-
if (method
|
406
|
-
|
407
|
-
|
408
|
-
//
|
409
|
-
|
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
|
-
//
|
471
|
-
|
472
|
-
|
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.
|
571
|
-
//
|
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
|
-
|
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.
|
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
|
632
|
-
if(!(model = this._prepareModel(models[i], options))) {
|
633
|
-
this.trigger(
|
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
|
643
|
-
if (options
|
644
|
-
existing.set(model.attributes, options);
|
645
|
-
|
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.
|
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 (
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
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
|
-
//
|
666
|
-
if (
|
636
|
+
// Silently sort the collection if appropriate.
|
637
|
+
if (doSort) this.sort({silent: true});
|
667
638
|
|
668
|
-
if (options
|
639
|
+
if (options.silent) return this;
|
669
640
|
|
670
641
|
// Trigger `add` events.
|
671
|
-
|
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.
|
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.
|
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
|
-
|
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
|
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
|
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 `
|
834
|
-
//
|
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(
|
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 (!
|
856
|
-
|
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', '
|
932
|
-
'
|
933
|
-
'
|
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 =
|
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
|
-
//
|
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).
|
1100
|
+
Backbone.$(window).on('popstate', this.checkUrl);
|
1125
1101
|
} else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
|
1126
|
-
Backbone.$(window).
|
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).
|
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
|
-
//
|
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.
|
1314
|
+
this.$el.on(eventName, method);
|
1351
1315
|
} else {
|
1352
|
-
this.$el.
|
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.
|
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
|
-
|
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
|
1465
|
-
if (success) success(
|
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
|
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.
|
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:
|
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:
|
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:
|
254
|
+
hash: 1693904627653384440
|
255
255
|
requirements: []
|
256
256
|
rubyforge_project:
|
257
257
|
rubygems_version: 1.8.23
|