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

 

MVC---Case 1

 

\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

 

 

你可能感兴趣的:(case)