js_stack 0.5.7 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (23) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +7 -7
  4. data/lib/js_stack/version.rb +1 -1
  5. data/vendor/assets/javascripts/js_stack/base/marionette/{1.8.0.js → 1.8.3.js} +173 -8
  6. data/vendor/assets/javascripts/js_stack/base/marionette.js +1 -1
  7. data/vendor/assets/javascripts/js_stack/plugins/backbone/stickit/0.8.0.js +595 -0
  8. data/vendor/assets/javascripts/js_stack/plugins/backbone.stickit.js +1 -1
  9. metadata +4 -17
  10. data/vendor/assets/javascripts/js_stack/base/backbone/1.1.0.js +0 -1581
  11. data/vendor/assets/javascripts/js_stack/base/backbone/1.1.1.js +0 -1609
  12. data/vendor/assets/javascripts/js_stack/base/marionette/1.6.2.js +0 -2555
  13. data/vendor/assets/javascripts/js_stack/base/marionette/1.7.0.js +0 -2746
  14. data/vendor/assets/javascripts/js_stack/base/marionette/1.7.3.js +0 -2765
  15. data/vendor/assets/javascripts/js_stack/plugins/backbone/associations/0.5.1.js +0 -533
  16. data/vendor/assets/javascripts/js_stack/plugins/backbone/associations/0.5.4.js +0 -574
  17. data/vendor/assets/javascripts/js_stack/plugins/backbone/mutators/0.4.1.js +0 -207
  18. data/vendor/assets/javascripts/js_stack/plugins/backbone/pageable/1.4.5.js +0 -1318
  19. data/vendor/assets/javascripts/js_stack/plugins/backbone/virtualcollection/0.4.11.js +0 -345
  20. data/vendor/assets/javascripts/js_stack/plugins/backbone/virtualcollection/0.4.12.js +0 -351
  21. data/vendor/assets/javascripts/js_stack/plugins/backbone/virtualcollection/0.4.14.js +0 -398
  22. data/vendor/assets/javascripts/js_stack/plugins/backbone/virtualcollection/0.4.5.js +0 -293
  23. data/vendor/assets/javascripts/js_stack/plugins/backbone/virtualcollection/0.4.8.js +0 -340
