MVC---Case 1

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4 <title>Backbone.js, Require.js, and jQuery Mobile</title>
 5 <meta name="description" content=""/>
 6 <meta name="viewport" content="width=device-width, initial-scale=1"/>
 7 <link rel="stylesheet" href="http://code.jquery.com/mobile/1.3.2/jquery.mobile-1.3.2.min.css" />
 8 <script src="js/libs/require.js" data-main="js/mobile"></script>
 9 <body>
10     <div id="categories" data-role="page" data-title="Categories">
11         <div data-role="header">
12             <h1>Categories</h1>
13         </div><!-- /header -->
14         
15         <div data-role="content">
16             <h2>Select a Category Below:</h2>
17             <ul data-role="listview" data-inset="true">
18                 <li>
19                     <a href="#category?animals" class="animals">Animals</a>
20                 </li>
21                 <li>
22                     <a href="#category?colors" class="colors">Colors</a>
23                 </li>
24                 <li>
25                     <a href="#category?vehicles" class="vehicles">Vehicles</a>
26                 </li>
27             </ul>
28         </div><!-- /content -->
29     </div>
30     
31     <div id="animals" data-role="page" data-title="Animals">
32         <div data-role="header">
33             <h1>Animals</h1>
34         </div><!-- /header -->
35         
36         <div data-role="content">
37             <ul data-role="listview" data-inset="true">
38             
39             </ul>
40         </div><!-- /content -->
41     </div>
42     
43     <div id="colors" data-role="page" data-title="Colors">
44         <div data-role="header">
45             <h1>Colors</h1>
46         </div><!-- /header -->
47         
48         <div data-role="content">
49             <ul data-role="listview" data-inset="true">
50             
51             </ul>
52         </div><!-- /content -->
53     </div>
54     
55     <div id="vehicles" data-role="page" data-title="Vehicles">
56         <div data-role="header">
57             <h1>Vehicles</h1>
58         </div><!-- /header -->
59         
60         <div data-role="content">
61             <ul data-role="listview" data-inset="true">
62             
63             </ul>
64         </div><!-- /content -->
65     </div>
66     
67     <script type="text/template" id="categoryItems">
68         <% _.each(collection.toJSON(), function(category, id) { %>
69             <li class="ui-li ui-li-static ui-btn-up-c ui-corner-top">
70                 <%= category.type %>
71             </li>
72         <% });%>
73     </script>
74 </body>
75 </html>
View Code

 

 

