rails-backbone 0.9.0 → 0.9.10
Sign up to get free protection for your applications and to get access to all the features.
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.
|
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
|