WordPress 源代码——主干(backbone-1.0.js)

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

联系我们
文章看不懂?联系我们为您免费解答!免费助力个人,小企站点!
电话:020-2206-9892
QQ咨询:1025174874
邮件:info@361sale.com
工作时间:周一至周五,9:30-18:30,节假日休息
© 转载声明
本文作者:Harry
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容