@@ -1,1581 +0,0 @@
1
- // Backbone.js 1.1.0
2
-
3
- // (c) 2010-2011 Jeremy Ashkenas, DocumentCloud Inc.
4
- // (c) 2011-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
5
- // Backbone may be freely distributed under the MIT license.
6
- // For all details and documentation:
7
- // http://backbonejs.org
8
-
9
- (function(){
10
-
11
- // Initial Setup
12
- // -------------
13
-
14
- // Save a reference to the global object (`window` in the browser, `exports`
15
- // on the server).
16
- var root = this;
17
-
18
- // Save the previous value of the `Backbone` variable, so that it can be
19
- // restored later on, if `noConflict` is used.
20
- var previousBackbone = root.Backbone;
21
-
22
- // Create local references to array methods we'll want to use later.
23
- var array = [];
24
- var push = array.push;
25
- var slice = array.slice;
26
- var splice = array.splice;
27
-
28
- // The top-level namespace. All public Backbone classes and modules will
29
- // be attached to this. Exported for both the browser and the server.
30
- var Backbone;
31
- if (typeof exports !== 'undefined') {
32
- Backbone = exports;
33
- } else {
34
- Backbone = root.Backbone = {};
35
- }
36
-
37
- // Current version of the library. Keep in sync with `package.json`.
38
- Backbone.VERSION = '1.1.0';
39
-
40
- // Require Underscore, if we're on the server, and it's not already present.
41
- var _ = root._;
42
- if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
43
-
44
- // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
45
- // the `$` variable.
46
- Backbone.$ = root.jQuery || root.Zepto || root.ender || root.$;
47
-
48
- // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
49
- // to its previous owner. Returns a reference to this Backbone object.
50
- Backbone.noConflict = function() {
51
- root.Backbone = previousBackbone;
52
- return this;
53
- };
54
-
55
- // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
56
- // will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and
57
- // set a `X-Http-Method-Override` header.
58
- Backbone.emulateHTTP = false;
59
-
60
- // Turn on `emulateJSON` to support legacy servers that can't deal with direct
61
- // `application/json` requests ... will encode the body as
62
- // `application/x-www-form-urlencoded` instead and will send the model in a
63
- // form param named `model`.
64
- Backbone.emulateJSON = false;
65
-
66
- // Backbone.Events
67
- // ---------------
68
-
69
- // A module that can be mixed in to *any object* in order to provide it with
70
- // custom events. You may bind with `on` or remove with `off` callback
71
- // functions to an event; `trigger`-ing an event fires all callbacks in
72
- // succession.
73
- //
74
- // var object = {};
75
- // _.extend(object, Backbone.Events);
76
- // object.on('expand', function(){ alert('expanded'); });
77
- // object.trigger('expand');
78
- //
79
- var Events = Backbone.Events = {
80
-
81
- // Bind an event to a `callback` function. Passing `"all"` will bind
82
- // the callback to all events fired.
83
- on: function(name, callback, context) {
84
- if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
85
- this._events || (this._events = {});
86
- var events = this._events[name] || (this._events[name] = []);
87
- events.push({callback: callback, context: context, ctx: context || this});
88
- return this;
89
- },
90
-
91
- // Bind an event to only be triggered a single time. After the first time
92
- // the callback is invoked, it will be removed.
93
- once: function(name, callback, context) {
94
- if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
95
- var self = this;
96
- var once = _.once(function() {
97
- self.off(name, once);
98
- callback.apply(this, arguments);
99
- });
100
- once._callback = callback;
101
- return this.on(name, once, context);
102
- },
103
-
104
- // Remove one or many callbacks. If `context` is null, removes all
105
- // callbacks with that function. If `callback` is null, removes all
106
- // callbacks for the event. If `name` is null, removes all bound
107
- // callbacks for all events.
108
- off: function(name, callback, context) {
109
- var retain, ev, events, names, i, l, j, k;
110
- if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
111
- if (!name && !callback && !context) {
112
- this._events = {};
113
- return this;
114
- }
115
- names = name ? [name] : _.keys(this._events);
116
- for (i = 0, l = names.length; i < l; i++) {
117
- name = names[i];
118
- if (events = this._events[name]) {
119
- this._events[name] = retain = [];
120
- if (callback || context) {
121
- for (j = 0, k = events.length; j < k; j++) {
122
- ev = events[j];
123
- if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
124
- (context && context !== ev.context)) {
125
- retain.push(ev);
126
- }
127
- }
128
- }
129
- if (!retain.length) delete this._events[name];
130
- }
131
- }
132
-
133
- return this;
134
- },
135
-
136
- // Trigger one or many events, firing all bound callbacks. Callbacks are
137
- // passed the same arguments as `trigger` is, apart from the event name
138
- // (unless you're listening on `"all"`, which will cause your callback to
139
- // receive the true name of the event as the first argument).
140
- trigger: function(name) {
141
- if (!this._events) return this;
142
- var args = slice.call(arguments, 1);
143
- if (!eventsApi(this, 'trigger', name, args)) return this;
144
- var events = this._events[name];
145
- var allEvents = this._events.all;
146
- if (events) triggerEvents(events, args);
147
- if (allEvents) triggerEvents(allEvents, arguments);
148
- return this;
149
- },
150
-
151
- // Tell this object to stop listening to either specific events ... or
152
- // to every object it's currently listening to.
153
- stopListening: function(obj, name, callback) {
154
- var listeningTo = this._listeningTo;
155
- if (!listeningTo) return this;
156
- var remove = !name && !callback;
157
- if (!callback && typeof name === 'object') callback = this;
158
- if (obj) (listeningTo = {})[obj._listenId] = obj;
159
- for (var id in listeningTo) {
160
- obj = listeningTo[id];
161
- obj.off(name, callback, this);
162
- if (remove || _.isEmpty(obj._events)) delete this._listeningTo[id];
163
- }
164
- return this;
165
- }
166
-
167
- };
168
-
169
- // Regular expression used to split event strings.
170
- var eventSplitter = /\s+/;
171
-
172
- // Implement fancy features of the Events API such as multiple event
173
- // names `"change blur"` and jQuery-style event maps `{change: action}`
174
- // in terms of the existing API.
175
- var eventsApi = function(obj, action, name, rest) {
176
- if (!name) return true;
177
-
178
- // Handle event maps.
179
- if (typeof name === 'object') {
180
- for (var key in name) {
181
- obj[action].apply(obj, [key, name[key]].concat(rest));
182
- }
183
- return false;
184
- }
185
-
186
- // Handle space separated event names.
187
- if (eventSplitter.test(name)) {
188
- var names = name.split(eventSplitter);
189
- for (var i = 0, l = names.length; i < l; i++) {
190
- obj[action].apply(obj, [names[i]].concat(rest));
191
- }
192
- return false;
193
- }
194
-
195
- return true;
196
- };
197
-
198
- // A difficult-to-believe, but optimized internal dispatch function for
199
- // triggering events. Tries to keep the usual cases speedy (most internal
200
- // Backbone events have 3 arguments).
201
- var triggerEvents = function(events, args) {
202
- var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
203
- switch (args.length) {
204
- case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
205
- case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
206
- case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
207
- case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
208
- default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
209
- }
210
- };
211
-
212
- var listenMethods = {listenTo: 'on', listenToOnce: 'once'};
213
-
214
- // Inversion-of-control versions of `on` and `once`. Tell *this* object to
215
- // listen to an event in another object ... keeping track of what it's
216
- // listening to.
217
- _.each(listenMethods, function(implementation, method) {
218
- Events[method] = function(obj, name, callback) {
219
- var listeningTo = this._listeningTo || (this._listeningTo = {});
220
- var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
221
- listeningTo[id] = obj;
222
- if (!callback && typeof name === 'object') callback = this;
223
- obj[implementation](name, callback, this);
224
- return this;
225
- };
226
- });
227
-
228
- // Aliases for backwards compatibility.
229
- Events.bind = Events.on;
230
- Events.unbind = Events.off;
231
-
232
- // Allow the `Backbone` object to serve as a global event bus, for folks who
233
- // want global "pubsub" in a convenient place.
234
- _.extend(Backbone, Events);
235
-
236
- // Backbone.Model
237
- // --------------
238
-
239
- // Backbone **Models** are the basic data object in the framework --
240
- // frequently representing a row in a table in a database on your server.
241
- // A discrete chunk of data and a bunch of useful, related methods for
242
- // performing computations and transformations on that data.
243
-
244
- // Create a new model with the specified attributes. A client id (`cid`)
245
- // is automatically generated and assigned for you.
246
- var Model = Backbone.Model = function(attributes, options) {
247
- var attrs = attributes || {};
248
- options || (options = {});
249
- this.cid = _.uniqueId('c');
250
- this.attributes = {};
251
- if (options.collection) this.collection = options.collection;
252
- if (options.parse) attrs = this.parse(attrs, options) || {};
253
- attrs = _.defaults({}, attrs, _.result(this, 'defaults'));
254
- this.set(attrs, options);
255
- this.changed = {};
256
- this.initialize.apply(this, arguments);
257
- };
258
-
259
- // Attach all inheritable methods to the Model prototype.
260
- _.extend(Model.prototype, Events, {
261
-
262
- // A hash of attributes whose current and previous value differ.
263
- changed: null,
264
-
265
- // The value returned during the last failed validation.
266
- validationError: null,
267
-
268
- // The default name for the JSON `id` attribute is `"id"`. MongoDB and
269
- // CouchDB users may want to set this to `"_id"`.
270
- idAttribute: 'id',
271
-
272
- // Initialize is an empty function by default. Override it with your own
273
- // initialization logic.
274
- initialize: function(){},
275
-
276
- // Return a copy of the model's `attributes` object.
277
- toJSON: function(options) {
278
- return _.clone(this.attributes);
279
- },
280
-
281
- // Proxy `Backbone.sync` by default -- but override this if you need
282
- // custom syncing semantics for *this* particular model.
283
- sync: function() {
284
- return Backbone.sync.apply(this, arguments);
285
- },
286
-
287
- // Get the value of an attribute.
288
- get: function(attr) {
289
- return this.attributes[attr];
290
- },
291
-
292
- // Get the HTML-escaped value of an attribute.
293
- escape: function(attr) {
294
- return _.escape(this.get(attr));
295
- },
296
-
297
- // Returns `true` if the attribute contains a value that is not null
298
- // or undefined.
299
- has: function(attr) {
300
- return this.get(attr) != null;
301
- },
302
-
303
- // Set a hash of model attributes on the object, firing `"change"`. This is
304
- // the core primitive operation of a model, updating the data and notifying
305
- // anyone who needs to know about the change in state. The heart of the beast.
306
- set: function(key, val, options) {
307
- var attr, attrs, unset, changes, silent, changing, prev, current;
308
- if (key == null) return this;
309
-
310
- // Handle both `"key", value` and `{key: value}` -style arguments.
311
- if (typeof key === 'object') {
312
- attrs = key;
313
- options = val;
314
- } else {
315
- (attrs = {})[key] = val;
316
- }
317
-
318
- options || (options = {});
319
-
320
- // Run validation.
321
- if (!this._validate(attrs, options)) return false;
322
-
323
- // Extract attributes and options.
324
- unset = options.unset;
325
- silent = options.silent;
326
- changes = [];
327
- changing = this._changing;
328
- this._changing = true;
329
-
330
- if (!changing) {
331
- this._previousAttributes = _.clone(this.attributes);
332
- this.changed = {};
333
- }
334
- current = this.attributes, prev = this._previousAttributes;
335
-
336
- // Check for changes of `id`.
337
- if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
338
-
339
- // For each `set` attribute, update or delete the current value.
340
- for (attr in attrs) {
341
- val = attrs[attr];
342
- if (!_.isEqual(current[attr], val)) changes.push(attr);
343
- if (!_.isEqual(prev[attr], val)) {
344
- this.changed[attr] = val;
345
- } else {
346
- delete this.changed[attr];
347
- }
348
- unset ? delete current[attr] : current[attr] = val;
349
- }
350
-
351
- // Trigger all relevant attribute changes.
352
- if (!silent) {
353
- if (changes.length) this._pending = true;
354
- for (var i = 0, l = changes.length; i < l; i++) {
355
- this.trigger('change:' + changes[i], this, current[changes[i]], options);
356
- }
357
- }
358
-
359
- // You might be wondering why there's a `while` loop here. Changes can
360
- // be recursively nested within `"change"` events.
361
- if (changing) return this;
362
- if (!silent) {
363
- while (this._pending) {
364
- this._pending = false;
365
- this.trigger('change', this, options);
366
- }
367
- }
368
- this._pending = false;
369
- this._changing = false;
370
- return this;
371
- },
372
-
373
- // Remove an attribute from the model, firing `"change"`. `unset` is a noop
374
- // if the attribute doesn't exist.
375
- unset: function(attr, options) {
376
- return this.set(attr, void 0, _.extend({}, options, {unset: true}));
377
- },
378
-
379
- // Clear all attributes on the model, firing `"change"`.
380
- clear: function(options) {
381
- var attrs = {};
382
- for (var key in this.attributes) attrs[key] = void 0;
383
- return this.set(attrs, _.extend({}, options, {unset: true}));
384
- },
385
-
386
- // Determine if the model has changed since the last `"change"` event.
387
- // If you specify an attribute name, determine if that attribute has changed.
388
- hasChanged: function(attr) {
389
- if (attr == null) return !_.isEmpty(this.changed);
390
- return _.has(this.changed, attr);
391
- },
392
-
393
- // Return an object containing all the attributes that have changed, or
394
- // false if there are no changed attributes. Useful for determining what
395
- // parts of a view need to be updated and/or what attributes need to be
396
- // persisted to the server. Unset attributes will be set to undefined.
397
- // You can also pass an attributes object to diff against the model,
398
- // determining if there *would be* a change.
399
- changedAttributes: function(diff) {
400
- if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
401
- var val, changed = false;
402
- var old = this._changing ? this._previousAttributes : this.attributes;
403
- for (var attr in diff) {
404
- if (_.isEqual(old[attr], (val = diff[attr]))) continue;
405
- (changed || (changed = {}))[attr] = val;
406
- }
407
- return changed;
408
- },
409
-
410
- // Get the previous value of an attribute, recorded at the time the last
411
- // `"change"` event was fired.
412
- previous: function(attr) {
413
- if (attr == null || !this._previousAttributes) return null;
414
- return this._previousAttributes[attr];
415
- },
416
-
417
- // Get all of the attributes of the model at the time of the previous
418
- // `"change"` event.
419
- previousAttributes: function() {
420
- return _.clone(this._previousAttributes);
421
- },
422
-
423
- // Fetch the model from the server. If the server's representation of the
424
- // model differs from its current attributes, they will be overridden,
425
- // triggering a `"change"` event.
426
- fetch: function(options) {
427
- options = options ? _.clone(options) : {};
428
- if (options.parse === void 0) options.parse = true;
429
- var model = this;
430
- var success = options.success;
431
- options.success = function(resp) {
432
- if (!model.set(model.parse(resp, options), options)) return false;
433
- if (success) success(model, resp, options);
434
- model.trigger('sync', model, resp, options);
435
- };
436
- wrapError(this, options);
437
- return this.sync('read', this, options);
438
- },
439
-
440
- // Set a hash of model attributes, and sync the model to the server.
441
- // If the server returns an attributes hash that differs, the model's
442
- // state will be `set` again.
443
- save: function(key, val, options) {
444
- var attrs, method, xhr, attributes = this.attributes;
445
-
446
- // Handle both `"key", value` and `{key: value}` -style arguments.
447
- if (key == null || typeof key === 'object') {
448
- attrs = key;
449
- options = val;
450
- } else {
451
- (attrs = {})[key] = val;
452
- }
453
-
454
- options = _.extend({validate: true}, options);
455
-
456
- // If we're not waiting and attributes exist, save acts as
457
- // `set(attr).save(null, opts)` with validation. Otherwise, check if
458
- // the model will be valid when the attributes, if any, are set.
459
- if (attrs && !options.wait) {
460
- if (!this.set(attrs, options)) return false;
461
- } else {
462
- if (!this._validate(attrs, options)) return false;
463
- }
464
-
465
- // Set temporary attributes if `{wait: true}`.
466
- if (attrs && options.wait) {
467
- this.attributes = _.extend({}, attributes, attrs);
468
- }
469
-
470
- // After a successful server-side save, the client is (optionally)
471
- // updated with the server-side state.
472
- if (options.parse === void 0) options.parse = true;
473
- var model = this;
474
- var success = options.success;
475
- options.success = function(resp) {
476
- // Ensure attributes are restored during synchronous saves.
477
- model.attributes = attributes;
478
- var serverAttrs = model.parse(resp, options);
479
- if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
480
- if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
481
- return false;
482
- }
483
- if (success) success(model, resp, options);
484
- model.trigger('sync', model, resp, options);
485
- };
486
- wrapError(this, options);
487
-
488
- method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
489
- if (method === 'patch') options.attrs = attrs;
490
- xhr = this.sync(method, this, options);
491
-
492
- // Restore attributes.
493
- if (attrs && options.wait) this.attributes = attributes;
494
-
495
- return xhr;
496
- },
497
-
498
- // Destroy this model on the server if it was already persisted.
499
- // Optimistically removes the model from its collection, if it has one.
500
- // If `wait: true` is passed, waits for the server to respond before removal.
501
- destroy: function(options) {
502
- options = options ? _.clone(options) : {};
503
- var model = this;
504
- var success = options.success;
505
-
506
- var destroy = function() {
507
- model.trigger('destroy', model, model.collection, options);
508
- };
509
-
510
- options.success = function(resp) {
511
- if (options.wait || model.isNew()) destroy();
512
- if (success) success(model, resp, options);
513
- if (!model.isNew()) model.trigger('sync', model, resp, options);
514
- };
515
-
516
- if (this.isNew()) {
517
- options.success();
518
- return false;
519
- }
520
- wrapError(this, options);
521
-
522
- var xhr = this.sync('delete', this, options);
523
- if (!options.wait) destroy();
524
- return xhr;
525
- },
526
-
527
- // Default URL for the model's representation on the server -- if you're
528
- // using Backbone's restful methods, override this to change the endpoint
529
- // that will be called.
530
- url: function() {
531
- var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError();
532
- if (this.isNew()) return base;
533
- return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id);
534
- },
535
-
536
- // **parse** converts a response into the hash of attributes to be `set` on
537
- // the model. The default implementation is just to pass the response along.
538
- parse: function(resp, options) {
539
- return resp;
540
- },
541
-
542
- // Create a new model with identical attributes to this one.
543
- clone: function() {
544
- return new this.constructor(this.attributes);
545
- },
546
-
547
- // A model is new if it has never been saved to the server, and lacks an id.
548
- isNew: function() {
549
- return this.id == null;
550
- },
551
-
552
- // Check if the model is currently in a valid state.
553
- isValid: function(options) {
554
- return this._validate({}, _.extend(options || {}, { validate: true }));
555
- },
556
-
557
- // Run validation against the next complete set of model attributes,
558
- // returning `true` if all is well. Otherwise, fire an `"invalid"` event.
559
- _validate: function(attrs, options) {
560
- if (!options.validate || !this.validate) return true;
561
- attrs = _.extend({}, this.attributes, attrs);
562
- var error = this.validationError = this.validate(attrs, options) || null;
563
- if (!error) return true;
564
- this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
565
- return false;
566
- }
567
-
568
- });
569
-
570
- // Underscore methods that we want to implement on the Model.
571
- var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit'];
572
-
573
- // Mix in each Underscore method as a proxy to `Model#attributes`.
574
- _.each(modelMethods, function(method) {
575
- Model.prototype[method] = function() {
576
- var args = slice.call(arguments);
577
- args.unshift(this.attributes);
578
- return _[method].apply(_, args);
579
- };
580
- });
581
-
582
- // Backbone.Collection
583
- // -------------------
584
-
585
- // If models tend to represent a single row of data, a Backbone Collection is
586
- // more analagous to a table full of data ... or a small slice or page of that
587
- // table, or a collection of rows that belong together for a particular reason
588
- // -- all of the messages in this particular folder, all of the documents
589
- // belonging to this particular author, and so on. Collections maintain
590
- // indexes of their models, both in order, and for lookup by `id`.
591
-
592
- // Create a new **Collection**, perhaps to contain a specific type of `model`.
593
- // If a `comparator` is specified, the Collection will maintain
594
- // its models in sort order, as they're added and removed.
595
- var Collection = Backbone.Collection = function(models, options) {
596
- options || (options = {});
597
- if (options.model) this.model = options.model;
598
- if (options.comparator !== void 0) this.comparator = options.comparator;
599
- this._reset();
600
- this.initialize.apply(this, arguments);
601
- if (models) this.reset(models, _.extend({silent: true}, options));
602
- };
603
-
604
- // Default options for `Collection#set`.
605
- var setOptions = {add: true, remove: true, merge: true};
606
- var addOptions = {add: true, remove: false};
607
-
608
- // Define the Collection's inheritable methods.
609
- _.extend(Collection.prototype, Events, {
610
-
611
- // The default model for a collection is just a **Backbone.Model**.
612
- // This should be overridden in most cases.
613
- model: Model,
614
-
615
- // Initialize is an empty function by default. Override it with your own
616
- // initialization logic.
617
- initialize: function(){},
618
-
619
- // The JSON representation of a Collection is an array of the
620
- // models' attributes.
621
- toJSON: function(options) {
622
- return this.map(function(model){ return model.toJSON(options); });
623
- },
624
-
625
- // Proxy `Backbone.sync` by default.
626
- sync: function() {
627
- return Backbone.sync.apply(this, arguments);
628
- },
629
-
630
- // Add a model, or list of models to the set.
631
- add: function(models, options) {
632
- return this.set(models, _.extend({merge: false}, options, addOptions));
633
- },
634
-
635
- // Remove a model, or a list of models from the set.
636
- remove: function(models, options) {
637
- var singular = !_.isArray(models);
638
- models = singular ? [models] : _.clone(models);
639
- options || (options = {});
640
- var i, l, index, model;
641
- for (i = 0, l = models.length; i < l; i++) {
642
- model = models[i] = this.get(models[i]);
643
- if (!model) continue;
644
- delete this._byId[model.id];
645
- delete this._byId[model.cid];
646
- index = this.indexOf(model);
647
- this.models.splice(index, 1);
648
- this.length--;
649
- if (!options.silent) {
650
- options.index = index;
651
- model.trigger('remove', model, this, options);
652
- }
653
- this._removeReference(model);
654
- }
655
- return singular ? models[0] : models;
656
- },
657
-
658
- // Update a collection by `set`-ing a new list of models, adding new ones,
659
- // removing models that are no longer present, and merging models that
660
- // already exist in the collection, as necessary. Similar to **Model#set**,
661
- // the core operation for updating the data contained by the collection.
662
- set: function(models, options) {
663
- options = _.defaults({}, options, setOptions);
664
- if (options.parse) models = this.parse(models, options);
665
- var singular = !_.isArray(models);
666
- models = singular ? (models ? [models] : []) : _.clone(models);
667
- var i, l, id, model, attrs, existing, sort;
668
- var at = options.at;
669
- var targetModel = this.model;
670
- var sortable = this.comparator && (at == null) && options.sort !== false;
671
- var sortAttr = _.isString(this.comparator) ? this.comparator : null;
672
- var toAdd = [], toRemove = [], modelMap = {};
673
- var add = options.add, merge = options.merge, remove = options.remove;
674
- var order = !sortable && add && remove ? [] : false;
675
-
676
- // Turn bare objects into model references, and prevent invalid models
677
- // from being added.
678
- for (i = 0, l = models.length; i < l; i++) {
679
- attrs = models[i];
680
- if (attrs instanceof Model) {
681
- id = model = attrs;
682
- } else {
683
- id = attrs[targetModel.prototype.idAttribute];
684
- }
685
-
686
- // If a duplicate is found, prevent it from being added and
687
- // optionally merge it into the existing model.
688
- if (existing = this.get(id)) {
689
- if (remove) modelMap[existing.cid] = true;
690
- if (merge) {
691
- attrs = attrs === model ? model.attributes : attrs;
692
- if (options.parse) attrs = existing.parse(attrs, options);
693
- existing.set(attrs, options);
694
- if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
695
- }
696
- models[i] = existing;
697
-
698
- // If this is a new, valid model, push it to the `toAdd` list.
699
- } else if (add) {
700
- model = models[i] = this._prepareModel(attrs, options);
701
- if (!model) continue;
702
- toAdd.push(model);
703
-
704
- // Listen to added models' events, and index models for lookup by
705
- // `id` and by `cid`.
706
- model.on('all', this._onModelEvent, this);
707
- this._byId[model.cid] = model;
708
- if (model.id != null) this._byId[model.id] = model;
709
- }
710
- if (order) order.push(existing || model);
711
- }
712
-
713
- // Remove nonexistent models if appropriate.
714
- if (remove) {
715
- for (i = 0, l = this.length; i < l; ++i) {
716
- if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
717
- }
718
- if (toRemove.length) this.remove(toRemove, options);
719
- }
720
-
721
- // See if sorting is needed, update `length` and splice in new models.
722
- if (toAdd.length || (order && order.length)) {
723
- if (sortable) sort = true;
724
- this.length += toAdd.length;
725
- if (at != null) {
726
- for (i = 0, l = toAdd.length; i < l; i++) {
727
- this.models.splice(at + i, 0, toAdd[i]);
728
- }
729
- } else {
730
- if (order) this.models.length = 0;
731
- var orderedModels = order || toAdd;
732
- for (i = 0, l = orderedModels.length; i < l; i++) {
733
- this.models.push(orderedModels[i]);
734
- }
735
- }
736
- }
737
-
738
- // Silently sort the collection if appropriate.
739
- if (sort) this.sort({silent: true});
740
-
741
- // Unless silenced, it's time to fire all appropriate add/sort events.
742
- if (!options.silent) {
743
- for (i = 0, l = toAdd.length; i < l; i++) {
744
- (model = toAdd[i]).trigger('add', model, this, options);
745
- }
746
- if (sort || (order && order.length)) this.trigger('sort', this, options);
747
- }
748
-
749
- // Return the added (or merged) model (or models).
750
- return singular ? models[0] : models;
751
- },
752
-
753
- // When you have more items than you want to add or remove individually,
754
- // you can reset the entire set with a new list of models, without firing
755
- // any granular `add` or `remove` events. Fires `reset` when finished.
756
- // Useful for bulk operations and optimizations.
757
- reset: function(models, options) {
758
- options || (options = {});
759
- for (var i = 0, l = this.models.length; i < l; i++) {
760
- this._removeReference(this.models[i]);
761
- }
762
- options.previousModels = this.models;
763
- this._reset();
764
- models = this.add(models, _.extend({silent: true}, options));
765
- if (!options.silent) this.trigger('reset', this, options);
766
- return models;
767
- },
768
-
769
- // Add a model to the end of the collection.
770
- push: function(model, options) {
771
- return this.add(model, _.extend({at: this.length}, options));
772
- },
773
-
774
- // Remove a model from the end of the collection.
775
- pop: function(options) {
776
- var model = this.at(this.length - 1);
777
- this.remove(model, options);
778
- return model;
779
- },
780
-
781
- // Add a model to the beginning of the collection.
782
- unshift: function(model, options) {
783
- return this.add(model, _.extend({at: 0}, options));
784
- },
785
-
786
- // Remove a model from the beginning of the collection.
787
- shift: function(options) {
788
- var model = this.at(0);
789
- this.remove(model, options);
790
- return model;
791
- },
792
-
793
- // Slice out a sub-array of models from the collection.
794
- slice: function() {
795
- return slice.apply(this.models, arguments);
796
- },
797
-
798
- // Get a model from the set by id.
799
- get: function(obj) {
800
- if (obj == null) return void 0;
801
- return this._byId[obj.id] || this._byId[obj.cid] || this._byId[obj];
802
- },
803
-
804
- // Get the model at the given index.
805
- at: function(index) {
806
- return this.models[index];
807
- },
808
-
809
- // Return models with matching attributes. Useful for simple cases of
810
- // `filter`.
811
- where: function(attrs, first) {
812
- if (_.isEmpty(attrs)) return first ? void 0 : [];
813
- return this[first ? 'find' : 'filter'](function(model) {
814
- for (var key in attrs) {
815
- if (attrs[key] !== model.get(key)) return false;
816
- }
817
- return true;
818
- });
819
- },
820
-
821
- // Return the first model with matching attributes. Useful for simple cases
822
- // of `find`.
823
- findWhere: function(attrs) {
824
- return this.where(attrs, true);
825
- },
826
-
827
- // Force the collection to re-sort itself. You don't need to call this under
828
- // normal circumstances, as the set will maintain sort order as each item
829
- // is added.
830
- sort: function(options) {
831
- if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
832
- options || (options = {});
833
-
834
- // Run sort based on type of `comparator`.
835
- if (_.isString(this.comparator) || this.comparator.length === 1) {
836
- this.models = this.sortBy(this.comparator, this);
837
- } else {
838
- this.models.sort(_.bind(this.comparator, this));
839
- }
840
-
841
- if (!options.silent) this.trigger('sort', this, options);
842
- return this;
843
- },
844
-
845
- // Pluck an attribute from each model in the collection.
846
- pluck: function(attr) {
847
- return _.invoke(this.models, 'get', attr);
848
- },
849
-
850
- // Fetch the default set of models for this collection, resetting the
851
- // collection when they arrive. If `reset: true` is passed, the response
852
- // data will be passed through the `reset` method instead of `set`.
853
- fetch: function(options) {
854
- options = options ? _.clone(options) : {};
855
- if (options.parse === void 0) options.parse = true;
856
- var success = options.success;
857
- var collection = this;
858
- options.success = function(resp) {
859
- var method = options.reset ? 'reset' : 'set';
860
- collection[method](resp, options);
861
- if (success) success(collection, resp, options);
862
- collection.trigger('sync', collection, resp, options);
863
- };
864
- wrapError(this, options);
865
- return this.sync('read', this, options);
866
- },
867
-
868
- // Create a new instance of a model in this collection. Add the model to the
869
- // collection immediately, unless `wait: true` is passed, in which case we
870
- // wait for the server to agree.
871
- create: function(model, options) {
872
- options = options ? _.clone(options) : {};
873
- if (!(model = this._prepareModel(model, options))) return false;
874
- if (!options.wait) this.add(model, options);
875
- var collection = this;
876
- var success = options.success;
877
- options.success = function(model, resp, options) {
878
- if (options.wait) collection.add(model, options);
879
- if (success) success(model, resp, options);
880
- };
881
- model.save(null, options);
882
- return model;
883
- },
884
-
885
- // **parse** converts a response into a list of models to be added to the
886
- // collection. The default implementation is just to pass it through.
887
- parse: function(resp, options) {
888
- return resp;
889
- },
890
-
891
- // Create a new collection with an identical list of models as this one.
892
- clone: function() {
893
- return new this.constructor(this.models);
894
- },
895
-
896
- // Private method to reset all internal state. Called when the collection
897
- // is first initialized or reset.
898
- _reset: function() {
899
- this.length = 0;
900
- this.models = [];
901
- this._byId = {};
902
- },
903
-
904
- // Prepare a hash of attributes (or other model) to be added to this
905
- // collection.
906
- _prepareModel: function(attrs, options) {
907
- if (attrs instanceof Model) {
908
- if (!attrs.collection) attrs.collection = this;
909
- return attrs;
910
- }
911
- options = options ? _.clone(options) : {};
912
- options.collection = this;
913
- var model = new this.model(attrs, options);
914
- if (!model.validationError) return model;
915
- this.trigger('invalid', this, model.validationError, options);
916
- return false;
917
- },
918
-
919
- // Internal method to sever a model's ties to a collection.
920
- _removeReference: function(model) {
921
- if (this === model.collection) delete model.collection;
922
- model.off('all', this._onModelEvent, this);
923
- },
924
-
925
- // Internal method called every time a model in the set fires an event.
926
- // Sets need to update their indexes when models change ids. All other
927
- // events simply proxy through. "add" and "remove" events that originate
928
- // in other collections are ignored.
929
- _onModelEvent: function(event, model, collection, options) {
930
- if ((event === 'add' || event === 'remove') && collection !== this) return;
931
- if (event === 'destroy') this.remove(model, options);
932
- if (model && event === 'change:' + model.idAttribute) {
933
- delete this._byId[model.previous(model.idAttribute)];
934
- if (model.id != null) this._byId[model.id] = model;
935
- }
936
- this.trigger.apply(this, arguments);
937
- }
938
-
939
- });
940
-
941
- // Underscore methods that we want to implement on the Collection.
942
- // 90% of the core usefulness of Backbone Collections is actually implemented
943
- // right here:
944
- var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
945
- 'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
946
- 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
947
- 'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
948
- 'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle',
949
- 'lastIndexOf', 'isEmpty', 'chain'];
950
-
951
- // Mix in each Underscore method as a proxy to `Collection#models`.
952
- _.each(methods, function(method) {
953
- Collection.prototype[method] = function() {
954
- var args = slice.call(arguments);
955
- args.unshift(this.models);
956
- return _[method].apply(_, args);
957
- };
958
- });
959
-
960
- // Underscore methods that take a property name as an argument.
961
- var attributeMethods = ['groupBy', 'countBy', 'sortBy'];
962
-
963
- // Use attributes instead of properties.
964
- _.each(attributeMethods, function(method) {
965
- Collection.prototype[method] = function(value, context) {
966
- var iterator = _.isFunction(value) ? value : function(model) {
967
- return model.get(value);
968
- };
969
- return _[method](this.models, iterator, context);
970
- };
971
- });
972
-
973
- // Backbone.View
974
- // -------------
975
-
976
- // Backbone Views are almost more convention than they are actual code. A View
977
- // is simply a JavaScript object that represents a logical chunk of UI in the
978
- // DOM. This might be a single item, an entire list, a sidebar or panel, or
979
- // even the surrounding frame which wraps your whole app. Defining a chunk of
980
- // UI as a **View** allows you to define your DOM events declaratively, without
981
- // having to worry about render order ... and makes it easy for the view to
982
- // react to specific changes in the state of your models.
983
-
984
- // Creating a Backbone.View creates its initial element outside of the DOM,
985
- // if an existing element is not provided...
986
- var View = Backbone.View = function(options) {
987
- this.cid = _.uniqueId('view');
988
- options || (options = {});
989
- _.extend(this, _.pick(options, viewOptions));
990
- this._ensureElement();
991
- this.initialize.apply(this, arguments);
992
- this.delegateEvents();
993
- };
994
-
995
- // Cached regex to split keys for `delegate`.
996
- var delegateEventSplitter = /^(\S+)\s*(.*)$/;
997
-
998
- // List of view options to be merged as properties.
999
- var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
1000
-
1001
- // Set up all inheritable **Backbone.View** properties and methods.
1002
- _.extend(View.prototype, Events, {
1003
-
1004
- // The default `tagName` of a View's element is `"div"`.
1005
- tagName: 'div',
1006
-
1007
- // jQuery delegate for element lookup, scoped to DOM elements within the
1008
- // current view. This should be preferred to global lookups where possible.
1009
- $: function(selector) {
1010
- return this.$el.find(selector);
1011
- },
1012
-
1013
- // Initialize is an empty function by default. Override it with your own
1014
- // initialization logic.
1015
- initialize: function(){},
1016
-
1017
- // **render** is the core function that your view should override, in order
1018
- // to populate its element (`this.el`), with the appropriate HTML. The
1019
- // convention is for **render** to always return `this`.
1020
- render: function() {
1021
- return this;
1022
- },
1023
-
1024
- // Remove this view by taking the element out of the DOM, and removing any
1025
- // applicable Backbone.Events listeners.
1026
- remove: function() {
1027
- this.$el.remove();
1028
- this.stopListening();
1029
- return this;
1030
- },
1031
-
1032
- // Change the view's element (`this.el` property), including event
1033
- // re-delegation.
1034
- setElement: function(element, delegate) {
1035
- if (this.$el) this.undelegateEvents();
1036
- this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
1037
- this.el = this.$el[0];
1038
- if (delegate !== false) this.delegateEvents();
1039
- return this;
1040
- },
1041
-
1042
- // Set callbacks, where `this.events` is a hash of
1043
- //
1044
- // *{"event selector": "callback"}*
1045
- //
1046
- // {
1047
- // 'mousedown .title': 'edit',
1048
- // 'click .button': 'save',
1049
- // 'click .open': function(e) { ... }
1050
- // }
1051
- //
1052
- // pairs. Callbacks will be bound to the view, with `this` set properly.
1053
- // Uses event delegation for efficiency.
1054
- // Omitting the selector binds the event to `this.el`.
1055
- // This only works for delegate-able events: not `focus`, `blur`, and
1056
- // not `change`, `submit`, and `reset` in Internet Explorer.
1057
- delegateEvents: function(events) {
1058
- if (!(events || (events = _.result(this, 'events')))) return this;
1059
- this.undelegateEvents();
1060
- for (var key in events) {
1061
- var method = events[key];
1062
- if (!_.isFunction(method)) method = this[events[key]];
1063
- if (!method) continue;
1064
-
1065
- var match = key.match(delegateEventSplitter);
1066
- var eventName = match[1], selector = match[2];
1067
- method = _.bind(method, this);
1068
- eventName += '.delegateEvents' + this.cid;
1069
- if (selector === '') {
1070
- this.$el.on(eventName, method);
1071
- } else {
1072
- this.$el.on(eventName, selector, method);
1073
- }
1074
- }
1075
- return this;
1076
- },
1077
-
1078
- // Clears all callbacks previously bound to the view with `delegateEvents`.
1079
- // You usually don't need to use this, but may wish to if you have multiple
1080
- // Backbone views attached to the same DOM element.
1081
- undelegateEvents: function() {
1082
- this.$el.off('.delegateEvents' + this.cid);
1083
- return this;
1084
- },
1085
-
1086
- // Ensure that the View has a DOM element to render into.
1087
- // If `this.el` is a string, pass it through `$()`, take the first
1088
- // matching element, and re-assign it to `el`. Otherwise, create
1089
- // an element from the `id`, `className` and `tagName` properties.
1090
- _ensureElement: function() {
1091
- if (!this.el) {
1092
- var attrs = _.extend({}, _.result(this, 'attributes'));
1093
- if (this.id) attrs.id = _.result(this, 'id');
1094
- if (this.className) attrs['class'] = _.result(this, 'className');
1095
- var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
1096
- this.setElement($el, false);
1097
- } else {
1098
- this.setElement(_.result(this, 'el'), false);
1099
- }
1100
- }
1101
-
1102
- });
1103
-
1104
- // Backbone.sync
1105
- // -------------
1106
-
1107
- // Override this function to change the manner in which Backbone persists
1108
- // models to the server. You will be passed the type of request, and the
1109
- // model in question. By default, makes a RESTful Ajax request
1110
- // to the model's `url()`. Some possible customizations could be:
1111
- //
1112
- // * Use `setTimeout` to batch rapid-fire updates into a single request.
1113
- // * Send up the models as XML instead of JSON.
1114
- // * Persist models via WebSockets instead of Ajax.
1115
- //
1116
- // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
1117
- // as `POST`, with a `_method` parameter containing the true HTTP method,
1118
- // as well as all requests with the body as `application/x-www-form-urlencoded`
1119
- // instead of `application/json` with the model in a param named `model`.
1120
- // Useful when interfacing with server-side languages like **PHP** that make
1121
- // it difficult to read the body of `PUT` requests.
1122
- Backbone.sync = function(method, model, options) {
1123
- var type = methodMap[method];
1124
-
1125
- // Default options, unless specified.
1126
- _.defaults(options || (options = {}), {
1127
- emulateHTTP: Backbone.emulateHTTP,
1128
- emulateJSON: Backbone.emulateJSON
1129
- });
1130
-
1131
- // Default JSON-request options.
1132
- var params = {type: type, dataType: 'json'};
1133
-
1134
- // Ensure that we have a URL.
1135
- if (!options.url) {
1136
- params.url = _.result(model, 'url') || urlError();
1137
- }
1138
-
1139
- // Ensure that we have the appropriate request data.
1140
- if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
1141
- params.contentType = 'application/json';
1142
- params.data = JSON.stringify(options.attrs || model.toJSON(options));
1143
- }
1144
-
1145
- // For older servers, emulate JSON by encoding the request into an HTML-form.
1146
- if (options.emulateJSON) {
1147
- params.contentType = 'application/x-www-form-urlencoded';
1148
- params.data = params.data ? {model: params.data} : {};
1149
- }
1150
-
1151
- // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1152
- // And an `X-HTTP-Method-Override` header.
1153
- if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
1154
- params.type = 'POST';
1155
- if (options.emulateJSON) params.data._method = type;
1156
- var beforeSend = options.beforeSend;
1157
- options.beforeSend = function(xhr) {
1158
- xhr.setRequestHeader('X-HTTP-Method-Override', type);
1159
- if (beforeSend) return beforeSend.apply(this, arguments);
1160
- };
1161
- }
1162
-
1163
- // Don't process data on a non-GET request.
1164
- if (params.type !== 'GET' && !options.emulateJSON) {
1165
- params.processData = false;
1166
- }
1167
-
1168
- // If we're sending a `PATCH` request, and we're in an old Internet Explorer
1169
- // that still has ActiveX enabled by default, override jQuery to use that
1170
- // for XHR instead. Remove this line when jQuery supports `PATCH` on IE8.
1171
- if (params.type === 'PATCH' && noXhrPatch) {
1172
- params.xhr = function() {
1173
- return new ActiveXObject("Microsoft.XMLHTTP");
1174
- };
1175
- }
1176
-
1177
- // Make the request, allowing the user to override any Ajax options.
1178
- var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
1179
- model.trigger('request', model, xhr, options);
1180
- return xhr;
1181
- };
1182
-
1183
- var noXhrPatch = typeof window !== 'undefined' && !!window.ActiveXObject && !(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent);
1184
-
1185
- // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1186
- var methodMap = {
1187
- 'create': 'POST',
1188
- 'update': 'PUT',
1189
- 'patch': 'PATCH',
1190
- 'delete': 'DELETE',
1191
- 'read': 'GET'
1192
- };
1193
-
1194
- // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
1195
- // Override this if you'd like to use a different library.
1196
- Backbone.ajax = function() {
1197
- return Backbone.$.ajax.apply(Backbone.$, arguments);
1198
- };
1199
-
1200
- // Backbone.Router
1201
- // ---------------
1202
-
1203
- // Routers map faux-URLs to actions, and fire events when routes are
1204
- // matched. Creating a new one sets its `routes` hash, if not set statically.
1205
- var Router = Backbone.Router = function(options) {
1206
- options || (options = {});
1207
- if (options.routes) this.routes = options.routes;
1208
- this._bindRoutes();
1209
- this.initialize.apply(this, arguments);
1210
- };
1211
-
1212
- // Cached regular expressions for matching named param parts and splatted
1213
- // parts of route strings.
1214
- var optionalParam = /\((.*?)\)/g;
1215
- var namedParam = /(\(\?)?:\w+/g;
1216
- var splatParam = /\*\w+/g;
1217
- var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
1218
-
1219
- // Set up all inheritable **Backbone.Router** properties and methods.
1220
- _.extend(Router.prototype, Events, {
1221
-
1222
- // Initialize is an empty function by default. Override it with your own
1223
- // initialization logic.
1224
- initialize: function(){},
1225
-
1226
- // Manually bind a single named route to a callback. For example:
1227
- //
1228
- // this.route('search/:query/p:num', 'search', function(query, num) {
1229
- // ...
1230
- // });
1231
- //
1232
- route: function(route, name, callback) {
1233
- if (!_.isRegExp(route)) route = this._routeToRegExp(route);
1234
- if (_.isFunction(name)) {
1235
- callback = name;
1236
- name = '';
1237
- }
1238
- if (!callback) callback = this[name];
1239
- var router = this;
1240
- Backbone.history.route(route, function(fragment) {
1241
- var args = router._extractParameters(route, fragment);
1242
- callback && callback.apply(router, args);
1243
- router.trigger.apply(router, ['route:' + name].concat(args));
1244
- router.trigger('route', name, args);
1245
- Backbone.history.trigger('route', router, name, args);
1246
- });
1247
- return this;
1248
- },
1249
-
1250
- // Simple proxy to `Backbone.history` to save a fragment into the history.
1251
- navigate: function(fragment, options) {
1252
- Backbone.history.navigate(fragment, options);
1253
- return this;
1254
- },
1255
-
1256
- // Bind all defined routes to `Backbone.history`. We have to reverse the
1257
- // order of the routes here to support behavior where the most general
1258
- // routes can be defined at the bottom of the route map.
1259
- _bindRoutes: function() {
1260
- if (!this.routes) return;
1261
- this.routes = _.result(this, 'routes');
1262
- var route, routes = _.keys(this.routes);
1263
- while ((route = routes.pop()) != null) {
1264
- this.route(route, this.routes[route]);
1265
- }
1266
- },
1267
-
1268
- // Convert a route string into a regular expression, suitable for matching
1269
- // against the current location hash.
1270
- _routeToRegExp: function(route) {
1271
- route = route.replace(escapeRegExp, '\\$&')
1272
- .replace(optionalParam, '(?:$1)?')
1273
- .replace(namedParam, function(match, optional) {
1274
- return optional ? match : '([^\/]+)';
1275
- })
1276
- .replace(splatParam, '(.*?)');
1277
- return new RegExp('^' + route + '$');
1278
- },
1279
-
1280
- // Given a route, and a URL fragment that it matches, return the array of
1281
- // extracted decoded parameters. Empty or unmatched parameters will be
1282
- // treated as `null` to normalize cross-browser behavior.
1283
- _extractParameters: function(route, fragment) {
1284
- var params = route.exec(fragment).slice(1);
1285
- return _.map(params, function(param) {
1286
- return param ? decodeURIComponent(param) : null;
1287
- });
1288
- }
1289
-
1290
- });
1291
-
1292
- // Backbone.History
1293
- // ----------------
1294
-
1295
- // Handles cross-browser history management, based on either
1296
- // [pushState](http://diveintohtml5.info/history.html) and real URLs, or
1297
- // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
1298
- // and URL fragments. If the browser supports neither (old IE, natch),
1299
- // falls back to polling.
1300
- var History = Backbone.History = function() {
1301
- this.handlers = [];
1302
- _.bindAll(this, 'checkUrl');
1303
-
1304
- // Ensure that `History` can be used outside of the browser.
1305
- if (typeof window !== 'undefined') {
1306
- this.location = window.location;
1307
- this.history = window.history;
1308
- }
1309
- };
1310
-
1311
- // Cached regex for stripping a leading hash/slash and trailing space.
1312
- var routeStripper = /^[#\/]|\s+$/g;
1313
-
1314
- // Cached regex for stripping leading and trailing slashes.
1315
- var rootStripper = /^\/+|\/+$/g;
1316
-
1317
- // Cached regex for detecting MSIE.
1318
- var isExplorer = /msie [\w.]+/;
1319
-
1320
- // Cached regex for removing a trailing slash.
1321
- var trailingSlash = /\/$/;
1322
-
1323
- // Cached regex for stripping urls of hash and query.
1324
- var pathStripper = /[?#].*$/;
1325
-
1326
- // Has the history handling already been started?
1327
- History.started = false;
1328
-
1329
- // Set up all inheritable **Backbone.History** properties and methods.
1330
- _.extend(History.prototype, Events, {
1331
-
1332
- // The default interval to poll for hash changes, if necessary, is
1333
- // twenty times a second.
1334
- interval: 50,
1335
-
1336
- // Gets the true hash value. Cannot use location.hash directly due to bug
1337
- // in Firefox where location.hash will always be decoded.
1338
- getHash: function(window) {
1339
- var match = (window || this).location.href.match(/#(.*)$/);
1340
- return match ? match[1] : '';
1341
- },
1342
-
1343
- // Get the cross-browser normalized URL fragment, either from the URL,
1344
- // the hash, or the override.
1345
- getFragment: function(fragment, forcePushState) {
1346
- if (fragment == null) {
1347
- if (this._hasPushState || !this._wantsHashChange || forcePushState) {
1348
- fragment = this.location.pathname;
1349
- var root = this.root.replace(trailingSlash, '');
1350
- if (!fragment.indexOf(root)) fragment = fragment.slice(root.length);
1351
- } else {
1352
- fragment = this.getHash();
1353
- }
1354
- }
1355
- return fragment.replace(routeStripper, '');
1356
- },
1357
-
1358
- // Start the hash change handling, returning `true` if the current URL matches
1359
- // an existing route, and `false` otherwise.
1360
- start: function(options) {
1361
- if (History.started) throw new Error("Backbone.history has already been started");
1362
- History.started = true;
1363
-
1364
- // Figure out the initial configuration. Do we need an iframe?
1365
- // Is pushState desired ... is it available?
1366
- this.options = _.extend({root: '/'}, this.options, options);
1367
- this.root = this.options.root;
1368
- this._wantsHashChange = this.options.hashChange !== false;
1369
- this._wantsPushState = !!this.options.pushState;
1370
- this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState);
1371
- var fragment = this.getFragment();
1372
- var docMode = document.documentMode;
1373
- var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
1374
-
1375
- // Normalize root to always include a leading and trailing slash.
1376
- this.root = ('/' + this.root + '/').replace(rootStripper, '/');
1377
-
1378
- if (oldIE && this._wantsHashChange) {
1379
- this.iframe = Backbone.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
1380
- this.navigate(fragment);
1381
- }
1382
-
1383
- // Depending on whether we're using pushState or hashes, and whether
1384
- // 'onhashchange' is supported, determine how we check the URL state.
1385
- if (this._hasPushState) {
1386
- Backbone.$(window).on('popstate', this.checkUrl);
1387
- } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
1388
- Backbone.$(window).on('hashchange', this.checkUrl);
1389
- } else if (this._wantsHashChange) {
1390
- this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
1391
- }
1392
-
1393
- // Determine if we need to change the base url, for a pushState link
1394
- // opened by a non-pushState browser.
1395
- this.fragment = fragment;
1396
- var loc = this.location;
1397
- var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root;
1398
-
1399
- // Transition from hashChange to pushState or vice versa if both are
1400
- // requested.
1401
- if (this._wantsHashChange && this._wantsPushState) {
1402
-
1403
- // If we've started off with a route from a `pushState`-enabled
1404
- // browser, but we're currently in a browser that doesn't support it...
1405
- if (!this._hasPushState && !atRoot) {
1406
- this.fragment = this.getFragment(null, true);
1407
- this.location.replace(this.root + this.location.search + '#' + this.fragment);
1408
- // Return immediately as browser will do redirect to new url
1409
- return true;
1410
-
1411
- // Or if we've started out with a hash-based route, but we're currently
1412
- // in a browser where it could be `pushState`-based instead...
1413
- } else if (this._hasPushState && atRoot && loc.hash) {
1414
- this.fragment = this.getHash().replace(routeStripper, '');
1415
- this.history.replaceState({}, document.title, this.root + this.fragment + loc.search);
1416
- }
1417
-
1418
- }
1419
-
1420
- if (!this.options.silent) return this.loadUrl();
1421
- },
1422
-
1423
- // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
1424
- // but possibly useful for unit testing Routers.
1425
- stop: function() {
1426
- Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl);
1427
- clearInterval(this._checkUrlInterval);
1428
- History.started = false;
1429
- },
1430
-
1431
- // Add a route to be tested when the fragment changes. Routes added later
1432
- // may override previous routes.
1433
- route: function(route, callback) {
1434
- this.handlers.unshift({route: route, callback: callback});
1435
- },
1436
-
1437
- // Checks the current URL to see if it has changed, and if it has,
1438
- // calls `loadUrl`, normalizing across the hidden iframe.
1439
- checkUrl: function(e) {
1440
- var current = this.getFragment();
1441
- if (current === this.fragment && this.iframe) {
1442
- current = this.getFragment(this.getHash(this.iframe));
1443
- }
1444
- if (current === this.fragment) return false;
1445
- if (this.iframe) this.navigate(current);
1446
- this.loadUrl();
1447
- },
1448
-
1449
- // Attempt to load the current URL fragment. If a route succeeds with a
1450
- // match, returns `true`. If no defined routes matches the fragment,
1451
- // returns `false`.
1452
- loadUrl: function(fragment) {
1453
- fragment = this.fragment = this.getFragment(fragment);
1454
- return _.any(this.handlers, function(handler) {
1455
- if (handler.route.test(fragment)) {
1456
- handler.callback(fragment);
1457
- return true;
1458
- }
1459
- });
1460
- },
1461
-
1462
- // Save a fragment into the hash history, or replace the URL state if the
1463
- // 'replace' option is passed. You are responsible for properly URL-encoding
1464
- // the fragment in advance.
1465
- //
1466
- // The options object can contain `trigger: true` if you wish to have the
1467
- // route callback be fired (not usually desirable), or `replace: true`, if
1468
- // you wish to modify the current URL without adding an entry to the history.
1469
- navigate: function(fragment, options) {
1470
- if (!History.started) return false;
1471
- if (!options || options === true) options = {trigger: !!options};
1472
-
1473
- var url = this.root + (fragment = this.getFragment(fragment || ''));
1474
-
1475
- // Strip the fragment of the query and hash for matching.
1476
- fragment = fragment.replace(pathStripper, '');
1477
-
1478
- if (this.fragment === fragment) return;
1479
- this.fragment = fragment;
1480
-
1481
- // Don't include a trailing slash on the root.
1482
- if (fragment === '' && url !== '/') url = url.slice(0, -1);
1483
-
1484
- // If pushState is available, we use it to set the fragment as a real URL.
1485
- if (this._hasPushState) {
1486
- this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
1487
-
1488
- // If hash changes haven't been explicitly disabled, update the hash
1489
- // fragment to store history.
1490
- } else if (this._wantsHashChange) {
1491
- this._updateHash(this.location, fragment, options.replace);
1492
- if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) {
1493
- // Opening and closing the iframe tricks IE7 and earlier to push a
1494
- // history entry on hash-tag change. When replace is true, we don't
1495
- // want this.
1496
- if(!options.replace) this.iframe.document.open().close();
1497
- this._updateHash(this.iframe.location, fragment, options.replace);
1498
- }
1499
-
1500
- // If you've told us that you explicitly don't want fallback hashchange-
1501
- // based history, then `navigate` becomes a page refresh.
1502
- } else {
1503
- return this.location.assign(url);
1504
- }
1505
- if (options.trigger) return this.loadUrl(fragment);
1506
- },
1507
-
1508
- // Update the hash location, either replacing the current entry, or adding
1509
- // a new one to the browser history.
1510
- _updateHash: function(location, fragment, replace) {
1511
- if (replace) {
1512
- var href = location.href.replace(/(javascript:|#).*$/, '');
1513
- location.replace(href + '#' + fragment);
1514
- } else {
1515
- // Some browsers require that `hash` contains a leading #.
1516
- location.hash = '#' + fragment;
1517
- }
1518
- }
1519
-
1520
- });
1521
-
1522
- // Create the default Backbone.history.
1523
- Backbone.history = new History;
1524
-
1525
- // Helpers
1526
- // -------
1527
-
1528
- // Helper function to correctly set up the prototype chain, for subclasses.
1529
- // Similar to `goog.inherits`, but uses a hash of prototype properties and
1530
- // class properties to be extended.
1531
- var extend = function(protoProps, staticProps) {
1532
- var parent = this;
1533
- var child;
1534
-
1535
- // The constructor function for the new subclass is either defined by you
1536
- // (the "constructor" property in your `extend` definition), or defaulted
1537
- // by us to simply call the parent's constructor.
1538
- if (protoProps && _.has(protoProps, 'constructor')) {
1539
- child = protoProps.constructor;
1540
- } else {
1541
- child = function(){ return parent.apply(this, arguments); };
1542
- }
1543
-
1544
- // Add static properties to the constructor function, if supplied.
1545
- _.extend(child, parent, staticProps);
1546
-
1547
- // Set the prototype chain to inherit from `parent`, without calling
1548
- // `parent`'s constructor function.
1549
- var Surrogate = function(){ this.constructor = child; };
1550
- Surrogate.prototype = parent.prototype;
1551
- child.prototype = new Surrogate;
1552
-
1553
- // Add prototype properties (instance properties) to the subclass,
1554
- // if supplied.
1555
- if (protoProps) _.extend(child.prototype, protoProps);
1556
-
1557
- // Set a convenience property in case the parent's prototype is needed
1558
- // later.
1559
- child.__super__ = parent.prototype;
1560
-
1561
- return child;
1562
- };
1563
-
1564
- // Set up inheritance for the model, collection, router, view and history.
1565
- Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
1566
-
1567
- // Throw an error when a URL is needed, and none is supplied.
1568
- var urlError = function() {
1569
- throw new Error('A "url" property or function must be specified');
1570
- };
1571
-
1572
- // Wrap an optional error callback with a fallback error event.
1573
- var wrapError = function(model, options) {
1574
- var error = options.error;
1575
- options.error = function(resp) {
1576
- if (error) error(model, resp, options);
1577
- model.trigger('error', model, resp, options);
1578
- };
1579
- };
1580
-
1581
- }).call(this);