zoukankan      html  css  js  c++  java
  • 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

    jscollectionsCategoriesCollection.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

     jslibs

       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
  • 相关阅读:
    SQL having 子句
    sqlserver2008 R2 创建作业(定时任务)
    3步完成chrome切换搜索引擎
    http模拟请求工具
    网页自动加载进度条插件
    span设为inline-block之后,未包含文字时下面会多出一条空白问题
    记一次特殊的下载字体方法
    团队冲刺第十三天
    团队冲刺第十二天
    人月神话03
  • 原文地址:https://www.cnblogs.com/daishuguang/p/3425819.html
Copyright © 2011-2022 走看看