\js\collections\CategoriesCollection.js

 

 1 // Category Collection
 2 // =======================
 3 
 4 // Includes file dependencies
 5 define(["jquery", "backbone", "models/CategoryModel"], function($, Backbone, CategoryModel) {
 6     
 7     // Extends Backbone.Router
 8     var Collection = Backbone.Collection.extend({
 9         
10         // The Collection constructor
11         initialize: function(models, options) {
12             
13             // Sets the type instance property (ie.animals)
14             this.type = options.type;
15         },
16         
17         // Sets the Collection model property to be a Category Model
18         model: CategoryModel,
19         
20         // Sample JSON data that in a real app will most likely come from a REST web service
21         jsonArray: [
22             {"category": "animals", "type": "Pets"},
23             {"category": "animals", "type": "Farm Animals"},
24             {"category": "animals", "type": "Wild Animals"},
25             {"category": "colors", "type": "Blue"},
26             {"category": "colors", "type": "Green"},
27             {"category": "colors", "type": "Orange"},
28             {"category": "color", "type": "Purple"},
29             {"category": "color", "type": "Red"},
30             {"category": "colors", "type": "Yellow"},
31             {"category": "color", "type": "Violet"},
32             {"category": "vehicles", "type": "Cars"},
33             {"category": "vehicles", "type": "Planes"},
34             {"category": "vehicles", "type": "Construction"}
35         ],
36         
37         // Overriding the Backbone.sync method (the Backbone.fetch method calls the sync method when trying to fetch data)
38         sync: function(method, model, options) {
39             
40             // Local Variables
41             // ==============
42             
43             // Initantiates an empty array
44             var categories = [],
45             
46                 // Stores the this context in the self variable
47                 self = this,
48                 
49                 // Creates a jQuery Deferred Object
50                 deferred = $.Deferred();
51                 
52             // Uses a setTimeout to mimic a real world application that retrieves data asynchronously
53             setTimeout(function() {
54                 
55                 // Filters the above sample JSON data to return an array of only the correct category type
56                 categories = _.filter(self.jsonArray, function(row) {
57                     return row.category === self.type;
58                 });
59                 
60                 // Calls the options.success method and passes an array of objects (Internally saves these objects as models to the current collection)
61                 options.success(categories);
62                 
63                 // Triggers the custom 'added' method (which the Category View listens for)
64                 self.trigger("added");
65                 
66                 // Resolves the deferred objects (this triggers the changePage method inside of the Category Router)
67                 deferred.resolve();
68             }, 1000);
69             
70             // Returns the deferred object
71             return deferred;
72         }
73     });
74     
75     // Returns the Model class
76     return Collection;
77 });
View Code

 \js\libs

   1 //     Backbone.js 0.9.2
   2 
   3 //     (c) 2010-2012 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, `global`
  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 a local reference to slice/splice.
  22   var slice = Array.prototype.slice;
  23   var splice = Array.prototype.splice;
  24 
  25   // The top-level namespace. All public Backbone classes and modules will
  26   // be attached to this. Exported for both CommonJS and the browser.
  27   var Backbone;
  28   if (typeof exports !== 'undefined') {
  29     Backbone = exports;
  30   } else {
  31     Backbone = root.Backbone = {};
  32   }
  33 
  34   // Current version of the library. Keep in sync with `package.json`.
  35   Backbone.VERSION = '0.9.2';
  36 
  37   // Require Underscore, if we're on the server, and it's not already present.
  38   var _ = root._;
  39   if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
  40 
  41   // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
  42   var $ = root.jQuery || root.Zepto || root.ender;
  43 
  44   // Set the JavaScript library that will be used for DOM manipulation and
  45   // Ajax calls (a.k.a. the `$` variable). By default Backbone will use: jQuery,
  46   // Zepto, or Ender; but the `setDomLibrary()` method lets you inject an
  47   // alternate JavaScript library (or a mock library for testing your views
  48   // outside of a browser).
  49   Backbone.setDomLibrary = function(lib) {
  50     $ = lib;
  51   };
  52 
  53   // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
  54   // to its previous owner. Returns a reference to this Backbone object.
  55   Backbone.noConflict = function() {
  56     root.Backbone = previousBackbone;
  57     return this;
  58   };
  59 
  60   // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
  61   // will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
  62   // set a `X-Http-Method-Override` header.
  63   Backbone.emulateHTTP = false;
  64 
  65   // Turn on `emulateJSON` to support legacy servers that can't deal with direct
  66   // `application/json` requests ... will encode the body as
  67   // `application/x-www-form-urlencoded` instead and will send the model in a
  68   // form param named `model`.
  69   Backbone.emulateJSON = false;
  70 
  71   // Backbone.Events
  72   // -----------------
  73 
  74   // Regular expression used to split event strings
  75   var eventSplitter = /\s+/;
  76 
  77   // A module that can be mixed in to *any object* in order to provide it with
  78   // custom events. You may bind with `on` or remove with `off` callback functions
  79   // to an event; trigger`-ing an event fires all callbacks in succession.
  80   //
  81   //     var object = {};
  82   //     _.extend(object, Backbone.Events);
  83   //     object.on('expand', function(){ alert('expanded'); });
  84   //     object.trigger('expand');
  85   //
  86   var Events = Backbone.Events = {
  87 
  88     // Bind one or more space separated events, `events`, to a `callback`
  89     // function. Passing `"all"` will bind the callback to all events fired.
  90     on: function(events, callback, context) {
  91 
  92       var calls, event, node, tail, list;
  93       if (!callback) return this;
  94       events = events.split(eventSplitter);
  95       calls = this._callbacks || (this._callbacks = {});
  96 
  97       // Create an immutable callback list, allowing traversal during
  98       // modification.  The tail is an empty object that will always be used
  99       // as the next node.
 100       while (event = events.shift()) {
 101         list = calls[event];
 102         node = list ? list.tail : {};
 103         node.next = tail = {};
 104         node.context = context;
 105         node.callback = callback;
 106         calls[event] = {tail: tail, next: list ? list.next : node};
 107       }
 108 
 109       return this;
 110     },
 111 
 112     // Remove one or many callbacks. If `context` is null, removes all callbacks
 113     // with that function. If `callback` is null, removes all callbacks for the
 114     // event. If `events` is null, removes all bound callbacks for all events.
 115     off: function(events, callback, context) {
 116       var event, calls, node, tail, cb, ctx;
 117 
 118       // No events, or removing *all* events.
 119       if (!(calls = this._callbacks)) return;
 120       if (!(events || callback || context)) {
 121         delete this._callbacks;
 122         return this;
 123       }
 124 
 125       // Loop through the listed events and contexts, splicing them out of the
 126       // linked list of callbacks if appropriate.
 127       events = events ? events.split(eventSplitter) : _.keys(calls);
 128       while (event = events.shift()) {
 129         node = calls[event];
 130         delete calls[event];
 131         if (!node || !(callback || context)) continue;
 132         // Create a new list, omitting the indicated callbacks.
 133         tail = node.tail;
 134         while ((node = node.next) !== tail) {
 135           cb = node.callback;
 136           ctx = node.context;
 137           if ((callback && cb !== callback) || (context && ctx !== context)) {
 138             this.on(event, cb, ctx);
 139           }
 140         }
 141       }
 142 
 143       return this;
 144     },
 145 
 146     // Trigger one or many events, firing all bound callbacks. Callbacks are
 147     // passed the same arguments as `trigger` is, apart from the event name
 148     // (unless you're listening on `"all"`, which will cause your callback to
 149     // receive the true name of the event as the first argument).
 150     trigger: function(events) {
 151       var event, node, calls, tail, args, all, rest;
 152       if (!(calls = this._callbacks)) return this;
 153       all = calls.all;
 154       events = events.split(eventSplitter);
 155       rest = slice.call(arguments, 1);
 156 
 157       // For each event, walk through the linked list of callbacks twice,
 158       // first to trigger the event, then to trigger any `"all"` callbacks.
 159       while (event = events.shift()) {
 160         if (node = calls[event]) {
 161           tail = node.tail;
 162           while ((node = node.next) !== tail) {
 163             node.callback.apply(node.context || this, rest);
 164           }
 165         }
 166         if (node = all) {
 167           tail = node.tail;
 168           args = [event].concat(rest);
 169           while ((node = node.next) !== tail) {
 170             node.callback.apply(node.context || this, args);
 171           }
 172         }
 173       }
 174 
 175       return this;
 176     }
 177 
 178   };
 179 
 180   // Aliases for backwards compatibility.
 181   Events.bind   = Events.on;
 182   Events.unbind = Events.off;
 183 
 184   // Backbone.Model
 185   // --------------
 186 
 187   // Create a new model, with defined attributes. A client id (`cid`)
 188   // is automatically generated and assigned for you.
 189   var Model = Backbone.Model = function(attributes, options) {
 190     var defaults;
 191     attributes || (attributes = {});
 192     if (options && options.parse) attributes = this.parse(attributes);
 193     if (defaults = getValue(this, 'defaults')) {
 194       attributes = _.extend({}, defaults, attributes);
 195     }
 196     if (options && options.collection) this.collection = options.collection;
 197     this.attributes = {};
 198     this._escapedAttributes = {};
 199     this.cid = _.uniqueId('c');
 200     this.changed = {};
 201     this._silent = {};
 202     this._pending = {};
 203     this.set(attributes, {silent: true});
 204     // Reset change tracking.
 205     this.changed = {};
 206     this._silent = {};
 207     this._pending = {};
 208     this._previousAttributes = _.clone(this.attributes);
 209     this.initialize.apply(this, arguments);
 210   };
 211 
 212   // Attach all inheritable methods to the Model prototype.
 213   _.extend(Model.prototype, Events, {
 214 
 215     // A hash of attributes whose current and previous value differ.
 216     changed: null,
 217 
 218     // A hash of attributes that have silently changed since the last time
 219     // `change` was called.  Will become pending attributes on the next call.
 220     _silent: null,
 221 
 222     // A hash of attributes that have changed since the last `'change'` event
 223     // began.
 224     _pending: null,
 225 
 226     // The default name for the JSON `id` attribute is `"id"`. MongoDB and
 227     // CouchDB users may want to set this to `"_id"`.
 228     idAttribute: 'id',
 229 
 230     // Initialize is an empty function by default. Override it with your own
 231     // initialization logic.
 232     initialize: function(){},
 233 
 234     // Return a copy of the model's `attributes` object.
 235     toJSON: function(options) {
 236       return _.clone(this.attributes);
 237     },
 238 
 239     // Get the value of an attribute.
 240     get: function(attr) {
 241       return this.attributes[attr];
 242     },
 243 
 244     // Get the HTML-escaped value of an attribute.
 245     escape: function(attr) {
 246       var html;
 247       if (html = this._escapedAttributes[attr]) return html;
 248       var val = this.get(attr);
 249       return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
 250     },
 251 
 252     // Returns `true` if the attribute contains a value that is not null
 253     // or undefined.
 254     has: function(attr) {
 255       return this.get(attr) != null;
 256     },
 257 
 258     // Set a hash of model attributes on the object, firing `"change"` unless
 259     // you choose to silence it.
 260     set: function(key, value, options) {
 261       var attrs, attr, val;
 262 
 263       // Handle both `"key", value` and `{key: value}` -style arguments.
 264       if (_.isObject(key) || key == null) {
 265         attrs = key;
 266         options = value;
 267       } else {
 268         attrs = {};
 269         attrs[key] = value;
 270       }
 271 
 272       // Extract attributes and options.
 273       options || (options = {});
 274       if (!attrs) return this;
 275       if (attrs instanceof Model) attrs = attrs.attributes;
 276       if (options.unset) for (attr in attrs) attrs[attr] = void 0;
 277 
 278       // Run validation.
 279       if (!this._validate(attrs, options)) return false;
 280 
 281       // Check for changes of `id`.
 282       if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
 283 
 284       var changes = options.changes = {};
 285       var now = this.attributes;
 286       var escaped = this._escapedAttributes;
 287       var prev = this._previousAttributes || {};
 288 
 289       // For each `set` attribute...
 290       for (attr in attrs) {
 291         val = attrs[attr];
 292 
 293         // If the new and current value differ, record the change.
 294         if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
 295           delete escaped[attr];
 296           (options.silent ? this._silent : changes)[attr] = true;
 297         }
 298 
 299         // Update or delete the current value.
 300         options.unset ? delete now[attr] : now[attr] = val;
 301 
 302         // If the new and previous value differ, record the change.  If not,
 303         // then remove changes for this attribute.
 304         if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
 305           this.changed[attr] = val;
 306           if (!options.silent) this._pending[attr] = true;
 307         } else {
 308           delete this.changed[attr];
 309           delete this._pending[attr];
 310         }
 311       }
 312 
 313       // Fire the `"change"` events.
 314       if (!options.silent) this.change(options);
 315       return this;
 316     },
 317 
 318     // Remove an attribute from the model, firing `"change"` unless you choose
 319     // to silence it. `unset` is a noop if the attribute doesn't exist.
 320     unset: function(attr, options) {
 321       (options || (options = {})).unset = true;
 322       return this.set(attr, null, options);
 323     },
 324 
 325     // Clear all attributes on the model, firing `"change"` unless you choose
 326     // to silence it.
 327     clear: function(options) {
 328       (options || (options = {})).unset = true;
 329       return this.set(_.clone(this.attributes), options);
 330     },
 331 
 332     // Fetch the model from the server. If the server's representation of the
 333     // model differs from its current attributes, they will be overriden,
 334     // triggering a `"change"` event.
 335     fetch: function(options) {
 336       options = options ? _.clone(options) : {};
 337       var model = this;
 338       var success = options.success;
 339       options.success = function(resp, status, xhr) {
 340         if (!model.set(model.parse(resp, xhr), options)) return false;
 341         if (success) success(model, resp);
 342       };
 343       options.error = Backbone.wrapError(options.error, model, options);
 344       return (this.sync || Backbone.sync).call(this, 'read', this, options);
 345     },
 346 
 347     // Set a hash of model attributes, and sync the model to the server.
 348     // If the server returns an attributes hash that differs, the model's
 349     // state will be `set` again.
 350     save: function(key, value, options) {
 351       var attrs, current;
 352 
 353       // Handle both `("key", value)` and `({key: value})` -style calls.
 354       if (_.isObject(key) || key == null) {
 355         attrs = key;
 356         options = value;
 357       } else {
 358         attrs = {};
 359         attrs[key] = value;
 360       }
 361       options = options ? _.clone(options) : {};
 362 
 363       // If we're "wait"-ing to set changed attributes, validate early.
 364       if (options.wait) {
 365         if (!this._validate(attrs, options)) return false;
 366         current = _.clone(this.attributes);
 367       }
 368 
 369       // Regular saves `set` attributes before persisting to the server.
 370       var silentOptions = _.extend({}, options, {silent: true});
 371       if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
 372         return false;
 373       }
 374 
 375       // After a successful server-side save, the client is (optionally)
 376       // updated with the server-side state.
 377       var model = this;
 378       var success = options.success;
 379       options.success = function(resp, status, xhr) {
 380         var serverAttrs = model.parse(resp, xhr);
 381         if (options.wait) {
 382           delete options.wait;
 383           serverAttrs = _.extend(attrs || {}, serverAttrs);
 384         }
 385         if (!model.set(serverAttrs, options)) return false;
 386         if (success) {
 387           success(model, resp);
 388         } else {
 389           model.trigger('sync', model, resp, options);
 390         }
 391       };
 392 
 393       // Finish configuring and sending the Ajax request.
 394       options.error = Backbone.wrapError(options.error, model, options);
 395       var method = this.isNew() ? 'create' : 'update';
 396       var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
 397       if (options.wait) this.set(current, silentOptions);
 398       return xhr;
 399     },
 400 
 401     // Destroy this model on the server if it was already persisted.
 402     // Optimistically removes the model from its collection, if it has one.
 403     // If `wait: true` is passed, waits for the server to respond before removal.
 404     destroy: function(options) {
 405       options = options ? _.clone(options) : {};
 406       var model = this;
 407       var success = options.success;
 408 
 409       var triggerDestroy = function() {
 410         model.trigger('destroy', model, model.collection, options);
 411       };
 412 
 413       if (this.isNew()) {
 414         triggerDestroy();
 415         return false;
 416       }
 417 
 418       options.success = function(resp) {
 419         if (options.wait) triggerDestroy();
 420         if (success) {
 421           success(model, resp);
 422         } else {
 423           model.trigger('sync', model, resp, options);
 424         }
 425       };
 426 
 427       options.error = Backbone.wrapError(options.error, model, options);
 428       var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);
 429       if (!options.wait) triggerDestroy();
 430       return xhr;
 431     },
 432 
 433     // Default URL for the model's representation on the server -- if you're
 434     // using Backbone's restful methods, override this to change the endpoint
 435     // that will be called.
 436     url: function() {
 437       var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
 438       if (this.isNew()) return base;
 439       return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
 440     },
 441 
 442     // **parse** converts a response into the hash of attributes to be `set` on
 443     // the model. The default implementation is just to pass the response along.
 444     parse: function(resp, xhr) {
 445       return resp;
 446     },
 447 
 448     // Create a new model with identical attributes to this one.
 449     clone: function() {
 450       return new this.constructor(this.attributes);
 451     },
 452 
 453     // A model is new if it has never been saved to the server, and lacks an id.
 454     isNew: function() {
 455       return this.id == null;
 456     },
 457 
 458     // Call this method to manually fire a `"change"` event for this model and
 459     // a `"change:attribute"` event for each changed attribute.
 460     // Calling this will cause all objects observing the model to update.
 461     change: function(options) {
 462       options || (options = {});
 463       var changing = this._changing;
 464       this._changing = true;
 465 
 466       // Silent changes become pending changes.
 467       for (var attr in this._silent) this._pending[attr] = true;
 468 
 469       // Silent changes are triggered.
 470       var changes = _.extend({}, options.changes, this._silent);
 471       this._silent = {};
 472       for (var attr in changes) {
 473         this.trigger('change:' + attr, this, this.get(attr), options);
 474       }
 475       if (changing) return this;
 476 
 477       // Continue firing `"change"` events while there are pending changes.
 478       while (!_.isEmpty(this._pending)) {
 479         this._pending = {};
 480         this.trigger('change', this, options);
 481         // Pending and silent changes still remain.
 482         for (var attr in this.changed) {
 483           if (this._pending[attr] || this._silent[attr]) continue;
 484           delete this.changed[attr];
 485         }
 486         this._previousAttributes = _.clone(this.attributes);
 487       }
 488 
 489       this._changing = false;
 490       return this;
 491     },
 492 
 493     // Determine if the model has changed since the last `"change"` event.
 494     // If you specify an attribute name, determine if that attribute has changed.
 495     hasChanged: function(attr) {
 496       if (!arguments.length) return !_.isEmpty(this.changed);
 497       return _.has(this.changed, attr);
 498     },
 499 
 500     // Return an object containing all the attributes that have changed, or
 501     // false if there are no changed attributes. Useful for determining what
 502     // parts of a view need to be updated and/or what attributes need to be
 503     // persisted to the server. Unset attributes will be set to undefined.
 504     // You can also pass an attributes object to diff against the model,
 505     // determining if there *would be* a change.
 506     changedAttributes: function(diff) {
 507       if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
 508       var val, changed = false, old = this._previousAttributes;
 509       for (var attr in diff) {
 510         if (_.isEqual(old[attr], (val = diff[attr]))) continue;
 511         (changed || (changed = {}))[attr] = val;
 512       }
 513       return changed;
 514     },
 515 
 516     // Get the previous value of an attribute, recorded at the time the last
 517     // `"change"` event was fired.
 518     previous: function(attr) {
 519       if (!arguments.length || !this._previousAttributes) return null;
 520       return this._previousAttributes[attr];
 521     },
 522 
 523     // Get all of the attributes of the model at the time of the previous
 524     // `"change"` event.
 525     previousAttributes: function() {
 526       return _.clone(this._previousAttributes);
 527     },
 528 
 529     // Check if the model is currently in a valid state. It's only possible to
 530     // get into an *invalid* state if you're using silent changes.
 531     isValid: function() {
 532       return !this.validate(this.attributes);
 533     },
 534 
 535     // Run validation against the next complete set of model attributes,
 536     // returning `true` if all is well. If a specific `error` callback has
 537     // been passed, call that instead of firing the general `"error"` event.
 538     _validate: function(attrs, options) {
 539       if (options.silent || !this.validate) return true;
 540       attrs = _.extend({}, this.attributes, attrs);
 541       var error = this.validate(attrs, options);
 542       if (!error) return true;
 543       if (options && options.error) {
 544         options.error(this, error, options);
 545       } else {
 546         this.trigger('error', this, error, options);
 547       }
 548       return false;
 549     }
 550 
 551   });
 552 
 553   // Backbone.Collection
 554   // -------------------
 555 
 556   // Provides a standard collection class for our sets of models, ordered
 557   // or unordered. If a `comparator` is specified, the Collection will maintain
 558   // its models in sort order, as they're added and removed.
 559   var Collection = Backbone.Collection = function(models, options) {
 560     options || (options = {});
 561     if (options.model) this.model = options.model;
 562     if (options.comparator) this.comparator = options.comparator;
 563     this._reset();
 564     this.initialize.apply(this, arguments);
 565     if (models) this.reset(models, {silent: true, parse: options.parse});
 566   };
 567 
 568   // Define the Collection's inheritable methods.
 569   _.extend(Collection.prototype, Events, {
 570 
 571     // The default model for a collection is just a **Backbone.Model**.
 572     // This should be overridden in most cases.
 573     model: Model,
 574 
 575     // Initialize is an empty function by default. Override it with your own
 576     // initialization logic.
 577     initialize: function(){},
 578 
 579     // The JSON representation of a Collection is an array of the
 580     // models' attributes.
 581     toJSON: function(options) {
 582       return this.map(function(model){ return model.toJSON(options); });
 583     },
 584 
 585     // Add a model, or list of models to the set. Pass **silent** to avoid
 586     // firing the `add` event for every new model.
 587     add: function(models, options) {
 588       var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
 589       options || (options = {});
 590       models = _.isArray(models) ? models.slice() : [models];
 591 
 592       // Begin by turning bare objects into model references, and preventing
 593       // invalid models or duplicate models from being added.
 594       for (i = 0, length = models.length; i < length; i++) {
 595         if (!(model = models[i] = this._prepareModel(models[i], options))) {
 596           throw new Error("Can't add an invalid model to a collection");
 597         }
 598         cid = model.cid;
 599         id = model.id;
 600         if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
 601           dups.push(i);
 602           continue;
 603         }
 604         cids[cid] = ids[id] = model;
 605       }
 606 
 607       // Remove duplicates.
 608       i = dups.length;
 609       while (i--) {
 610         models.splice(dups[i], 1);
 611       }
 612 
 613       // Listen to added models' events, and index models for lookup by
 614       // `id` and by `cid`.
 615       for (i = 0, length = models.length; i < length; i++) {
 616         (model = models[i]).on('all', this._onModelEvent, this);
 617         this._byCid[model.cid] = model;
 618         if (model.id != null) this._byId[model.id] = model;
 619       }
 620 
 621       // Insert models into the collection, re-sorting if needed, and triggering
 622       // `add` events unless silenced.
 623       this.length += length;
 624       index = options.at != null ? options.at : this.models.length;
 625       splice.apply(this.models, [index, 0].concat(models));
 626       if (this.comparator) this.sort({silent: true});
 627       if (options.silent) return this;
 628       for (i = 0, length = this.models.length; i < length; i++) {
 629         if (!cids[(model = this.models[i]).cid]) continue;
 630         options.index = i;
 631         model.trigger('add', model, this, options);
 632       }
 633       return this;
 634     },
 635 
 636     // Remove a model, or a list of models from the set. Pass silent to avoid
 637     // firing the `remove` event for every model removed.
 638     remove: function(models, options) {
 639       var i, l, index, model;
 640       options || (options = {});
 641       models = _.isArray(models) ? models.slice() : [models];
 642       for (i = 0, l = models.length; i < l; i++) {
 643         model = this.getByCid(models[i]) || this.get(models[i]);
 644         if (!model) continue;
 645         delete this._byId[model.id];
 646         delete this._byCid[model.cid];
 647         index = this.indexOf(model);
 648         this.models.splice(index, 1);
 649         this.length--;
 650         if (!options.silent) {
 651           options.index = index;
 652           model.trigger('remove', model, this, options);
 653         }
 654         this._removeReference(model);
 655       }
 656       return this;
 657     },
 658 
 659     // Add a model to the end of the collection.
 660     push: function(model, options) {
 661       model = this._prepareModel(model, options);
 662       this.add(model, options);
 663       return model;
 664     },
 665 
 666     // Remove a model from the end of the collection.
 667     pop: function(options) {
 668       var model = this.at(this.length - 1);
 669       this.remove(model, options);
 670       return model;
 671     },
 672 
 673     // Add a model to the beginning of the collection.
 674     unshift: function(model, options) {
 675       model = this._prepareModel(model, options);
 676       this.add(model, _.extend({at: 0}, options));
 677       return model;
 678     },
 679 
 680     // Remove a model from the beginning of the collection.
 681     shift: function(options) {
 682       var model = this.at(0);
 683       this.remove(model, options);
 684       return model;
 685     },
 686 
 687     // Get a model from the set by id.
 688     get: function(id) {
 689       if (id == null) return void 0;
 690       return this._byId[id.id != null ? id.id : id];
 691     },
 692 
 693     // Get a model from the set by client id.
 694     getByCid: function(cid) {
 695       return cid && this._byCid[cid.cid || cid];
 696     },
 697 
 698     // Get the model at the given index.
 699     at: function(index) {
 700       return this.models[index];
 701     },
 702 
 703     // Return models with matching attributes. Useful for simple cases of `filter`.
 704     where: function(attrs) {
 705       if (_.isEmpty(attrs)) return [];
 706       return this.filter(function(model) {
 707         for (var key in attrs) {
 708           if (attrs[key] !== model.get(key)) return false;
 709         }
 710         return true;
 711       });
 712     },
 713 
 714     // Force the collection to re-sort itself. You don't need to call this under
 715     // normal circumstances, as the set will maintain sort order as each item
 716     // is added.
 717     sort: function(options) {
 718       options || (options = {});
 719       if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
 720       var boundComparator = _.bind(this.comparator, this);
 721       if (this.comparator.length == 1) {
 722         this.models = this.sortBy(boundComparator);
 723       } else {
 724         this.models.sort(boundComparator);
 725       }
 726       if (!options.silent) this.trigger('reset', this, options);
 727       return this;
 728     },
 729 
 730     // Pluck an attribute from each model in the collection.
 731     pluck: function(attr) {
 732       return _.map(this.models, function(model){ return model.get(attr); });
 733     },
 734 
 735     // When you have more items than you want to add or remove individually,
 736     // you can reset the entire set with a new list of models, without firing
 737     // any `add` or `remove` events. Fires `reset` when finished.
 738     reset: function(models, options) {
 739       models  || (models = []);
 740       options || (options = {});
 741       for (var i = 0, l = this.models.length; i < l; i++) {
 742         this._removeReference(this.models[i]);
 743       }
 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     // Fetch the default set of models for this collection, resetting the
 751     // collection when they arrive. If `add: true` is passed, appends the
 752     // models to the collection instead of resetting.
 753     fetch: function(options) {
 754       options = options ? _.clone(options) : {};
 755       if (options.parse === undefined) options.parse = true;
 756       var collection = this;
 757       var success = options.success;
 758       options.success = function(resp, status, xhr) {
 759         collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
 760         if (success) success(collection, resp);
 761       };
 762       options.error = Backbone.wrapError(options.error, collection, options);
 763       return (this.sync || Backbone.sync).call(this, 'read', this, options);
 764     },
 765 
 766     // Create a new instance of a model in this collection. Add the model to the
 767     // collection immediately, unless `wait: true` is passed, in which case we
 768     // wait for the server to agree.
 769     create: function(model, options) {
 770       var coll = this;
 771       options = options ? _.clone(options) : {};
 772       model = this._prepareModel(model, options);
 773       if (!model) return false;
 774       if (!options.wait) coll.add(model, options);
 775       var success = options.success;
 776       options.success = function(nextModel, resp, xhr) {
 777         if (options.wait) coll.add(nextModel, options);
 778         if (success) {
 779           success(nextModel, resp);
 780         } else {
 781           nextModel.trigger('sync', model, resp, options);
 782         }
 783       };
 784       model.save(null, options);
 785       return model;
 786     },
 787 
 788     // **parse** converts a response into a list of models to be added to the
 789     // collection. The default implementation is just to pass it through.
 790     parse: function(resp, xhr) {
 791       return resp;
 792     },
 793 
 794     // Proxy to _'s chain. Can't be proxied the same way the rest of the
 795     // underscore methods are proxied because it relies on the underscore
 796     // constructor.
 797     chain: function () {
 798       return _(this.models).chain();
 799     },
 800 
 801     // Reset all internal state. Called when the collection is reset.
 802     _reset: function(options) {
 803       this.length = 0;
 804       this.models = [];
 805       this._byId  = {};
 806       this._byCid = {};
 807     },
 808 
 809     // Prepare a model or hash of attributes to be added to this collection.
 810     _prepareModel: function(model, options) {
 811       options || (options = {});
 812       if (!(model instanceof Model)) {
 813         var attrs = model;
 814         options.collection = this;
 815         model = new this.model(attrs, options);
 816         if (!model._validate(model.attributes, options)) model = false;
 817       } else if (!model.collection) {
 818         model.collection = this;
 819       }
 820       return model;
 821     },
 822 
 823     // Internal method to remove a model's ties to a collection.
 824     _removeReference: function(model) {
 825       if (this == model.collection) {
 826         delete model.collection;
 827       }
 828       model.off('all', this._onModelEvent, this);
 829     },
 830 
 831     // Internal method called every time a model in the set fires an event.
 832     // Sets need to update their indexes when models change ids. All other
 833     // events simply proxy through. "add" and "remove" events that originate
 834     // in other collections are ignored.
 835     _onModelEvent: function(event, model, collection, options) {
 836       if ((event == 'add' || event == 'remove') && collection != this) return;
 837       if (event == 'destroy') {
 838         this.remove(model, options);
 839       }
 840       if (model && event === 'change:' + model.idAttribute) {
 841         delete this._byId[model.previous(model.idAttribute)];
 842         this._byId[model.id] = model;
 843       }
 844       this.trigger.apply(this, arguments);
 845     }
 846 
 847   });
 848 
 849   // Underscore methods that we want to implement on the Collection.
 850   var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
 851     'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
 852     'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
 853     'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
 854     'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
 855 
 856   // Mix in each Underscore method as a proxy to `Collection#models`.
 857   _.each(methods, function(method) {
 858     Collection.prototype[method] = function() {
 859       return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
 860     };
 861   });
 862 
 863   // Backbone.Router
 864   // -------------------
 865 
 866   // Routers map faux-URLs to actions, and fire events when routes are
 867   // matched. Creating a new one sets its `routes` hash, if not set statically.
 868   var Router = Backbone.Router = function(options) {
 869     options || (options = {});
 870     if (options.routes) this.routes = options.routes;
 871     this._bindRoutes();
 872     this.initialize.apply(this, arguments);
 873   };
 874 
 875   // Cached regular expressions for matching named param parts and splatted
 876   // parts of route strings.
 877   var namedParam    = /:\w+/g;
 878   var splatParam    = /\*\w+/g;
 879   var escapeRegExp  = /[-[\]{}()+?.,\\^$|#\s]/g;
 880 
 881   // Set up all inheritable **Backbone.Router** properties and methods.
 882   _.extend(Router.prototype, Events, {
 883 
 884     // Initialize is an empty function by default. Override it with your own
 885     // initialization logic.
 886     initialize: function(){},
 887 
 888     // Manually bind a single named route to a callback. For example:
 889     //
 890     //     this.route('search/:query/p:num', 'search', function(query, num) {
 891     //       ...
 892     //     });
 893     //
 894     route: function(route, name, callback) {
 895       Backbone.history || (Backbone.history = new History);
 896       if (!_.isRegExp(route)) route = this._routeToRegExp(route);
 897       if (!callback) callback = this[name];
 898       Backbone.history.route(route, _.bind(function(fragment) {
 899         var args = this._extractParameters(route, fragment);
 900         callback && callback.apply(this, args);
 901         this.trigger.apply(this, ['route:' + name].concat(args));
 902         Backbone.history.trigger('route', this, name, args);
 903       }, this));
 904       return this;
 905     },
 906 
 907     // Simple proxy to `Backbone.history` to save a fragment into the history.
 908     navigate: function(fragment, options) {
 909       Backbone.history.navigate(fragment, options);
 910     },
 911 
 912     // Bind all defined routes to `Backbone.history`. We have to reverse the
 913     // order of the routes here to support behavior where the most general
 914     // routes can be defined at the bottom of the route map.
 915     _bindRoutes: function() {
 916       if (!this.routes) return;
 917       var routes = [];
 918       for (var route in this.routes) {
 919         routes.unshift([route, this.routes[route]]);
 920       }
 921       for (var i = 0, l = routes.length; i < l; i++) {
 922         this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
 923       }
 924     },
 925 
 926     // Convert a route string into a regular expression, suitable for matching
 927     // against the current location hash.
 928     _routeToRegExp: function(route) {
 929       route = route.replace(escapeRegExp, '\\$&')
 930                    .replace(namedParam, '([^\/]+)')
 931                    .replace(splatParam, '(.*?)');
 932       return new RegExp('^' + route + '$');
 933     },
 934 
 935     // Given a route, and a URL fragment that it matches, return the array of
 936     // extracted parameters.
 937     _extractParameters: function(route, fragment) {
 938       return route.exec(fragment).slice(1);
 939     }
 940 
 941   });
 942 
 943   // Backbone.History
 944   // ----------------
 945 
 946   // Handles cross-browser history management, based on URL fragments. If the
 947   // browser does not support `onhashchange`, falls back to polling.
 948   var History = Backbone.History = function() {
 949     this.handlers = [];
 950     _.bindAll(this, 'checkUrl');
 951   };
 952 
 953   // Cached regex for cleaning leading hashes and slashes .
 954   var routeStripper = /^[#\/]/;
 955 
 956   // Cached regex for detecting MSIE.
 957   var isExplorer = /msie [\w.]+/;
 958 
 959   // Has the history handling already been started?
 960   History.started = false;
 961 
 962   // Set up all inheritable **Backbone.History** properties and methods.
 963   _.extend(History.prototype, Events, {
 964 
 965     // The default interval to poll for hash changes, if necessary, is
 966     // twenty times a second.
 967     interval: 50,
 968 
 969     // Gets the true hash value. Cannot use location.hash directly due to bug
 970     // in Firefox where location.hash will always be decoded.
 971     getHash: function(windowOverride) {
 972       var loc = windowOverride ? windowOverride.location : window.location;
 973       var match = loc.href.match(/#(.*)$/);
 974       return match ? match[1] : '';
 975     },
 976 
 977     // Get the cross-browser normalized URL fragment, either from the URL,
 978     // the hash, or the override.
 979     getFragment: function(fragment, forcePushState) {
 980       if (fragment == null) {
 981         if (this._hasPushState || forcePushState) {
 982           fragment = window.location.pathname;
 983           var search = window.location.search;
 984           if (search) fragment += search;
 985         } else {
 986           fragment = this.getHash();
 987         }
 988       }
 989       if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
 990       return fragment.replace(routeStripper, '');
 991     },
 992 
 993     // Start the hash change handling, returning `true` if the current URL matches
 994     // an existing route, and `false` otherwise.
 995     start: function(options) {
 996       if (History.started) throw new Error("Backbone.history has already been started");
 997       History.started = true;
 998 
 999       // Figure out the initial configuration. Do we need an iframe?
1000       // Is pushState desired ... is it available?
1001       this.options          = _.extend({}, {root: '/'}, this.options, options);
1002       this._wantsHashChange = this.options.hashChange !== false;
1003       this._wantsPushState  = !!this.options.pushState;
1004       this._hasPushState    = !!(this.options.pushState && window.history && window.history.pushState);
1005       var fragment          = this.getFragment();
1006       var docMode           = document.documentMode;
1007       var oldIE             = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
1008 
1009       if (oldIE) {
1010         this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
1011         this.navigate(fragment);
1012       }
1013 
1014       // Depending on whether we're using pushState or hashes, and whether
1015       // 'onhashchange' is supported, determine how we check the URL state.
1016       if (this._hasPushState) {
1017         $(window).on('popstate', this.checkUrl);
1018       } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
1019         $(window).on('hashchange', this.checkUrl);
1020       } else if (this._wantsHashChange) {
1021         this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
1022       }
1023 
1024       // Determine if we need to change the base url, for a pushState link
1025       // opened by a non-pushState browser.
1026       this.fragment = fragment;
1027       var loc = window.location;
1028       var atRoot  = loc.pathname == this.options.root;
1029 
1030       // If we've started off with a route from a `pushState`-enabled browser,
1031       // but we're currently in a browser that doesn't support it...
1032       if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
1033         this.fragment = this.getFragment(null, true);
1034         window.location.replace(this.options.root + '#' + this.fragment);
1035         // Return immediately as browser will do redirect to new url
1036         return true;
1037 
1038       // Or if we've started out with a hash-based route, but we're currently
1039       // in a browser where it could be `pushState`-based instead...
1040       } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
1041         this.fragment = this.getHash().replace(routeStripper, '');
1042         window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
1043       }
1044 
1045       if (!this.options.silent) {
1046         return this.loadUrl();
1047       }
1048     },
1049 
1050     // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
1051     // but possibly useful for unit testing Routers.
1052     stop: function() {
1053       $(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
1054       clearInterval(this._checkUrlInterval);
1055       History.started = false;
1056     },
1057 
1058     // Add a route to be tested when the fragment changes. Routes added later
1059     // may override previous routes.
1060     route: function(route, callback) {
1061       this.handlers.unshift({route: route, callback: callback});
1062     },
1063 
1064     // Checks the current URL to see if it has changed, and if it has,
1065     // calls `loadUrl`, normalizing across the hidden iframe.
1066     checkUrl: function(e) {
1067       var current = this.getFragment();
1068       if (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe));
1069       if (current == this.fragment) return false;
1070       if (this.iframe) this.navigate(current);
1071       this.loadUrl() || this.loadUrl(this.getHash());
1072     },
1073 
1074     // Attempt to load the current URL fragment. If a route succeeds with a
1075     // match, returns `true`. If no defined routes matches the fragment,
1076     // returns `false`.
1077     loadUrl: function(fragmentOverride) {
1078       var fragment = this.fragment = this.getFragment(fragmentOverride);
1079       var matched = _.any(this.handlers, function(handler) {
1080         if (handler.route.test(fragment)) {
1081           handler.callback(fragment);
1082           return true;
1083         }
1084       });
1085       return matched;
1086     },
1087 
1088     // Save a fragment into the hash history, or replace the URL state if the
1089     // 'replace' option is passed. You are responsible for properly URL-encoding
1090     // the fragment in advance.
1091     //
1092     // The options object can contain `trigger: true` if you wish to have the
1093     // route callback be fired (not usually desirable), or `replace: true`, if
1094     // you wish to modify the current URL without adding an entry to the history.
1095     navigate: function(fragment, options) {
1096       if (!History.started) return false;
1097       if (!options || options === true) options = {trigger: options};
1098       var frag = (fragment || '').replace(routeStripper, '');
1099       if (this.fragment == frag) return;
1100 
1101       // If pushState is available, we use it to set the fragment as a real URL.
1102       if (this._hasPushState) {
1103         if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
1104         this.fragment = frag;
1105         window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag);
1106 
1107       // If hash changes haven't been explicitly disabled, update the hash
1108       // fragment to store history.
1109       } else if (this._wantsHashChange) {
1110         this.fragment = frag;
1111         this._updateHash(window.location, frag, options.replace);
1112         if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
1113           // Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
1114           // When replace is true, we don't want this.
1115           if(!options.replace) this.iframe.document.open().close();
1116           this._updateHash(this.iframe.location, frag, options.replace);
1117         }
1118 
1119       // If you've told us that you explicitly don't want fallback hashchange-
1120       // based history, then `navigate` becomes a page refresh.
1121       } else {
1122         window.location.assign(this.options.root + fragment);
1123       }
1124       if (options.trigger) this.loadUrl(fragment);
1125     },
1126 
1127     // Update the hash location, either replacing the current entry, or adding
1128     // a new one to the browser history.
1129     _updateHash: function(location, fragment, replace) {
1130       if (replace) {
1131         location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment);
1132       } else {
1133         location.hash = fragment;
1134       }
1135     }
1136   });
1137 
1138   // Backbone.View
1139   // -------------
1140 
1141   // Creating a Backbone.View creates its initial element outside of the DOM,
1142   // if an existing element is not provided...
1143   var View = Backbone.View = function(options) {
1144     this.cid = _.uniqueId('view');
1145     this._configure(options || {});
1146     this._ensureElement();
1147     this.initialize.apply(this, arguments);
1148     this.delegateEvents();
1149   };
1150 
1151   // Cached regex to split keys for `delegate`.
1152   var delegateEventSplitter = /^(\S+)\s*(.*)$/;
1153 
1154   // List of view options to be merged as properties.
1155   var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
1156 
1157   // Set up all inheritable **Backbone.View** properties and methods.
1158   _.extend(View.prototype, Events, {
1159 
1160     // The default `tagName` of a View's element is `"div"`.
1161     tagName: 'div',
1162 
1163     // jQuery delegate for element lookup, scoped to DOM elements within the
1164     // current view. This should be prefered to global lookups where possible.
1165     $: function(selector) {
1166       return this.$el.find(selector);
1167     },
1168 
1169     // Initialize is an empty function by default. Override it with your own
1170     // initialization logic.
1171     initialize: function(){},
1172 
1173     // **render** is the core function that your view should override, in order
1174     // to populate its element (`this.el`), with the appropriate HTML. The
1175     // convention is for **render** to always return `this`.
1176     render: function() {
1177       return this;
1178     },
1179 
1180     // Remove this view from the DOM. Note that the view isn't present in the
1181     // DOM by default, so calling this method may be a no-op.
1182     remove: function() {
1183       this.$el.remove();
1184       return this;
1185     },
1186 
1187     // For small amounts of DOM Elements, where a full-blown template isn't
1188     // needed, use **make** to manufacture elements, one at a time.
1189     //
1190     //     var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
1191     //
1192     make: function(tagName, attributes, content) {
1193       var el = document.createElement(tagName);
1194       if (attributes) $(el).attr(attributes);
1195       if (content) $(el).html(content);
1196       return el;
1197     },
1198 
1199     // Change the view's element (`this.el` property), including event
1200     // re-delegation.
1201     setElement: function(element, delegate) {
1202       if (this.$el) this.undelegateEvents();
1203       this.$el = (element instanceof $) ? element : $(element);
1204       this.el = this.$el[0];
1205       if (delegate !== false) this.delegateEvents();
1206       return this;
1207     },
1208 
1209     // Set callbacks, where `this.events` is a hash of
1210     //
1211     // *{"event selector": "callback"}*
1212     //
1213     //     {
1214     //       'mousedown .title':  'edit',
1215     //       'click .button':     'save'
1216     //       'click .open':       function(e) { ... }
1217     //     }
1218     //
1219     // pairs. Callbacks will be bound to the view, with `this` set properly.
1220     // Uses event delegation for efficiency.
1221     // Omitting the selector binds the event to `this.el`.
1222     // This only works for delegate-able events: not `focus`, `blur`, and
1223     // not `change`, `submit`, and `reset` in Internet Explorer.
1224     delegateEvents: function(events) {
1225       if (!(events || (events = getValue(this, 'events')))) return;
1226       this.undelegateEvents();
1227       for (var key in events) {
1228         var method = events[key];
1229         if (!_.isFunction(method)) method = this[events[key]];
1230         if (!method) throw new Error('Method "' + events[key] + '" does not exist');
1231         var match = key.match(delegateEventSplitter);
1232         var eventName = match[1], selector = match[2];
1233         method = _.bind(method, this);
1234         eventName += '.delegateEvents' + this.cid;
1235         if (selector === '') {
1236           this.$el.bind(eventName, method);
1237         } else {
1238           this.$el.delegate(selector, eventName, method);
1239         }
1240       }
1241     },
1242 
1243     // Clears all callbacks previously bound to the view with `delegateEvents`.
1244     // You usually don't need to use this, but may wish to if you have multiple
1245     // Backbone views attached to the same DOM element.
1246     undelegateEvents: function() {
1247       this.$el.unbind('.delegateEvents' + this.cid);
1248     },
1249 
1250     // Performs the initial configuration of a View with a set of options.
1251     // Keys with special meaning *(model, collection, id, className)*, are
1252     // attached directly to the view.
1253     _configure: function(options) {
1254       if (this.options) options = _.extend({}, this.options, options);
1255       for (var i = 0, l = viewOptions.length; i < l; i++) {
1256         var attr = viewOptions[i];
1257         if (options[attr]) this[attr] = options[attr];
1258       }
1259       this.options = options;
1260     },
1261 
1262     // Ensure that the View has a DOM element to render into.
1263     // If `this.el` is a string, pass it through `$()`, take the first
1264     // matching element, and re-assign it to `el`. Otherwise, create
1265     // an element from the `id`, `className` and `tagName` properties.
1266     _ensureElement: function() {
1267       if (!this.el) {
1268         var attrs = getValue(this, 'attributes') || {};
1269         if (this.id) attrs.id = this.id;
1270         if (this.className) attrs['class'] = this.className;
1271         this.setElement(this.make(this.tagName, attrs), false);
1272       } else {
1273         this.setElement(this.el, false);
1274       }
1275     }
1276 
1277   });
1278 
1279   // The self-propagating extend function that Backbone classes use.
1280   var extend = function (protoProps, classProps) {
1281     var child = inherits(this, protoProps, classProps);
1282     child.extend = this.extend;
1283     return child;
1284   };
1285 
1286   // Set up inheritance for the model, collection, and view.
1287   Model.extend = Collection.extend = Router.extend = View.extend = extend;
1288 
1289   // Backbone.sync
1290   // -------------
1291 
1292   // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1293   var methodMap = {
1294     'create': 'POST',
1295     'update': 'PUT',
1296     'delete': 'DELETE',
1297     'read':   'GET'
1298   };
1299 
1300   // Override this function to change the manner in which Backbone persists
1301   // models to the server. You will be passed the type of request, and the
1302   // model in question. By default, makes a RESTful Ajax request
1303   // to the model's `url()`. Some possible customizations could be:
1304   //
1305   // * Use `setTimeout` to batch rapid-fire updates into a single request.
1306   // * Send up the models as XML instead of JSON.
1307   // * Persist models via WebSockets instead of Ajax.
1308   //
1309   // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
1310   // as `POST`, with a `_method` parameter containing the true HTTP method,
1311   // as well as all requests with the body as `application/x-www-form-urlencoded`
1312   // instead of `application/json` with the model in a param named `model`.
1313   // Useful when interfacing with server-side languages like **PHP** that make
1314   // it difficult to read the body of `PUT` requests.
1315   Backbone.sync = function(method, model, options) {
1316     var type = methodMap[method];
1317 
1318     // Default options, unless specified.
1319     options || (options = {});
1320 
1321     // Default JSON-request options.
1322     var params = {type: type, dataType: 'json'};
1323 
1324     // Ensure that we have a URL.
1325     if (!options.url) {
1326       params.url = getValue(model, 'url') || urlError();
1327     }
1328 
1329     // Ensure that we have the appropriate request data.
1330     if (!options.data && model && (method == 'create' || method == 'update')) {
1331       params.contentType = 'application/json';
1332       params.data = JSON.stringify(model.toJSON());
1333     }
1334 
1335     // For older servers, emulate JSON by encoding the request into an HTML-form.
1336     if (Backbone.emulateJSON) {
1337       params.contentType = 'application/x-www-form-urlencoded';
1338       params.data = params.data ? {model: params.data} : {};
1339     }
1340 
1341     // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1342     // And an `X-HTTP-Method-Override` header.
1343     if (Backbone.emulateHTTP) {
1344       if (type === 'PUT' || type === 'DELETE') {
1345         if (Backbone.emulateJSON) params.data._method = type;
1346         params.type = 'POST';
1347         params.beforeSend = function(xhr) {
1348           xhr.setRequestHeader('X-HTTP-Method-Override', type);
1349         };
1350       }
1351     }
1352 
1353     // Don't process data on a non-GET request.
1354     if (params.type !== 'GET' && !Backbone.emulateJSON) {
1355       params.processData = false;
1356     }
1357 
1358     // Make the request, allowing the user to override any Ajax options.
1359     return $.ajax(_.extend(params, options));
1360   };
1361 
1362   // Wrap an optional error callback with a fallback error event.
1363   Backbone.wrapError = function(onError, originalModel, options) {
1364     return function(model, resp) {
1365       resp = model === originalModel ? resp : model;
1366       if (onError) {
1367         onError(originalModel, resp, options);
1368       } else {
1369         originalModel.trigger('error', originalModel, resp, options);
1370       }
1371     };
1372   };
1373 
1374   // Helpers
1375   // -------
1376 
1377   // Shared empty constructor function to aid in prototype-chain creation.
1378   var ctor = function(){};
1379 
1380   // Helper function to correctly set up the prototype chain, for subclasses.
1381   // Similar to `goog.inherits`, but uses a hash of prototype properties and
1382   // class properties to be extended.
1383   var inherits = function(parent, protoProps, staticProps) {
1384     var child;
1385 
1386     // The constructor function for the new subclass is either defined by you
1387     // (the "constructor" property in your `extend` definition), or defaulted
1388     // by us to simply call the parent's constructor.
1389     if (protoProps && protoProps.hasOwnProperty('constructor')) {
1390       child = protoProps.constructor;
1391     } else {
1392       child = function(){ parent.apply(this, arguments); };
1393     }
1394 
1395     // Inherit class (static) properties from parent.
1396     _.extend(child, parent);
1397 
1398     // Set the prototype chain to inherit from `parent`, without calling
1399     // `parent`'s constructor function.
1400     ctor.prototype = parent.prototype;
1401     child.prototype = new ctor();
1402 
1403     // Add prototype properties (instance properties) to the subclass,
1404     // if supplied.
1405     if (protoProps) _.extend(child.prototype, protoProps);
1406 
1407     // Add static properties to the constructor function, if supplied.
1408     if (staticProps) _.extend(child, staticProps);
1409 
1410     // Correctly set child's `prototype.constructor`.
1411     child.prototype.constructor = child;
1412 
1413     // Set a convenience property in case the parent's prototype is needed later.
1414     child.__super__ = parent.prototype;
1415 
1416     return child;
1417   };
1418 
1419   // Helper function to get a value from a Backbone object as a property
1420   // or as a function.
1421   var getValue = function(object, prop) {
1422     if (!(object && object[prop])) return null;
1423     return _.isFunction(object[prop]) ? object[prop]() : object[prop];
1424   };
1425 
1426   // Throw an error when a URL is needed, and none is supplied.
1427   var urlError = function() {
1428     throw new Error('A "url" property or function must be specified');
1429   };
1430 
1431 }).call(this);
View Code

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值