2015-10-08 11:40:12 -07:00
// Backbone.js 1.2.3
2015-08-27 12:03:05 -07:00
// (c) 2010-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://backbonejs.org
( function ( factory ) {
// Establish the root object, `window` (`self`) in the browser, or `global` on the server.
// We use `self` instead of `window` for `WebWorker` support.
var root = ( typeof self == 'object' && self . self == self && self ) ||
( typeof global == 'object' && global . global == global && global ) ;
// Set up Backbone appropriately for the environment. Start with AMD.
if ( typeof define === 'function' && define . amd ) {
define ( [ 'underscore' , 'jquery' , 'exports' ] , function ( _ , $ , exports ) {
// Export global even in AMD case in case this script is loaded with
// others that may still expect a global Backbone.
root . Backbone = factory ( root , exports , _ , $ ) ;
} ) ;
// Next for Node.js or CommonJS. jQuery may not be needed as a module.
} else if ( typeof exports !== 'undefined' ) {
var _ = require ( 'underscore' ) , $ ;
try { $ = require ( 'jquery' ) ; } catch ( e ) { }
factory ( root , exports , _ , $ ) ;
// Finally, as a browser global.
} else {
root . Backbone = factory ( root , { } , root . _ , ( root . jQuery || root . Zepto || root . ender || root . $ ) ) ;
}
} ( function ( root , Backbone , _ , $ ) {
// Initial Setup
// -------------
// Save the previous value of the `Backbone` variable, so that it can be
// restored later on, if `noConflict` is used.
var previousBackbone = root . Backbone ;
// Create a local reference to a common array method we'll want to use later.
2015-10-08 11:40:12 -07:00
var slice = Array . prototype . slice ;
2015-08-27 12:03:05 -07:00
// Current version of the library. Keep in sync with `package.json`.
2015-10-08 11:40:12 -07:00
Backbone . VERSION = '1.2.3' ;
2015-08-27 12:03:05 -07:00
// For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
// the `$` variable.
Backbone . $ = $ ;
// Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
// to its previous owner. Returns a reference to this Backbone object.
Backbone . noConflict = function ( ) {
root . Backbone = previousBackbone ;
return this ;
} ;
// Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
// will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and
// set a `X-Http-Method-Override` header.
Backbone . emulateHTTP = false ;
// Turn on `emulateJSON` to support legacy servers that can't deal with direct
// `application/json` requests ... this will encode the body as
// `application/x-www-form-urlencoded` instead and will send the model in a
// form param named `model`.
Backbone . emulateJSON = false ;
2015-10-08 11:40:12 -07:00
// Proxy Backbone class methods to Underscore functions, wrapping the model's
// `attributes` object or collection's `models` array behind the scenes.
//
// collection.filter(function(model) { return model.get('age') > 10 });
// collection.each(this.addView);
//
// `Function#apply` can be slow so we use the method's arg count, if we know it.
2015-08-27 12:03:05 -07:00
var addMethod = function ( length , method , attribute ) {
switch ( length ) {
case 1 : return function ( ) {
return _ [ method ] ( this [ attribute ] ) ;
} ;
case 2 : return function ( value ) {
return _ [ method ] ( this [ attribute ] , value ) ;
} ;
case 3 : return function ( iteratee , context ) {
2015-10-08 11:40:12 -07:00
return _ [ method ] ( this [ attribute ] , cb ( iteratee , this ) , context ) ;
2015-08-27 12:03:05 -07:00
} ;
case 4 : return function ( iteratee , defaultVal , context ) {
2015-10-08 11:40:12 -07:00
return _ [ method ] ( this [ attribute ] , cb ( iteratee , this ) , defaultVal , context ) ;
2015-08-27 12:03:05 -07:00
} ;
default : return function ( ) {
var args = slice . call ( arguments ) ;
args . unshift ( this [ attribute ] ) ;
return _ [ method ] . apply ( _ , args ) ;
} ;
}
} ;
var addUnderscoreMethods = function ( Class , methods , attribute ) {
_ . each ( methods , function ( length , method ) {
if ( _ [ method ] ) Class . prototype [ method ] = addMethod ( length , method , attribute ) ;
} ) ;
} ;
2015-10-08 11:40:12 -07:00
// Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`.
var cb = function ( iteratee , instance ) {
if ( _ . isFunction ( iteratee ) ) return iteratee ;
if ( _ . isObject ( iteratee ) && ! instance . _isModel ( iteratee ) ) return modelMatcher ( iteratee ) ;
if ( _ . isString ( iteratee ) ) return function ( model ) { return model . get ( iteratee ) ; } ;
return iteratee ;
} ;
var modelMatcher = function ( attrs ) {
var matcher = _ . matches ( attrs ) ;
return function ( model ) {
return matcher ( model . attributes ) ;
} ;
} ;
2015-08-27 12:03:05 -07:00
// Backbone.Events
// ---------------
// A module that can be mixed in to *any object* in order to provide it with
2015-10-08 11:40:12 -07:00
// a custom event channel. You may bind a callback to an event with `on` or
// remove with `off`; `trigger`-ing an event fires all callbacks in
2015-08-27 12:03:05 -07:00
// succession.
//
// var object = {};
// _.extend(object, Backbone.Events);
// object.on('expand', function(){ alert('expanded'); });
// object.trigger('expand');
//
var Events = Backbone . Events = { } ;
// Regular expression used to split event strings.
var eventSplitter = /\s+/ ;
// Iterates over the standard `event, callback` (as well as the fancy multiple
// space-separated events `"change blur", callback` and jQuery-style event
2015-10-08 11:40:12 -07:00
// maps `{event: callback}`).
var eventsApi = function ( iteratee , events , name , callback , opts ) {
2015-08-27 12:03:05 -07:00
var i = 0 , names ;
if ( name && typeof name === 'object' ) {
// Handle event maps.
if ( callback !== void 0 && 'context' in opts && opts . context === void 0 ) opts . context = callback ;
for ( names = _ . keys ( name ) ; i < names . length ; i ++ ) {
2015-10-08 11:40:12 -07:00
events = eventsApi ( iteratee , events , names [ i ] , name [ names [ i ] ] , opts ) ;
2015-08-27 12:03:05 -07:00
}
} else if ( name && eventSplitter . test ( name ) ) {
2015-10-08 11:40:12 -07:00
// Handle space separated event names by delegating them individually.
2015-08-27 12:03:05 -07:00
for ( names = name . split ( eventSplitter ) ; i < names . length ; i ++ ) {
2015-10-08 11:40:12 -07:00
events = iteratee ( events , names [ i ] , callback , opts ) ;
2015-08-27 12:03:05 -07:00
}
} else {
2015-10-08 11:40:12 -07:00
// Finally, standard events.
events = iteratee ( events , name , callback , opts ) ;
2015-08-27 12:03:05 -07:00
}
2015-10-08 11:40:12 -07:00
return events ;
2015-08-27 12:03:05 -07:00
} ;
// Bind an event to a `callback` function. Passing `"all"` will bind
// the callback to all events fired.
Events . on = function ( name , callback , context ) {
return internalOn ( this , name , callback , context ) ;
} ;
2015-10-08 11:40:12 -07:00
// Guard the `listening` argument from the public API.
2015-08-27 12:03:05 -07:00
var internalOn = function ( obj , name , callback , context , listening ) {
obj . _events = eventsApi ( onApi , obj . _events || { } , name , callback , {
context : context ,
ctx : obj ,
listening : listening
} ) ;
if ( listening ) {
var listeners = obj . _listeners || ( obj . _listeners = { } ) ;
listeners [ listening . id ] = listening ;
}
return obj ;
} ;
// Inversion-of-control versions of `on`. Tell *this* object to listen to
2015-10-08 11:40:12 -07:00
// an event in another object... keeping track of what it's listening to
// for easier unbinding later.
2015-08-27 12:03:05 -07:00
Events . listenTo = function ( obj , name , callback ) {
if ( ! obj ) return this ;
var id = obj . _listenId || ( obj . _listenId = _ . uniqueId ( 'l' ) ) ;
var listeningTo = this . _listeningTo || ( this . _listeningTo = { } ) ;
var listening = listeningTo [ id ] ;
// This object is not listening to any other events on `obj` yet.
// Setup the necessary references to track the listening callbacks.
if ( ! listening ) {
var thisId = this . _listenId || ( this . _listenId = _ . uniqueId ( 'l' ) ) ;
listening = listeningTo [ id ] = { obj : obj , objId : id , id : thisId , listeningTo : listeningTo , count : 0 } ;
}
// Bind callbacks on obj, and keep track of them on listening.
internalOn ( obj , name , callback , this , listening ) ;
return this ;
} ;
// The reducing API that adds a callback to the `events` object.
var onApi = function ( events , name , callback , options ) {
if ( callback ) {
var handlers = events [ name ] || ( events [ name ] = [ ] ) ;
var context = options . context , ctx = options . ctx , listening = options . listening ;
if ( listening ) listening . count ++ ;
handlers . push ( { callback : callback , context : context , ctx : context || ctx , listening : listening } ) ;
}
return events ;
} ;
// Remove one or many callbacks. If `context` is null, removes all
// callbacks with that function. If `callback` is null, removes all
// callbacks for the event. If `name` is null, removes all bound
// callbacks for all events.
Events . off = function ( name , callback , context ) {
if ( ! this . _events ) return this ;
this . _events = eventsApi ( offApi , this . _events , name , callback , {
context : context ,
listeners : this . _listeners
} ) ;
return this ;
} ;
// Tell this object to stop listening to either specific events ... or
// to every object it's currently listening to.
Events . stopListening = function ( obj , name , callback ) {
var listeningTo = this . _listeningTo ;
if ( ! listeningTo ) return this ;
var ids = obj ? [ obj . _listenId ] : _ . keys ( listeningTo ) ;
for ( var i = 0 ; i < ids . length ; i ++ ) {
var listening = listeningTo [ ids [ i ] ] ;
// If listening doesn't exist, this object is not currently
// listening to obj. Break out early.
if ( ! listening ) break ;
listening . obj . off ( name , callback , this ) ;
}
if ( _ . isEmpty ( listeningTo ) ) this . _listeningTo = void 0 ;
return this ;
} ;
// The reducing API that removes a callback from the `events` object.
var offApi = function ( events , name , callback , options ) {
if ( ! events ) return ;
var i = 0 , listening ;
var context = options . context , listeners = options . listeners ;
// Delete all events listeners and "drop" events.
if ( ! name && ! callback && ! context ) {
var ids = _ . keys ( listeners ) ;
for ( ; i < ids . length ; i ++ ) {
listening = listeners [ ids [ i ] ] ;
delete listeners [ listening . id ] ;
delete listening . listeningTo [ listening . objId ] ;
}
return ;
}
var names = name ? [ name ] : _ . keys ( events ) ;
for ( ; i < names . length ; i ++ ) {
name = names [ i ] ;
var handlers = events [ name ] ;
// Bail out if there are no events stored.
if ( ! handlers ) break ;
// Replace events if there are any remaining. Otherwise, clean up.
var remaining = [ ] ;
for ( var j = 0 ; j < handlers . length ; j ++ ) {
var handler = handlers [ j ] ;
if (
callback && callback !== handler . callback &&
callback !== handler . callback . _callback ||
context && context !== handler . context
) {
remaining . push ( handler ) ;
} else {
listening = handler . listening ;
if ( listening && -- listening . count === 0 ) {
delete listeners [ listening . id ] ;
delete listening . listeningTo [ listening . objId ] ;
}
}
}
// Update tail event if the list has any events. Otherwise, clean up.
if ( remaining . length ) {
events [ name ] = remaining ;
} else {
delete events [ name ] ;
}
}
if ( _ . size ( events ) ) return events ;
} ;
// Bind an event to only be triggered a single time. After the first time
2015-10-08 11:40:12 -07:00
// the callback is invoked, its listener will be removed. If multiple events
// are passed in using the space-separated syntax, the handler will fire
// once for each event, not once for a combination of all events.
2015-08-27 12:03:05 -07:00
Events . once = function ( name , callback , context ) {
// Map the event into a `{event: once}` object.
var events = eventsApi ( onceMap , { } , name , callback , _ . bind ( this . off , this ) ) ;
return this . on ( events , void 0 , context ) ;
} ;
// Inversion-of-control versions of `once`.
Events . listenToOnce = function ( obj , name , callback ) {
// Map the event into a `{event: once}` object.
var events = eventsApi ( onceMap , { } , name , callback , _ . bind ( this . stopListening , this , obj ) ) ;
return this . listenTo ( obj , events ) ;
} ;
// Reduces the event callbacks into a map of `{event: onceWrapper}`.
// `offer` unbinds the `onceWrapper` after it has been called.
var onceMap = function ( map , name , callback , offer ) {
if ( callback ) {
var once = map [ name ] = _ . once ( function ( ) {
offer ( name , once ) ;
callback . apply ( this , arguments ) ;
} ) ;
once . _callback = callback ;
}
return map ;
} ;
// Trigger one or many events, firing all bound callbacks. Callbacks are
// passed the same arguments as `trigger` is, apart from the event name
// (unless you're listening on `"all"`, which will cause your callback to
// receive the true name of the event as the first argument).
Events . trigger = function ( name ) {
if ( ! this . _events ) return this ;
var length = Math . max ( 0 , arguments . length - 1 ) ;
var args = Array ( length ) ;
for ( var i = 0 ; i < length ; i ++ ) args [ i ] = arguments [ i + 1 ] ;
eventsApi ( triggerApi , this . _events , name , void 0 , args ) ;
return this ;
} ;
// Handles triggering the appropriate event callbacks.
var triggerApi = function ( objEvents , name , cb , args ) {
if ( objEvents ) {
var events = objEvents [ name ] ;
var allEvents = objEvents . all ;
if ( events && allEvents ) allEvents = allEvents . slice ( ) ;
if ( events ) triggerEvents ( events , args ) ;
if ( allEvents ) triggerEvents ( allEvents , [ name ] . concat ( args ) ) ;
}
return objEvents ;
} ;
// A difficult-to-believe, but optimized internal dispatch function for
// triggering events. Tries to keep the usual cases speedy (most internal
// Backbone events have 3 arguments).
var triggerEvents = function ( events , args ) {
var ev , i = - 1 , l = events . length , a1 = args [ 0 ] , a2 = args [ 1 ] , a3 = args [ 2 ] ;
switch ( args . length ) {
case 0 : while ( ++ i < l ) ( ev = events [ i ] ) . callback . call ( ev . ctx ) ; return ;
case 1 : while ( ++ i < l ) ( ev = events [ i ] ) . callback . call ( ev . ctx , a1 ) ; return ;
case 2 : while ( ++ i < l ) ( ev = events [ i ] ) . callback . call ( ev . ctx , a1 , a2 ) ; return ;
case 3 : while ( ++ i < l ) ( ev = events [ i ] ) . callback . call ( ev . ctx , a1 , a2 , a3 ) ; return ;
default : while ( ++ i < l ) ( ev = events [ i ] ) . callback . apply ( ev . ctx , args ) ; return ;
}
} ;
// Aliases for backwards compatibility.
Events . bind = Events . on ;
Events . unbind = Events . off ;
// Allow the `Backbone` object to serve as a global event bus, for folks who
// want global "pubsub" in a convenient place.
_ . extend ( Backbone , Events ) ;
// Backbone.Model
// --------------
// Backbone **Models** are the basic data object in the framework --
// frequently representing a row in a table in a database on your server.
// A discrete chunk of data and a bunch of useful, related methods for
// performing computations and transformations on that data.
// Create a new model with the specified attributes. A client id (`cid`)
// is automatically generated and assigned for you.
var Model = Backbone . Model = function ( attributes , options ) {
var attrs = attributes || { } ;
options || ( options = { } ) ;
this . cid = _ . uniqueId ( this . cidPrefix ) ;
this . attributes = { } ;
if ( options . collection ) this . collection = options . collection ;
if ( options . parse ) attrs = this . parse ( attrs , options ) || { } ;
attrs = _ . defaults ( { } , attrs , _ . result ( this , 'defaults' ) ) ;
this . set ( attrs , options ) ;
this . changed = { } ;
this . initialize . apply ( this , arguments ) ;
} ;
// Attach all inheritable methods to the Model prototype.
_ . extend ( Model . prototype , Events , {
// A hash of attributes whose current and previous value differ.
changed : null ,
// The value returned during the last failed validation.
validationError : null ,
// The default name for the JSON `id` attribute is `"id"`. MongoDB and
// CouchDB users may want to set this to `"_id"`.
idAttribute : 'id' ,
// The prefix is used to create the client id which is used to identify models locally.
// You may want to override this if you're experiencing name clashes with model ids.
cidPrefix : 'c' ,
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize : function ( ) { } ,
// Return a copy of the model's `attributes` object.
toJSON : function ( options ) {
return _ . clone ( this . attributes ) ;
} ,
// Proxy `Backbone.sync` by default -- but override this if you need
// custom syncing semantics for *this* particular model.
sync : function ( ) {
return Backbone . sync . apply ( this , arguments ) ;
} ,
// Get the value of an attribute.
get : function ( attr ) {
return this . attributes [ attr ] ;
} ,
// Get the HTML-escaped value of an attribute.
escape : function ( attr ) {
return _ . escape ( this . get ( attr ) ) ;
} ,
// Returns `true` if the attribute contains a value that is not null
// or undefined.
has : function ( attr ) {
return this . get ( attr ) != null ;
} ,
// Special-cased proxy to underscore's `_.matches` method.
matches : function ( attrs ) {
return ! ! _ . iteratee ( attrs , this ) ( this . attributes ) ;
} ,
// Set a hash of model attributes on the object, firing `"change"`. This is
// the core primitive operation of a model, updating the data and notifying
// anyone who needs to know about the change in state. The heart of the beast.
set : function ( key , val , options ) {
if ( key == null ) return this ;
// Handle both `"key", value` and `{key: value}` -style arguments.
var attrs ;
if ( typeof key === 'object' ) {
attrs = key ;
options = val ;
} else {
( attrs = { } ) [ key ] = val ;
}
options || ( options = { } ) ;
// Run validation.
if ( ! this . _validate ( attrs , options ) ) return false ;
// Extract attributes and options.
var unset = options . unset ;
var silent = options . silent ;
var changes = [ ] ;
var changing = this . _changing ;
this . _changing = true ;
if ( ! changing ) {
this . _previousAttributes = _ . clone ( this . attributes ) ;
this . changed = { } ;
}
var current = this . attributes ;
var changed = this . changed ;
var prev = this . _previousAttributes ;
// For each `set` attribute, update or delete the current value.
for ( var attr in attrs ) {
val = attrs [ attr ] ;
if ( ! _ . isEqual ( current [ attr ] , val ) ) changes . push ( attr ) ;
if ( ! _ . isEqual ( prev [ attr ] , val ) ) {
changed [ attr ] = val ;
} else {
delete changed [ attr ] ;
}
unset ? delete current [ attr ] : current [ attr ] = val ;
}
2015-10-08 11:40:12 -07:00
// Update the `id`.
this . id = this . get ( this . idAttribute ) ;
2015-08-27 12:03:05 -07:00
// Trigger all relevant attribute changes.
if ( ! silent ) {
if ( changes . length ) this . _pending = options ;
for ( var i = 0 ; i < changes . length ; i ++ ) {
this . trigger ( 'change:' + changes [ i ] , this , current [ changes [ i ] ] , options ) ;
}
}
// You might be wondering why there's a `while` loop here. Changes can
// be recursively nested within `"change"` events.
if ( changing ) return this ;
if ( ! silent ) {
while ( this . _pending ) {
options = this . _pending ;
this . _pending = false ;
this . trigger ( 'change' , this , options ) ;
}
}
this . _pending = false ;
this . _changing = false ;
return this ;
} ,
// Remove an attribute from the model, firing `"change"`. `unset` is a noop
// if the attribute doesn't exist.
unset : function ( attr , options ) {
return this . set ( attr , void 0 , _ . extend ( { } , options , { unset : true } ) ) ;
} ,
// Clear all attributes on the model, firing `"change"`.
clear : function ( options ) {
var attrs = { } ;
for ( var key in this . attributes ) attrs [ key ] = void 0 ;
return this . set ( attrs , _ . extend ( { } , options , { unset : true } ) ) ;
} ,
// Determine if the model has changed since the last `"change"` event.
// If you specify an attribute name, determine if that attribute has changed.
hasChanged : function ( attr ) {
if ( attr == null ) return ! _ . isEmpty ( this . changed ) ;
return _ . has ( this . changed , attr ) ;
} ,
// Return an object containing all the attributes that have changed, or
// false if there are no changed attributes. Useful for determining what
// parts of a view need to be updated and/or what attributes need to be
// persisted to the server. Unset attributes will be set to undefined.
// You can also pass an attributes object to diff against the model,
// determining if there *would be* a change.
changedAttributes : function ( diff ) {
if ( ! diff ) return this . hasChanged ( ) ? _ . clone ( this . changed ) : false ;
var old = this . _changing ? this . _previousAttributes : this . attributes ;
var changed = { } ;
for ( var attr in diff ) {
var val = diff [ attr ] ;
if ( _ . isEqual ( old [ attr ] , val ) ) continue ;
changed [ attr ] = val ;
}
return _ . size ( changed ) ? changed : false ;
} ,
// Get the previous value of an attribute, recorded at the time the last
// `"change"` event was fired.
previous : function ( attr ) {
if ( attr == null || ! this . _previousAttributes ) return null ;
return this . _previousAttributes [ attr ] ;
} ,
// Get all of the attributes of the model at the time of the previous
// `"change"` event.
previousAttributes : function ( ) {
return _ . clone ( this . _previousAttributes ) ;
} ,
// Fetch the model from the server, merging the response with the model's
// local attributes. Any changed attributes will trigger a "change" event.
fetch : function ( options ) {
options = _ . extend ( { parse : true } , options ) ;
var model = this ;
var success = options . success ;
options . success = function ( resp ) {
var serverAttrs = options . parse ? model . parse ( resp , options ) : resp ;
if ( ! model . set ( serverAttrs , options ) ) return false ;
if ( success ) success . call ( options . context , model , resp , options ) ;
model . trigger ( 'sync' , model , resp , options ) ;
} ;
wrapError ( this , options ) ;
return this . sync ( 'read' , this , options ) ;
} ,
// Set a hash of model attributes, and sync the model to the server.
// If the server returns an attributes hash that differs, the model's
// state will be `set` again.
save : function ( key , val , options ) {
// Handle both `"key", value` and `{key: value}` -style arguments.
var attrs ;
if ( key == null || typeof key === 'object' ) {
attrs = key ;
options = val ;
} else {
( attrs = { } ) [ key ] = val ;
}
options = _ . extend ( { validate : true , parse : true } , options ) ;
var wait = options . wait ;
// If we're not waiting and attributes exist, save acts as
// `set(attr).save(null, opts)` with validation. Otherwise, check if
// the model will be valid when the attributes, if any, are set.
if ( attrs && ! wait ) {
if ( ! this . set ( attrs , options ) ) return false ;
} else {
if ( ! this . _validate ( attrs , options ) ) return false ;
}
// After a successful server-side save, the client is (optionally)
// updated with the server-side state.
var model = this ;
var success = options . success ;
var attributes = this . attributes ;
options . success = function ( resp ) {
// Ensure attributes are restored during synchronous saves.
model . attributes = attributes ;
var serverAttrs = options . parse ? model . parse ( resp , options ) : resp ;
if ( wait ) serverAttrs = _ . extend ( { } , attrs , serverAttrs ) ;
if ( serverAttrs && ! model . set ( serverAttrs , options ) ) return false ;
if ( success ) success . call ( options . context , model , resp , options ) ;
model . trigger ( 'sync' , model , resp , options ) ;
} ;
wrapError ( this , options ) ;
// Set temporary attributes if `{wait: true}` to properly find new ids.
if ( attrs && wait ) this . attributes = _ . extend ( { } , attributes , attrs ) ;
var method = this . isNew ( ) ? 'create' : ( options . patch ? 'patch' : 'update' ) ;
if ( method === 'patch' && ! options . attrs ) options . attrs = attrs ;
var xhr = this . sync ( method , this , options ) ;
// Restore attributes.
this . attributes = attributes ;
return xhr ;
} ,
// Destroy this model on the server if it was already persisted.
// Optimistically removes the model from its collection, if it has one.
// If `wait: true` is passed, waits for the server to respond before removal.
destroy : function ( options ) {
options = options ? _ . clone ( options ) : { } ;
var model = this ;
var success = options . success ;
var wait = options . wait ;
var destroy = function ( ) {
model . stopListening ( ) ;
model . trigger ( 'destroy' , model , model . collection , options ) ;
} ;
options . success = function ( resp ) {
if ( wait ) destroy ( ) ;
if ( success ) success . call ( options . context , model , resp , options ) ;
if ( ! model . isNew ( ) ) model . trigger ( 'sync' , model , resp , options ) ;
} ;
var xhr = false ;
if ( this . isNew ( ) ) {
_ . defer ( options . success ) ;
} else {
wrapError ( this , options ) ;
xhr = this . sync ( 'delete' , this , options ) ;
}
if ( ! wait ) destroy ( ) ;
return xhr ;
} ,
// Default URL for the model's representation on the server -- if you're
// using Backbone's restful methods, override this to change the endpoint
// that will be called.
url : function ( ) {
var base =
_ . result ( this , 'urlRoot' ) ||
_ . result ( this . collection , 'url' ) ||
urlError ( ) ;
if ( this . isNew ( ) ) return base ;
var id = this . get ( this . idAttribute ) ;
return base . replace ( /[^\/]$/ , '$&/' ) + encodeURIComponent ( id ) ;
} ,
// **parse** converts a response into the hash of attributes to be `set` on
// the model. The default implementation is just to pass the response along.
parse : function ( resp , options ) {
return resp ;
} ,
// Create a new model with identical attributes to this one.
clone : function ( ) {
return new this . constructor ( this . attributes ) ;
} ,
// A model is new if it has never been saved to the server, and lacks an id.
isNew : function ( ) {
return ! this . has ( this . idAttribute ) ;
} ,
// Check if the model is currently in a valid state.
isValid : function ( options ) {
return this . _validate ( { } , _ . defaults ( { validate : true } , options ) ) ;
} ,
// Run validation against the next complete set of model attributes,
// returning `true` if all is well. Otherwise, fire an `"invalid"` event.
_validate : function ( attrs , options ) {
if ( ! options . validate || ! this . validate ) return true ;
attrs = _ . extend ( { } , this . attributes , attrs ) ;
var error = this . validationError = this . validate ( attrs , options ) || null ;
if ( ! error ) return true ;
this . trigger ( 'invalid' , this , error , _ . extend ( options , { validationError : error } ) ) ;
return false ;
}
} ) ;
2015-10-08 11:40:12 -07:00
// Underscore methods that we want to implement on the Model, mapped to the
// number of arguments they take.
2015-08-27 12:03:05 -07:00
var modelMethods = { keys : 1 , values : 1 , pairs : 1 , invert : 1 , pick : 0 ,
omit : 0 , chain : 1 , isEmpty : 1 } ;
// Mix in each Underscore method as a proxy to `Model#attributes`.
addUnderscoreMethods ( Model , modelMethods , 'attributes' ) ;
// Backbone.Collection
// -------------------
// If models tend to represent a single row of data, a Backbone Collection is
// more analogous to a table full of data ... or a small slice or page of that
// table, or a collection of rows that belong together for a particular reason
// -- all of the messages in this particular folder, all of the documents
// belonging to this particular author, and so on. Collections maintain
// indexes of their models, both in order, and for lookup by `id`.
// Create a new **Collection**, perhaps to contain a specific type of `model`.
// If a `comparator` is specified, the Collection will maintain
// its models in sort order, as they're added and removed.
var Collection = Backbone . Collection = function ( models , options ) {
options || ( options = { } ) ;
if ( options . model ) this . model = options . model ;
if ( options . comparator !== void 0 ) this . comparator = options . comparator ;
this . _reset ( ) ;
this . initialize . apply ( this , arguments ) ;
if ( models ) this . reset ( models , _ . extend ( { silent : true } , options ) ) ;
} ;
// Default options for `Collection#set`.
var setOptions = { add : true , remove : true , merge : true } ;
var addOptions = { add : true , remove : false } ;
2015-10-08 11:40:12 -07:00
// Splices `insert` into `array` at index `at`.
var splice = function ( array , insert , at ) {
at = Math . min ( Math . max ( at , 0 ) , array . length ) ;
var tail = Array ( array . length - at ) ;
var length = insert . length ;
for ( var i = 0 ; i < tail . length ; i ++ ) tail [ i ] = array [ i + at ] ;
for ( i = 0 ; i < length ; i ++ ) array [ i + at ] = insert [ i ] ;
for ( i = 0 ; i < tail . length ; i ++ ) array [ i + length + at ] = tail [ i ] ;
} ;
2015-08-27 12:03:05 -07:00
// Define the Collection's inheritable methods.
_ . extend ( Collection . prototype , Events , {
// The default model for a collection is just a **Backbone.Model**.
// This should be overridden in most cases.
model : Model ,
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize : function ( ) { } ,
// The JSON representation of a Collection is an array of the
// models' attributes.
toJSON : function ( options ) {
return this . map ( function ( model ) { return model . toJSON ( options ) ; } ) ;
} ,
// Proxy `Backbone.sync` by default.
sync : function ( ) {
return Backbone . sync . apply ( this , arguments ) ;
} ,
2015-10-08 11:40:12 -07:00
// Add a model, or list of models to the set. `models` may be Backbone
// Models or raw JavaScript objects to be converted to Models, or any
// combination of the two.
2015-08-27 12:03:05 -07:00
add : function ( models , options ) {
return this . set ( models , _ . extend ( { merge : false } , options , addOptions ) ) ;
} ,
// Remove a model, or a list of models from the set.
remove : function ( models , options ) {
options = _ . extend ( { } , options ) ;
var singular = ! _ . isArray ( models ) ;
models = singular ? [ models ] : _ . clone ( models ) ;
var removed = this . _removeModels ( models , options ) ;
if ( ! options . silent && removed ) this . trigger ( 'update' , this , options ) ;
return singular ? removed [ 0 ] : removed ;
} ,
// Update a collection by `set`-ing a new list of models, adding new ones,
// removing models that are no longer present, and merging models that
// already exist in the collection, as necessary. Similar to **Model#set**,
// the core operation for updating the data contained by the collection.
set : function ( models , options ) {
2015-10-08 11:40:12 -07:00
if ( models == null ) return ;
2015-08-27 12:03:05 -07:00
options = _ . defaults ( { } , options , setOptions ) ;
if ( options . parse && ! this . _isModel ( models ) ) models = this . parse ( models , options ) ;
2015-10-08 11:40:12 -07:00
2015-08-27 12:03:05 -07:00
var singular = ! _ . isArray ( models ) ;
2015-10-08 11:40:12 -07:00
models = singular ? [ models ] : models . slice ( ) ;
2015-08-27 12:03:05 -07:00
var at = options . at ;
if ( at != null ) at = + at ;
if ( at < 0 ) at += this . length + 1 ;
2015-10-08 11:40:12 -07:00
var set = [ ] ;
var toAdd = [ ] ;
var toRemove = [ ] ;
var modelMap = { } ;
var add = options . add ;
var merge = options . merge ;
var remove = options . remove ;
var sort = false ;
2015-08-27 12:03:05 -07:00
var sortable = this . comparator && ( at == null ) && options . sort !== false ;
var sortAttr = _ . isString ( this . comparator ) ? this . comparator : null ;
// Turn bare objects into model references, and prevent invalid models
// from being added.
2015-10-08 11:40:12 -07:00
var model ;
2015-08-27 12:03:05 -07:00
for ( var i = 0 ; i < models . length ; i ++ ) {
2015-10-08 11:40:12 -07:00
model = models [ i ] ;
2015-08-27 12:03:05 -07:00
// If a duplicate is found, prevent it from being added and
// optionally merge it into the existing model.
2015-10-08 11:40:12 -07:00
var existing = this . get ( model ) ;
if ( existing ) {
if ( merge && model !== existing ) {
var attrs = this . _isModel ( model ) ? model . attributes : model ;
2015-08-27 12:03:05 -07:00
if ( options . parse ) attrs = existing . parse ( attrs , options ) ;
existing . set ( attrs , options ) ;
2015-10-08 11:40:12 -07:00
if ( sortable && ! sort ) sort = existing . hasChanged ( sortAttr ) ;
}
if ( ! modelMap [ existing . cid ] ) {
modelMap [ existing . cid ] = true ;
set . push ( existing ) ;
2015-08-27 12:03:05 -07:00
}
models [ i ] = existing ;
// If this is a new, valid model, push it to the `toAdd` list.
} else if ( add ) {
2015-10-08 11:40:12 -07:00
model = models [ i ] = this . _prepareModel ( model , options ) ;
if ( model ) {
toAdd . push ( model ) ;
this . _addReference ( model , options ) ;
modelMap [ model . cid ] = true ;
set . push ( model ) ;
}
2015-08-27 12:03:05 -07:00
}
}
2015-10-08 11:40:12 -07:00
// Remove stale models.
2015-08-27 12:03:05 -07:00
if ( remove ) {
2015-10-08 11:40:12 -07:00
for ( i = 0 ; i < this . length ; i ++ ) {
model = this . models [ i ] ;
if ( ! modelMap [ model . cid ] ) toRemove . push ( model ) ;
2015-08-27 12:03:05 -07:00
}
if ( toRemove . length ) this . _removeModels ( toRemove , options ) ;
}
// See if sorting is needed, update `length` and splice in new models.
2015-10-08 11:40:12 -07:00
var orderChanged = false ;
var replace = ! sortable && add && remove ;
if ( set . length && replace ) {
orderChanged = this . length != set . length || _ . some ( this . models , function ( model , index ) {
return model !== set [ index ] ;
} ) ;
this . models . length = 0 ;
splice ( this . models , set , 0 ) ;
this . length = this . models . length ;
} else if ( toAdd . length ) {
2015-08-27 12:03:05 -07:00
if ( sortable ) sort = true ;
2015-10-08 11:40:12 -07:00
splice ( this . models , toAdd , at == null ? this . length : at ) ;
this . length = this . models . length ;
2015-08-27 12:03:05 -07:00
}
// Silently sort the collection if appropriate.
if ( sort ) this . sort ( { silent : true } ) ;
// Unless silenced, it's time to fire all appropriate add/sort events.
if ( ! options . silent ) {
2015-10-08 11:40:12 -07:00
for ( i = 0 ; i < toAdd . length ; i ++ ) {
if ( at != null ) options . index = at + i ;
model = toAdd [ i ] ;
model . trigger ( 'add' , model , this , options ) ;
2015-08-27 12:03:05 -07:00
}
if ( sort || orderChanged ) this . trigger ( 'sort' , this , options ) ;
if ( toAdd . length || toRemove . length ) this . trigger ( 'update' , this , options ) ;
}
// Return the added (or merged) model (or models).
return singular ? models [ 0 ] : models ;
} ,
// When you have more items than you want to add or remove individually,
// you can reset the entire set with a new list of models, without firing
// any granular `add` or `remove` events. Fires `reset` when finished.
// Useful for bulk operations and optimizations.
reset : function ( models , options ) {
options = options ? _ . clone ( options ) : { } ;
for ( var i = 0 ; i < this . models . length ; i ++ ) {
this . _removeReference ( this . models [ i ] , options ) ;
}
options . previousModels = this . models ;
this . _reset ( ) ;
models = this . add ( models , _ . extend ( { silent : true } , options ) ) ;
if ( ! options . silent ) this . trigger ( 'reset' , this , options ) ;
return models ;
} ,
// Add a model to the end of the collection.
push : function ( model , options ) {
return this . add ( model , _ . extend ( { at : this . length } , options ) ) ;
} ,
// Remove a model from the end of the collection.
pop : function ( options ) {
var model = this . at ( this . length - 1 ) ;
return this . remove ( model , options ) ;
} ,
// Add a model to the beginning of the collection.
unshift : function ( model , options ) {
return this . add ( model , _ . extend ( { at : 0 } , options ) ) ;
} ,
// Remove a model from the beginning of the collection.
shift : function ( options ) {
var model = this . at ( 0 ) ;
return this . remove ( model , options ) ;
} ,
// Slice out a sub-array of models from the collection.
slice : function ( ) {
return slice . apply ( this . models , arguments ) ;
} ,
// Get a model from the set by id.
get : function ( obj ) {
if ( obj == null ) return void 0 ;
var id = this . modelId ( this . _isModel ( obj ) ? obj . attributes : obj ) ;
return this . _byId [ obj ] || this . _byId [ id ] || this . _byId [ obj . cid ] ;
} ,
// Get the model at the given index.
at : function ( index ) {
if ( index < 0 ) index += this . length ;
return this . models [ index ] ;
} ,
// Return models with matching attributes. Useful for simple cases of
// `filter`.
where : function ( attrs , first ) {
2015-10-08 11:40:12 -07:00
return this [ first ? 'find' : 'filter' ] ( attrs ) ;
2015-08-27 12:03:05 -07:00
} ,
// Return the first model with matching attributes. Useful for simple cases
// of `find`.
findWhere : function ( attrs ) {
return this . where ( attrs , true ) ;
} ,
// Force the collection to re-sort itself. You don't need to call this under
// normal circumstances, as the set will maintain sort order as each item
// is added.
sort : function ( options ) {
2015-10-08 11:40:12 -07:00
var comparator = this . comparator ;
if ( ! comparator ) throw new Error ( 'Cannot sort a set without a comparator' ) ;
2015-08-27 12:03:05 -07:00
options || ( options = { } ) ;
2015-10-08 11:40:12 -07:00
var length = comparator . length ;
if ( _ . isFunction ( comparator ) ) comparator = _ . bind ( comparator , this ) ;
2015-08-27 12:03:05 -07:00
// Run sort based on type of `comparator`.
2015-10-08 11:40:12 -07:00
if ( length === 1 || _ . isString ( comparator ) ) {
this . models = this . sortBy ( comparator ) ;
2015-08-27 12:03:05 -07:00
} else {
2015-10-08 11:40:12 -07:00
this . models . sort ( comparator ) ;
2015-08-27 12:03:05 -07:00
}
if ( ! options . silent ) this . trigger ( 'sort' , this , options ) ;
return this ;
} ,
// Pluck an attribute from each model in the collection.
pluck : function ( attr ) {
return _ . invoke ( this . models , 'get' , attr ) ;
} ,
// Fetch the default set of models for this collection, resetting the
// collection when they arrive. If `reset: true` is passed, the response
// data will be passed through the `reset` method instead of `set`.
fetch : function ( options ) {
options = _ . extend ( { parse : true } , options ) ;
var success = options . success ;
var collection = this ;
options . success = function ( resp ) {
var method = options . reset ? 'reset' : 'set' ;
collection [ method ] ( resp , options ) ;
if ( success ) success . call ( options . context , collection , resp , options ) ;
collection . trigger ( 'sync' , collection , resp , options ) ;
} ;
wrapError ( this , options ) ;
return this . sync ( 'read' , this , options ) ;
} ,
// Create a new instance of a model in this collection. Add the model to the
// collection immediately, unless `wait: true` is passed, in which case we
// wait for the server to agree.
create : function ( model , options ) {
options = options ? _ . clone ( options ) : { } ;
var wait = options . wait ;
model = this . _prepareModel ( model , options ) ;
if ( ! model ) return false ;
if ( ! wait ) this . add ( model , options ) ;
var collection = this ;
var success = options . success ;
options . success = function ( model , resp , callbackOpts ) {
if ( wait ) collection . add ( model , callbackOpts ) ;
if ( success ) success . call ( callbackOpts . context , model , resp , callbackOpts ) ;
} ;
model . save ( null , options ) ;
return model ;
} ,
// **parse** converts a response into a list of models to be added to the
// collection. The default implementation is just to pass it through.
parse : function ( resp , options ) {
return resp ;
} ,
// Create a new collection with an identical list of models as this one.
clone : function ( ) {
return new this . constructor ( this . models , {
model : this . model ,
comparator : this . comparator
} ) ;
} ,
// Define how to uniquely identify models in the collection.
modelId : function ( attrs ) {
return attrs [ this . model . prototype . idAttribute || 'id' ] ;
} ,
// Private method to reset all internal state. Called when the collection
// is first initialized or reset.
_reset : function ( ) {
this . length = 0 ;
this . models = [ ] ;
this . _byId = { } ;
} ,
// Prepare a hash of attributes (or other model) to be added to this
// collection.
_prepareModel : function ( attrs , options ) {
if ( this . _isModel ( attrs ) ) {
if ( ! attrs . collection ) attrs . collection = this ;
return attrs ;
}
options = options ? _ . clone ( options ) : { } ;
options . collection = this ;
var model = new this . model ( attrs , options ) ;
if ( ! model . validationError ) return model ;
this . trigger ( 'invalid' , this , model . validationError , options ) ;
return false ;
} ,
// Internal method called by both remove and set.
_removeModels : function ( models , options ) {
var removed = [ ] ;
for ( var i = 0 ; i < models . length ; i ++ ) {
var model = this . get ( models [ i ] ) ;
if ( ! model ) continue ;
var index = this . indexOf ( model ) ;
this . models . splice ( index , 1 ) ;
this . length -- ;
if ( ! options . silent ) {
options . index = index ;
model . trigger ( 'remove' , model , this , options ) ;
}
removed . push ( model ) ;
this . _removeReference ( model , options ) ;
}
return removed . length ? removed : false ;
} ,
// Method for checking whether an object should be considered a model for
// the purposes of adding to the collection.
_isModel : function ( model ) {
return model instanceof Model ;
} ,
// Internal method to create a model's ties to a collection.
_addReference : function ( model , options ) {
this . _byId [ model . cid ] = model ;
var id = this . modelId ( model . attributes ) ;
if ( id != null ) this . _byId [ id ] = model ;
model . on ( 'all' , this . _onModelEvent , this ) ;
} ,
// Internal method to sever a model's ties to a collection.
_removeReference : function ( model , options ) {
delete this . _byId [ model . cid ] ;
var id = this . modelId ( model . attributes ) ;
if ( id != null ) delete this . _byId [ id ] ;
if ( this === model . collection ) delete model . collection ;
model . off ( 'all' , this . _onModelEvent , this ) ;
} ,
// Internal method called every time a model in the set fires an event.
// Sets need to update their indexes when models change ids. All other
// events simply proxy through. "add" and "remove" events that originate
// in other collections are ignored.
_onModelEvent : function ( event , model , collection , options ) {
if ( ( event === 'add' || event === 'remove' ) && collection !== this ) return ;
if ( event === 'destroy' ) this . remove ( model , options ) ;
if ( event === 'change' ) {
var prevId = this . modelId ( model . previousAttributes ( ) ) ;
var id = this . modelId ( model . attributes ) ;
if ( prevId !== id ) {
if ( prevId != null ) delete this . _byId [ prevId ] ;
if ( id != null ) this . _byId [ id ] = model ;
}
}
this . trigger . apply ( this , arguments ) ;
}
} ) ;
// Underscore methods that we want to implement on the Collection.
// 90% of the core usefulness of Backbone Collections is actually implemented
// right here:
var collectionMethods = { forEach : 3 , each : 3 , map : 3 , collect : 3 , reduce : 4 ,
foldl : 4 , inject : 4 , reduceRight : 4 , foldr : 4 , find : 3 , detect : 3 , filter : 3 ,
2015-10-08 11:40:12 -07:00
select : 3 , reject : 3 , every : 3 , all : 3 , some : 3 , any : 3 , include : 3 , includes : 3 ,
contains : 3 , invoke : 0 , max : 3 , min : 3 , toArray : 1 , size : 1 , first : 3 ,
2015-08-27 12:03:05 -07:00
head : 3 , take : 3 , initial : 3 , rest : 3 , tail : 3 , drop : 3 , last : 3 ,
without : 0 , difference : 0 , indexOf : 3 , shuffle : 1 , lastIndexOf : 3 ,
2015-10-08 11:40:12 -07:00
isEmpty : 1 , chain : 1 , sample : 3 , partition : 3 , groupBy : 3 , countBy : 3 ,
sortBy : 3 , indexBy : 3 } ;
2015-08-27 12:03:05 -07:00
// Mix in each Underscore method as a proxy to `Collection#models`.
addUnderscoreMethods ( Collection , collectionMethods , 'models' ) ;
// Backbone.View
// -------------
// Backbone Views are almost more convention than they are actual code. A View
// is simply a JavaScript object that represents a logical chunk of UI in the
// DOM. This might be a single item, an entire list, a sidebar or panel, or
// even the surrounding frame which wraps your whole app. Defining a chunk of
// UI as a **View** allows you to define your DOM events declaratively, without
// having to worry about render order ... and makes it easy for the view to
// react to specific changes in the state of your models.
// Creating a Backbone.View creates its initial element outside of the DOM,
// if an existing element is not provided...
var View = Backbone . View = function ( options ) {
this . cid = _ . uniqueId ( 'view' ) ;
_ . extend ( this , _ . pick ( options , viewOptions ) ) ;
this . _ensureElement ( ) ;
this . initialize . apply ( this , arguments ) ;
} ;
// Cached regex to split keys for `delegate`.
var delegateEventSplitter = /^(\S+)\s*(.*)$/ ;
2015-10-08 11:40:12 -07:00
// List of view options to be set as properties.
2015-08-27 12:03:05 -07:00
var viewOptions = [ 'model' , 'collection' , 'el' , 'id' , 'attributes' , 'className' , 'tagName' , 'events' ] ;
// Set up all inheritable **Backbone.View** properties and methods.
_ . extend ( View . prototype , Events , {
// The default `tagName` of a View's element is `"div"`.
tagName : 'div' ,
// jQuery delegate for element lookup, scoped to DOM elements within the
// current view. This should be preferred to global lookups where possible.
$ : function ( selector ) {
return this . $el . find ( selector ) ;
} ,
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize : function ( ) { } ,
// **render** is the core function that your view should override, in order
// to populate its element (`this.el`), with the appropriate HTML. The
// convention is for **render** to always return `this`.
render : function ( ) {
return this ;
} ,
// Remove this view by taking the element out of the DOM, and removing any
// applicable Backbone.Events listeners.
remove : function ( ) {
this . _removeElement ( ) ;
this . stopListening ( ) ;
return this ;
} ,
// Remove this view's element from the document and all event listeners
// attached to it. Exposed for subclasses using an alternative DOM
// manipulation API.
_removeElement : function ( ) {
this . $el . remove ( ) ;
} ,
// Change the view's element (`this.el` property) and re-delegate the
// view's events on the new element.
setElement : function ( element ) {
this . undelegateEvents ( ) ;
this . _setElement ( element ) ;
this . delegateEvents ( ) ;
return this ;
} ,
// Creates the `this.el` and `this.$el` references for this view using the
// given `el`. `el` can be a CSS selector or an HTML string, a jQuery
// context or an element. Subclasses can override this to utilize an
// alternative DOM manipulation API and are only required to set the
// `this.el` property.
_setElement : function ( el ) {
this . $el = el instanceof Backbone . $ ? el : Backbone . $ ( el ) ;
this . el = this . $el [ 0 ] ;
} ,
// Set callbacks, where `this.events` is a hash of
//
// *{"event selector": "callback"}*
//
// {
// 'mousedown .title': 'edit',
// 'click .button': 'save',
// 'click .open': function(e) { ... }
// }
//
// pairs. Callbacks will be bound to the view, with `this` set properly.
// Uses event delegation for efficiency.
// Omitting the selector binds the event to `this.el`.
delegateEvents : function ( events ) {
events || ( events = _ . result ( this , 'events' ) ) ;
if ( ! events ) return this ;
this . undelegateEvents ( ) ;
for ( var key in events ) {
var method = events [ key ] ;
if ( ! _ . isFunction ( method ) ) method = this [ method ] ;
if ( ! method ) continue ;
var match = key . match ( delegateEventSplitter ) ;
this . delegate ( match [ 1 ] , match [ 2 ] , _ . bind ( method , this ) ) ;
}
return this ;
} ,
// Add a single event listener to the view's element (or a child element
// using `selector`). This only works for delegate-able events: not `focus`,
// `blur`, and not `change`, `submit`, and `reset` in Internet Explorer.
delegate : function ( eventName , selector , listener ) {
this . $el . on ( eventName + '.delegateEvents' + this . cid , selector , listener ) ;
return this ;
} ,
// Clears all callbacks previously bound to the view by `delegateEvents`.
// You usually don't need to use this, but may wish to if you have multiple
// Backbone views attached to the same DOM element.
undelegateEvents : function ( ) {
if ( this . $el ) this . $el . off ( '.delegateEvents' + this . cid ) ;
return this ;
} ,
// A finer-grained `undelegateEvents` for removing a single delegated event.
// `selector` and `listener` are both optional.
undelegate : function ( eventName , selector , listener ) {
this . $el . off ( eventName + '.delegateEvents' + this . cid , selector , listener ) ;
return this ;
} ,
// Produces a DOM element to be assigned to your view. Exposed for
// subclasses using an alternative DOM manipulation API.
_createElement : function ( tagName ) {
return document . createElement ( tagName ) ;
} ,
// Ensure that the View has a DOM element to render into.
// If `this.el` is a string, pass it through `$()`, take the first
// matching element, and re-assign it to `el`. Otherwise, create
// an element from the `id`, `className` and `tagName` properties.
_ensureElement : function ( ) {
if ( ! this . el ) {
var attrs = _ . extend ( { } , _ . result ( this , 'attributes' ) ) ;
if ( this . id ) attrs . id = _ . result ( this , 'id' ) ;
if ( this . className ) attrs [ 'class' ] = _ . result ( this , 'className' ) ;
this . setElement ( this . _createElement ( _ . result ( this , 'tagName' ) ) ) ;
this . _setAttributes ( attrs ) ;
} else {
this . setElement ( _ . result ( this , 'el' ) ) ;
}
} ,
// Set attributes from a hash on this view's element. Exposed for
// subclasses using an alternative DOM manipulation API.
_setAttributes : function ( attributes ) {
this . $el . attr ( attributes ) ;
}
} ) ;
// Backbone.sync
// -------------
// Override this function to change the manner in which Backbone persists
// models to the server. You will be passed the type of request, and the
// model in question. By default, makes a RESTful Ajax request
// to the model's `url()`. Some possible customizations could be:
//
// * Use `setTimeout` to batch rapid-fire updates into a single request.
// * Send up the models as XML instead of JSON.
// * Persist models via WebSockets instead of Ajax.
//
// Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
// as `POST`, with a `_method` parameter containing the true HTTP method,
// as well as all requests with the body as `application/x-www-form-urlencoded`
// instead of `application/json` with the model in a param named `model`.
// Useful when interfacing with server-side languages like **PHP** that make
// it difficult to read the body of `PUT` requests.
Backbone . sync = function ( method , model , options ) {
var type = methodMap [ method ] ;
// Default options, unless specified.
_ . defaults ( options || ( options = { } ) , {
emulateHTTP : Backbone . emulateHTTP ,
emulateJSON : Backbone . emulateJSON
} ) ;
// Default JSON-request options.
var params = { type : type , dataType : 'json' } ;
// Ensure that we have a URL.
if ( ! options . url ) {
params . url = _ . result ( model , 'url' ) || urlError ( ) ;
}
// Ensure that we have the appropriate request data.
if ( options . data == null && model && ( method === 'create' || method === 'update' || method === 'patch' ) ) {
params . contentType = 'application/json' ;
params . data = JSON . stringify ( options . attrs || model . toJSON ( options ) ) ;
}
// For older servers, emulate JSON by encoding the request into an HTML-form.
if ( options . emulateJSON ) {
params . contentType = 'application/x-www-form-urlencoded' ;
params . data = params . data ? { model : params . data } : { } ;
}
// For older servers, emulate HTTP by mimicking the HTTP method with `_method`
// And an `X-HTTP-Method-Override` header.
if ( options . emulateHTTP && ( type === 'PUT' || type === 'DELETE' || type === 'PATCH' ) ) {
params . type = 'POST' ;
if ( options . emulateJSON ) params . data . _method = type ;
var beforeSend = options . beforeSend ;
options . beforeSend = function ( xhr ) {
xhr . setRequestHeader ( 'X-HTTP-Method-Override' , type ) ;
if ( beforeSend ) return beforeSend . apply ( this , arguments ) ;
} ;
}
// Don't process data on a non-GET request.
if ( params . type !== 'GET' && ! options . emulateJSON ) {
params . processData = false ;
}
// Pass along `textStatus` and `errorThrown` from jQuery.
var error = options . error ;
options . error = function ( xhr , textStatus , errorThrown ) {
options . textStatus = textStatus ;
options . errorThrown = errorThrown ;
if ( error ) error . call ( options . context , xhr , textStatus , errorThrown ) ;
} ;
// Make the request, allowing the user to override any Ajax options.
var xhr = options . xhr = Backbone . ajax ( _ . extend ( params , options ) ) ;
model . trigger ( 'request' , model , xhr , options ) ;
return xhr ;
} ;
// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
var methodMap = {
'create' : 'POST' ,
'update' : 'PUT' ,
'patch' : 'PATCH' ,
'delete' : 'DELETE' ,
'read' : 'GET'
} ;
// Set the default implementation of `Backbone.ajax` to proxy through to `$`.
// Override this if you'd like to use a different library.
Backbone . ajax = function ( ) {
return Backbone . $ . ajax . apply ( Backbone . $ , arguments ) ;
} ;
// Backbone.Router
// ---------------
// Routers map faux-URLs to actions, and fire events when routes are
// matched. Creating a new one sets its `routes` hash, if not set statically.
var Router = Backbone . Router = function ( options ) {
options || ( options = { } ) ;
if ( options . routes ) this . routes = options . routes ;
this . _bindRoutes ( ) ;
this . initialize . apply ( this , arguments ) ;
} ;
// Cached regular expressions for matching named param parts and splatted
// parts of route strings.
var optionalParam = /\((.*?)\)/g ;
var namedParam = /(\(\?)?:\w+/g ;
var splatParam = /\*\w+/g ;
var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g ;
// Set up all inheritable **Backbone.Router** properties and methods.
_ . extend ( Router . prototype , Events , {
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize : function ( ) { } ,
// Manually bind a single named route to a callback. For example:
//
// this.route('search/:query/p:num', 'search', function(query, num) {
// ...
// });
//
route : function ( route , name , callback ) {
if ( ! _ . isRegExp ( route ) ) route = this . _routeToRegExp ( route ) ;
if ( _ . isFunction ( name ) ) {
callback = name ;
name = '' ;
}
if ( ! callback ) callback = this [ name ] ;
var router = this ;
Backbone . history . route ( route , function ( fragment ) {
var args = router . _extractParameters ( route , fragment ) ;
if ( router . execute ( callback , args , name ) !== false ) {
router . trigger . apply ( router , [ 'route:' + name ] . concat ( args ) ) ;
router . trigger ( 'route' , name , args ) ;
Backbone . history . trigger ( 'route' , router , name , args ) ;
}
} ) ;
return this ;
} ,
// Execute a route handler with the provided parameters. This is an
// excellent place to do pre-route setup or post-route cleanup.
execute : function ( callback , args , name ) {
if ( callback ) callback . apply ( this , args ) ;
} ,
// Simple proxy to `Backbone.history` to save a fragment into the history.
navigate : function ( fragment , options ) {
Backbone . history . navigate ( fragment , options ) ;
return this ;
} ,
// Bind all defined routes to `Backbone.history`. We have to reverse the
// order of the routes here to support behavior where the most general
// routes can be defined at the bottom of the route map.
_bindRoutes : function ( ) {
if ( ! this . routes ) return ;
this . routes = _ . result ( this , 'routes' ) ;
var route , routes = _ . keys ( this . routes ) ;
while ( ( route = routes . pop ( ) ) != null ) {
this . route ( route , this . routes [ route ] ) ;
}
} ,
// Convert a route string into a regular expression, suitable for matching
// against the current location hash.
_routeToRegExp : function ( route ) {
route = route . replace ( escapeRegExp , '\\$&' )
. replace ( optionalParam , '(?:$1)?' )
. replace ( namedParam , function ( match , optional ) {
return optional ? match : '([^/?]+)' ;
} )
. replace ( splatParam , '([^?]*?)' ) ;
return new RegExp ( '^' + route + '(?:\\?([\\s\\S]*))?$' ) ;
} ,
// Given a route, and a URL fragment that it matches, return the array of
// extracted decoded parameters. Empty or unmatched parameters will be
// treated as `null` to normalize cross-browser behavior.
_extractParameters : function ( route , fragment ) {
var params = route . exec ( fragment ) . slice ( 1 ) ;
return _ . map ( params , function ( param , i ) {
// Don't decode the search params.
if ( i === params . length - 1 ) return param || null ;
return param ? decodeURIComponent ( param ) : null ;
} ) ;
}
} ) ;
// Backbone.History
// ----------------
// Handles cross-browser history management, based on either
// [pushState](http://diveintohtml5.info/history.html) and real URLs, or
// [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
// and URL fragments. If the browser supports neither (old IE, natch),
// falls back to polling.
var History = Backbone . History = function ( ) {
this . handlers = [ ] ;
2015-10-08 11:40:12 -07:00
this . checkUrl = _ . bind ( this . checkUrl , this ) ;
2015-08-27 12:03:05 -07:00
// Ensure that `History` can be used outside of the browser.
if ( typeof window !== 'undefined' ) {
this . location = window . location ;
this . history = window . history ;
}
} ;
// Cached regex for stripping a leading hash/slash and trailing space.
var routeStripper = /^[#\/]|\s+$/g ;
// Cached regex for stripping leading and trailing slashes.
var rootStripper = /^\/+|\/+$/g ;
// Cached regex for stripping urls of hash.
var pathStripper = /#.*$/ ;
// Has the history handling already been started?
History . started = false ;
// Set up all inheritable **Backbone.History** properties and methods.
_ . extend ( History . prototype , Events , {
// The default interval to poll for hash changes, if necessary, is
// twenty times a second.
interval : 50 ,
// Are we at the app root?
atRoot : function ( ) {
var path = this . location . pathname . replace ( /[^\/]$/ , '$&/' ) ;
return path === this . root && ! this . getSearch ( ) ;
} ,
// Does the pathname match the root?
matchRoot : function ( ) {
var path = this . decodeFragment ( this . location . pathname ) ;
var root = path . slice ( 0 , this . root . length - 1 ) + '/' ;
return root === this . root ;
} ,
// Unicode characters in `location.pathname` are percent encoded so they're
// decoded for comparison. `%25` should not be decoded since it may be part
// of an encoded parameter.
decodeFragment : function ( fragment ) {
return decodeURI ( fragment . replace ( /%25/g , '%2525' ) ) ;
} ,
// In IE6, the hash fragment and search params are incorrect if the
// fragment contains `?`.
getSearch : function ( ) {
var match = this . location . href . replace ( /#.*/ , '' ) . match ( /\?.+/ ) ;
return match ? match [ 0 ] : '' ;
} ,
// Gets the true hash value. Cannot use location.hash directly due to bug
// in Firefox where location.hash will always be decoded.
getHash : function ( window ) {
var match = ( window || this ) . location . href . match ( /#(.*)$/ ) ;
return match ? match [ 1 ] : '' ;
} ,
// Get the pathname and search params, without the root.
getPath : function ( ) {
var path = this . decodeFragment (
this . location . pathname + this . getSearch ( )
) . slice ( this . root . length - 1 ) ;
return path . charAt ( 0 ) === '/' ? path . slice ( 1 ) : path ;
} ,
// Get the cross-browser normalized URL fragment from the path or hash.
getFragment : function ( fragment ) {
if ( fragment == null ) {
if ( this . _usePushState || ! this . _wantsHashChange ) {
fragment = this . getPath ( ) ;
} else {
fragment = this . getHash ( ) ;
}
}
return fragment . replace ( routeStripper , '' ) ;
} ,
// Start the hash change handling, returning `true` if the current URL matches
// an existing route, and `false` otherwise.
start : function ( options ) {
if ( History . started ) throw new Error ( 'Backbone.history has already been started' ) ;
History . started = true ;
// Figure out the initial configuration. Do we need an iframe?
// Is pushState desired ... is it available?
this . options = _ . extend ( { root : '/' } , this . options , options ) ;
this . root = this . options . root ;
this . _wantsHashChange = this . options . hashChange !== false ;
2015-10-08 11:40:12 -07:00
this . _hasHashChange = 'onhashchange' in window && ( document . documentMode === void 0 || document . documentMode > 7 ) ;
2015-08-27 12:03:05 -07:00
this . _useHashChange = this . _wantsHashChange && this . _hasHashChange ;
this . _wantsPushState = ! ! this . options . pushState ;
this . _hasPushState = ! ! ( this . history && this . history . pushState ) ;
this . _usePushState = this . _wantsPushState && this . _hasPushState ;
this . fragment = this . getFragment ( ) ;
// Normalize root to always include a leading and trailing slash.
this . root = ( '/' + this . root + '/' ) . replace ( rootStripper , '/' ) ;
// Transition from hashChange to pushState or vice versa if both are
// requested.
if ( this . _wantsHashChange && this . _wantsPushState ) {
// If we've started off with a route from a `pushState`-enabled
// browser, but we're currently in a browser that doesn't support it...
if ( ! this . _hasPushState && ! this . atRoot ( ) ) {
var root = this . root . slice ( 0 , - 1 ) || '/' ;
this . location . replace ( root + '#' + this . getPath ( ) ) ;
// Return immediately as browser will do redirect to new url
return true ;
// Or if we've started out with a hash-based route, but we're currently
// in a browser where it could be `pushState`-based instead...
} else if ( this . _hasPushState && this . atRoot ( ) ) {
this . navigate ( this . getHash ( ) , { replace : true } ) ;
}
}
// Proxy an iframe to handle location events if the browser doesn't
// support the `hashchange` event, HTML5 history, or the user wants
// `hashChange` but not `pushState`.
if ( ! this . _hasHashChange && this . _wantsHashChange && ! this . _usePushState ) {
this . iframe = document . createElement ( 'iframe' ) ;
this . iframe . src = 'javascript:0' ;
this . iframe . style . display = 'none' ;
this . iframe . tabIndex = - 1 ;
var body = document . body ;
// Using `appendChild` will throw on IE < 9 if the document is not ready.
var iWindow = body . insertBefore ( this . iframe , body . firstChild ) . contentWindow ;
iWindow . document . open ( ) ;
iWindow . document . close ( ) ;
iWindow . location . hash = '#' + this . fragment ;
}
// Add a cross-platform `addEventListener` shim for older browsers.
var addEventListener = window . addEventListener || function ( eventName , listener ) {
return attachEvent ( 'on' + eventName , listener ) ;
} ;
// Depending on whether we're using pushState or hashes, and whether
// 'onhashchange' is supported, determine how we check the URL state.
if ( this . _usePushState ) {
addEventListener ( 'popstate' , this . checkUrl , false ) ;
} else if ( this . _useHashChange && ! this . iframe ) {
addEventListener ( 'hashchange' , this . checkUrl , false ) ;
} else if ( this . _wantsHashChange ) {
this . _checkUrlInterval = setInterval ( this . checkUrl , this . interval ) ;
}
if ( ! this . options . silent ) return this . loadUrl ( ) ;
} ,
// Disable Backbone.history, perhaps temporarily. Not useful in a real app,
// but possibly useful for unit testing Routers.
stop : function ( ) {
// Add a cross-platform `removeEventListener` shim for older browsers.
var removeEventListener = window . removeEventListener || function ( eventName , listener ) {
return detachEvent ( 'on' + eventName , listener ) ;
} ;
// Remove window listeners.
if ( this . _usePushState ) {
removeEventListener ( 'popstate' , this . checkUrl , false ) ;
} else if ( this . _useHashChange && ! this . iframe ) {
removeEventListener ( 'hashchange' , this . checkUrl , false ) ;
}
// Clean up the iframe if necessary.
if ( this . iframe ) {
document . body . removeChild ( this . iframe ) ;
this . iframe = null ;
}
// Some environments will throw when clearing an undefined interval.
if ( this . _checkUrlInterval ) clearInterval ( this . _checkUrlInterval ) ;
History . started = false ;
} ,
// Add a route to be tested when the fragment changes. Routes added later
// may override previous routes.
route : function ( route , callback ) {
this . handlers . unshift ( { route : route , callback : callback } ) ;
} ,
// Checks the current URL to see if it has changed, and if it has,
// calls `loadUrl`, normalizing across the hidden iframe.
checkUrl : function ( e ) {
var current = this . getFragment ( ) ;
// If the user pressed the back button, the iframe's hash will have
// changed and we should use that for comparison.
if ( current === this . fragment && this . iframe ) {
current = this . getHash ( this . iframe . contentWindow ) ;
}
if ( current === this . fragment ) return false ;
if ( this . iframe ) this . navigate ( current ) ;
this . loadUrl ( ) ;
} ,
// Attempt to load the current URL fragment. If a route succeeds with a
// match, returns `true`. If no defined routes matches the fragment,
// returns `false`.
loadUrl : function ( fragment ) {
// If the root doesn't match, no routes can match either.
if ( ! this . matchRoot ( ) ) return false ;
fragment = this . fragment = this . getFragment ( fragment ) ;
2015-10-08 11:40:12 -07:00
return _ . some ( this . handlers , function ( handler ) {
2015-08-27 12:03:05 -07:00
if ( handler . route . test ( fragment ) ) {
handler . callback ( fragment ) ;
return true ;
}
} ) ;
} ,
// Save a fragment into the hash history, or replace the URL state if the
// 'replace' option is passed. You are responsible for properly URL-encoding
// the fragment in advance.
//
// The options object can contain `trigger: true` if you wish to have the
// route callback be fired (not usually desirable), or `replace: true`, if
// you wish to modify the current URL without adding an entry to the history.
navigate : function ( fragment , options ) {
if ( ! History . started ) return false ;
if ( ! options || options === true ) options = { trigger : ! ! options } ;
// Normalize the fragment.
fragment = this . getFragment ( fragment || '' ) ;
// Don't include a trailing slash on the root.
var root = this . root ;
if ( fragment === '' || fragment . charAt ( 0 ) === '?' ) {
root = root . slice ( 0 , - 1 ) || '/' ;
}
var url = root + fragment ;
// Strip the hash and decode for matching.
fragment = this . decodeFragment ( fragment . replace ( pathStripper , '' ) ) ;
if ( this . fragment === fragment ) return ;
this . fragment = fragment ;
// If pushState is available, we use it to set the fragment as a real URL.
if ( this . _usePushState ) {
this . history [ options . replace ? 'replaceState' : 'pushState' ] ( { } , document . title , url ) ;
// If hash changes haven't been explicitly disabled, update the hash
// fragment to store history.
} else if ( this . _wantsHashChange ) {
this . _updateHash ( this . location , fragment , options . replace ) ;
if ( this . iframe && ( fragment !== this . getHash ( this . iframe . contentWindow ) ) ) {
var iWindow = this . iframe . contentWindow ;
// Opening and closing the iframe tricks IE7 and earlier to push a
// history entry on hash-tag change. When replace is true, we don't
// want this.
if ( ! options . replace ) {
iWindow . document . open ( ) ;
iWindow . document . close ( ) ;
}
this . _updateHash ( iWindow . location , fragment , options . replace ) ;
}
// If you've told us that you explicitly don't want fallback hashchange-
// based history, then `navigate` becomes a page refresh.
} else {
return this . location . assign ( url ) ;
}
if ( options . trigger ) return this . loadUrl ( fragment ) ;
} ,
// Update the hash location, either replacing the current entry, or adding
// a new one to the browser history.
_updateHash : function ( location , fragment , replace ) {
if ( replace ) {
var href = location . href . replace ( /(javascript:|#).*$/ , '' ) ;
location . replace ( href + '#' + fragment ) ;
} else {
// Some browsers require that `hash` contains a leading #.
location . hash = '#' + fragment ;
}
}
} ) ;
// Create the default Backbone.history.
Backbone . history = new History ;
// Helpers
// -------
// Helper function to correctly set up the prototype chain for subclasses.
// Similar to `goog.inherits`, but uses a hash of prototype properties and
// class properties to be extended.
var extend = function ( protoProps , staticProps ) {
var parent = this ;
var child ;
// The constructor function for the new subclass is either defined by you
// (the "constructor" property in your `extend` definition), or defaulted
// by us to simply call the parent constructor.
if ( protoProps && _ . has ( protoProps , 'constructor' ) ) {
child = protoProps . constructor ;
} else {
child = function ( ) { return parent . apply ( this , arguments ) ; } ;
}
// Add static properties to the constructor function, if supplied.
_ . extend ( child , parent , staticProps ) ;
// Set the prototype chain to inherit from `parent`, without calling
// `parent` constructor function.
var Surrogate = function ( ) { this . constructor = child ; } ;
Surrogate . prototype = parent . prototype ;
child . prototype = new Surrogate ;
// Add prototype properties (instance properties) to the subclass,
// if supplied.
if ( protoProps ) _ . extend ( child . prototype , protoProps ) ;
// Set a convenience property in case the parent's prototype is needed
// later.
child . _ _super _ _ = parent . prototype ;
return child ;
} ;
// Set up inheritance for the model, collection, router, view and history.
Model . extend = Collection . extend = Router . extend = View . extend = History . extend = extend ;
// Throw an error when a URL is needed, and none is supplied.
var urlError = function ( ) {
throw new Error ( 'A "url" property or function must be specified' ) ;
} ;
// Wrap an optional error callback with a fallback error event.
var wrapError = function ( model , options ) {
var error = options . error ;
options . error = function ( resp ) {
if ( error ) error . call ( options . context , model , resp , options ) ;
model . trigger ( 'error' , model , resp , options ) ;
} ;
} ;
return Backbone ;
} ) ) ;