rocky 0.1.2.pre → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -133
- data/lib/rocky/version.rb +1 -1
- data/vendor/assets/javascripts/backbone.js +589 -505
- data/vendor/assets/javascripts/middleware/component/json_form.js.coffee +8 -13
- data/vendor/assets/javascripts/middleware/system/env.js.coffee +9 -0
- data/vendor/assets/javascripts/middleware.js.coffee +4 -1
- metadata +5 -5
- data/vendor/assets/javascripts/middleware/component/modal.js.coffee +0 -10
@@ -1,15 +1,15 @@
|
|
1
|
-
//
|
2
|
-
// ===========
|
1
|
+
// Backbone.js 1.0.0
|
3
2
|
|
4
|
-
//
|
5
|
-
//
|
6
|
-
//
|
7
|
-
|
8
|
-
// Initial Setup
|
9
|
-
// -------------
|
3
|
+
// (c) 2010-2013 Jeremy Ashkenas, DocumentCloud Inc.
|
4
|
+
// Backbone may be freely distributed under the MIT license.
|
5
|
+
// For all details and documentation:
|
6
|
+
// http://backbonejs.org
|
10
7
|
|
11
8
|
(function(){
|
12
9
|
|
10
|
+
// Initial Setup
|
11
|
+
// -------------
|
12
|
+
|
13
13
|
// Save a reference to the global object (`window` in the browser, `exports`
|
14
14
|
// on the server).
|
15
15
|
var root = this;
|
@@ -18,14 +18,14 @@
|
|
18
18
|
// restored later on, if `noConflict` is used.
|
19
19
|
var previousBackbone = root.Backbone;
|
20
20
|
|
21
|
-
// Create
|
21
|
+
// Create local references to array methods we'll want to use later.
|
22
22
|
var array = [];
|
23
23
|
var push = array.push;
|
24
24
|
var slice = array.slice;
|
25
25
|
var splice = array.splice;
|
26
26
|
|
27
27
|
// The top-level namespace. All public Backbone classes and modules will
|
28
|
-
// be attached to this. Exported for both
|
28
|
+
// be attached to this. Exported for both the browser and the server.
|
29
29
|
var Backbone;
|
30
30
|
if (typeof exports !== 'undefined') {
|
31
31
|
Backbone = exports;
|
@@ -34,14 +34,15 @@
|
|
34
34
|
}
|
35
35
|
|
36
36
|
// Current version of the library. Keep in sync with `package.json`.
|
37
|
-
Backbone.VERSION = '0.
|
37
|
+
Backbone.VERSION = '1.0.0';
|
38
38
|
|
39
39
|
// Require Underscore, if we're on the server, and it's not already present.
|
40
40
|
var _ = root._;
|
41
41
|
if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
|
42
42
|
|
43
|
-
// For Backbone's purposes, jQuery, Zepto, or
|
44
|
-
|
43
|
+
// For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
|
44
|
+
// the `$` variable.
|
45
|
+
Backbone.$ = root.jQuery || root.Zepto || root.ender || root.$;
|
45
46
|
|
46
47
|
// Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
|
47
48
|
// to its previous owner. Returns a reference to this Backbone object.
|
@@ -64,74 +65,39 @@
|
|
64
65
|
// Backbone.Events
|
65
66
|
// ---------------
|
66
67
|
|
67
|
-
// Regular expression used to split event strings.
|
68
|
-
var eventSplitter = /\s+/;
|
69
|
-
|
70
|
-
// Implement fancy features of the Events API such as multiple event
|
71
|
-
// names `"change blur"` and jQuery-style event maps `{change: action}`
|
72
|
-
// in terms of the existing API.
|
73
|
-
var eventsApi = function(obj, action, name, rest) {
|
74
|
-
if (!name) return true;
|
75
|
-
if (typeof name === 'object') {
|
76
|
-
for (var key in name) {
|
77
|
-
obj[action].apply(obj, [key, name[key]].concat(rest));
|
78
|
-
}
|
79
|
-
} else if (eventSplitter.test(name)) {
|
80
|
-
var names = name.split(eventSplitter);
|
81
|
-
for (var i = 0, l = names.length; i < l; i++) {
|
82
|
-
obj[action].apply(obj, [names[i]].concat(rest));
|
83
|
-
}
|
84
|
-
} else {
|
85
|
-
return true;
|
86
|
-
}
|
87
|
-
};
|
88
|
-
|
89
|
-
// Optimized internal dispatch function for triggering events. Tries to
|
90
|
-
// keep the usual cases speedy (most Backbone events have 3 arguments).
|
91
|
-
var triggerEvents = function(events, args) {
|
92
|
-
var ev, i = -1, l = events.length;
|
93
|
-
switch (args.length) {
|
94
|
-
case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx);
|
95
|
-
return;
|
96
|
-
case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0]);
|
97
|
-
return;
|
98
|
-
case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0], args[1]);
|
99
|
-
return;
|
100
|
-
case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0], args[1], args[2]);
|
101
|
-
return;
|
102
|
-
default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
|
103
|
-
}
|
104
|
-
};
|
105
|
-
|
106
68
|
// A module that can be mixed in to *any object* in order to provide it with
|
107
69
|
// custom events. You may bind with `on` or remove with `off` callback
|
108
70
|
// functions to an event; `trigger`-ing an event fires all callbacks in
|
109
71
|
// succession.
|
72
|
+
//
|
73
|
+
// var object = {};
|
74
|
+
// _.extend(object, Backbone.Events);
|
75
|
+
// object.on('expand', function(){ alert('expanded'); });
|
76
|
+
// object.trigger('expand');
|
77
|
+
//
|
110
78
|
var Events = Backbone.Events = {
|
111
79
|
|
112
|
-
// Bind
|
113
|
-
//
|
114
|
-
// all events fired.
|
80
|
+
// Bind an event to a `callback` function. Passing `"all"` will bind
|
81
|
+
// the callback to all events fired.
|
115
82
|
on: function(name, callback, context) {
|
116
|
-
if (!
|
83
|
+
if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
|
117
84
|
this._events || (this._events = {});
|
118
|
-
var
|
119
|
-
|
85
|
+
var events = this._events[name] || (this._events[name] = []);
|
86
|
+
events.push({callback: callback, context: context, ctx: context || this});
|
120
87
|
return this;
|
121
88
|
},
|
122
89
|
|
123
|
-
// Bind
|
90
|
+
// Bind an event to only be triggered a single time. After the first time
|
124
91
|
// the callback is invoked, it will be removed.
|
125
92
|
once: function(name, callback, context) {
|
126
|
-
if (!
|
93
|
+
if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
|
127
94
|
var self = this;
|
128
95
|
var once = _.once(function() {
|
129
96
|
self.off(name, once);
|
130
97
|
callback.apply(this, arguments);
|
131
98
|
});
|
132
99
|
once._callback = callback;
|
133
|
-
this.on(name, once, context);
|
134
|
-
return this;
|
100
|
+
return this.on(name, once, context);
|
135
101
|
},
|
136
102
|
|
137
103
|
// Remove one or many callbacks. If `context` is null, removes all
|
@@ -139,7 +105,7 @@
|
|
139
105
|
// callbacks for the event. If `name` is null, removes all bound
|
140
106
|
// callbacks for all events.
|
141
107
|
off: function(name, callback, context) {
|
142
|
-
var
|
108
|
+
var retain, ev, events, names, i, l, j, k;
|
143
109
|
if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
|
144
110
|
if (!name && !callback && !context) {
|
145
111
|
this._events = {};
|
@@ -149,19 +115,18 @@
|
|
149
115
|
names = name ? [name] : _.keys(this._events);
|
150
116
|
for (i = 0, l = names.length; i < l; i++) {
|
151
117
|
name = names[i];
|
152
|
-
if (
|
153
|
-
|
118
|
+
if (events = this._events[name]) {
|
119
|
+
this._events[name] = retain = [];
|
154
120
|
if (callback || context) {
|
155
|
-
for (j = 0, k =
|
156
|
-
ev =
|
157
|
-
if ((callback && callback !== ev.callback &&
|
158
|
-
callback !== ev.callback._callback) ||
|
121
|
+
for (j = 0, k = events.length; j < k; j++) {
|
122
|
+
ev = events[j];
|
123
|
+
if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
|
159
124
|
(context && context !== ev.context)) {
|
160
|
-
|
125
|
+
retain.push(ev);
|
161
126
|
}
|
162
127
|
}
|
163
128
|
}
|
164
|
-
this._events[name]
|
129
|
+
if (!retain.length) delete this._events[name];
|
165
130
|
}
|
166
131
|
}
|
167
132
|
|
@@ -183,35 +148,82 @@
|
|
183
148
|
return this;
|
184
149
|
},
|
185
150
|
|
186
|
-
// An inversion-of-control version of `on`. Tell *this* object to listen to
|
187
|
-
// an event in another object ... keeping track of what it's listening to.
|
188
|
-
listenTo: function(obj, name, callback) {
|
189
|
-
var listeners = this._listeners || (this._listeners = {});
|
190
|
-
var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
|
191
|
-
listeners[id] = obj;
|
192
|
-
obj.on(name, typeof name === 'object' ? this : callback, this);
|
193
|
-
return this;
|
194
|
-
},
|
195
|
-
|
196
151
|
// Tell this object to stop listening to either specific events ... or
|
197
152
|
// to every object it's currently listening to.
|
198
153
|
stopListening: function(obj, name, callback) {
|
199
154
|
var listeners = this._listeners;
|
200
|
-
if (!listeners) return;
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
listeners[id].off(name, callback, this);
|
208
|
-
}
|
209
|
-
this._listeners = {};
|
155
|
+
if (!listeners) return this;
|
156
|
+
var deleteListener = !name && !callback;
|
157
|
+
if (typeof name === 'object') callback = this;
|
158
|
+
if (obj) (listeners = {})[obj._listenerId] = obj;
|
159
|
+
for (var id in listeners) {
|
160
|
+
listeners[id].off(name, callback, this);
|
161
|
+
if (deleteListener) delete this._listeners[id];
|
210
162
|
}
|
211
163
|
return this;
|
212
164
|
}
|
165
|
+
|
166
|
+
};
|
167
|
+
|
168
|
+
// Regular expression used to split event strings.
|
169
|
+
var eventSplitter = /\s+/;
|
170
|
+
|
171
|
+
// Implement fancy features of the Events API such as multiple event
|
172
|
+
// names `"change blur"` and jQuery-style event maps `{change: action}`
|
173
|
+
// in terms of the existing API.
|
174
|
+
var eventsApi = function(obj, action, name, rest) {
|
175
|
+
if (!name) return true;
|
176
|
+
|
177
|
+
// Handle event maps.
|
178
|
+
if (typeof name === 'object') {
|
179
|
+
for (var key in name) {
|
180
|
+
obj[action].apply(obj, [key, name[key]].concat(rest));
|
181
|
+
}
|
182
|
+
return false;
|
183
|
+
}
|
184
|
+
|
185
|
+
// Handle space separated event names.
|
186
|
+
if (eventSplitter.test(name)) {
|
187
|
+
var names = name.split(eventSplitter);
|
188
|
+
for (var i = 0, l = names.length; i < l; i++) {
|
189
|
+
obj[action].apply(obj, [names[i]].concat(rest));
|
190
|
+
}
|
191
|
+
return false;
|
192
|
+
}
|
193
|
+
|
194
|
+
return true;
|
195
|
+
};
|
196
|
+
|
197
|
+
// A difficult-to-believe, but optimized internal dispatch function for
|
198
|
+
// triggering events. Tries to keep the usual cases speedy (most internal
|
199
|
+
// Backbone events have 3 arguments).
|
200
|
+
var triggerEvents = function(events, args) {
|
201
|
+
var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
|
202
|
+
switch (args.length) {
|
203
|
+
case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
|
204
|
+
case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
|
205
|
+
case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
|
206
|
+
case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
|
207
|
+
default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
|
208
|
+
}
|
213
209
|
};
|
214
210
|
|
211
|
+
var listenMethods = {listenTo: 'on', listenToOnce: 'once'};
|
212
|
+
|
213
|
+
// Inversion-of-control versions of `on` and `once`. Tell *this* object to
|
214
|
+
// listen to an event in another object ... keeping track of what it's
|
215
|
+
// listening to.
|
216
|
+
_.each(listenMethods, function(implementation, method) {
|
217
|
+
Events[method] = function(obj, name, callback) {
|
218
|
+
var listeners = this._listeners || (this._listeners = {});
|
219
|
+
var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
|
220
|
+
listeners[id] = obj;
|
221
|
+
if (typeof name === 'object') callback = this;
|
222
|
+
obj[implementation](name, callback, this);
|
223
|
+
return this;
|
224
|
+
};
|
225
|
+
});
|
226
|
+
|
215
227
|
// Aliases for backwards compatibility.
|
216
228
|
Events.bind = Events.on;
|
217
229
|
Events.unbind = Events.off;
|
@@ -223,15 +235,21 @@
|
|
223
235
|
// Backbone.Model
|
224
236
|
// --------------
|
225
237
|
|
226
|
-
//
|
238
|
+
// Backbone **Models** are the basic data object in the framework --
|
239
|
+
// frequently representing a row in a table in a database on your server.
|
240
|
+
// A discrete chunk of data and a bunch of useful, related methods for
|
241
|
+
// performing computations and transformations on that data.
|
242
|
+
|
243
|
+
// Create a new model with the specified attributes. A client id (`cid`)
|
227
244
|
// is automatically generated and assigned for you.
|
228
245
|
var Model = Backbone.Model = function(attributes, options) {
|
229
246
|
var defaults;
|
230
247
|
var attrs = attributes || {};
|
248
|
+
options || (options = {});
|
231
249
|
this.cid = _.uniqueId('c');
|
232
250
|
this.attributes = {};
|
233
|
-
|
234
|
-
if (options
|
251
|
+
_.extend(this, _.pick(options, modelOptions));
|
252
|
+
if (options.parse) attrs = this.parse(attrs, options) || {};
|
235
253
|
if (defaults = _.result(this, 'defaults')) {
|
236
254
|
attrs = _.defaults({}, attrs, defaults);
|
237
255
|
}
|
@@ -240,12 +258,18 @@
|
|
240
258
|
this.initialize.apply(this, arguments);
|
241
259
|
};
|
242
260
|
|
261
|
+
// A list of options to be attached directly to the model, if provided.
|
262
|
+
var modelOptions = ['url', 'urlRoot', 'collection'];
|
263
|
+
|
243
264
|
// Attach all inheritable methods to the Model prototype.
|
244
265
|
_.extend(Model.prototype, Events, {
|
245
266
|
|
246
267
|
// A hash of attributes whose current and previous value differ.
|
247
268
|
changed: null,
|
248
269
|
|
270
|
+
// The value returned during the last failed validation.
|
271
|
+
validationError: null,
|
272
|
+
|
249
273
|
// The default name for the JSON `id` attribute is `"id"`. MongoDB and
|
250
274
|
// CouchDB users may want to set this to `"_id"`.
|
251
275
|
idAttribute: 'id',
|
@@ -259,7 +283,8 @@
|
|
259
283
|
return _.clone(this.attributes);
|
260
284
|
},
|
261
285
|
|
262
|
-
// Proxy `Backbone.sync` by default
|
286
|
+
// Proxy `Backbone.sync` by default -- but override this if you need
|
287
|
+
// custom syncing semantics for *this* particular model.
|
263
288
|
sync: function() {
|
264
289
|
return Backbone.sync.apply(this, arguments);
|
265
290
|
},
|
@@ -280,10 +305,9 @@
|
|
280
305
|
return this.get(attr) != null;
|
281
306
|
},
|
282
307
|
|
283
|
-
//
|
284
|
-
|
285
|
-
//
|
286
|
-
// you choose to silence it.
|
308
|
+
// Set a hash of model attributes on the object, firing `"change"`. This is
|
309
|
+
// the core primitive operation of a model, updating the data and notifying
|
310
|
+
// anyone who needs to know about the change in state. The heart of the beast.
|
287
311
|
set: function(key, val, options) {
|
288
312
|
var attr, attrs, unset, changes, silent, changing, prev, current;
|
289
313
|
if (key == null) return this;
|
@@ -337,6 +361,8 @@
|
|
337
361
|
}
|
338
362
|
}
|
339
363
|
|
364
|
+
// You might be wondering why there's a `while` loop here. Changes can
|
365
|
+
// be recursively nested within `"change"` events.
|
340
366
|
if (changing) return this;
|
341
367
|
if (!silent) {
|
342
368
|
while (this._pending) {
|
@@ -349,14 +375,13 @@
|
|
349
375
|
return this;
|
350
376
|
},
|
351
377
|
|
352
|
-
// Remove an attribute from the model, firing `"change"`
|
353
|
-
//
|
378
|
+
// Remove an attribute from the model, firing `"change"`. `unset` is a noop
|
379
|
+
// if the attribute doesn't exist.
|
354
380
|
unset: function(attr, options) {
|
355
381
|
return this.set(attr, void 0, _.extend({}, options, {unset: true}));
|
356
382
|
},
|
357
383
|
|
358
|
-
// Clear all attributes on the model, firing `"change"
|
359
|
-
// to silence it.
|
384
|
+
// Clear all attributes on the model, firing `"change"`.
|
360
385
|
clear: function(options) {
|
361
386
|
var attrs = {};
|
362
387
|
for (var key in this.attributes) attrs[key] = void 0;
|
@@ -400,19 +425,20 @@
|
|
400
425
|
return _.clone(this._previousAttributes);
|
401
426
|
},
|
402
427
|
|
403
|
-
// ---------------------------------------------------------------------
|
404
|
-
|
405
428
|
// Fetch the model from the server. If the server's representation of the
|
406
|
-
// model differs from its current attributes, they will be
|
429
|
+
// model differs from its current attributes, they will be overridden,
|
407
430
|
// triggering a `"change"` event.
|
408
431
|
fetch: function(options) {
|
409
432
|
options = options ? _.clone(options) : {};
|
410
433
|
if (options.parse === void 0) options.parse = true;
|
434
|
+
var model = this;
|
411
435
|
var success = options.success;
|
412
|
-
options.success = function(
|
436
|
+
options.success = function(resp) {
|
413
437
|
if (!model.set(model.parse(resp, options), options)) return false;
|
414
438
|
if (success) success(model, resp, options);
|
439
|
+
model.trigger('sync', model, resp, options);
|
415
440
|
};
|
441
|
+
wrapError(this, options);
|
416
442
|
return this.sync('read', this, options);
|
417
443
|
},
|
418
444
|
|
@@ -420,7 +446,7 @@
|
|
420
446
|
// If the server returns an attributes hash that differs, the model's
|
421
447
|
// state will be `set` again.
|
422
448
|
save: function(key, val, options) {
|
423
|
-
var attrs,
|
449
|
+
var attrs, method, xhr, attributes = this.attributes;
|
424
450
|
|
425
451
|
// Handle both `"key", value` and `{key: value}` -style arguments.
|
426
452
|
if (key == null || typeof key === 'object') {
|
@@ -446,8 +472,9 @@
|
|
446
472
|
// After a successful server-side save, the client is (optionally)
|
447
473
|
// updated with the server-side state.
|
448
474
|
if (options.parse === void 0) options.parse = true;
|
449
|
-
|
450
|
-
|
475
|
+
var model = this;
|
476
|
+
var success = options.success;
|
477
|
+
options.success = function(resp) {
|
451
478
|
// Ensure attributes are restored during synchronous saves.
|
452
479
|
model.attributes = attributes;
|
453
480
|
var serverAttrs = model.parse(resp, options);
|
@@ -456,9 +483,10 @@
|
|
456
483
|
return false;
|
457
484
|
}
|
458
485
|
if (success) success(model, resp, options);
|
486
|
+
model.trigger('sync', model, resp, options);
|
459
487
|
};
|
488
|
+
wrapError(this, options);
|
460
489
|
|
461
|
-
// Finish configuring and sending the Ajax request.
|
462
490
|
method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
|
463
491
|
if (method === 'patch') options.attrs = attrs;
|
464
492
|
xhr = this.sync(method, this, options);
|
@@ -481,15 +509,17 @@
|
|
481
509
|
model.trigger('destroy', model, model.collection, options);
|
482
510
|
};
|
483
511
|
|
484
|
-
options.success = function(
|
512
|
+
options.success = function(resp) {
|
485
513
|
if (options.wait || model.isNew()) destroy();
|
486
514
|
if (success) success(model, resp, options);
|
515
|
+
if (!model.isNew()) model.trigger('sync', model, resp, options);
|
487
516
|
};
|
488
517
|
|
489
518
|
if (this.isNew()) {
|
490
|
-
options.success(
|
519
|
+
options.success();
|
491
520
|
return false;
|
492
521
|
}
|
522
|
+
wrapError(this, options);
|
493
523
|
|
494
524
|
var xhr = this.sync('delete', this, options);
|
495
525
|
if (!options.wait) destroy();
|
@@ -523,39 +553,61 @@
|
|
523
553
|
|
524
554
|
// Check if the model is currently in a valid state.
|
525
555
|
isValid: function(options) {
|
526
|
-
return
|
556
|
+
return this._validate({}, _.extend(options || {}, { validate: true }));
|
527
557
|
},
|
528
558
|
|
529
559
|
// Run validation against the next complete set of model attributes,
|
530
|
-
// returning `true` if all is well. Otherwise, fire
|
531
|
-
// `"error"` event and call the error callback, if specified.
|
560
|
+
// returning `true` if all is well. Otherwise, fire an `"invalid"` event.
|
532
561
|
_validate: function(attrs, options) {
|
533
562
|
if (!options.validate || !this.validate) return true;
|
534
563
|
attrs = _.extend({}, this.attributes, attrs);
|
535
564
|
var error = this.validationError = this.validate(attrs, options) || null;
|
536
565
|
if (!error) return true;
|
537
|
-
this.trigger('invalid', this, error, options || {});
|
566
|
+
this.trigger('invalid', this, error, _.extend(options || {}, {validationError: error}));
|
538
567
|
return false;
|
539
568
|
}
|
540
569
|
|
541
570
|
});
|
542
571
|
|
572
|
+
// Underscore methods that we want to implement on the Model.
|
573
|
+
var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit'];
|
574
|
+
|
575
|
+
// Mix in each Underscore method as a proxy to `Model#attributes`.
|
576
|
+
_.each(modelMethods, function(method) {
|
577
|
+
Model.prototype[method] = function() {
|
578
|
+
var args = slice.call(arguments);
|
579
|
+
args.unshift(this.attributes);
|
580
|
+
return _[method].apply(_, args);
|
581
|
+
};
|
582
|
+
});
|
583
|
+
|
543
584
|
// Backbone.Collection
|
544
585
|
// -------------------
|
545
586
|
|
546
|
-
//
|
547
|
-
//
|
587
|
+
// If models tend to represent a single row of data, a Backbone Collection is
|
588
|
+
// more analagous to a table full of data ... or a small slice or page of that
|
589
|
+
// table, or a collection of rows that belong together for a particular reason
|
590
|
+
// -- all of the messages in this particular folder, all of the documents
|
591
|
+
// belonging to this particular author, and so on. Collections maintain
|
592
|
+
// indexes of their models, both in order, and for lookup by `id`.
|
593
|
+
|
594
|
+
// Create a new **Collection**, perhaps to contain a specific type of `model`.
|
595
|
+
// If a `comparator` is specified, the Collection will maintain
|
548
596
|
// its models in sort order, as they're added and removed.
|
549
597
|
var Collection = Backbone.Collection = function(models, options) {
|
550
598
|
options || (options = {});
|
599
|
+
if (options.url) this.url = options.url;
|
551
600
|
if (options.model) this.model = options.model;
|
552
601
|
if (options.comparator !== void 0) this.comparator = options.comparator;
|
553
|
-
this.models = [];
|
554
602
|
this._reset();
|
555
603
|
this.initialize.apply(this, arguments);
|
556
604
|
if (models) this.reset(models, _.extend({silent: true}, options));
|
557
605
|
};
|
558
606
|
|
607
|
+
// Default options for `Collection#set`.
|
608
|
+
var setOptions = {add: true, remove: true, merge: true};
|
609
|
+
var addOptions = {add: true, merge: false, remove: false};
|
610
|
+
|
559
611
|
// Define the Collection's inheritable methods.
|
560
612
|
_.extend(Collection.prototype, Events, {
|
561
613
|
|
@@ -580,88 +632,118 @@
|
|
580
632
|
|
581
633
|
// Add a model, or list of models to the set.
|
582
634
|
add: function(models, options) {
|
635
|
+
return this.set(models, _.defaults(options || {}, addOptions));
|
636
|
+
},
|
637
|
+
|
638
|
+
// Remove a model, or a list of models from the set.
|
639
|
+
remove: function(models, options) {
|
583
640
|
models = _.isArray(models) ? models.slice() : [models];
|
584
641
|
options || (options = {});
|
585
|
-
var i, l,
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
642
|
+
var i, l, index, model;
|
643
|
+
for (i = 0, l = models.length; i < l; i++) {
|
644
|
+
model = this.get(models[i]);
|
645
|
+
if (!model) continue;
|
646
|
+
delete this._byId[model.id];
|
647
|
+
delete this._byId[model.cid];
|
648
|
+
index = this.indexOf(model);
|
649
|
+
this.models.splice(index, 1);
|
650
|
+
this.length--;
|
651
|
+
if (!options.silent) {
|
652
|
+
options.index = index;
|
653
|
+
model.trigger('remove', model, this, options);
|
654
|
+
}
|
655
|
+
this._removeReference(model);
|
656
|
+
}
|
657
|
+
return this;
|
658
|
+
},
|
659
|
+
|
660
|
+
// Update a collection by `set`-ing a new list of models, adding new ones,
|
661
|
+
// removing models that are no longer present, and merging models that
|
662
|
+
// already exist in the collection, as necessary. Similar to **Model#set**,
|
663
|
+
// the core operation for updating the data contained by the collection.
|
664
|
+
set: function(models, options) {
|
665
|
+
options = _.defaults(options || {}, setOptions);
|
666
|
+
if (options.parse) models = this.parse(models, options);
|
667
|
+
if (!_.isArray(models)) models = models ? [models] : [];
|
668
|
+
var i, l, model, attrs, existing, sort;
|
669
|
+
var at = options.at;
|
670
|
+
var sortable = this.comparator && (at == null) && options.sort !== false;
|
671
|
+
var sortAttr = _.isString(this.comparator) ? this.comparator : null;
|
672
|
+
var toAdd = [], toRemove = [], modelMap = {};
|
590
673
|
|
591
674
|
// Turn bare objects into model references, and prevent invalid models
|
592
675
|
// from being added.
|
593
676
|
for (i = 0, l = models.length; i < l; i++) {
|
594
|
-
if (!(model = this._prepareModel(
|
595
|
-
this.trigger('invalid', this, attrs, options);
|
596
|
-
continue;
|
597
|
-
}
|
677
|
+
if (!(model = this._prepareModel(models[i], options))) continue;
|
598
678
|
|
599
679
|
// If a duplicate is found, prevent it from being added and
|
600
680
|
// optionally merge it into the existing model.
|
601
681
|
if (existing = this.get(model)) {
|
682
|
+
if (options.remove) modelMap[existing.cid] = true;
|
602
683
|
if (options.merge) {
|
603
|
-
existing.set(
|
604
|
-
if (
|
684
|
+
existing.set(model.attributes, options);
|
685
|
+
if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
|
605
686
|
}
|
606
|
-
continue;
|
607
|
-
}
|
608
687
|
|
609
|
-
|
610
|
-
add
|
688
|
+
// This is a new model, push it to the `toAdd` list.
|
689
|
+
} else if (options.add) {
|
690
|
+
toAdd.push(model);
|
611
691
|
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
692
|
+
// Listen to added models' events, and index models for lookup by
|
693
|
+
// `id` and by `cid`.
|
694
|
+
model.on('all', this._onModelEvent, this);
|
695
|
+
this._byId[model.cid] = model;
|
696
|
+
if (model.id != null) this._byId[model.id] = model;
|
697
|
+
}
|
698
|
+
}
|
699
|
+
|
700
|
+
// Remove nonexistent models if appropriate.
|
701
|
+
if (options.remove) {
|
702
|
+
for (i = 0, l = this.length; i < l; ++i) {
|
703
|
+
if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
|
704
|
+
}
|
705
|
+
if (toRemove.length) this.remove(toRemove, options);
|
617
706
|
}
|
618
707
|
|
619
708
|
// See if sorting is needed, update `length` and splice in new models.
|
620
|
-
if (
|
621
|
-
if (
|
622
|
-
this.length +=
|
709
|
+
if (toAdd.length) {
|
710
|
+
if (sortable) sort = true;
|
711
|
+
this.length += toAdd.length;
|
623
712
|
if (at != null) {
|
624
|
-
splice.apply(this.models, [at, 0].concat(
|
713
|
+
splice.apply(this.models, [at, 0].concat(toAdd));
|
625
714
|
} else {
|
626
|
-
push.apply(this.models,
|
715
|
+
push.apply(this.models, toAdd);
|
627
716
|
}
|
628
717
|
}
|
629
718
|
|
630
719
|
// Silently sort the collection if appropriate.
|
631
|
-
if (
|
720
|
+
if (sort) this.sort({silent: true});
|
632
721
|
|
633
722
|
if (options.silent) return this;
|
634
723
|
|
635
724
|
// Trigger `add` events.
|
636
|
-
for (i = 0, l =
|
637
|
-
(model =
|
725
|
+
for (i = 0, l = toAdd.length; i < l; i++) {
|
726
|
+
(model = toAdd[i]).trigger('add', model, this, options);
|
638
727
|
}
|
639
728
|
|
640
729
|
// Trigger `sort` if the collection was sorted.
|
641
|
-
if (
|
642
|
-
|
730
|
+
if (sort) this.trigger('sort', this, options);
|
643
731
|
return this;
|
644
732
|
},
|
645
733
|
|
646
|
-
//
|
647
|
-
|
648
|
-
|
734
|
+
// When you have more items than you want to add or remove individually,
|
735
|
+
// you can reset the entire set with a new list of models, without firing
|
736
|
+
// any granular `add` or `remove` events. Fires `reset` when finished.
|
737
|
+
// Useful for bulk operations and optimizations.
|
738
|
+
reset: function(models, options) {
|
649
739
|
options || (options = {});
|
650
|
-
var i, l
|
651
|
-
|
652
|
-
model = this.get(models[i]);
|
653
|
-
if (!model) continue;
|
654
|
-
delete this._byId[model.id];
|
655
|
-
delete this._byId[model.cid];
|
656
|
-
index = this.indexOf(model);
|
657
|
-
this.models.splice(index, 1);
|
658
|
-
this.length--;
|
659
|
-
if (!options.silent) {
|
660
|
-
options.index = index;
|
661
|
-
model.trigger('remove', model, this, options);
|
662
|
-
}
|
663
|
-
this._removeReference(model);
|
740
|
+
for (var i = 0, l = this.models.length; i < l; i++) {
|
741
|
+
this._removeReference(this.models[i]);
|
664
742
|
}
|
743
|
+
options.previousModels = this.models;
|
744
|
+
this._reset();
|
745
|
+
this.add(models, _.extend({silent: true}, options));
|
746
|
+
if (!options.silent) this.trigger('reset', this, options);
|
665
747
|
return this;
|
666
748
|
},
|
667
749
|
|
@@ -701,8 +783,7 @@
|
|
701
783
|
// Get a model from the set by id.
|
702
784
|
get: function(obj) {
|
703
785
|
if (obj == null) return void 0;
|
704
|
-
this.
|
705
|
-
return this._byId[obj.id || obj.cid || obj[this._idAttr] || obj];
|
786
|
+
return this._byId[obj.id != null ? obj.id : obj.cid || obj];
|
706
787
|
},
|
707
788
|
|
708
789
|
// Get the model at the given index.
|
@@ -710,10 +791,11 @@
|
|
710
791
|
return this.models[index];
|
711
792
|
},
|
712
793
|
|
713
|
-
// Return models with matching attributes. Useful for simple cases of
|
714
|
-
|
715
|
-
|
716
|
-
|
794
|
+
// Return models with matching attributes. Useful for simple cases of
|
795
|
+
// `filter`.
|
796
|
+
where: function(attrs, first) {
|
797
|
+
if (_.isEmpty(attrs)) return first ? void 0 : [];
|
798
|
+
return this[first ? 'find' : 'filter'](function(model) {
|
717
799
|
for (var key in attrs) {
|
718
800
|
if (attrs[key] !== model.get(key)) return false;
|
719
801
|
}
|
@@ -721,13 +803,17 @@
|
|
721
803
|
});
|
722
804
|
},
|
723
805
|
|
806
|
+
// Return the first model with matching attributes. Useful for simple cases
|
807
|
+
// of `find`.
|
808
|
+
findWhere: function(attrs) {
|
809
|
+
return this.where(attrs, true);
|
810
|
+
},
|
811
|
+
|
724
812
|
// Force the collection to re-sort itself. You don't need to call this under
|
725
813
|
// normal circumstances, as the set will maintain sort order as each item
|
726
814
|
// is added.
|
727
815
|
sort: function(options) {
|
728
|
-
if (!this.comparator)
|
729
|
-
throw new Error('Cannot sort a set without a comparator');
|
730
|
-
}
|
816
|
+
if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
|
731
817
|
options || (options = {});
|
732
818
|
|
733
819
|
// Run sort based on type of `comparator`.
|
@@ -741,75 +827,36 @@
|
|
741
827
|
return this;
|
742
828
|
},
|
743
829
|
|
830
|
+
// Figure out the smallest index at which a model should be inserted so as
|
831
|
+
// to maintain order.
|
832
|
+
sortedIndex: function(model, value, context) {
|
833
|
+
value || (value = this.comparator);
|
834
|
+
var iterator = _.isFunction(value) ? value : function(model) {
|
835
|
+
return model.get(value);
|
836
|
+
};
|
837
|
+
return _.sortedIndex(this.models, model, iterator, context);
|
838
|
+
},
|
839
|
+
|
744
840
|
// Pluck an attribute from each model in the collection.
|
745
841
|
pluck: function(attr) {
|
746
842
|
return _.invoke(this.models, 'get', attr);
|
747
843
|
},
|
748
844
|
|
749
|
-
// Smartly update a collection with a change set of models, adding,
|
750
|
-
// removing, and merging as necessary.
|
751
|
-
update: function(models, options) {
|
752
|
-
options = _.extend({add: true, merge: true, remove: true}, options);
|
753
|
-
if (options.parse) models = this.parse(models, options);
|
754
|
-
var model, i, l, existing;
|
755
|
-
var add = [], remove = [], modelMap = {};
|
756
|
-
|
757
|
-
// Allow a single model (or no argument) to be passed.
|
758
|
-
if (!_.isArray(models)) models = models ? [models] : [];
|
759
|
-
|
760
|
-
// Proxy to `add` for this case, no need to iterate...
|
761
|
-
if (options.add && !options.remove) return this.add(models, options);
|
762
|
-
|
763
|
-
// Determine which models to add and merge, and which to remove.
|
764
|
-
for (i = 0, l = models.length; i < l; i++) {
|
765
|
-
model = models[i];
|
766
|
-
existing = this.get(model);
|
767
|
-
if (options.remove && existing) modelMap[existing.cid] = true;
|
768
|
-
if ((options.add && !existing) || (options.merge && existing)) {
|
769
|
-
add.push(model);
|
770
|
-
}
|
771
|
-
}
|
772
|
-
if (options.remove) {
|
773
|
-
for (i = 0, l = this.models.length; i < l; i++) {
|
774
|
-
model = this.models[i];
|
775
|
-
if (!modelMap[model.cid]) remove.push(model);
|
776
|
-
}
|
777
|
-
}
|
778
|
-
|
779
|
-
// Remove models (if applicable) before we add and merge the rest.
|
780
|
-
if (remove.length) this.remove(remove, options);
|
781
|
-
if (add.length) this.add(add, options);
|
782
|
-
return this;
|
783
|
-
},
|
784
|
-
|
785
|
-
// When you have more items than you want to add or remove individually,
|
786
|
-
// you can reset the entire set with a new list of models, without firing
|
787
|
-
// any `add` or `remove` events. Fires `reset` when finished.
|
788
|
-
reset: function(models, options) {
|
789
|
-
options || (options = {});
|
790
|
-
if (options.parse) models = this.parse(models, options);
|
791
|
-
for (var i = 0, l = this.models.length; i < l; i++) {
|
792
|
-
this._removeReference(this.models[i]);
|
793
|
-
}
|
794
|
-
options.previousModels = this.models.slice();
|
795
|
-
this._reset();
|
796
|
-
if (models) this.add(models, _.extend({silent: true}, options));
|
797
|
-
if (!options.silent) this.trigger('reset', this, options);
|
798
|
-
return this;
|
799
|
-
},
|
800
|
-
|
801
845
|
// Fetch the default set of models for this collection, resetting the
|
802
|
-
// collection when they arrive. If `
|
803
|
-
// data will be passed through the `
|
846
|
+
// collection when they arrive. If `reset: true` is passed, the response
|
847
|
+
// data will be passed through the `reset` method instead of `set`.
|
804
848
|
fetch: function(options) {
|
805
849
|
options = options ? _.clone(options) : {};
|
806
850
|
if (options.parse === void 0) options.parse = true;
|
807
851
|
var success = options.success;
|
808
|
-
|
809
|
-
|
852
|
+
var collection = this;
|
853
|
+
options.success = function(resp) {
|
854
|
+
var method = options.reset ? 'reset' : 'set';
|
810
855
|
collection[method](resp, options);
|
811
856
|
if (success) success(collection, resp, options);
|
857
|
+
collection.trigger('sync', collection, resp, options);
|
812
858
|
};
|
859
|
+
wrapError(this, options);
|
813
860
|
return this.sync('read', this, options);
|
814
861
|
},
|
815
862
|
|
@@ -822,7 +869,7 @@
|
|
822
869
|
if (!options.wait) this.add(model, options);
|
823
870
|
var collection = this;
|
824
871
|
var success = options.success;
|
825
|
-
options.success = function(
|
872
|
+
options.success = function(resp) {
|
826
873
|
if (options.wait) collection.add(model, options);
|
827
874
|
if (success) success(model, resp, options);
|
828
875
|
};
|
@@ -841,14 +888,16 @@
|
|
841
888
|
return new this.constructor(this.models);
|
842
889
|
},
|
843
890
|
|
844
|
-
//
|
891
|
+
// Private method to reset all internal state. Called when the collection
|
892
|
+
// is first initialized or reset.
|
845
893
|
_reset: function() {
|
846
894
|
this.length = 0;
|
847
|
-
this.models
|
895
|
+
this.models = [];
|
848
896
|
this._byId = {};
|
849
897
|
},
|
850
898
|
|
851
|
-
// Prepare a
|
899
|
+
// Prepare a hash of attributes (or other model) to be added to this
|
900
|
+
// collection.
|
852
901
|
_prepareModel: function(attrs, options) {
|
853
902
|
if (attrs instanceof Model) {
|
854
903
|
if (!attrs.collection) attrs.collection = this;
|
@@ -857,11 +906,14 @@
|
|
857
906
|
options || (options = {});
|
858
907
|
options.collection = this;
|
859
908
|
var model = new this.model(attrs, options);
|
860
|
-
if (!model._validate(attrs, options))
|
909
|
+
if (!model._validate(attrs, options)) {
|
910
|
+
this.trigger('invalid', this, attrs, options);
|
911
|
+
return false;
|
912
|
+
}
|
861
913
|
return model;
|
862
914
|
},
|
863
915
|
|
864
|
-
// Internal method to
|
916
|
+
// Internal method to sever a model's ties to a collection.
|
865
917
|
_removeReference: function(model) {
|
866
918
|
if (this === model.collection) delete model.collection;
|
867
919
|
model.off('all', this._onModelEvent, this);
|
@@ -879,19 +931,13 @@
|
|
879
931
|
if (model.id != null) this._byId[model.id] = model;
|
880
932
|
}
|
881
933
|
this.trigger.apply(this, arguments);
|
882
|
-
},
|
883
|
-
|
884
|
-
sortedIndex: function (model, value, context) {
|
885
|
-
value || (value = this.comparator);
|
886
|
-
var iterator = _.isFunction(value) ? value : function(model) {
|
887
|
-
return model.get(value);
|
888
|
-
};
|
889
|
-
return _.sortedIndex(this.models, model, iterator, context);
|
890
934
|
}
|
891
935
|
|
892
936
|
});
|
893
937
|
|
894
938
|
// Underscore methods that we want to implement on the Collection.
|
939
|
+
// 90% of the core usefulness of Backbone Collections is actually implemented
|
940
|
+
// right here:
|
895
941
|
var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
|
896
942
|
'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
|
897
943
|
'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
|
@@ -921,50 +967,295 @@
|
|
921
967
|
};
|
922
968
|
});
|
923
969
|
|
924
|
-
// Backbone.
|
925
|
-
//
|
970
|
+
// Backbone.View
|
971
|
+
// -------------
|
926
972
|
|
927
|
-
//
|
928
|
-
//
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
973
|
+
// Backbone Views are almost more convention than they are actual code. A View
|
974
|
+
// is simply a JavaScript object that represents a logical chunk of UI in the
|
975
|
+
// DOM. This might be a single item, an entire list, a sidebar or panel, or
|
976
|
+
// even the surrounding frame which wraps your whole app. Defining a chunk of
|
977
|
+
// UI as a **View** allows you to define your DOM events declaratively, without
|
978
|
+
// having to worry about render order ... and makes it easy for the view to
|
979
|
+
// react to specific changes in the state of your models.
|
980
|
+
|
981
|
+
// Creating a Backbone.View creates its initial element outside of the DOM,
|
982
|
+
// if an existing element is not provided...
|
983
|
+
var View = Backbone.View = function(options) {
|
984
|
+
this.cid = _.uniqueId('view');
|
985
|
+
this._configure(options || {});
|
986
|
+
this._ensureElement();
|
933
987
|
this.initialize.apply(this, arguments);
|
988
|
+
this.delegateEvents();
|
934
989
|
};
|
935
990
|
|
936
|
-
// Cached
|
937
|
-
|
938
|
-
var optionalParam = /\((.*?)\)/g;
|
939
|
-
var namedParam = /(\(\?)?:\w+/g;
|
940
|
-
var splatParam = /\*\w+/g;
|
941
|
-
var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
|
991
|
+
// Cached regex to split keys for `delegate`.
|
992
|
+
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
|
942
993
|
|
943
|
-
//
|
944
|
-
|
994
|
+
// List of view options to be merged as properties.
|
995
|
+
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
|
996
|
+
|
997
|
+
// Set up all inheritable **Backbone.View** properties and methods.
|
998
|
+
_.extend(View.prototype, Events, {
|
999
|
+
|
1000
|
+
// The default `tagName` of a View's element is `"div"`.
|
1001
|
+
tagName: 'div',
|
1002
|
+
|
1003
|
+
// jQuery delegate for element lookup, scoped to DOM elements within the
|
1004
|
+
// current view. This should be prefered to global lookups where possible.
|
1005
|
+
$: function(selector) {
|
1006
|
+
return this.$el.find(selector);
|
1007
|
+
},
|
945
1008
|
|
946
1009
|
// Initialize is an empty function by default. Override it with your own
|
947
1010
|
// initialization logic.
|
948
1011
|
initialize: function(){},
|
949
1012
|
|
950
|
-
//
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
Backbone.history.route(route, _.bind(function(fragment) {
|
955
|
-
var args = this._extractParameters(route, fragment);
|
956
|
-
callback && callback.apply(this, args);
|
957
|
-
this.trigger.apply(this, ['route:' + name].concat(args));
|
958
|
-
this.trigger('route', name, args);
|
959
|
-
Backbone.history.trigger('route', this, name, args);
|
960
|
-
}, this));
|
1013
|
+
// **render** is the core function that your view should override, in order
|
1014
|
+
// to populate its element (`this.el`), with the appropriate HTML. The
|
1015
|
+
// convention is for **render** to always return `this`.
|
1016
|
+
render: function() {
|
961
1017
|
return this;
|
962
1018
|
},
|
963
1019
|
|
964
|
-
//
|
965
|
-
|
966
|
-
|
967
|
-
|
1020
|
+
// Remove this view by taking the element out of the DOM, and removing any
|
1021
|
+
// applicable Backbone.Events listeners.
|
1022
|
+
remove: function() {
|
1023
|
+
this.$el.remove();
|
1024
|
+
this.stopListening();
|
1025
|
+
return this;
|
1026
|
+
},
|
1027
|
+
|
1028
|
+
// Change the view's element (`this.el` property), including event
|
1029
|
+
// re-delegation.
|
1030
|
+
setElement: function(element, delegate) {
|
1031
|
+
if (this.$el) this.undelegateEvents();
|
1032
|
+
this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
|
1033
|
+
this.el = this.$el[0];
|
1034
|
+
if (delegate !== false) this.delegateEvents();
|
1035
|
+
return this;
|
1036
|
+
},
|
1037
|
+
|
1038
|
+
// Set callbacks, where `this.events` is a hash of
|
1039
|
+
//
|
1040
|
+
// *{"event selector": "callback"}*
|
1041
|
+
//
|
1042
|
+
// {
|
1043
|
+
// 'mousedown .title': 'edit',
|
1044
|
+
// 'click .button': 'save'
|
1045
|
+
// 'click .open': function(e) { ... }
|
1046
|
+
// }
|
1047
|
+
//
|
1048
|
+
// pairs. Callbacks will be bound to the view, with `this` set properly.
|
1049
|
+
// Uses event delegation for efficiency.
|
1050
|
+
// Omitting the selector binds the event to `this.el`.
|
1051
|
+
// This only works for delegate-able events: not `focus`, `blur`, and
|
1052
|
+
// not `change`, `submit`, and `reset` in Internet Explorer.
|
1053
|
+
delegateEvents: function(events) {
|
1054
|
+
if (!(events || (events = _.result(this, 'events')))) return this;
|
1055
|
+
this.undelegateEvents();
|
1056
|
+
for (var key in events) {
|
1057
|
+
var method = events[key];
|
1058
|
+
if (!_.isFunction(method)) method = this[events[key]];
|
1059
|
+
if (!method) continue;
|
1060
|
+
|
1061
|
+
var match = key.match(delegateEventSplitter);
|
1062
|
+
var eventName = match[1], selector = match[2];
|
1063
|
+
method = _.bind(method, this);
|
1064
|
+
eventName += '.delegateEvents' + this.cid;
|
1065
|
+
if (selector === '') {
|
1066
|
+
this.$el.on(eventName, method);
|
1067
|
+
} else {
|
1068
|
+
this.$el.on(eventName, selector, method);
|
1069
|
+
}
|
1070
|
+
}
|
1071
|
+
return this;
|
1072
|
+
},
|
1073
|
+
|
1074
|
+
// Clears all callbacks previously bound to the view with `delegateEvents`.
|
1075
|
+
// You usually don't need to use this, but may wish to if you have multiple
|
1076
|
+
// Backbone views attached to the same DOM element.
|
1077
|
+
undelegateEvents: function() {
|
1078
|
+
this.$el.off('.delegateEvents' + this.cid);
|
1079
|
+
return this;
|
1080
|
+
},
|
1081
|
+
|
1082
|
+
// Performs the initial configuration of a View with a set of options.
|
1083
|
+
// Keys with special meaning *(e.g. model, collection, id, className)* are
|
1084
|
+
// attached directly to the view. See `viewOptions` for an exhaustive
|
1085
|
+
// list.
|
1086
|
+
_configure: function(options) {
|
1087
|
+
if (this.options) options = _.extend({}, _.result(this, 'options'), options);
|
1088
|
+
_.extend(this, _.pick(options, viewOptions));
|
1089
|
+
this.options = options;
|
1090
|
+
},
|
1091
|
+
|
1092
|
+
// Ensure that the View has a DOM element to render into.
|
1093
|
+
// If `this.el` is a string, pass it through `$()`, take the first
|
1094
|
+
// matching element, and re-assign it to `el`. Otherwise, create
|
1095
|
+
// an element from the `id`, `className` and `tagName` properties.
|
1096
|
+
_ensureElement: function() {
|
1097
|
+
if (!this.el) {
|
1098
|
+
var attrs = _.extend({}, _.result(this, 'attributes'));
|
1099
|
+
if (this.id) attrs.id = _.result(this, 'id');
|
1100
|
+
if (this.className) attrs['class'] = _.result(this, 'className');
|
1101
|
+
var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
|
1102
|
+
this.setElement($el, false);
|
1103
|
+
} else {
|
1104
|
+
this.setElement(_.result(this, 'el'), false);
|
1105
|
+
}
|
1106
|
+
}
|
1107
|
+
|
1108
|
+
});
|
1109
|
+
|
1110
|
+
// Backbone.sync
|
1111
|
+
// -------------
|
1112
|
+
|
1113
|
+
// Override this function to change the manner in which Backbone persists
|
1114
|
+
// models to the server. You will be passed the type of request, and the
|
1115
|
+
// model in question. By default, makes a RESTful Ajax request
|
1116
|
+
// to the model's `url()`. Some possible customizations could be:
|
1117
|
+
//
|
1118
|
+
// * Use `setTimeout` to batch rapid-fire updates into a single request.
|
1119
|
+
// * Send up the models as XML instead of JSON.
|
1120
|
+
// * Persist models via WebSockets instead of Ajax.
|
1121
|
+
//
|
1122
|
+
// Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
|
1123
|
+
// as `POST`, with a `_method` parameter containing the true HTTP method,
|
1124
|
+
// as well as all requests with the body as `application/x-www-form-urlencoded`
|
1125
|
+
// instead of `application/json` with the model in a param named `model`.
|
1126
|
+
// Useful when interfacing with server-side languages like **PHP** that make
|
1127
|
+
// it difficult to read the body of `PUT` requests.
|
1128
|
+
Backbone.sync = function(method, model, options) {
|
1129
|
+
var type = methodMap[method];
|
1130
|
+
|
1131
|
+
// Default options, unless specified.
|
1132
|
+
_.defaults(options || (options = {}), {
|
1133
|
+
emulateHTTP: Backbone.emulateHTTP,
|
1134
|
+
emulateJSON: Backbone.emulateJSON
|
1135
|
+
});
|
1136
|
+
|
1137
|
+
// Default JSON-request options.
|
1138
|
+
var params = {type: type, dataType: 'json'};
|
1139
|
+
|
1140
|
+
// Ensure that we have a URL.
|
1141
|
+
if (!options.url) {
|
1142
|
+
params.url = _.result(model, 'url') || urlError();
|
1143
|
+
}
|
1144
|
+
|
1145
|
+
// Ensure that we have the appropriate request data.
|
1146
|
+
if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
|
1147
|
+
params.contentType = 'application/json';
|
1148
|
+
params.data = JSON.stringify(options.attrs || model.toJSON(options));
|
1149
|
+
}
|
1150
|
+
|
1151
|
+
// For older servers, emulate JSON by encoding the request into an HTML-form.
|
1152
|
+
if (options.emulateJSON) {
|
1153
|
+
params.contentType = 'application/x-www-form-urlencoded';
|
1154
|
+
params.data = params.data ? {model: params.data} : {};
|
1155
|
+
}
|
1156
|
+
|
1157
|
+
// For older servers, emulate HTTP by mimicking the HTTP method with `_method`
|
1158
|
+
// And an `X-HTTP-Method-Override` header.
|
1159
|
+
if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
|
1160
|
+
params.type = 'POST';
|
1161
|
+
if (options.emulateJSON) params.data._method = type;
|
1162
|
+
var beforeSend = options.beforeSend;
|
1163
|
+
options.beforeSend = function(xhr) {
|
1164
|
+
xhr.setRequestHeader('X-HTTP-Method-Override', type);
|
1165
|
+
if (beforeSend) return beforeSend.apply(this, arguments);
|
1166
|
+
};
|
1167
|
+
}
|
1168
|
+
|
1169
|
+
// Don't process data on a non-GET request.
|
1170
|
+
if (params.type !== 'GET' && !options.emulateJSON) {
|
1171
|
+
params.processData = false;
|
1172
|
+
}
|
1173
|
+
|
1174
|
+
// If we're sending a `PATCH` request, and we're in an old Internet Explorer
|
1175
|
+
// that still has ActiveX enabled by default, override jQuery to use that
|
1176
|
+
// for XHR instead. Remove this line when jQuery supports `PATCH` on IE8.
|
1177
|
+
if (params.type === 'PATCH' && window.ActiveXObject &&
|
1178
|
+
!(window.external && window.external.msActiveXFilteringEnabled)) {
|
1179
|
+
params.xhr = function() {
|
1180
|
+
return new ActiveXObject("Microsoft.XMLHTTP");
|
1181
|
+
};
|
1182
|
+
}
|
1183
|
+
|
1184
|
+
// Make the request, allowing the user to override any Ajax options.
|
1185
|
+
var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
|
1186
|
+
model.trigger('request', model, xhr, options);
|
1187
|
+
return xhr;
|
1188
|
+
};
|
1189
|
+
|
1190
|
+
// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
|
1191
|
+
var methodMap = {
|
1192
|
+
'create': 'POST',
|
1193
|
+
'update': 'PUT',
|
1194
|
+
'patch': 'PATCH',
|
1195
|
+
'delete': 'DELETE',
|
1196
|
+
'read': 'GET'
|
1197
|
+
};
|
1198
|
+
|
1199
|
+
// Set the default implementation of `Backbone.ajax` to proxy through to `$`.
|
1200
|
+
// Override this if you'd like to use a different library.
|
1201
|
+
Backbone.ajax = function() {
|
1202
|
+
return Backbone.$.ajax.apply(Backbone.$, arguments);
|
1203
|
+
};
|
1204
|
+
|
1205
|
+
// Backbone.Router
|
1206
|
+
// ---------------
|
1207
|
+
|
1208
|
+
// Routers map faux-URLs to actions, and fire events when routes are
|
1209
|
+
// matched. Creating a new one sets its `routes` hash, if not set statically.
|
1210
|
+
var Router = Backbone.Router = function(options) {
|
1211
|
+
options || (options = {});
|
1212
|
+
if (options.routes) this.routes = options.routes;
|
1213
|
+
this._bindRoutes();
|
1214
|
+
this.initialize.apply(this, arguments);
|
1215
|
+
};
|
1216
|
+
|
1217
|
+
// Cached regular expressions for matching named param parts and splatted
|
1218
|
+
// parts of route strings.
|
1219
|
+
var optionalParam = /\((.*?)\)/g;
|
1220
|
+
var namedParam = /(\(\?)?:\w+/g;
|
1221
|
+
var splatParam = /\*\w+/g;
|
1222
|
+
var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
|
1223
|
+
|
1224
|
+
// Set up all inheritable **Backbone.Router** properties and methods.
|
1225
|
+
_.extend(Router.prototype, Events, {
|
1226
|
+
|
1227
|
+
// Initialize is an empty function by default. Override it with your own
|
1228
|
+
// initialization logic.
|
1229
|
+
initialize: function(){},
|
1230
|
+
|
1231
|
+
// Manually bind a single named route to a callback. For example:
|
1232
|
+
//
|
1233
|
+
// this.route('search/:query/p:num', 'search', function(query, num) {
|
1234
|
+
// ...
|
1235
|
+
// });
|
1236
|
+
//
|
1237
|
+
route: function(route, name, callback) {
|
1238
|
+
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
|
1239
|
+
if (_.isFunction(name)) {
|
1240
|
+
callback = name;
|
1241
|
+
name = '';
|
1242
|
+
}
|
1243
|
+
if (!callback) callback = this[name];
|
1244
|
+
var router = this;
|
1245
|
+
Backbone.history.route(route, function(fragment) {
|
1246
|
+
var args = router._extractParameters(route, fragment);
|
1247
|
+
callback && callback.apply(router, args);
|
1248
|
+
router.trigger.apply(router, ['route:' + name].concat(args));
|
1249
|
+
router.trigger('route', name, args);
|
1250
|
+
Backbone.history.trigger('route', router, name, args);
|
1251
|
+
});
|
1252
|
+
return this;
|
1253
|
+
},
|
1254
|
+
|
1255
|
+
// Simple proxy to `Backbone.history` to save a fragment into the history.
|
1256
|
+
navigate: function(fragment, options) {
|
1257
|
+
Backbone.history.navigate(fragment, options);
|
1258
|
+
return this;
|
968
1259
|
},
|
969
1260
|
|
970
1261
|
// Bind all defined routes to `Backbone.history`. We have to reverse the
|
@@ -972,6 +1263,7 @@
|
|
972
1263
|
// routes can be defined at the bottom of the route map.
|
973
1264
|
_bindRoutes: function() {
|
974
1265
|
if (!this.routes) return;
|
1266
|
+
this.routes = _.result(this, 'routes');
|
975
1267
|
var route, routes = _.keys(this.routes);
|
976
1268
|
while ((route = routes.pop()) != null) {
|
977
1269
|
this.route(route, this.routes[route]);
|
@@ -991,9 +1283,13 @@
|
|
991
1283
|
},
|
992
1284
|
|
993
1285
|
// Given a route, and a URL fragment that it matches, return the array of
|
994
|
-
// extracted parameters.
|
1286
|
+
// extracted decoded parameters. Empty or unmatched parameters will be
|
1287
|
+
// treated as `null` to normalize cross-browser behavior.
|
995
1288
|
_extractParameters: function(route, fragment) {
|
996
|
-
|
1289
|
+
var params = route.exec(fragment).slice(1);
|
1290
|
+
return _.map(params, function(param) {
|
1291
|
+
return param ? decodeURIComponent(param) : null;
|
1292
|
+
});
|
997
1293
|
}
|
998
1294
|
|
999
1295
|
});
|
@@ -1001,8 +1297,11 @@
|
|
1001
1297
|
// Backbone.History
|
1002
1298
|
// ----------------
|
1003
1299
|
|
1004
|
-
// Handles cross-browser history management, based on
|
1005
|
-
//
|
1300
|
+
// Handles cross-browser history management, based on either
|
1301
|
+
// [pushState](http://diveintohtml5.info/history.html) and real URLs, or
|
1302
|
+
// [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
|
1303
|
+
// and URL fragments. If the browser supports neither (old IE, natch),
|
1304
|
+
// falls back to polling.
|
1006
1305
|
var History = Backbone.History = function() {
|
1007
1306
|
this.handlers = [];
|
1008
1307
|
_.bindAll(this, 'checkUrl');
|
@@ -1213,230 +1512,6 @@
|
|
1213
1512
|
// Create the default Backbone.history.
|
1214
1513
|
Backbone.history = new History;
|
1215
1514
|
|
1216
|
-
// Backbone.View
|
1217
|
-
// -------------
|
1218
|
-
|
1219
|
-
// Creating a Backbone.View creates its initial element outside of the DOM,
|
1220
|
-
// if an existing element is not provided...
|
1221
|
-
var View = Backbone.View = function(options) {
|
1222
|
-
this.cid = _.uniqueId('view');
|
1223
|
-
this._configure(options || {});
|
1224
|
-
this._ensureElement();
|
1225
|
-
this.initialize.apply(this, arguments);
|
1226
|
-
this.delegateEvents();
|
1227
|
-
};
|
1228
|
-
|
1229
|
-
// Cached regex to split keys for `delegate`.
|
1230
|
-
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
|
1231
|
-
|
1232
|
-
// List of view options to be merged as properties.
|
1233
|
-
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
|
1234
|
-
|
1235
|
-
// Set up all inheritable **Backbone.View** properties and methods.
|
1236
|
-
_.extend(View.prototype, Events, {
|
1237
|
-
|
1238
|
-
// The default `tagName` of a View's element is `"div"`.
|
1239
|
-
tagName: 'div',
|
1240
|
-
|
1241
|
-
// jQuery delegate for element lookup, scoped to DOM elements within the
|
1242
|
-
// current view. This should be prefered to global lookups where possible.
|
1243
|
-
$: function(selector) {
|
1244
|
-
return this.$el.find(selector);
|
1245
|
-
},
|
1246
|
-
|
1247
|
-
// Initialize is an empty function by default. Override it with your own
|
1248
|
-
// initialization logic.
|
1249
|
-
initialize: function(){},
|
1250
|
-
|
1251
|
-
// **render** is the core function that your view should override, in order
|
1252
|
-
// to populate its element (`this.el`), with the appropriate HTML. The
|
1253
|
-
// convention is for **render** to always return `this`.
|
1254
|
-
render: function() {
|
1255
|
-
return this;
|
1256
|
-
},
|
1257
|
-
|
1258
|
-
// Remove this view by taking the element out of the DOM, and removing any
|
1259
|
-
// applicable Backbone.Events listeners.
|
1260
|
-
remove: function() {
|
1261
|
-
this.$el.remove();
|
1262
|
-
this.stopListening();
|
1263
|
-
return this;
|
1264
|
-
},
|
1265
|
-
|
1266
|
-
// Change the view's element (`this.el` property), including event
|
1267
|
-
// re-delegation.
|
1268
|
-
setElement: function(element, delegate) {
|
1269
|
-
if (this.$el) this.undelegateEvents();
|
1270
|
-
this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
|
1271
|
-
this.el = this.$el[0];
|
1272
|
-
if (delegate !== false) this.delegateEvents();
|
1273
|
-
return this;
|
1274
|
-
},
|
1275
|
-
|
1276
|
-
// Set callbacks, where `this.events` is a hash of
|
1277
|
-
//
|
1278
|
-
// *{"event selector": "callback"}*
|
1279
|
-
//
|
1280
|
-
// {
|
1281
|
-
// 'mousedown .title': 'edit',
|
1282
|
-
// 'click .button': 'save'
|
1283
|
-
// 'click .open': function(e) { ... }
|
1284
|
-
// }
|
1285
|
-
//
|
1286
|
-
// pairs. Callbacks will be bound to the view, with `this` set properly.
|
1287
|
-
// Uses event delegation for efficiency.
|
1288
|
-
// Omitting the selector binds the event to `this.el`.
|
1289
|
-
// This only works for delegate-able events: not `focus`, `blur`, and
|
1290
|
-
// not `change`, `submit`, and `reset` in Internet Explorer.
|
1291
|
-
delegateEvents: function(events) {
|
1292
|
-
if (!(events || (events = _.result(this, 'events')))) return;
|
1293
|
-
this.undelegateEvents();
|
1294
|
-
for (var key in events) {
|
1295
|
-
var method = events[key];
|
1296
|
-
if (!_.isFunction(method)) method = this[events[key]];
|
1297
|
-
if (!method) throw new Error('Method "' + events[key] + '" does not exist');
|
1298
|
-
var match = key.match(delegateEventSplitter);
|
1299
|
-
var eventName = match[1], selector = match[2];
|
1300
|
-
method = _.bind(method, this);
|
1301
|
-
eventName += '.delegateEvents' + this.cid;
|
1302
|
-
if (selector === '') {
|
1303
|
-
this.$el.on(eventName, method);
|
1304
|
-
} else {
|
1305
|
-
this.$el.on(eventName, selector, method);
|
1306
|
-
}
|
1307
|
-
}
|
1308
|
-
},
|
1309
|
-
|
1310
|
-
// Clears all callbacks previously bound to the view with `delegateEvents`.
|
1311
|
-
// You usually don't need to use this, but may wish to if you have multiple
|
1312
|
-
// Backbone views attached to the same DOM element.
|
1313
|
-
undelegateEvents: function() {
|
1314
|
-
this.$el.off('.delegateEvents' + this.cid);
|
1315
|
-
},
|
1316
|
-
|
1317
|
-
// Performs the initial configuration of a View with a set of options.
|
1318
|
-
// Keys with special meaning *(model, collection, id, className)*, are
|
1319
|
-
// attached directly to the view.
|
1320
|
-
_configure: function(options) {
|
1321
|
-
if (this.options) options = _.extend({}, _.result(this, 'options'), options);
|
1322
|
-
_.extend(this, _.pick(options, viewOptions));
|
1323
|
-
this.options = options;
|
1324
|
-
},
|
1325
|
-
|
1326
|
-
// Ensure that the View has a DOM element to render into.
|
1327
|
-
// If `this.el` is a string, pass it through `$()`, take the first
|
1328
|
-
// matching element, and re-assign it to `el`. Otherwise, create
|
1329
|
-
// an element from the `id`, `className` and `tagName` properties.
|
1330
|
-
_ensureElement: function() {
|
1331
|
-
if (!this.el) {
|
1332
|
-
var attrs = _.extend({}, _.result(this, 'attributes'));
|
1333
|
-
if (this.id) attrs.id = _.result(this, 'id');
|
1334
|
-
if (this.className) attrs['class'] = _.result(this, 'className');
|
1335
|
-
var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
|
1336
|
-
this.setElement($el, false);
|
1337
|
-
} else {
|
1338
|
-
this.setElement(_.result(this, 'el'), false);
|
1339
|
-
}
|
1340
|
-
}
|
1341
|
-
|
1342
|
-
});
|
1343
|
-
|
1344
|
-
// Backbone.sync
|
1345
|
-
// -------------
|
1346
|
-
|
1347
|
-
// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
|
1348
|
-
var methodMap = {
|
1349
|
-
'create': 'POST',
|
1350
|
-
'update': 'PUT',
|
1351
|
-
'patch': 'PATCH',
|
1352
|
-
'delete': 'DELETE',
|
1353
|
-
'read': 'GET'
|
1354
|
-
};
|
1355
|
-
|
1356
|
-
// Override this function to change the manner in which Backbone persists
|
1357
|
-
// models to the server. You will be passed the type of request, and the
|
1358
|
-
// model in question. By default, makes a RESTful Ajax request
|
1359
|
-
// to the model's `url()`. Some possible customizations could be:
|
1360
|
-
//
|
1361
|
-
// * Use `setTimeout` to batch rapid-fire updates into a single request.
|
1362
|
-
// * Send up the models as XML instead of JSON.
|
1363
|
-
// * Persist models via WebSockets instead of Ajax.
|
1364
|
-
//
|
1365
|
-
// Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
|
1366
|
-
// as `POST`, with a `_method` parameter containing the true HTTP method,
|
1367
|
-
// as well as all requests with the body as `application/x-www-form-urlencoded`
|
1368
|
-
// instead of `application/json` with the model in a param named `model`.
|
1369
|
-
// Useful when interfacing with server-side languages like **PHP** that make
|
1370
|
-
// it difficult to read the body of `PUT` requests.
|
1371
|
-
Backbone.sync = function(method, model, options) {
|
1372
|
-
var type = methodMap[method];
|
1373
|
-
|
1374
|
-
// Default options, unless specified.
|
1375
|
-
_.defaults(options || (options = {}), {
|
1376
|
-
emulateHTTP: Backbone.emulateHTTP,
|
1377
|
-
emulateJSON: Backbone.emulateJSON
|
1378
|
-
});
|
1379
|
-
|
1380
|
-
// Default JSON-request options.
|
1381
|
-
var params = {type: type, dataType: 'json'};
|
1382
|
-
|
1383
|
-
// Ensure that we have a URL.
|
1384
|
-
if (!options.url) {
|
1385
|
-
params.url = _.result(model, 'url') || urlError();
|
1386
|
-
}
|
1387
|
-
|
1388
|
-
// Ensure that we have the appropriate request data.
|
1389
|
-
if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
|
1390
|
-
params.contentType = 'application/json';
|
1391
|
-
params.data = JSON.stringify(options.attrs || model.toJSON(options));
|
1392
|
-
}
|
1393
|
-
|
1394
|
-
// For older servers, emulate JSON by encoding the request into an HTML-form.
|
1395
|
-
if (options.emulateJSON) {
|
1396
|
-
params.contentType = 'application/x-www-form-urlencoded';
|
1397
|
-
params.data = params.data ? {model: params.data} : {};
|
1398
|
-
}
|
1399
|
-
|
1400
|
-
// For older servers, emulate HTTP by mimicking the HTTP method with `_method`
|
1401
|
-
// And an `X-HTTP-Method-Override` header.
|
1402
|
-
if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
|
1403
|
-
params.type = 'POST';
|
1404
|
-
if (options.emulateJSON) params.data._method = type;
|
1405
|
-
var beforeSend = options.beforeSend;
|
1406
|
-
options.beforeSend = function(xhr) {
|
1407
|
-
xhr.setRequestHeader('X-HTTP-Method-Override', type);
|
1408
|
-
if (beforeSend) return beforeSend.apply(this, arguments);
|
1409
|
-
};
|
1410
|
-
}
|
1411
|
-
|
1412
|
-
// Don't process data on a non-GET request.
|
1413
|
-
if (params.type !== 'GET' && !options.emulateJSON) {
|
1414
|
-
params.processData = false;
|
1415
|
-
}
|
1416
|
-
|
1417
|
-
var success = options.success;
|
1418
|
-
options.success = function(resp) {
|
1419
|
-
if (success) success(model, resp, options);
|
1420
|
-
model.trigger('sync', model, resp, options);
|
1421
|
-
};
|
1422
|
-
|
1423
|
-
var error = options.error;
|
1424
|
-
options.error = function(xhr) {
|
1425
|
-
if (error) error(model, xhr, options);
|
1426
|
-
model.trigger('error', model, xhr, options);
|
1427
|
-
};
|
1428
|
-
|
1429
|
-
// Make the request, allowing the user to override any Ajax options.
|
1430
|
-
var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
|
1431
|
-
model.trigger('request', model, xhr, options);
|
1432
|
-
return xhr;
|
1433
|
-
};
|
1434
|
-
|
1435
|
-
// Set the default implementation of `Backbone.ajax` to proxy through to `$`.
|
1436
|
-
Backbone.ajax = function() {
|
1437
|
-
return Backbone.$.ajax.apply(Backbone.$, arguments);
|
1438
|
-
};
|
1439
|
-
|
1440
1515
|
// Helpers
|
1441
1516
|
// -------
|
1442
1517
|
|
@@ -1484,4 +1559,13 @@
|
|
1484
1559
|
throw new Error('A "url" property or function must be specified');
|
1485
1560
|
};
|
1486
1561
|
|
1562
|
+
// Wrap an optional error callback with a fallback error event.
|
1563
|
+
var wrapError = function (model, options) {
|
1564
|
+
var error = options.error;
|
1565
|
+
options.error = function(resp) {
|
1566
|
+
if (error) error(model, resp, options);
|
1567
|
+
model.trigger('error', model, resp, options);
|
1568
|
+
};
|
1569
|
+
};
|
1570
|
+
|
1487
1571
|
}).call(this);
|