// Backbone.js 0.9.2
// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://backbonejs.org
(function() {
// create 1 Global object , Is represented in the browser as window object , in Node.js In the said global object
var root = this;
// save "Backbone" The value before the variable is overridden
// If a naming conflict occurs or specification is considered , through Backbone.noConflict() Method to restore the variable by Backbone Take the previous value , And return Backbone Object to rename
var previousBackbone = root.Backbone;
// will Array.prototype In the slice and splice Method is cached in a local variable for invocation
var slice = Array.prototype.slice;
var splice = Array.prototype.splice;
var Backbone;
if( typeof exports !== 'undefined') {
Backbone = exports;
} else {
Backbone = root.Backbone = {};
}
// define Backbone version
Backbone.VERSION = '0.9.2';
// Automatic import in the server environment Underscore, in Backbone Parts of a method depend on or inherit from Underscore
var _ = root._;
if(!_ && ( typeof require !== 'undefined'))
_ = require('underscore');
// Define the first 3 Party libraries for the system 1 The variables of "$", Used in view (View), Event processing and synchronization with server data (sync) Calls a method in the library
// Supported libraries include jQuery, Zepto Etc. , They have the same syntax , but Zepto More suitable for mobile development , It's aimed at Webkit Kernel browser
// It can also be customized 1 with jQuery Custom libraries with similar syntax , for Backbone use ( Sometimes we might 1 than jQuery, Zepto A lighter custom version )
// The one defined here "$" Is a local variable , So it won't affect in Backbone Out-of-frame control 3 Normal use of square library
var $ = root.jQuery || root.Zepto || root.ender;
// Manual setting control 3 Party libraries
// If it's imported Backbone No control was imported before 3 Party libraries , Can be achieved by setDomLibrary Methods set up "$" A local variable
// setDomLibrary Methods are also commonly used in Backbone Dynamically import custom libraries in
Backbone.setDomLibrary = function(lib) {
$ = lib;
};
// Give up to "Backbone" Naming framework , And return Backbone object , 1 Commonly used to avoid naming conflicts or to standardize naming conventions
// For example, :
// var bk = Backbone.noConflict(); // cancel "Backbone" named , And will be Backbone Objects are stored in bk variable
// console.log(Backbone); // The variable is no longer accessible Backbone object , And return to Backbone The value before the definition
// var MyBackbone = bk; // while bk Store the Backbone object , Let's rename it MyBackbone
Backbone.noConflict = function() {
root.Backbone = previousBackbone;
return this;
};
// For non-support REST Mode browser , You can set the Backbone.emulateHTTP = true
// A request with the server will be made POST Way to send , And add it to the data _method Parameter identifies the operation name , It will also be sent X-HTTP-Method-Override Header information
Backbone.emulateHTTP = false;
// For non-support application/json Coded browser , You can set the Backbone.emulateJSON = true;
// Set the request type to application/x-www-form-urlencoded, And place the data in model Parameter to achieve compatibility
Backbone.emulateJSON = false;
// Backbone.Events Custom event correlation
// -----------------
// eventSplitter Specifies when to process more than one event , Rules for resolving event names
var eventSplitter = /\s+/;
// Custom event manager
// By binding in an object Events Relevant methods , Allows to add to an object , Delete and trigger custom events
var Events = Backbone.Events = {
// Events will be customized (events) And callback functions (callback) Binds to the current object
// The context object in the callback function is specified context, If it's not set context The context object defaults to the object of the current binding event
// The method is similar to DOM Level2 In the addEventListener methods
// events Allows you to specify multiple event names , Separated by whitespace characters ( Such as the blank space , Tabs, etc. )
// When the event name is "all" when , In the call trigger Method triggers any event , Will call "all" All of the bound callback functions in the event
on : function(events, callback, context) {
// define 1 Local variables used in some functions
var calls, event, node, tail, list;
// You must set up callback The callback function
if(!callback)
return this;
// through eventSplitter Resolve the event name , use split Split multiple event names into 1 An array
// 1 Use white space characters to specify multiple event names
events = events.split(eventSplitter);
// calls Records a list of bound events and callbacks in the current object
calls = this._callbacks || (this._callbacks = {});
// List of circular event names , Store event names from beginning to end event variable
while( event = events.shift()) {
// Get bound event Event callback function
// list Stores bindings in a single event name callback List of callback functions
// The list of functions is not stored as an array , It goes through multiple objects next Attributes are correlated in turn
/** Data formats such as :
* {
* tail: {Object},
* next: {
* callback: {Function},
* context: {Object},
* next: {
* callback: {Function},
* context: {Object},
* next: {Object}
* }
* }
* }
*/
// List each 1 layer next The object is stored 1 Information about the secondary callback event ( The body of the function , Context and under 1 The secondary callback event )
// The top level of the event list is stored 1 a tail object , It stores the end 1 The identity of the secondary binding callback event ( And the last 1 Of the secondary callback event next Point to the same 1 An object )
// through tail logo , You can tell when you've reached the end by walking through the callback list 1 A callback function
list = calls[event];
// node Variables are used to record information about the callback function
// tail Only store the last 1 The identity of the secondary binding callback function
// So if you've already bound a callback function before , Will be the previous tail Assigned to the node As a 1 Object usage , Then create a 1 A new object is identified to tail
// The reason for adding this callback event to 1 The callback tail object , The object hierarchy for the list of callback functions is arranged in binding order ( The most recently bound events are placed at the bottom )
node = list ? list.tail : {};
node.next = tail = {};
// Record the function body and context information for this callback
node.context = context;
node.callback = callback;
// Reassemble the callback list for the current event , The callback event has been added to the list
calls[event] = {
tail : tail,
next : list ? list.next : node
};
}
// Returns the current object , Facilitate method chain calls
return this;
},
// Removes a bound event or callback function from the object , Can be achieved by events, callback and context Filter events or callbacks that need to be removed
// - if context Is empty , Remove all of them callback Specified function
// - if callback Is empty , Removes all callbacks in the event
// - if events Is empty , But specifies the callback or context, The remove callback or context The specified callback function ( Does not distinguish between event names )
// - If no parameters are passed , Removes all bound events and callbacks from the object
off : function(events, callback, context) {
var event, calls, node, tail, cb, ctx;
// No events, or removing *all* events.
// The current object is not bound to any event
if(!( calls = this._callbacks))
return;
// If no parameters are specified , Removes all events and callbacks ( delete _callbacks attribute )
if(!(events || callback || context)) {
delete this._callbacks;
return this;
}
// Resolves the list of events that need to be removed
// - If you specify events, According to eventSplitter Parse the event name
// - If not specified events, Resolves a list of names for all events that have been bound
events = events ? events.split(eventSplitter) : _.keys(calls);
// List of circular event names
while( event = events.shift()) {
// Removes the current event object from the list , And the cache to node variable
node = calls[event];
delete calls[event];
// If the current event object does not exist ( Or there is no removal filter condition specified , The current event and all callbacks are removed ), Terminates the operation ( The event object is on 1 Steps have been removed )
if(!node || !(callback || context))
continue;
// Create a new list, omitting the indicated callbacks.
// Filter conditions based on callback functions or context , The assembly 1 A new event object and rebinding
tail = node.tail;
// Iterate through all the callback objects in the event
while(( node = node.next) !== tail) {
cb = node.callback;
ctx = node.context;
// Depending on the callback function and context in the argument , Filter the callback function , Rebind the callback function that does not meet the filter criteria to the event ( Because all the callback functions in the event have been removed above )
if((callback && cb !== callback) || (context && ctx !== context)) {
this.on(event, cb, ctx);
}
}
}
return this;
},
// Triggers a defined one 1 One or more events , List of callback functions that execute the binding in turn
trigger : function(events) {
var event, node, calls, tail, args, all, rest;
// The current object is not bound to any event
if(!( calls = this._callbacks))
return this;
// Gets the bound in the list of callback functions "all" The event list
all = calls.all;
// The name of the event that will need to be fired , In accordance with the eventSplitter Rule resolution is 1 An array
events = events.split(eventSplitter);
// will trigger From the first 2 The number of arguments after , recorded rest variable , Will in turn be passed to the callback function
rest = slice.call(arguments, 1);
// A list of events that the loop needs to fire
while( event = events.shift()) {
// Here the node A variable records a list of all callback functions for the current event
if( node = calls[event]) {
// tail Variable recording end 1 The object identity of the secondary bound event
tail = node.tail;
// node The value of the variable , In the order in which the events are bound , A single callback event object that is, in turn, assigned to a binding
// The last 1 Secondary bound events next attribute , with tail With reference to 1 An object , This is used to determine whether the end of the list has been reached
while(( node = node.next) !== tail) {
// Execute all bound events , And will call trigger Is passed to the callback function
node.callback.apply(node.context || this, rest);
}
}
// variable all The binding time is recorded "all" The event , That is, when any event is called , "all" The callback functions in the event are executed
// - "all" Callbacks in events regardless of binding order , This is done after the list of callback functions for the current event has all been executed
// - "all" Events should be called automatically when normal events are triggered , If you force it to trigger "all" The event , The callback function in the event will be executed twice
if( node = all) {
tail = node.tail;
// The difference is that it calls a callback function to a normal event , all The event takes the name of the event currently being called as the first 1 Three arguments are passed to the callback function
args = [event].concat(rest);
// Iterate and execute "all" List of callback functions in the event
while(( node = node.next) !== tail) {
node.callback.apply(node.context || this, args);
}
}
}
return this;
}
};
// Alias for binding and releasing events , Also for compatibility Backbone Previous version
Events.bind = Events.on;
Events.unbind = Events.off;
// Backbone.Model Data object model
// --------------
// Model is Backbone The base class for all data object models in , Used to create 1 Data model
// @param {Object} attributes Specifies the initialization data when the model is created
// @param {Object} options
/**
* @format options
* {
* parse: {Boolean},
* collection: {Collection}
* }
*/
var Model = Backbone.Model = function(attributes, options) {
// defaults Variables are used to store default data for the model
var defaults;
// If not specified attributes parameter , Is set attributes Null object
attributes || ( attributes = {});
// Set up the attributes The default method for parsing data , For example, the default data is fetched from the server ( Or the original data is XML format ), In order to compatible set The format of the data required by the , You can use parse Method analysis
if(options && options.parse)
attributes = this.parse(attributes);
if( defaults = getValue(this, 'defaults')) {
// if Model Set at definition time defaults The default data , Then initialize data usage defaults with attributes Data after parameter merging (attributes The data will be overwritten defaults Data with the same name )
attributes = _.extend({}, defaults, attributes);
}
// Explicitly specify to which the model belongs Collection object ( In the call Collection the add, push When you add a model to a collection of methods , The model is automatically set to Collection object )
if(options && options.collection)
this.collection = options.collection;
// attributes Property stores the current model's JSON Objectified data , The model is created empty by default
this.attributes = {};
// define _escapedAttributes Cache object , It will cache through escape Method to process data
this._escapedAttributes = {};
// For each 1 Model configuration 1 A wei 1 logo
this.cid = _.uniqueId('c');
// define 1 A collection of objects used to record the state of the data , Refer to the comments at the time the object is defined
this.changed = {};
this._silent = {};
this._pending = {};
// Sets initialization data when an instance is created , First set use silent parameter , Not trigger change The event
this.set(attributes, {
silent : true
});
// The initialization data has been set above , changed, _silent, _pending The state of the object may have changed , I'm going to reinitialize here
this.changed = {};
this._silent = {};
this._pending = {};
// _previousAttributes Variables that store model data 1 A copy of the
// For use in change Event to retrieve the state of model data before it is changed , through previous or previousAttributes Method acquisition 1 The data of the states
this._previousAttributes = _.clone(this.attributes);
// call initialize Initialization method
this.initialize.apply(this, arguments);
};
// use extend Methods for Model The prototype defines 1 Series of properties and methods
_.extend(Model.prototype, Events, {
// changed Property records each call set methods , Altered data key A collection of
changed : null,
// // When specifying silent When the attribute , Not trigger change The event , The changed data is recorded , Until the 1 Once the trigger change The event
// _silent Property is used to record usage silent The changed data of
_silent : null,
_pending : null,
// The unique of each model 1 Identifying property ( The default is "id", By modifying the idAttribute Can be customized id The property name )
// If included when setting data id attribute , the id Will override the model id
// id For use in Collection Find and identify models in a collection , When communicating with the background interface id As a 1 The identity of a bar record
idAttribute : 'id',
// Model initialization method , Automatically called after the model has been constructed
initialize : function() {
},
// Returns the data in the current model 1 A copy of the (JSON Object format )
toJSON : function(options) {
return _.clone(this.attributes);
},
// According to the attr The property name , Gets the data values in the model
get : function(attr) {
return this.attributes[attr];
},
// According to the attr The property name , Gets the data values in the model , Contains data values HTML Special characters will be converted to HTML entity , contains & < > " ' \
// through _.escape Method implementation
escape : function(attr) {
var html;
// from _escapedAttributes Find the data in the cache object , Returns directly if the data has been cached
if( html = this._escapedAttributes[attr])
return html;
// _escapedAttributes No data was found in the cache object
// Get the data from the model first
var val = this.get(attr);
// Take the data HTML use _.escape Method to an entity , And the cache to _escapedAttributes object , So you can get it directly next time
return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
},
// Check if an attribute exists in the model , When the value of the property is converted to Boolean The post-type value is false, It doesn't exist
// If the value is false, null, undefined, 0, NaN, Or an empty string , Will be converted to false
has : function(attr) {
return this.get(attr) != null;
},
// Set the data in the model , if key Value does not exist , Is added to the model as a new property , if key Value already exists , Changes to the new value
set : function(key, value, options) {
// attrs The variable records the data objects that need to be set
var attrs, attr, val;
// Parameter form allows key-value Objects form , Or through key, value The two parameters are set separately
// if key is 1 An object , Is determined to use object form Settings , The first 2 The parameters will be treated as options parameter
if(_.isObject(key) || key == null) {
attrs = key;
options = value;
} else {
// through key, value The two parameters are set separately , Put the data in attrs Object convenience system 1 To deal with
attrs = {};
attrs[key] = value;
}
// options The configuration item must be 1 An object , If it's not set options The default value is 1 An empty object
options || ( options = {});
// No action is performed when no parameters are set
if(!attrs)
return this;
// If the data object being set belongs to Model Of the class 1 An instance , will Model The object's attributes Data object assignment attrs
// 1 As in the copy 1 a Model Object data to another 1 a Model When the object , The action is performed
if( attrs instanceof Model)
attrs = attrs.attributes;
// if options Config object unset attribute , will attrs All properties in the data object are reset to undefined
// 1 As in the copy 1 a Model Object data to another 1 a Model When the object , But you just have to copy Model This operation is performed when the data in the
if(options.unset)
for(attr in attrs)
attrs[attr] =
void 0;
// Validate the current data , If the validation fails, the execution is stopped
if(!this._validate(attrs, options))
return false;
// If you set id The property name is contained in the data set , will id Overlay to the model id attribute
// This is to ensure in the custom id The property name after , Access model id When the attribute , It can also be accessed correctly id
if(this.idAttribute in attrs)
this.id = attrs[this.idAttribute];
var changes = options.changes = {};
// now Record the data objects in the current model
var now = this.attributes;
// escaped Record the pass in the current model escape Cached data
var escaped = this._escapedAttributes;
// prev Record the value of the data in the model before it is changed
var prev = this._previousAttributes || {};
// Iterate over the data objects that need to be set
for(attr in attrs) {
// attr Stores the current property name , val Stores the value of the current property
val = attrs[attr];
// If the current data does not exist in the model , Or has changed , Or in the options Specified in the unset Attribute to delete , Deletes the data and replaces it _escapedAttributes The data in the
if(!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
// Delete only through escape Cached data , This is to ensure that the data in the cache stays in sync with the real data in the model
delete escaped[attr];
// If you specify silent attribute , Is this set The method call does not fire change The event , Therefore, the changed data will be recorded _silent Properties of the , To facilitate the 1 Once the trigger change When an event is , Notification event listeners that this data has changed
// If not specified silent attribute , Set directly changes Property for the current state of the data
(options.silent ? this._silent : changes)[attr] = true;
}
// If the options Set in the unset, Deletes the data from the model ( including key)
// If not specified unset attribute , The data will be added or modified , Add new data to the data object of the model
options.unset ?
delete now[attr] : now[attr] = val;
// If the data in the model does not match the new data 1 to , Indicates that the data has changed
if(!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
// in changed Property records the state of the current property that has changed
this.changed[attr] = val;
if(!options.silent)
this._pending[attr] = true;
} else {
// If the data doesn't change , from changed Property to remove the changed state
delete this.changed[attr];
delete this._pending[attr];
}
}
// call change methods , Will trigger change Event-bound functions
if(!options.silent)
this.change(options);
return this;
},
// Removes the specified data from the current model ( Properties will also be deleted )
unset : function(attr, options) {
(options || ( options = {})).unset = true;
// through options.unset Configuration item notification set Method to delete
return this.set(attr, null, options);
},
// Clears all data and properties in the current model
clear : function(options) {
(options || ( options = {})).unset = true;
// cloning 1 Three copies of the properties of the current model , And through the options.unset Configuration item notification set Method performs a delete operation
return this.set(_.clone(this.attributes), options);
},
// Get the default model data from the server , Use after getting the data set Method to populate the model with data , So if the data you get is not the same as the data in the current model 1 to , Would trigger change The event
fetch : function(options) {
// Make sure that options is 1 A new object , And then that will change options The properties in the
options = options ? _.clone(options) : {};
var model = this;
// in options You can specify a custom callback function after the data is obtained successfully
var success = options.success;
// When the data is obtained successfully, populate the data and call the custom success callback function
options.success = function(resp, status, xhr) {
// through parse Method converts the data returned by the server
// through set Method to populate the model with the transformed data , So it might trigger change The event ( When the data changes )
// If validation fails while populating the data , The custom is not invoked success The callback function
if(!model.set(model.parse(resp, xhr), options))
return false;
// Call custom success The callback function
if(success)
success(model, resp);
};
// Pass when the request has an error wrapError To deal with error The event
options.error = Backbone.wrapError(options.error, model, options);
// call sync Method to retrieve data from the server
return (this.sync || Backbone.sync).call(this, 'read', this, options);
},
// Save the data from the model to the server
save : function(key, value, options) {
// attrs Stores data objects that need to be saved to the server
var attrs, current;
// Supports ways to set individual properties key: value
// Supports batch setup in object form {key: value}
if(_.isObject(key) || key == null) {
// if key is 1 An object , It is thought to be set by means of objects
// At this time the first 2 The parameters are considered to be options
attrs = key;
options = value;
} else {
// If it's through key: value Form sets a single property , Set directly attrs
attrs = {};
attrs[key] = value;
}
// The configuration object must be 1 A new object
options = options ? _.clone(options) : {};
// If the options Set in the wait options , The changed data will be validated in advance , And the server is not responding to new data ( Or the response fails ) when , Local data is restored to its pre-modified state
// If it's not set wait options , Whether the server was set up successfully or not , Local data will be modified to the latest state
if(options.wait) {
// Verify ahead of time the data that needs to be saved
if(!this._validate(attrs, options))
return false;
// Record the data in the current model , Used after sending data to the server , Restore the data
// If the server fails to respond or returns no data , The state before modification can be maintained
current = _.clone(this.attributes);
}
// silentOptions in options Object silent( The data is not validated )
// When using wait When using parameters silentOptions Configuration items , Because the data has been validated above
// If it's not set wait parameter , The original is still used options Configuration items
var silentOptions = _.extend({}, options, {
silent : true
});
// Save the updated data to the model , To facilitate the sync Method to get the model data to save to the server
if(attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
return false;
}
var model = this;
// in options You can specify a custom callback function after successfully saving the data
var success = options.success;
// Execute after server response is successful success
options.success = function(resp, status, xhr) {
// Gets the latest status of the server response
var serverAttrs = model.parse(resp, xhr);
// If you use wait parameter , The modified data state is first set directly to the model
if(options.wait) {
delete options.wait;
serverAttrs = _.extend(attrs || {}, serverAttrs);
}
// Set the latest data state to the model
// If the call set Validation failed at method time , The custom one will not be called success The callback function
if(!model.set(serverAttrs, options))
return false;
if(success) {
// Custom after successful invocation response success The callback function
success(model, resp);
} else {
// If custom callbacks are not specified , Is triggered by default sync The event
model.trigger('sync', model, resp, options);
}
};
// Pass when the request has an error wrapError To deal with error The event
options.error = Backbone.wrapError(options.error, model, options);
// Save the data from the model to the server
// If the current model is 1 I'm going to create a new model ( There is no id), Use the create methods ( new ), Otherwise it's considered update methods ( Modify the )
var method = this.isNew() ? 'create' : 'update';
var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
// If it's set options.wait, The data is restored to its pre-modified state
// The saved request has not yet been responded to , So if the response fails , The model will remain as it was before the modification , If the server responds successfully , Will be in success , sets the data in the model to the latest state
if(options.wait)
this.set(current, silentOptions);
return xhr;
},
// Remove the model , The model will also be derived from the owner Collection Collection has been deleted
// If the model is created on the client side , It is removed directly from the client
// If the model data also exists on the server , The data on the server side is also deleted
destroy : function(options) {
// The configuration item must be 1 A new object
options = options ? _.clone(options) : {};
var model = this;
// in options You can specify a custom callback function after a successful deletion of data
var success = options.success;
// Data deletion was successfully invoked , The trigger destroy The event , If the model exists in Collection In the collection , The collection will listen destroy Event and removes the model from the collection when triggered
// When the model is deleted , The data in the model has not been emptied , But the model has been removed from the collection , So when there is no reference anywhere to the model , Will be automatically released from memory
// It is recommended that you delete the model , Set the model object's reference variable to null
var triggerDestroy = function() {
model.trigger('destroy', model, model.collection, options);
};
// If the model is 1 A new model for the client , Call directly triggerDestroy Remove the model from the collection
if(this.isNew()) {
triggerDestroy();
return false;
}
// When deleting data from the server succeeds
options.success = function(resp) {
// If the options Configuration in object wait item , Represents model data in local memory , Will be deleted after the server data was successfully deleted
// If the server fails to respond , The local data will not be deleted
if(options.wait)
triggerDestroy();
if(success) {
// Calls the custom successful callback function
success(model, resp);
} else {
// If there is no custom callback , Is triggered by default sync The event
model.trigger('sync', model, resp, options);
}
};
// Pass when the request has an error wrapError To deal with error The event
options.error = Backbone.wrapError(options.error, model, options);
// through sync Method sends a request to delete data
var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);
// If there is no options Configuration in object wait item , The local data is deleted first , The request is then sent to delete the server data
// This is regardless of whether the server deletion was successful , The local model data has been deleted
if(!options.wait)
triggerDestroy();
return xhr;
},
// Gets the corresponding model in the server interface url, In the call save, fetch, destroy And other ways to interact with the server , This method will be used to get url
// The generated url Similar to the "PATHINFO" model , The server operates only on the model 1 a url, For modify and delete operations will be in url Postappend model id Easy to identify
// If it's defined in the model urlRoot, The server interface should be [urlRoot/id] In the form of
// If the model belongs to Collection The set defines url Method or property , Is used in the collection url In the form of : [collection.url/id]
// On access server url When in url The model is appended to it id, Facilitate server identification 1 records , So in the model id Needs to correspond to server records
// If the model or collection cannot be obtained url, Will call urlError Method to throw a 1 An abnormal
// If the server interface is not followed "PATHINFO" Way to organize , You can reload url Method to achieve seamless interaction with the server
url : function() {
// Define the corresponding server url The path
var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
// If the current model is a new model created by the client , There are no id attribute , The server url Direct use of base
if(this.isNew())
return base;
// If the current model has id attribute , Maybe it's called save or destroy methods , Will be in base The model is appended id
// Next we will determine base The last 1 Is a character "/", The generated url Format for [base/id]
return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
},
// parse Method is used to parse the data fetched from the server , return 1 A can be set Method To parse the model data
// 1 a parse Methods are overloaded based on the data returned by the server , To build a seamless connection to the server
// When the server returns the data structure with set Method does not require a data structure 1 to ( For example, server returns XML Format data time ), You can use parse Method transformation
parse : function(resp, xhr) {
return resp;
},
// create 1 A new model , It has the same data as the current model
clone : function() {
return new this.constructor(this.attributes);
},
// Check if the current model is a new model created by the client
// The check is based on whether the model exists id logo , The new model created by the client does not id logo
// Therefore, the model data that the server responds to must be included id logo , The attribute name of the identity defaults to "id", It can also be modified idAttribute Property to customize the identity
isNew : function() {
return this.id == null;
},
// Triggered when data is updated change Event-bound functions
// when set Method is called , Will automatically call change methods , If the set Specified when the method is called silent configuration , You need to call it manually change methods
change : function(options) {
// options It must be 1 An object
options || ( options = {});
// this._changing There are some problems with the logic
// this._changing At the end of the method is set to false, So above the method changing The value of the variable is always zero false( The first 1 Time for undefined)
// The author's original intention should be to use this variable change Method has completed execution , It doesn't make sense for a single-threaded script on the browser side , Because this method will block other scripts when executed
// changing To get on 1 The state of the second execution , If the 1 The script did not complete execution , The value of true
var changing = this._changing;
// Start execution identification , The value is always zero during execution true, After execution this._changing Be modified to false
this._changing = true;
// Adds a data state that is not changed this time to _pending In the object
for(var attr in this._silent)
this._pending[attr] = true;
// changes Object contains the current data 1 Time to perform change Events so far , All data that has been changed
// If you used it before silent Not trigger change The event , I'm going to put it in changes In the object
var changes = _.extend({}, options.changes, this._silent);
// reset _silent object
this._silent = {};
// traverse changes object , For each 1 Attributes trigger individual attributes change The event
for(var attr in changes) {
// will Model object , Attribute values , The listener function to which the configuration item is passed as a parameter to the event
this.trigger('change:' + attr, this, this.get(attr), options);
}
// If the method is in execution , Stop execution
if(changing)
return this;
// The trigger change The event , After any data has been changed , Will trigger in turn "change: attribute " Events and "change" The event
while(!_.isEmpty(this._pending)) {
this._pending = {};
// The trigger change The event , And will be Model Instances and configuration items are passed as parameters to the listener function
this.trigger('change', this, options);
// traverse changed Data in an object , In turn, the state of the changed data is changed from changed Remove the
// If called after that hasChanged Check data status , Will get false( Did not change )
for(var attr in this.changed) {
if(this._pending[attr] || this._silent[attr])
continue;
// remove changed The state of the data in
delete this.changed[attr];
}
// change Event execution complete , _previousAttributes Property records the most recent copy of the data for the current model
// So if you want to get on the data 1 A state of , 1 General only passes in the triggered change Pass through the event previous or previousAttributes Methods to obtain
this._previousAttributes = _.clone(this.attributes);
}
// Completion mark
this._changing = false;
return this;
},
// Check to see if some data is on 1 Time to perform change It was changed after the event
/**
* 1 As in the change Event coordination previous or previousAttributes Methods using , Such as :
* if(model.hasChanged('attr')) {
* var attrPrev = model.previous('attr');
* }
*/
hasChanged : function(attr) {
if(!arguments.length)
return !_.isEmpty(this.changed);
return _.has(this.changed, attr);
},
// Gets the data in the current model and above 1 A set of data that has changed in the secondary data
// (1 As in the use of silent Property is not called change methods , So the data will be held temporarily changed Properties of the , on 1 Secondary data can be passed previousAttributes Methods to obtain )
// If passed on diff A collection of , Will be used on 1 Submodel data and diff The data in the collection is compared , Return not 1 To a data set
// If there is no difference in the comparison results , It returns false
changedAttributes : function(diff) {
// If not specified diff, Returns the current model above 1 A collection of data whose substate has changed , That data already exists changed Properties of the , Therefore return changed A collection of 1 A copy of the
if(!diff)
return this.hasChanged() ? _.clone(this.changed) : false;
// Specifies what you want to compare diff A collection of , Will return on 1 Secondary data and diff Set comparison results
// old The variable is stored on 1 Model data for each state
var val, changed = false, old = this._previousAttributes;
// traverse diff A collection of , And every 1 On the items with 1 A set of states for comparison
for(var attr in diff) {
// Will compare the results not 1 To temporarily store the data to changed variable
if(_.isEqual(old[attr], ( val = diff[attr])))
continue;
(changed || (changed = {}))[attr] = val;
}
// Return comparison results
return changed;
},
// Triggered in the model change In the event , Gets an attribute before it is changed 1 The data of the states , 1 Generally used for data comparison or rollback
// The method 1 As in the change Call in event , change After the event is triggered , _previousAttributes Property holds the most recent data
previous : function(attr) {
// attr Specify the need to get on 1 Attribute names for states
if(!arguments.length || !this._previousAttributes)
return null;
return this._previousAttributes[attr];
},
// Trigger in model change In the event , Gets all properties 1 A collection of data of states
// The method is similar to previous() methods , 1 As in the change Call in event , Used for data comparison or rollback
previousAttributes : function() {
// Will be on 1 The data object of the state is cloned as 1 Five new objects and return
return _.clone(this._previousAttributes);
},
// Check if the model is currently in a valid state. It's only possible to
// get into an *invalid* state if you're using silent changes.
// Verify that the data in the current model passes validate Methods validation , Make sure it is defined before invoking it validate methods
isValid : function() {
return !this.validate(this.attributes);
},
// Data validation method , In the call set, save, add Wait for data to update the method , Be executed automatically
// Failure to verify will trigger the model object "error" The event , If the options Specified in the error The processing function , Will only be executed options.error function
// @param {Object} attrs Data model attributes attribute , Store the objectified data for the model
// @param {Object} options Configuration items
// @return {Boolean} Verify through return true, Do not pass back false
_validate : function(attrs, options) {
// If you are calling set, save, add When the data update method is set options.silent attribute , Ignore validation
// if Model Not added in validate methods , Ignore validation
if(options.silent || !this.validate)
return true;
// Gets all property values in the object , And in the validate Method to verify
// validate Method contains 2 A parameter , Are data sets and configuration objects in the model respectively , If the validation passes, no data is returned ( The default is undefined), Failure to verify returns data with an error message
attrs = _.extend({}, this.attributes, attrs);
var error = this.validate(attrs, options);
// Verification by
if(!error)
return true;
// Failed validation
// If it is set in the configuration object error Error handling , The method is called and error data and configuration objects are passed to the method
if(options && options.error) {
options.error(this, error, options);
} else {
// If the model is bound error Event listeners , Triggers the binding event
this.trigger('error', this, error, options);
}
// Returns an authentication failed identity
return false;
}
});
// Backbone.Collection The data model set is related
// -------------------
// Collection A collection of stored 1 A series of data models of the same class , It also provides methods to operate on the model
var Collection = Backbone.Collection = function(models, options) {
// A configuration object
options || ( options = {});
// Sets the model class of the collection in the configuration parameter
if(options.model)
this.model = options.model;
// If it's set comparator attribute , The data in the collection will follow comparator The sorting algorithm in the method sorts ( in add Method is automatically called )
if(options.comparator)
this.comparator = options.comparator;
// Resets the internal state of the collection when instantiated ( The first 1 The call is understood to define the state )
this._reset();
// Invoke the custom initialization method , If you need 1 As will be overloaded initialize methods
this.initialize.apply(this, arguments);
// If you specify models data , The call reset Method adds data to the collection
// Set on the first call silent parameter , So it doesn't trigger "reset" The event
if(models)
this.reset(models, {
silent : true,
parse : options.parse
});
};
// through extend Method defines a collection class prototype method
_.extend(Collection.prototype, Events, {
// The model class that defines the collection , The model class must be 1 a Backbone.Model A subclass of
// Collection dependent methods are used ( Such as add, create Etc. ) when , Allows passing in data objects , The collection method automatically creates the corresponding instance based on the defined model class
// The data models stored in the collection should all be the same 1 An instance of a model class
model : Model,
// Initialization method , This method is called automatically after the collection instance is created
// 1 This method is overridden when a collection class is defined
initialize : function() {
},
// return 1 An array , Contains data objects for each model in the collection
toJSON : function(options) {
// through Undersocre the map Method will be used in each of the sets 1 A model of toJSON The results of 1 An array , And return
return this.map(function(model) {
// Call each model object in turn toJSON methods , This method returns the data object of the model by default ( Duplicate copy )
// If you want to return a string or something like that , Can be overloaded toJSON methods
return model.toJSON(options);
});
},
// Add to the collection 1 One or more model objects
// It will trigger by default "add" The event , If the options Set in the silent attribute , This event trigger can be turned off
// The incoming models Can be 1 or 1 A series of model objects (Model Instances of the class ), If it's set in the collection model attribute , Allows the data object to be passed in directly ( Such as {name: 'test'}), Data objects are automatically instantiated model Point to the model object
add : function(models, options) {
// Local variable definition
var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
options || ( options = {});
// models It must be 1 An array , If only passed in 1 A model , Converts it to an array
models = _.isArray(models) ? models.slice() : [models];
// Iterate through the list of models that need to be added , During the traversal , The following actions will be performed :
// - Transform data objects into model objects
// - Establish a reference between the model and the collection
// - Record invalid and duplicate models , And filter it in the back
for( i = 0, length = models.length; i < length; i++) {
// Convert data objects to model objects , References to resume models and collections , And stored in the model( At the same time models The corresponding model has been replaced with the model object )
if(!( model = models[i] = this._prepareModel(models[i], options))) {
throw new Error("Can't add an invalid model to a collection");
}
// Current model cid and id
cid = model.cid;
id = model.id;
// dups Invalid or duplicate model indexes are recorded in the array (models Index in array ), And the next 1 Step to filter and delete
// if cids, ids The model's index already exists in the variable , They think it's the same 1 Two models are passed in models The array is declared multiple times
// if _byCid, _byId An index of the model already exists in the object , Is that with 1 Five models already exist in the current set
// In both cases , Record the index of the model to dups Filter and delete
if(cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
dups.push(i);
continue;
}
// will models Record the models that have been traversed in , For the next 1 Repeat the check on the secondary loop
cids[cid] = ids[id] = model;
}
// from models Delete invalid or duplicate models from , Keep the list of models that really need to be added to the current collection
i = dups.length;
while(i--) {
models.splice(dups[i], 1);
}
// Iterate through the models you need to add , Listen for model events and record them _byCid, _byId The list of , Used in a call get and getByCid Method as an index
for( i = 0, length = models.length; i < length; i++) {
// Listen for all events in the model , And perform _onModelEvent methods
// _onModelEvent Method will be thrown at the model add, remove, destroy and change Event processing , So that the model is in sync with the state in the collection
( model = models[i]).on('all', this._onModelEvent, this);
// Base the model on cid recorded _byCid object , Facilitate according to cid To look up
this._byCid[model.cid] = model;
// Base the model on id recorded _byId object , Facilitate according to id To look up
if(model.id != null)
this._byId[model.id] = model;
}
// Variable set length attribute , length Property records the number of models in the current collection
this.length += length;
// Sets the location where the new model list is inserted into the collection , If the options Set in the at parameter , It's in the set at Position insert
// The default is to insert to the end of the collection
// If it's set comparator Custom sorting methods , Is set at After will also follow comparator Method to sort , So the final order may not be at Specified location
index = options.at != null ? options.at : this.models.length;
splice.apply(this.models, [index, 0].concat(models));
// If it's set comparator methods , According to the data comparator Algorithm to sort
// Automatic sort use silent Property prevents triggering reset The event
if(this.comparator)
this.sort({
silent : true
});
// Fires on each model object in turn "add" The event , If it's set silent attribute , Prevents the event from being triggered
if(options.silent)
return this;
// Iterate through the list of newly added models
for( i = 0, length = this.models.length; i < length; i++) {
if(!cids[( model = this.models[i]).cid])
continue;
options.index = i;
// Triggering model "add" The event , Because the collection listens for the model "all" The event , So in _onModelEvent In the method , The collection will also fire "add" The event
// Details are available Collection.prototype._onModelEvent methods
model.trigger('add', model, this, options);
}
return this;
},
// Removes model objects from the collection ( Support for removing multiple models )
// The incoming models It could be a model object that needs to be removed , Or model cid And the model of id
// Removing the model does not invoke the model destroy methods
// If it's not set options.silent parameter , Will trigger the model remove The event , Will also trigger the set remove The event ( Set by _onModelEvent Method listens for all events in the model )
remove : function(models, options) {
var i, l, index, model;
// options Default is an empty object
options || ( options = {});
// models Must be of array type , When only remove 1 A model , Put it in the 1 An array
models = _.isArray(models) ? models.slice() : [models];
// Iterate through the list of models that need to be removed
for( i = 0, l = models.length; i < l; i++) {
// The incoming models The list can be model objects that need to be removed , Or model cid And the model of id
// ( in getByCid and get In the method , through cid, id To get the model , So if I pass in 1 Model objects , Returns the model itself )
model = this.getByCid(models[i]) || this.get(models[i]);
// No model is obtained
if(!model)
continue;
// from _byId Remove the model from the list id reference
delete this._byId[model.id];
// from _byCid Remove the model from the list cid reference
delete this._byCid[model.cid];
// indexOf is Underscore Methods in objects , Here by indexOf Method gets the location where the model first appears in the collection
index = this.indexOf(model);
// Removes the model from the collection list
this.models.splice(index, 1);
// Reset the current collection length attribute ( Record the number of models in the collection )
this.length--;
// If it's not set silent attribute , Triggers the model remove The event
if(!options.silent) {
// Adds the location of the current model in the collection to options Object and pass to remove Listen for an event , So that it can be used in event functions
options.index = index;
model.trigger('remove', model, this, options);
}
// Detach the model from the set , Includes references to models and event listeners in the collection
this._removeReference(model);
}
return this;
},
// Adds a model object to the end of the collection
// If it's defined in the collection class comparator Sorting method , by push Method to add the model as follows comparator Define the algorithm to sort , So the order of the models may change
push : function(model, options) {
// through _prepareModel Method will be model Instantiate model objects , This code is redundant , Because it's called down here add Method will also pass _prepareModel To obtain 1 Time model
model = this._prepareModel(model, options);
// call add Method adds the model to the collection ( Adds to the end of the collection by default )
this.add(model, options);
return model;
},
// Removes the last part of the set 1 Model objects
pop : function(options) {
// Gets the last in the collection 1 A model
var model = this.at(this.length - 1);
// through remove Method to remove the model
this.remove(model, options);
return model;
},
// To the set 1 Four positions are inserted into the model
// If it's defined in the collection class comparator Sorting method , by unshift Method to add the model as follows comparator Define the algorithm to sort , So the order of the models may change
unshift : function(model, options) {
// through _prepareModel Method will be model Instantiate model objects
model = this._prepareModel(model, options);
// call add Method inserts the model to the end of the collection 1 A position ( Set up the at for 0)
// If it's defined comparator Sorting method , The order of the sets will be rearranged
this.add(model, _.extend({
at : 0
}, options));
return model;
},
// Removes and returns the table in the collection 1 Model objects
shift : function(options) {
// Gets the order in the set 1 A model
var model = this.at(0);
// Delete the model from the collection
this.remove(model, options);
// Return model object
return model;
},
// According to the id Find the model from the collection and return it
get : function(id) {
if(id == null)
return
void 0;
return this._byId[id.id != null ? id.id : id];
},
// According to the cid Find the model from the collection and return it
getByCid : function(cid) {
return cid && this._byCid[cid.cid || cid];
},
// According to the index ( The subscript , from 0 start ) Find the model from the collection and return it
at : function(index) {
return this.models[index];
},
// The models in the collection are filtered by value
// attrs is 1 10 filter objects , Such as {name: 'Jack'}, Returns all of the values in the collection name for "Jack" The model ( An array of )
where : function(attrs) {
// attrs Cannot be null
if(_.isEmpty(attrs))
return [];
// through filter Method to filter the models in the collection
// filter Method is Underscore The methods in , Used to iterate over elements in a collection , And will be able to pass processor validation ( The return value is true) Is returned as an array
return this.filter(function(model) {
// traverse attrs Validation rules in an object
for(var key in attrs) {
// will attrs The validation rules in the collection match the models in the collection
if(attrs[key] !== model.get(key))
return false;
}
return true;
});
},
// Follow the model in the set comparator Property specifies the method to sort
// If there is no options Set in the silent parameter , Then the sort will trigger reset The event
sort : function(options) {
// options The default is 1 An object
options || ( options = {});
// call sort Method must be specified comparator attribute ( Sorting algorithm method ), Otherwise it will be thrown 1 A mistake
if(!this.comparator)
throw new Error('Cannot sort a set without a comparator');
// boundComparator Stored bound to the current collection context object comparator Sorting algorithm method
var boundComparator = _.bind(this.comparator, this);
if(this.comparator.length == 1) {
this.models = this.sortBy(boundComparator);
} else {
// call Array.prototype.sort through comparator The algorithm performs a custom sort on the data
this.models.sort(boundComparator);
}
// If not specified silent parameter , The trigger reset The event
if(!options.silent)
this.trigger('reset', this, options);
return this;
},
// Will set the values of all models attr Attribute values are stored in 1 Array and returns
pluck : function(attr) {
// map is Underscore The methods in , Used to traverse the 1 A collection of , And the return value of all processors as 1 Number of arrays returned
return _.map(this.models, function(model) {
// Returns the values of the current model attr Attribute values
return model.get(attr);
});
},
// Replace all model data in the collection (models)
// This action deletes all the current data and state in the collection , And reset the data to models
// models It should be 1 An array , Can contain 1 A series of Model The model object , Or the original object ( Will be in add Method is automatically created as a model object )
reset : function(models, options) {
// models It's the model that makes the substitution ( Or data ) An array of
models || ( models = []);
// options The default is 1 An empty object
options || ( options = {});
// Traversal the models in the current collection , In turn, they are deleted and unreferenced from the collection
for(var i = 0, l = this.models.length; i < l; i++) {
this._removeReference(this.models[i]);
}
// Deletes the collection data and resets the state
this._reset();
// through add Method adds new model data to the collection
// Here by exnted Method