malm 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,1149 +0,0 @@
1
- // Backbone.js 0.5.1
2
- // (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
3
- // Backbone may be freely distributed under the MIT license.
4
- // For all details and documentation:
5
- // http://documentcloud.github.com/backbone
6
-
7
- (function(){
8
-
9
- // Initial Setup
10
- // -------------
11
-
12
- // Save a reference to the global object.
13
- var root = this;
14
-
15
- // Save the previous value of the `Backbone` variable.
16
- var previousBackbone = root.Backbone;
17
-
18
- // The top-level namespace. All public Backbone classes and modules will
19
- // be attached to this. Exported for both CommonJS and the browser.
20
- var Backbone;
21
- if (typeof exports !== 'undefined') {
22
- Backbone = exports;
23
- } else {
24
- Backbone = root.Backbone = {};
25
- }
26
-
27
- // Current version of the library. Keep in sync with `package.json`.
28
- Backbone.VERSION = '0.5.1';
29
-
30
- // Require Underscore, if we're on the server, and it's not already present.
31
- var _ = root._;
32
- if (!_ && (typeof require !== 'undefined')) _ = require('underscore')._;
33
-
34
- // For Backbone's purposes, jQuery or Zepto owns the `$` variable.
35
- var $ = root.jQuery || root.Zepto;
36
-
37
- // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
38
- // to its previous owner. Returns a reference to this Backbone object.
39
- Backbone.noConflict = function() {
40
- root.Backbone = previousBackbone;
41
- return this;
42
- };
43
-
44
- // Turn on `emulateHTTP` to use support legacy HTTP servers. Setting this option will
45
- // fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and set a
46
- // `X-Http-Method-Override` header.
47
- Backbone.emulateHTTP = false;
48
-
49
- // Turn on `emulateJSON` to support legacy servers that can't deal with direct
50
- // `application/json` requests ... will encode the body as
51
- // `application/x-www-form-urlencoded` instead and will send the model in a
52
- // form param named `model`.
53
- Backbone.emulateJSON = false;
54
-
55
- // Backbone.Events
56
- // -----------------
57
-
58
- // A module that can be mixed in to *any object* in order to provide it with
59
- // custom events. You may `bind` or `unbind` a callback function to an event;
60
- // `trigger`-ing an event fires all callbacks in succession.
61
- //
62
- // var object = {};
63
- // _.extend(object, Backbone.Events);
64
- // object.bind('expand', function(){ alert('expanded'); });
65
- // object.trigger('expand');
66
- //
67
- Backbone.Events = {
68
-
69
- // Bind an event, specified by a string name, `ev`, to a `callback` function.
70
- // Passing `"all"` will bind the callback to all events fired.
71
- bind : function(ev, callback) {
72
- var calls = this._callbacks || (this._callbacks = {});
73
- var list = calls[ev] || (calls[ev] = []);
74
- list.push(callback);
75
- return this;
76
- },
77
-
78
- // Remove one or many callbacks. If `callback` is null, removes all
79
- // callbacks for the event. If `ev` is null, removes all bound callbacks
80
- // for all events.
81
- unbind : function(ev, callback) {
82
- var calls;
83
- if (!ev) {
84
- this._callbacks = {};
85
- } else if (calls = this._callbacks) {
86
- if (!callback) {
87
- calls[ev] = [];
88
- } else {
89
- var list = calls[ev];
90
- if (!list) return this;
91
- for (var i = 0, l = list.length; i < l; i++) {
92
- if (callback === list[i]) {
93
- list[i] = null;
94
- break;
95
- }
96
- }
97
- }
98
- }
99
- return this;
100
- },
101
-
102
- // Trigger an event, firing all bound callbacks. Callbacks are passed the
103
- // same arguments as `trigger` is, apart from the event name.
104
- // Listening for `"all"` passes the true event name as the first argument.
105
- trigger : function(eventName) {
106
- var list, calls, ev, callback, args;
107
- var both = 2;
108
- if (!(calls = this._callbacks)) return this;
109
- while (both--) {
110
- ev = both ? eventName : 'all';
111
- if (list = calls[ev]) {
112
- for (var i = 0, l = list.length; i < l; i++) {
113
- if (!(callback = list[i])) {
114
- list.splice(i, 1); i--; l--;
115
- } else {
116
- args = both ? Array.prototype.slice.call(arguments, 1) : arguments;
117
- callback.apply(this, args);
118
- }
119
- }
120
- }
121
- }
122
- return this;
123
- }
124
-
125
- };
126
-
127
- // Backbone.Model
128
- // --------------
129
-
130
- // Create a new model, with defined attributes. A client id (`cid`)
131
- // is automatically generated and assigned for you.
132
- Backbone.Model = function(attributes, options) {
133
- var defaults;
134
- attributes || (attributes = {});
135
- if (defaults = this.defaults) {
136
- if (_.isFunction(defaults)) defaults = defaults();
137
- attributes = _.extend({}, defaults, attributes);
138
- }
139
- this.attributes = {};
140
- this._escapedAttributes = {};
141
- this.cid = _.uniqueId('c');
142
- this.set(attributes, {silent : true});
143
- this._changed = false;
144
- this._previousAttributes = _.clone(this.attributes);
145
- if (options && options.collection) this.collection = options.collection;
146
- this.initialize(attributes, options);
147
- };
148
-
149
- // Attach all inheritable methods to the Model prototype.
150
- _.extend(Backbone.Model.prototype, Backbone.Events, {
151
-
152
- // A snapshot of the model's previous attributes, taken immediately
153
- // after the last `"change"` event was fired.
154
- _previousAttributes : null,
155
-
156
- // Has the item been changed since the last `"change"` event?
157
- _changed : false,
158
-
159
- // The default name for the JSON `id` attribute is `"id"`. MongoDB and
160
- // CouchDB users may want to set this to `"_id"`.
161
- idAttribute : 'id',
162
-
163
- // Initialize is an empty function by default. Override it with your own
164
- // initialization logic.
165
- initialize : function(){},
166
-
167
- // Return a copy of the model's `attributes` object.
168
- toJSON : function() {
169
- return _.clone(this.attributes);
170
- },
171
-
172
- // Get the value of an attribute.
173
- get : function(attr) {
174
- return this.attributes[attr];
175
- },
176
-
177
- // Get the HTML-escaped value of an attribute.
178
- escape : function(attr) {
179
- var html;
180
- if (html = this._escapedAttributes[attr]) return html;
181
- var val = this.attributes[attr];
182
- return this._escapedAttributes[attr] = escapeHTML(val == null ? '' : '' + val);
183
- },
184
-
185
- // Returns `true` if the attribute contains a value that is not null
186
- // or undefined.
187
- has : function(attr) {
188
- return this.attributes[attr] != null;
189
- },
190
-
191
- // Set a hash of model attributes on the object, firing `"change"` unless you
192
- // choose to silence it.
193
- set : function(attrs, options) {
194
-
195
- // Extract attributes and options.
196
- options || (options = {});
197
- if (!attrs) return this;
198
- if (attrs.attributes) attrs = attrs.attributes;
199
- var now = this.attributes, escaped = this._escapedAttributes;
200
-
201
- // Run validation.
202
- if (!options.silent && this.validate && !this._performValidation(attrs, options)) return false;
203
-
204
- // Check for changes of `id`.
205
- if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
206
-
207
- // We're about to start triggering change events.
208
- var alreadyChanging = this._changing;
209
- this._changing = true;
210
-
211
- // Update attributes.
212
- for (var attr in attrs) {
213
- var val = attrs[attr];
214
- if (!_.isEqual(now[attr], val)) {
215
- now[attr] = val;
216
- delete escaped[attr];
217
- this._changed = true;
218
- if (!options.silent) this.trigger('change:' + attr, this, val, options);
219
- }
220
- }
221
-
222
- // Fire the `"change"` event, if the model has been changed.
223
- if (!alreadyChanging && !options.silent && this._changed) this.change(options);
224
- this._changing = false;
225
- return this;
226
- },
227
-
228
- // Remove an attribute from the model, firing `"change"` unless you choose
229
- // to silence it. `unset` is a noop if the attribute doesn't exist.
230
- unset : function(attr, options) {
231
- if (!(attr in this.attributes)) return this;
232
- options || (options = {});
233
- var value = this.attributes[attr];
234
-
235
- // Run validation.
236
- var validObj = {};
237
- validObj[attr] = void 0;
238
- if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false;
239
-
240
- // Remove the attribute.
241
- delete this.attributes[attr];
242
- delete this._escapedAttributes[attr];
243
- if (attr == this.idAttribute) delete this.id;
244
- this._changed = true;
245
- if (!options.silent) {
246
- this.trigger('change:' + attr, this, void 0, options);
247
- this.change(options);
248
- }
249
- return this;
250
- },
251
-
252
- // Clear all attributes on the model, firing `"change"` unless you choose
253
- // to silence it.
254
- clear : function(options) {
255
- options || (options = {});
256
- var attr;
257
- var old = this.attributes;
258
-
259
- // Run validation.
260
- var validObj = {};
261
- for (attr in old) validObj[attr] = void 0;
262
- if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false;
263
-
264
- this.attributes = {};
265
- this._escapedAttributes = {};
266
- this._changed = true;
267
- if (!options.silent) {
268
- for (attr in old) {
269
- this.trigger('change:' + attr, this, void 0, options);
270
- }
271
- this.change(options);
272
- }
273
- return this;
274
- },
275
-
276
- // Fetch the model from the server. If the server's representation of the
277
- // model differs from its current attributes, they will be overriden,
278
- // triggering a `"change"` event.
279
- fetch : function(options) {
280
- options || (options = {});
281
- var model = this;
282
- var success = options.success;
283
- options.success = function(resp, status, xhr) {
284
- if (!model.set(model.parse(resp, xhr), options)) return false;
285
- if (success) success(model, resp);
286
- };
287
- options.error = wrapError(options.error, model, options);
288
- return (this.sync || Backbone.sync).call(this, 'read', this, options);
289
- },
290
-
291
- // Set a hash of model attributes, and sync the model to the server.
292
- // If the server returns an attributes hash that differs, the model's
293
- // state will be `set` again.
294
- save : function(attrs, options) {
295
- options || (options = {});
296
- if (attrs && !this.set(attrs, options)) return false;
297
- var model = this;
298
- var success = options.success;
299
- options.success = function(resp, status, xhr) {
300
- if (!model.set(model.parse(resp, xhr), options)) return false;
301
- if (success) success(model, resp, xhr);
302
- };
303
- options.error = wrapError(options.error, model, options);
304
- var method = this.isNew() ? 'create' : 'update';
305
- return (this.sync || Backbone.sync).call(this, method, this, options);
306
- },
307
-
308
- // Destroy this model on the server if it was already persisted. Upon success, the model is removed
309
- // from its collection, if it has one.
310
- destroy : function(options) {
311
- options || (options = {});
312
- if (this.isNew()) return this.trigger('destroy', this, this.collection, options);
313
- var model = this;
314
- var success = options.success;
315
- options.success = function(resp) {
316
- model.trigger('destroy', model, model.collection, options);
317
- if (success) success(model, resp);
318
- };
319
- options.error = wrapError(options.error, model, options);
320
- return (this.sync || Backbone.sync).call(this, 'delete', this, options);
321
- },
322
-
323
- // Default URL for the model's representation on the server -- if you're
324
- // using Backbone's restful methods, override this to change the endpoint
325
- // that will be called.
326
- url : function() {
327
- var base = getUrl(this.collection) || this.urlRoot || urlError();
328
- if (this.isNew()) return base;
329
- return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
330
- },
331
-
332
- // **parse** converts a response into the hash of attributes to be `set` on
333
- // the model. The default implementation is just to pass the response along.
334
- parse : function(resp, xhr) {
335
- return resp;
336
- },
337
-
338
- // Create a new model with identical attributes to this one.
339
- clone : function() {
340
- return new this.constructor(this);
341
- },
342
-
343
- // A model is new if it has never been saved to the server, and lacks an id.
344
- isNew : function() {
345
- return this.id == null;
346
- },
347
-
348
- // Call this method to manually fire a `change` event for this model.
349
- // Calling this will cause all objects observing the model to update.
350
- change : function(options) {
351
- this.trigger('change', this, options);
352
- this._previousAttributes = _.clone(this.attributes);
353
- this._changed = false;
354
- },
355
-
356
- // Determine if the model has changed since the last `"change"` event.
357
- // If you specify an attribute name, determine if that attribute has changed.
358
- hasChanged : function(attr) {
359
- if (attr) return this._previousAttributes[attr] != this.attributes[attr];
360
- return this._changed;
361
- },
362
-
363
- // Return an object containing all the attributes that have changed, or false
364
- // if there are no changed attributes. Useful for determining what parts of a
365
- // view need to be updated and/or what attributes need to be persisted to
366
- // the server.
367
- changedAttributes : function(now) {
368
- now || (now = this.attributes);
369
- var old = this._previousAttributes;
370
- var changed = false;
371
- for (var attr in now) {
372
- if (!_.isEqual(old[attr], now[attr])) {
373
- changed = changed || {};
374
- changed[attr] = now[attr];
375
- }
376
- }
377
- return changed;
378
- },
379
-
380
- // Get the previous value of an attribute, recorded at the time the last
381
- // `"change"` event was fired.
382
- previous : function(attr) {
383
- if (!attr || !this._previousAttributes) return null;
384
- return this._previousAttributes[attr];
385
- },
386
-
387
- // Get all of the attributes of the model at the time of the previous
388
- // `"change"` event.
389
- previousAttributes : function() {
390
- return _.clone(this._previousAttributes);
391
- },
392
-
393
- // Run validation against a set of incoming attributes, returning `true`
394
- // if all is well. If a specific `error` callback has been passed,
395
- // call that instead of firing the general `"error"` event.
396
- _performValidation : function(attrs, options) {
397
- var error = this.validate(attrs);
398
- if (error) {
399
- if (options.error) {
400
- options.error(this, error, options);
401
- } else {
402
- this.trigger('error', this, error, options);
403
- }
404
- return false;
405
- }
406
- return true;
407
- }
408
-
409
- });
410
-
411
- // Backbone.Collection
412
- // -------------------
413
-
414
- // Provides a standard collection class for our sets of models, ordered
415
- // or unordered. If a `comparator` is specified, the Collection will maintain
416
- // its models in sort order, as they're added and removed.
417
- Backbone.Collection = function(models, options) {
418
- options || (options = {});
419
- if (options.comparator) this.comparator = options.comparator;
420
- _.bindAll(this, '_onModelEvent', '_removeReference');
421
- this._reset();
422
- if (models) this.reset(models, {silent: true});
423
- this.initialize.apply(this, arguments);
424
- };
425
-
426
- // Define the Collection's inheritable methods.
427
- _.extend(Backbone.Collection.prototype, Backbone.Events, {
428
-
429
- // The default model for a collection is just a **Backbone.Model**.
430
- // This should be overridden in most cases.
431
- model : Backbone.Model,
432
-
433
- // Initialize is an empty function by default. Override it with your own
434
- // initialization logic.
435
- initialize : function(){},
436
-
437
- // The JSON representation of a Collection is an array of the
438
- // models' attributes.
439
- toJSON : function() {
440
- return this.map(function(model){ return model.toJSON(); });
441
- },
442
-
443
- // Add a model, or list of models to the set. Pass **silent** to avoid
444
- // firing the `added` event for every new model.
445
- add : function(models, options) {
446
- if (_.isArray(models)) {
447
- for (var i = 0, l = models.length; i < l; i++) {
448
- this._add(models[i], options);
449
- }
450
- } else {
451
- this._add(models, options);
452
- }
453
- return this;
454
- },
455
-
456
- // Remove a model, or a list of models from the set. Pass silent to avoid
457
- // firing the `removed` event for every model removed.
458
- remove : function(models, options) {
459
- if (_.isArray(models)) {
460
- for (var i = 0, l = models.length; i < l; i++) {
461
- this._remove(models[i], options);
462
- }
463
- } else {
464
- this._remove(models, options);
465
- }
466
- return this;
467
- },
468
-
469
- // Get a model from the set by id.
470
- get : function(id) {
471
- if (id == null) return null;
472
- return this._byId[id.id != null ? id.id : id];
473
- },
474
-
475
- // Get a model from the set by client id.
476
- getByCid : function(cid) {
477
- return cid && this._byCid[cid.cid || cid];
478
- },
479
-
480
- // Get the model at the given index.
481
- at: function(index) {
482
- return this.models[index];
483
- },
484
-
485
- // Force the collection to re-sort itself. You don't need to call this under normal
486
- // circumstances, as the set will maintain sort order as each item is added.
487
- sort : function(options) {
488
- options || (options = {});
489
- if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
490
- this.models = this.sortBy(this.comparator);
491
- if (!options.silent) this.trigger('reset', this, options);
492
- return this;
493
- },
494
-
495
- // Pluck an attribute from each model in the collection.
496
- pluck : function(attr) {
497
- return _.map(this.models, function(model){ return model.get(attr); });
498
- },
499
-
500
- // When you have more items than you want to add or remove individually,
501
- // you can reset the entire set with a new list of models, without firing
502
- // any `added` or `removed` events. Fires `reset` when finished.
503
- reset : function(models, options) {
504
- models || (models = []);
505
- options || (options = {});
506
- this.each(this._removeReference);
507
- this._reset();
508
- this.add(models, {silent: true});
509
- if (!options.silent) this.trigger('reset', this, options);
510
- return this;
511
- },
512
-
513
- // Fetch the default set of models for this collection, resetting the
514
- // collection when they arrive. If `add: true` is passed, appends the
515
- // models to the collection instead of resetting.
516
- fetch : function(options) {
517
- options || (options = {});
518
- var collection = this;
519
- var success = options.success;
520
- options.success = function(resp, status, xhr) {
521
- collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
522
- if (success) success(collection, resp);
523
- };
524
- options.error = wrapError(options.error, collection, options);
525
- return (this.sync || Backbone.sync).call(this, 'read', this, options);
526
- },
527
-
528
- // Create a new instance of a model in this collection. After the model
529
- // has been created on the server, it will be added to the collection.
530
- // Returns the model, or 'false' if validation on a new model fails.
531
- create : function(model, options) {
532
- var coll = this;
533
- options || (options = {});
534
- model = this._prepareModel(model, options);
535
- if (!model) return false;
536
- var success = options.success;
537
- options.success = function(nextModel, resp, xhr) {
538
- coll.add(nextModel, options);
539
- if (success) success(nextModel, resp, xhr);
540
- };
541
- model.save(null, options);
542
- return model;
543
- },
544
-
545
- // **parse** converts a response into a list of models to be added to the
546
- // collection. The default implementation is just to pass it through.
547
- parse : function(resp, xhr) {
548
- return resp;
549
- },
550
-
551
- // Proxy to _'s chain. Can't be proxied the same way the rest of the
552
- // underscore methods are proxied because it relies on the underscore
553
- // constructor.
554
- chain: function () {
555
- return _(this.models).chain();
556
- },
557
-
558
- // Reset all internal state. Called when the collection is reset.
559
- _reset : function(options) {
560
- this.length = 0;
561
- this.models = [];
562
- this._byId = {};
563
- this._byCid = {};
564
- },
565
-
566
- // Prepare a model to be added to this collection
567
- _prepareModel: function(model, options) {
568
- if (!(model instanceof Backbone.Model)) {
569
- var attrs = model;
570
- model = new this.model(attrs, {collection: this});
571
- if (model.validate && !model._performValidation(attrs, options)) model = false;
572
- } else if (!model.collection) {
573
- model.collection = this;
574
- }
575
- return model;
576
- },
577
-
578
- // Internal implementation of adding a single model to the set, updating
579
- // hash indexes for `id` and `cid` lookups.
580
- // Returns the model, or 'false' if validation on a new model fails.
581
- _add : function(model, options) {
582
- options || (options = {});
583
- model = this._prepareModel(model, options);
584
- if (!model) return false;
585
- var already = this.getByCid(model) || this.get(model);
586
- if (already) throw new Error(["Can't add the same model to a set twice", already.id]);
587
- this._byId[model.id] = model;
588
- this._byCid[model.cid] = model;
589
- var index = options.at != null ? options.at :
590
- this.comparator ? this.sortedIndex(model, this.comparator) :
591
- this.length;
592
- this.models.splice(index, 0, model);
593
- model.bind('all', this._onModelEvent);
594
- this.length++;
595
- if (!options.silent) model.trigger('add', model, this, options);
596
- return model;
597
- },
598
-
599
- // Internal implementation of removing a single model from the set, updating
600
- // hash indexes for `id` and `cid` lookups.
601
- _remove : function(model, options) {
602
- options || (options = {});
603
- model = this.getByCid(model) || this.get(model);
604
- if (!model) return null;
605
- delete this._byId[model.id];
606
- delete this._byCid[model.cid];
607
- this.models.splice(this.indexOf(model), 1);
608
- this.length--;
609
- if (!options.silent) model.trigger('remove', model, this, options);
610
- this._removeReference(model);
611
- return model;
612
- },
613
-
614
- // Internal method to remove a model's ties to a collection.
615
- _removeReference : function(model) {
616
- if (this == model.collection) {
617
- delete model.collection;
618
- }
619
- model.unbind('all', this._onModelEvent);
620
- },
621
-
622
- // Internal method called every time a model in the set fires an event.
623
- // Sets need to update their indexes when models change ids. All other
624
- // events simply proxy through. "add" and "remove" events that originate
625
- // in other collections are ignored.
626
- _onModelEvent : function(ev, model, collection, options) {
627
- if ((ev == 'add' || ev == 'remove') && collection != this) return;
628
- if (ev == 'destroy') {
629
- this._remove(model, options);
630
- }
631
- if (model && ev === 'change:' + model.idAttribute) {
632
- delete this._byId[model.previous(model.idAttribute)];
633
- this._byId[model.id] = model;
634
- }
635
- this.trigger.apply(this, arguments);
636
- }
637
-
638
- });
639
-
640
- // Underscore methods that we want to implement on the Collection.
641
- var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', 'detect',
642
- 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
643
- 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size',
644
- 'first', 'rest', 'last', 'without', 'indexOf', 'lastIndexOf', 'isEmpty'];
645
-
646
- // Mix in each Underscore method as a proxy to `Collection#models`.
647
- _.each(methods, function(method) {
648
- Backbone.Collection.prototype[method] = function() {
649
- return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
650
- };
651
- });
652
-
653
- // Backbone.Router
654
- // -------------------
655
-
656
- // Routers map faux-URLs to actions, and fire events when routes are
657
- // matched. Creating a new one sets its `routes` hash, if not set statically.
658
- Backbone.Router = function(options) {
659
- options || (options = {});
660
- if (options.routes) this.routes = options.routes;
661
- this._bindRoutes();
662
- this.initialize.apply(this, arguments);
663
- };
664
-
665
- // Cached regular expressions for matching named param parts and splatted
666
- // parts of route strings.
667
- var namedParam = /:([\w\d]+)/g;
668
- var splatParam = /\*([\w\d]+)/g;
669
- var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
670
-
671
- // Set up all inheritable **Backbone.Router** properties and methods.
672
- _.extend(Backbone.Router.prototype, Backbone.Events, {
673
-
674
- // Initialize is an empty function by default. Override it with your own
675
- // initialization logic.
676
- initialize : function(){},
677
-
678
- // Manually bind a single named route to a callback. For example:
679
- //
680
- // this.route('search/:query/p:num', 'search', function(query, num) {
681
- // ...
682
- // });
683
- //
684
- route : function(route, name, callback) {
685
- Backbone.history || (Backbone.history = new Backbone.History);
686
- if (!_.isRegExp(route)) route = this._routeToRegExp(route);
687
- Backbone.history.route(route, _.bind(function(fragment) {
688
- var args = this._extractParameters(route, fragment);
689
- callback.apply(this, args);
690
- this.trigger.apply(this, ['route:' + name].concat(args));
691
- }, this));
692
- },
693
-
694
- // Simple proxy to `Backbone.history` to save a fragment into the history.
695
- navigate : function(fragment, triggerRoute) {
696
- Backbone.history.navigate(fragment, triggerRoute);
697
- },
698
-
699
- // Bind all defined routes to `Backbone.history`. We have to reverse the
700
- // order of the routes here to support behavior where the most general
701
- // routes can be defined at the bottom of the route map.
702
- _bindRoutes : function() {
703
- if (!this.routes) return;
704
- var routes = [];
705
- for (var route in this.routes) {
706
- routes.unshift([route, this.routes[route]]);
707
- }
708
- for (var i = 0, l = routes.length; i < l; i++) {
709
- this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
710
- }
711
- },
712
-
713
- // Convert a route string into a regular expression, suitable for matching
714
- // against the current location hash.
715
- _routeToRegExp : function(route) {
716
- route = route.replace(escapeRegExp, "\\$&")
717
- .replace(namedParam, "([^\/]*)")
718
- .replace(splatParam, "(.*?)");
719
- return new RegExp('^' + route + '$');
720
- },
721
-
722
- // Given a route, and a URL fragment that it matches, return the array of
723
- // extracted parameters.
724
- _extractParameters : function(route, fragment) {
725
- return route.exec(fragment).slice(1);
726
- }
727
-
728
- });
729
-
730
- // Backbone.History
731
- // ----------------
732
-
733
- // Handles cross-browser history management, based on URL fragments. If the
734
- // browser does not support `onhashchange`, falls back to polling.
735
- Backbone.History = function() {
736
- this.handlers = [];
737
- _.bindAll(this, 'checkUrl');
738
- };
739
-
740
- // Cached regex for cleaning hashes.
741
- var hashStrip = /^#*/;
742
-
743
- // Cached regex for detecting MSIE.
744
- var isExplorer = /msie [\w.]+/;
745
-
746
- // Has the history handling already been started?
747
- var historyStarted = false;
748
-
749
- // Set up all inheritable **Backbone.History** properties and methods.
750
- _.extend(Backbone.History.prototype, {
751
-
752
- // The default interval to poll for hash changes, if necessary, is
753
- // twenty times a second.
754
- interval: 50,
755
-
756
- // Get the cross-browser normalized URL fragment, either from the URL,
757
- // the hash, or the override.
758
- getFragment : function(fragment, forcePushState) {
759
- if (fragment == null) {
760
- if (this._hasPushState || forcePushState) {
761
- fragment = window.location.pathname;
762
- var search = window.location.search;
763
- if (search) fragment += search;
764
- if (fragment.indexOf(this.options.root) == 0) fragment = fragment.substr(this.options.root.length);
765
- } else {
766
- fragment = window.location.hash;
767
- }
768
- }
769
- return fragment.replace(hashStrip, '');
770
- },
771
-
772
- // Start the hash change handling, returning `true` if the current URL matches
773
- // an existing route, and `false` otherwise.
774
- start : function(options) {
775
-
776
- // Figure out the initial configuration. Do we need an iframe?
777
- // Is pushState desired ... is it available?
778
- if (historyStarted) throw new Error("Backbone.history has already been started");
779
- this.options = _.extend({}, {root: '/'}, this.options, options);
780
- this._wantsPushState = !!this.options.pushState;
781
- this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
782
- var fragment = this.getFragment();
783
- var docMode = document.documentMode;
784
- var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
785
- if (oldIE) {
786
- this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
787
- this.navigate(fragment);
788
- }
789
-
790
- // Depending on whether we're using pushState or hashes, and whether
791
- // 'onhashchange' is supported, determine how we check the URL state.
792
- if (this._hasPushState) {
793
- $(window).bind('popstate', this.checkUrl);
794
- } else if ('onhashchange' in window && !oldIE) {
795
- $(window).bind('hashchange', this.checkUrl);
796
- } else {
797
- setInterval(this.checkUrl, this.interval);
798
- }
799
-
800
- // Determine if we need to change the base url, for a pushState link
801
- // opened by a non-pushState browser.
802
- this.fragment = fragment;
803
- historyStarted = true;
804
- var loc = window.location;
805
- var atRoot = loc.pathname == this.options.root;
806
- if (this._wantsPushState && !this._hasPushState && !atRoot) {
807
- this.fragment = this.getFragment(null, true);
808
- window.location.replace(this.options.root + '#' + this.fragment);
809
- } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
810
- this.fragment = loc.hash.replace(hashStrip, '');
811
- window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
812
- }
813
- return this.loadUrl();
814
- },
815
-
816
- // Add a route to be tested when the fragment changes. Routes added later may
817
- // override previous routes.
818
- route : function(route, callback) {
819
- this.handlers.unshift({route : route, callback : callback});
820
- },
821
-
822
- // Checks the current URL to see if it has changed, and if it has,
823
- // calls `loadUrl`, normalizing across the hidden iframe.
824
- checkUrl : function(e) {
825
- var current = this.getFragment();
826
- if (current == this.fragment && this.iframe) current = this.getFragment(this.iframe.location.hash);
827
- if (current == this.fragment || current == decodeURIComponent(this.fragment)) return false;
828
- if (this.iframe) this.navigate(current);
829
- this.loadUrl() || this.loadUrl(window.location.hash);
830
- },
831
-
832
- // Attempt to load the current URL fragment. If a route succeeds with a
833
- // match, returns `true`. If no defined routes matches the fragment,
834
- // returns `false`.
835
- loadUrl : function(fragmentOverride) {
836
- var fragment = this.fragment = this.getFragment(fragmentOverride);
837
- var matched = _.any(this.handlers, function(handler) {
838
- if (handler.route.test(fragment)) {
839
- handler.callback(fragment);
840
- return true;
841
- }
842
- });
843
- return matched;
844
- },
845
-
846
- // Save a fragment into the hash history. You are responsible for properly
847
- // URL-encoding the fragment in advance. This does not trigger
848
- // a `hashchange` event.
849
- navigate : function(fragment, triggerRoute) {
850
- var frag = (fragment || '').replace(hashStrip, '');
851
- if (this.fragment == frag || this.fragment == decodeURIComponent(frag)) return;
852
- if (this._hasPushState) {
853
- var loc = window.location;
854
- if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
855
- this.fragment = frag;
856
- window.history.pushState({}, document.title, loc.protocol + '//' + loc.host + frag);
857
- } else {
858
- window.location.hash = this.fragment = frag;
859
- if (this.iframe && (frag != this.getFragment(this.iframe.location.hash))) {
860
- this.iframe.document.open().close();
861
- this.iframe.location.hash = frag;
862
- }
863
- }
864
- if (triggerRoute) this.loadUrl(fragment);
865
- }
866
-
867
- });
868
-
869
- // Backbone.View
870
- // -------------
871
-
872
- // Creating a Backbone.View creates its initial element outside of the DOM,
873
- // if an existing element is not provided...
874
- Backbone.View = function(options) {
875
- this.cid = _.uniqueId('view');
876
- this._configure(options || {});
877
- this._ensureElement();
878
- this.delegateEvents();
879
- this.initialize.apply(this, arguments);
880
- };
881
-
882
- // Element lookup, scoped to DOM elements within the current view.
883
- // This should be prefered to global lookups, if you're dealing with
884
- // a specific view.
885
- var selectorDelegate = function(selector) {
886
- return $(selector, this.el);
887
- };
888
-
889
- // Cached regex to split keys for `delegate`.
890
- var eventSplitter = /^(\S+)\s*(.*)$/;
891
-
892
- // List of view options to be merged as properties.
893
- var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
894
-
895
- // Set up all inheritable **Backbone.View** properties and methods.
896
- _.extend(Backbone.View.prototype, Backbone.Events, {
897
-
898
- // The default `tagName` of a View's element is `"div"`.
899
- tagName : 'div',
900
-
901
- // Attach the `selectorDelegate` function as the `$` property.
902
- $ : selectorDelegate,
903
-
904
- // Initialize is an empty function by default. Override it with your own
905
- // initialization logic.
906
- initialize : function(){},
907
-
908
- // **render** is the core function that your view should override, in order
909
- // to populate its element (`this.el`), with the appropriate HTML. The
910
- // convention is for **render** to always return `this`.
911
- render : function() {
912
- return this;
913
- },
914
-
915
- // Remove this view from the DOM. Note that the view isn't present in the
916
- // DOM by default, so calling this method may be a no-op.
917
- remove : function() {
918
- $(this.el).remove();
919
- return this;
920
- },
921
-
922
- // For small amounts of DOM Elements, where a full-blown template isn't
923
- // needed, use **make** to manufacture elements, one at a time.
924
- //
925
- // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
926
- //
927
- make : function(tagName, attributes, content) {
928
- var el = document.createElement(tagName);
929
- if (attributes) $(el).attr(attributes);
930
- if (content) $(el).html(content);
931
- return el;
932
- },
933
-
934
- // Set callbacks, where `this.callbacks` is a hash of
935
- //
936
- // *{"event selector": "callback"}*
937
- //
938
- // {
939
- // 'mousedown .title': 'edit',
940
- // 'click .button': 'save'
941
- // }
942
- //
943
- // pairs. Callbacks will be bound to the view, with `this` set properly.
944
- // Uses event delegation for efficiency.
945
- // Omitting the selector binds the event to `this.el`.
946
- // This only works for delegate-able events: not `focus`, `blur`, and
947
- // not `change`, `submit`, and `reset` in Internet Explorer.
948
- delegateEvents : function(events) {
949
- if (!(events || (events = this.events))) return;
950
- $(this.el).unbind('.delegateEvents' + this.cid);
951
- for (var key in events) {
952
- var method = this[events[key]];
953
- if (!method) throw new Error('Event "' + events[key] + '" does not exist');
954
- var match = key.match(eventSplitter);
955
- var eventName = match[1], selector = match[2];
956
- method = _.bind(method, this);
957
- eventName += '.delegateEvents' + this.cid;
958
- if (selector === '') {
959
- $(this.el).bind(eventName, method);
960
- } else {
961
- $(this.el).delegate(selector, eventName, method);
962
- }
963
- }
964
- },
965
-
966
- // Performs the initial configuration of a View with a set of options.
967
- // Keys with special meaning *(model, collection, id, className)*, are
968
- // attached directly to the view.
969
- _configure : function(options) {
970
- if (this.options) options = _.extend({}, this.options, options);
971
- for (var i = 0, l = viewOptions.length; i < l; i++) {
972
- var attr = viewOptions[i];
973
- if (options[attr]) this[attr] = options[attr];
974
- }
975
- this.options = options;
976
- },
977
-
978
- // Ensure that the View has a DOM element to render into.
979
- // If `this.el` is a string, pass it through `$()`, take the first
980
- // matching element, and re-assign it to `el`. Otherwise, create
981
- // an element from the `id`, `className` and `tagName` proeprties.
982
- _ensureElement : function() {
983
- if (!this.el) {
984
- var attrs = this.attributes || {};
985
- if (this.id) attrs.id = this.id;
986
- if (this.className) attrs['class'] = this.className;
987
- this.el = this.make(this.tagName, attrs);
988
- } else if (_.isString(this.el)) {
989
- this.el = $(this.el).get(0);
990
- }
991
- }
992
-
993
- });
994
-
995
- // The self-propagating extend function that Backbone classes use.
996
- var extend = function (protoProps, classProps) {
997
- var child = inherits(this, protoProps, classProps);
998
- child.extend = this.extend;
999
- return child;
1000
- };
1001
-
1002
- // Set up inheritance for the model, collection, and view.
1003
- Backbone.Model.extend = Backbone.Collection.extend =
1004
- Backbone.Router.extend = Backbone.View.extend = extend;
1005
-
1006
- // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1007
- var methodMap = {
1008
- 'create': 'POST',
1009
- 'update': 'PUT',
1010
- 'delete': 'DELETE',
1011
- 'read' : 'GET'
1012
- };
1013
-
1014
- // Backbone.sync
1015
- // -------------
1016
-
1017
- // Override this function to change the manner in which Backbone persists
1018
- // models to the server. You will be passed the type of request, and the
1019
- // model in question. By default, uses makes a RESTful Ajax request
1020
- // to the model's `url()`. Some possible customizations could be:
1021
- //
1022
- // * Use `setTimeout` to batch rapid-fire updates into a single request.
1023
- // * Send up the models as XML instead of JSON.
1024
- // * Persist models via WebSockets instead of Ajax.
1025
- //
1026
- // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
1027
- // as `POST`, with a `_method` parameter containing the true HTTP method,
1028
- // as well as all requests with the body as `application/x-www-form-urlencoded` instead of
1029
- // `application/json` with the model in a param named `model`.
1030
- // Useful when interfacing with server-side languages like **PHP** that make
1031
- // it difficult to read the body of `PUT` requests.
1032
- Backbone.sync = function(method, model, options) {
1033
- var type = methodMap[method];
1034
-
1035
- // Default JSON-request options.
1036
- var params = _.extend({
1037
- type: type,
1038
- dataType: 'json',
1039
- processData: false
1040
- }, options);
1041
-
1042
- // Ensure that we have a URL.
1043
- if (!params.url) {
1044
- params.url = getUrl(model) || urlError();
1045
- }
1046
-
1047
- // Ensure that we have the appropriate request data.
1048
- if (!params.data && model && (method == 'create' || method == 'update')) {
1049
- params.contentType = 'application/json';
1050
- params.data = JSON.stringify(model.toJSON());
1051
- }
1052
-
1053
- // For older servers, emulate JSON by encoding the request into an HTML-form.
1054
- if (Backbone.emulateJSON) {
1055
- params.contentType = 'application/x-www-form-urlencoded';
1056
- params.processData = true;
1057
- params.data = params.data ? {model : params.data} : {};
1058
- }
1059
-
1060
- // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1061
- // And an `X-HTTP-Method-Override` header.
1062
- if (Backbone.emulateHTTP) {
1063
- if (type === 'PUT' || type === 'DELETE') {
1064
- if (Backbone.emulateJSON) params.data._method = type;
1065
- params.type = 'POST';
1066
- params.beforeSend = function(xhr) {
1067
- xhr.setRequestHeader('X-HTTP-Method-Override', type);
1068
- };
1069
- }
1070
- }
1071
-
1072
- // Make the request.
1073
- return $.ajax(params);
1074
- };
1075
-
1076
- // Helpers
1077
- // -------
1078
-
1079
- // Shared empty constructor function to aid in prototype-chain creation.
1080
- var ctor = function(){};
1081
-
1082
- // Helper function to correctly set up the prototype chain, for subclasses.
1083
- // Similar to `goog.inherits`, but uses a hash of prototype properties and
1084
- // class properties to be extended.
1085
- var inherits = function(parent, protoProps, staticProps) {
1086
- var child;
1087
-
1088
- // The constructor function for the new subclass is either defined by you
1089
- // (the "constructor" property in your `extend` definition), or defaulted
1090
- // by us to simply call `super()`.
1091
- if (protoProps && protoProps.hasOwnProperty('constructor')) {
1092
- child = protoProps.constructor;
1093
- } else {
1094
- child = function(){ return parent.apply(this, arguments); };
1095
- }
1096
-
1097
- // Inherit class (static) properties from parent.
1098
- _.extend(child, parent);
1099
-
1100
- // Set the prototype chain to inherit from `parent`, without calling
1101
- // `parent`'s constructor function.
1102
- ctor.prototype = parent.prototype;
1103
- child.prototype = new ctor();
1104
-
1105
- // Add prototype properties (instance properties) to the subclass,
1106
- // if supplied.
1107
- if (protoProps) _.extend(child.prototype, protoProps);
1108
-
1109
- // Add static properties to the constructor function, if supplied.
1110
- if (staticProps) _.extend(child, staticProps);
1111
-
1112
- // Correctly set child's `prototype.constructor`.
1113
- child.prototype.constructor = child;
1114
-
1115
- // Set a convenience property in case the parent's prototype is needed later.
1116
- child.__super__ = parent.prototype;
1117
-
1118
- return child;
1119
- };
1120
-
1121
- // Helper function to get a URL from a Model or Collection as a property
1122
- // or as a function.
1123
- var getUrl = function(object) {
1124
- if (!(object && object.url)) return null;
1125
- return _.isFunction(object.url) ? object.url() : object.url;
1126
- };
1127
-
1128
- // Throw an error when a URL is needed, and none is supplied.
1129
- var urlError = function() {
1130
- throw new Error('A "url" property or function must be specified');
1131
- };
1132
-
1133
- // Wrap an optional error callback with a fallback error event.
1134
- var wrapError = function(onError, model, options) {
1135
- return function(resp) {
1136
- if (onError) {
1137
- onError(model, resp, options);
1138
- } else {
1139
- model.trigger('error', model, resp, options);
1140
- }
1141
- };
1142
- };
1143
-
1144
- // Helper function to escape a string for HTML rendering.
1145
- var escapeHTML = function(string) {
1146
- return string.replace(/&(?!\w+;|#\d+;|#x[\da-f]+;)/gi, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27').replace(/\//g,'&#x2F;');
1147
- };
1148
-
1149
- }).call(this);