diff --git a/.eslintignore b/.eslintignore index 2f8826912..50ab58c5d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,5 @@ +/neckbone* /docs +/node_modules +/test/vendor +/test diff --git a/.eslintrc.json b/.eslintrc.json index 88e2ae2b8..1b31d6192 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -8,11 +8,9 @@ }, "env": { "browser": true, + "es6": true, "node": true }, - "globals": { - "Promise": false - }, "rules": { "prettier/prettier": "error", diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3920f7d51..59fc2f8ac 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,13 +1,13 @@ -## How to Open a Backbone.js Ticket +## How to Open a Ticket - Before you open a ticket or send a pull request, - [search](https://github.com/mixmaxhq/boddle/issues) for previous discussions + [search](https://github.com/mixmaxhq/neckbone/issues) for previous discussions about the same feature or issue. Add to the earlier ticket if you find one. - Before sending a pull request for a feature or bug fix, be sure to have [tests](http://backbonejs.org/test/). - Use the same coding style as the rest of the - [codebase](https://github.com/mixmaxhq/boddle/blob/master/backbone.js). + [codebase](https://github.com/mixmaxhq/neckbone/tree/master). - All pull requests should be made to the `master` branch. diff --git a/README.md b/README.md index 0bc01ac34..66421931a 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,34 @@ -# boddle +# neckbone Backbone-inspired data layer, aiming at majority API compatibility with modern promises and no underscore/jquery dependency. To suggest a feature or report a bug: -https://github.com/mixmaxh/boddle/issues +https://github.com/mixmaxh/neckbone/issues Many thanks to Backbone's contributors: https://github.com/jashkenas/backbone/graphs/contributors Special thanks to Robert Kieffer for the original philosophy behind Backbone (and, indirectly, -boddle). +neckbone). https://github.com/broofa ## install ```sh -$ npm i -P boddle +$ npm i -P neckbone ``` + +## notable differences + +- No support for `View`, `Router`, `History`. +- Does not use jQuery, and replaces underscore with lodash. +- Does not support `Model#mixin` or `Collection#mixin`. +- Does not provide a global event bus (e.g. `Backbone.trigger` on global `Backbone`). +- Exports setter functions for previously-rewriteable exports: + - `ajax` -> `setAJAXImplementation` + - `emulateHTTP` -> `setEmulateHTTP` + - `emulateJSON` -> `setEmulateJSON` + - `sync` -> `setSyncImplementation` +- Supports standard `class`-`extend` syntax instead of requiring e.g. `Model.extend`. +- Potential for minor differences in method semantics. e.g. due to the `lodash` switch `Model#pick` does not support a function getter - use `Model#pickBy` instead. diff --git a/backbone-min.js b/backbone-min.js deleted file mode 100644 index c8c33e0d7..000000000 --- a/backbone-min.js +++ /dev/null @@ -1,2 +0,0 @@ -(function(t){var e=typeof self=="object"&&self.self===self&&self||typeof global=="object"&&global.global===global&&global;if(typeof define==="function"&&define.amd){define(["underscore","jquery","exports"],function(i,n,r){e.Backbone=t(e,r,i,n)})}else if(typeof exports!=="undefined"){var i=require("underscore"),n;try{n=require("jquery")}catch(r){}t(e,exports,i,n)}else{e.Backbone=t(e,{},e._,e.jQuery||e.Zepto||e.ender||e.$)}})(function(t,e,i,n){var r=t.Backbone;var s=Array.prototype.slice;e.VERSION="1.4.0";e.$=n;e.noConflict=function(){t.Backbone=r;return this};e.emulateHTTP=false;e.emulateJSON=false;var a=e.Events={};var o=/\s+/;var h;var u=function(t,e,n,r,s){var a=0,h;if(n&&typeof n==="object"){if(r!==void 0&&"context"in s&&s.context===void 0)s.context=r;for(h=i.keys(n);athis.length)r=this.length;if(r<0)r+=this.length+1;var s=[];var a=[];var o=[];var h=[];var u={};var l=e.add;var c=e.merge;var f=e.remove;var d=false;var v=this.comparator&&r==null&&e.sort!==false;var p=i.isString(this.comparator)?this.comparator:null;var g,m;for(m=0;m7);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();this.root=("/"+this.root+"/").replace(L,"/");if(this._wantsHashChange&&this._wantsPushState){if(!this._hasPushState&&!this.atRoot()){var e=this.root.slice(0,-1)||"/";this.location.replace(e+"#"+this.getPath());return true}else if(this._hasPushState&&this.atRoot()){this.navigate(this.getHash(),{replace:true})}}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 n=document.body;var r=n.insertBefore(this.iframe,n.firstChild).contentWindow;r.document.open();r.document.close();r.location.hash="#"+this.fragment}var s=window.addEventListener||function(t,e){return attachEvent("on"+t,e)};if(this._usePushState){s("popstate",this.checkUrl,false)}else if(this._useHashChange&&!this.iframe){s("hashchange",this.checkUrl,false)}else if(this._wantsHashChange){this._checkUrlInterval=setInterval(this.checkUrl,this.interval)}if(!this.options.silent)return this.loadUrl()},stop:function(){var t=window.removeEventListener||function(t,e){return detachEvent("on"+t,e)};if(this._usePushState){t("popstate",this.checkUrl,false)}else if(this._useHashChange&&!this.iframe){t("hashchange",this.checkUrl,false)}if(this.iframe){document.body.removeChild(this.iframe);this.iframe=null}if(this._checkUrlInterval)clearInterval(this._checkUrlInterval);B.started=false},route:function(t,e){this.handlers.unshift({route:t,callback:e})},checkUrl:function(t){var e=this.getFragment();if(e===this.fragment&&this.iframe){e=this.getHash(this.iframe.contentWindow)}if(e===this.fragment)return false;if(this.iframe)this.navigate(e);this.loadUrl()},loadUrl:function(t){if(!this.matchRoot())return false;t=this.fragment=this.getFragment(t);return i.some(this.handlers,function(e){if(e.route.test(t)){e.callback(t);return true}})},navigate:function(t,e){if(!B.started)return false;if(!e||e===true)e={trigger:!!e};t=this.getFragment(t||"");var i=this.root;if(t===""||t.charAt(0)==="?"){i=i.slice(0,-1)||"/"}var n=i+t;t=t.replace(W,"");var r=this.decodeFragment(t);if(this.fragment===r)return;this.fragment=r;if(this._usePushState){this.history[e.replace?"replaceState":"pushState"]({},document.title,n)}else if(this._wantsHashChange){this._updateHash(this.location,t,e.replace);if(this.iframe&&t!==this.getHash(this.iframe.contentWindow)){var s=this.iframe.contentWindow;if(!e.replace){s.document.open();s.document.close()}this._updateHash(s.location,t,e.replace)}}else{return this.location.assign(n)}if(e.trigger)return this.loadUrl(t)},_updateHash:function(t,e,i){if(i){var n=t.href.replace(/(javascript:|#).*$/,"");t.replace(n+"#"+e)}else{t.hash="#"+e}}});e.history=new B;var D=function(t,e){var n=this;var r;if(t&&i.has(t,"constructor")){r=t.constructor}else{r=function(){return n.apply(this,arguments)}}i.extend(r,n,e);r.prototype=i.create(n.prototype,t);r.prototype.constructor=r;r.__super__=n.prototype;return r};m.extend=_.extend=O.extend=T.extend=B.extend=D;var V=function(){throw new Error('A "url" property or function must be specified')};var G=function(t,e){var i=e.error;e.error=function(n){if(i)i.call(e.context,t,n,e);t.trigger("error",t,n,e)}};return e}); -//# sourceMappingURL=backbone-min.map \ No newline at end of file diff --git a/backbone-min.map b/backbone-min.map deleted file mode 100644 index a45f7c13e..000000000 --- a/backbone-min.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"backbone-min.js","sources":["backbone.js"],"names":["factory","root","self","global","define","amd","_","$","exports","Backbone","require","e","jQuery","Zepto","ender","previousBackbone","slice","Array","prototype","VERSION","noConflict","this","emulateHTTP","emulateJSON","Events","eventSplitter","_listening","eventsApi","iteratee","events","name","callback","opts","i","names","context","keys","length","test","split","on","_events","onApi","ctx","listening","listeners","_listeners","id","interop","listenTo","obj","_listenId","uniqueId","listeningTo","_listeningTo","Listening","error","tryCatchOn","options","handlers","count","push","off","offApi","stopListening","ids","isEmpty","cleanup","remaining","j","handler","_callback","once","onceMap","bind","listenToOnce","map","offer","apply","arguments","trigger","Math","max","args","triggerApi","objEvents","allEvents","all","triggerEvents","concat","ev","l","a1","a2","a3","call","listener","unbind","extend","Model","attributes","attrs","preinitialize","cid","cidPrefix","collection","parse","defaults","result","set","changed","initialize","validationError","idAttribute","toJSON","clone","sync","get","attr","escape","has","matches","key","val","_validate","unset","silent","changes","changing","_changing","_previousAttributes","current","prev","isEqual","_pending","clear","hasChanged","changedAttributes","diff","old","previous","previousAttributes","fetch","model","success","resp","serverAttrs","wrapError","save","validate","wait","method","isNew","patch","xhr","destroy","defer","url","base","urlError","replace","encodeURIComponent","constructor","isValid","Collection","models","comparator","_reset","reset","setOptions","add","remove","merge","addOptions","splice","array","insert","at","min","tail","singular","isArray","removed","_removeModels","added","merged","_isModel","toAdd","toMerge","toRemove","modelMap","sort","sortable","sortAttr","isString","existing","_prepareModel","_addReference","orderChanged","some","m","index","_removeReference","previousModels","pop","unshift","shift","_byId","modelId","where","first","findWhere","Error","isFunction","sortBy","pluck","create","callbackOpts","values","CollectionIterator","ITERATOR_VALUES","ITERATOR_KEYS","entries","ITERATOR_KEYSVALUES","indexOf","_onModelEvent","event","prevId","$$iterator","Symbol","iterator","kind","_collection","_kind","_index","next","value","done","View","pick","viewOptions","_ensureElement","delegateEventSplitter","tagName","selector","$el","find","render","_removeElement","setElement","element","undelegateEvents","_setElement","delegateEvents","el","match","delegate","eventName","undelegate","_createElement","document","createElement","className","_setAttributes","addMethod","attribute","cb","defaultVal","addUnderscoreMethods","Class","methods","each","instance","isObject","modelMatcher","matcher","collectionMethods","forEach","collect","reduce","foldl","inject","reduceRight","foldr","detect","filter","select","reject","every","any","include","includes","contains","invoke","toArray","size","head","take","initial","rest","drop","last","without","difference","shuffle","lastIndexOf","chain","sample","partition","groupBy","countBy","indexBy","findIndex","findLastIndex","modelMethods","pairs","invert","omit","config","Base","mixin","mappings","functions","memo","type","methodMap","params","dataType","data","contentType","JSON","stringify","_method","beforeSend","setRequestHeader","processData","textStatus","errorThrown","ajax","update","delete","read","Router","routes","_bindRoutes","optionalParam","namedParam","splatParam","escapeRegExp","route","isRegExp","_routeToRegExp","router","history","fragment","_extractParameters","execute","navigate","optional","RegExp","exec","param","decodeURIComponent","History","checkUrl","window","location","routeStripper","rootStripper","pathStripper","started","interval","atRoot","path","pathname","getSearch","matchRoot","decodeFragment","rootPath","decodeURI","href","getHash","getPath","charAt","getFragment","_usePushState","_wantsHashChange","start","hashChange","_hasHashChange","documentMode","_useHashChange","_wantsPushState","pushState","_hasPushState","iframe","src","style","display","tabIndex","body","iWindow","insertBefore","firstChild","contentWindow","open","close","hash","addEventListener","attachEvent","_checkUrlInterval","setInterval","loadUrl","stop","removeEventListener","detachEvent","removeChild","clearInterval","decodedFragment","title","_updateHash","assign","protoProps","staticProps","parent","child","__super__"],"mappings":"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"} \ No newline at end of file diff --git a/backbone.js b/backbone.js deleted file mode 100644 index 4ed524e89..000000000 --- a/backbone.js +++ /dev/null @@ -1,2096 +0,0 @@ -// Backbone.js 1.4.0 - -// (c) 2010-2019 Jeremy Ashkenas and DocumentCloud -// 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. - var slice = Array.prototype.slice; - - // Current version of the library. Keep in sync with `package.json`. - Backbone.VERSION = '1.4.0'; - - // 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; - - // Backbone.Events - // --------------- - - // A module that can be mixed in to *any object* in order to provide it with - // 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 - // 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+/; - - // A private global variable to share between listeners and listenees. - var _listening; - - // Iterates over the standard `event, callback` (as well as the fancy multiple - // space-separated events `"change blur", callback` and jQuery-style event - // maps `{event: callback}`). - var eventsApi = function(iteratee, events, name, callback, opts) { - 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++) { - events = eventsApi(iteratee, events, names[i], name[names[i]], opts); - } - } else if (name && eventSplitter.test(name)) { - // Handle space-separated event names by delegating them individually. - for (names = name.split(eventSplitter); i < names.length; i++) { - events = iteratee(events, names[i], callback, opts); - } - } else { - // Finally, standard events. - events = iteratee(events, name, callback, opts); - } - return events; - }; - - // Bind an event to a `callback` function. Passing `"all"` will bind - // the callback to all events fired. - Events.on = function(name, callback, context) { - this._events = eventsApi(onApi, this._events || {}, name, callback, { - context: context, - ctx: this, - listening: _listening - }); - - if (_listening) { - var listeners = this._listeners || (this._listeners = {}); - listeners[_listening.id] = _listening; - // Allow the listening to use a counter, instead of tracking - // callbacks for library interop - _listening.interop = false; - } - - return this; - }; - - // Inversion-of-control versions of `on`. Tell *this* object to listen to - // an event in another object... keeping track of what it's listening to - // for easier unbinding later. - 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 = _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) { - this._listenId || (this._listenId = _.uniqueId('l')); - listening = _listening = listeningTo[id] = new Listening(this, obj); - } - - // Bind callbacks on obj. - var error = tryCatchOn(obj, name, callback, this); - _listening = void 0; - - if (error) throw error; - // If the target obj is not Backbone.Events, track events manually. - if (listening.interop) listening.on(name, callback); - - 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; - }; - - // An try-catch guarded #on function, to prevent poisoning the global - // `_listening` variable. - var tryCatchOn = function(obj, name, callback, context) { - try { - obj.on(name, callback, context); - } catch (e) { - return e; - } - }; - - // 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 (listening.interop) listening.off(name, callback); - } - 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 context = options.context, listeners = options.listeners; - var i = 0, names; - - // Delete all event listeners and "drop" events. - if (!name && !context && !callback) { - for (names = _.keys(listeners); i < names.length; i++) { - listeners[names[i]].cleanup(); - } - return; - } - - 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; - - // Find any remaining events. - 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 { - var listening = handler.listening; - if (listening) listening.off(name, callback); - } - } - - // Replace events if there are any remaining. Otherwise, clean up. - if (remaining.length) { - events[name] = remaining; - } else { - delete events[name]; - } - } - - return events; - }; - - // Bind an event to only be triggered a single time. After the first time - // 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. - Events.once = function(name, callback, context) { - // Map the event into a `{event: once}` object. - var events = eventsApi(onceMap, {}, name, callback, this.off.bind(this)); - if (typeof name === 'string' && context == null) callback = void 0; - return this.on(events, callback, 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, this.stopListening.bind(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, callback, 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; - } - }; - - // A listening class that tracks and cleans up memory bindings - // when all callbacks have been offed. - var Listening = function(listener, obj) { - this.id = listener._listenId; - this.listener = listener; - this.obj = obj; - this.interop = true; - this.count = 0; - this._events = void 0; - }; - - Listening.prototype.on = Events.on; - - // Offs a callback (or several). - // Uses an optimized counter if the listenee uses Backbone.Events. - // Otherwise, falls back to manual tracking to support events - // library interop. - Listening.prototype.off = function(name, callback) { - var cleanup; - if (this.interop) { - this._events = eventsApi(offApi, this._events, name, callback, { - context: void 0, - listeners: void 0 - }); - cleanup = !this._events; - } else { - this.count--; - cleanup = this.count === 0; - } - if (cleanup) this.cleanup(); - }; - - // Cleans up memory bindings between the listener and the listenee. - Listening.prototype.cleanup = function() { - delete this.listener._listeningTo[this.obj._listenId]; - if (!this.interop) delete this.obj._listeners[this.id]; - }; - - // 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.preinitialize.apply(this, arguments); - this.cid = _.uniqueId(this.cidPrefix); - this.attributes = {}; - if (options.collection) this.collection = options.collection; - if (options.parse) attrs = this.parse(attrs, options) || {}; - var defaults = _.result(this, 'defaults'); - attrs = _.defaults(_.extend({}, defaults, attrs), 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', - - // preinitialize is an empty function by default. You can override it with a function - // or object. preinitialize will run before any instantiation logic is run in the Model. - preinitialize: function(){}, - - // 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; - } - - // Update the `id`. - if (this.idAttribute in attrs) this.id = this.get(this.idAttribute); - - // 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 = {}; - var hasChanged; - for (var attr in diff) { - var val = diff[attr]; - if (_.isEqual(old[attr], val)) continue; - changed[attr] = val; - hasChanged = true; - } - return hasChanged ? 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({}, _.extend({}, options, {validate: true})); - }, - - // 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; - } - - }); - - // 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 = {}); - this.preinitialize.apply(this, arguments); - 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}; - - // 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; - var i; - for (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]; - }; - - // 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, - - - // preinitialize is an empty function by default. You can override it with a function - // or object. preinitialize will run before any instantiation logic is run in the Collection. - preinitialize: function(){}, - - // 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); - }, - - // 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. - 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] : models.slice(); - var removed = this._removeModels(models, options); - if (!options.silent && removed.length) { - options.changes = {added: [], merged: [], removed: 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) { - if (models == null) return; - - options = _.extend({}, setOptions, options); - if (options.parse && !this._isModel(models)) { - models = this.parse(models, options) || []; - } - - var singular = !_.isArray(models); - models = singular ? [models] : models.slice(); - - var at = options.at; - if (at != null) at = +at; - if (at > this.length) at = this.length; - if (at < 0) at += this.length + 1; - - var set = []; - var toAdd = []; - var toMerge = []; - var toRemove = []; - var modelMap = {}; - - var add = options.add; - var merge = options.merge; - var remove = options.remove; - - var sort = false; - 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. - var model, i; - for (i = 0; i < models.length; i++) { - model = models[i]; - - // If a duplicate is found, prevent it from being added and - // optionally merge it into the existing model. - var existing = this.get(model); - if (existing) { - if (merge && model !== existing) { - var attrs = this._isModel(model) ? model.attributes : model; - if (options.parse) attrs = existing.parse(attrs, options); - existing.set(attrs, options); - toMerge.push(existing); - if (sortable && !sort) sort = existing.hasChanged(sortAttr); - } - if (!modelMap[existing.cid]) { - modelMap[existing.cid] = true; - set.push(existing); - } - models[i] = existing; - - // If this is a new, valid model, push it to the `toAdd` list. - } else if (add) { - model = models[i] = this._prepareModel(model, options); - if (model) { - toAdd.push(model); - this._addReference(model, options); - modelMap[model.cid] = true; - set.push(model); - } - } - } - - // Remove stale models. - if (remove) { - for (i = 0; i < this.length; i++) { - model = this.models[i]; - if (!modelMap[model.cid]) toRemove.push(model); - } - if (toRemove.length) this._removeModels(toRemove, options); - } - - // See if sorting is needed, update `length` and splice in new models. - var orderChanged = false; - var replace = !sortable && add && remove; - if (set.length && replace) { - orderChanged = this.length !== set.length || _.some(this.models, function(m, index) { - return m !== set[index]; - }); - this.models.length = 0; - splice(this.models, set, 0); - this.length = this.models.length; - } else if (toAdd.length) { - if (sortable) sort = true; - splice(this.models, toAdd, at == null ? this.length : at); - this.length = this.models.length; - } - - // Silently sort the collection if appropriate. - if (sort) this.sort({silent: true}); - - // Unless silenced, it's time to fire all appropriate add/sort/update events. - if (!options.silent) { - for (i = 0; i < toAdd.length; i++) { - if (at != null) options.index = at + i; - model = toAdd[i]; - model.trigger('add', model, this, options); - } - if (sort || orderChanged) this.trigger('sort', this, options); - if (toAdd.length || toRemove.length || toMerge.length) { - options.changes = { - added: toAdd, - removed: toRemove, - merged: toMerge - }; - 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, cid, model object with id or cid - // properties, or an attributes object that is transformed through modelId. - get: function(obj) { - if (obj == null) return void 0; - return this._byId[obj] || - this._byId[this.modelId(this._isModel(obj) ? obj.attributes : obj, obj.idAttribute)] || - obj.cid && this._byId[obj.cid]; - }, - - // Returns `true` if the model is in the collection. - has: function(obj) { - return this.get(obj) != null; - }, - - // 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) { - return this[first ? 'find' : 'filter'](attrs); - }, - - // 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) { - var comparator = this.comparator; - if (!comparator) throw new Error('Cannot sort a set without a comparator'); - options || (options = {}); - - var length = comparator.length; - if (_.isFunction(comparator)) comparator = comparator.bind(this); - - // Run sort based on type of `comparator`. - if (length === 1 || _.isString(comparator)) { - this.models = this.sortBy(comparator); - } else { - this.models.sort(comparator); - } - if (!options.silent) this.trigger('sort', this, options); - return this; - }, - - // Pluck an attribute from each model in the collection. - pluck: function(attr) { - return this.map(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(m, resp, callbackOpts) { - if (wait) collection.add(m, callbackOpts); - if (success) success.call(callbackOpts.context, m, 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, idAttribute) { - return attrs[idAttribute || this.model.prototype.idAttribute || 'id']; - }, - - // Get an iterator of all models in this collection. - values: function() { - return new CollectionIterator(this, ITERATOR_VALUES); - }, - - // Get an iterator of all model IDs in this collection. - keys: function() { - return new CollectionIterator(this, ITERATOR_KEYS); - }, - - // Get an iterator of all [ID, model] tuples in this collection. - entries: function() { - return new CollectionIterator(this, ITERATOR_KEYSVALUES); - }, - - // 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--; - - // Remove references before triggering 'remove' event to prevent an - // infinite loop. #3693 - delete this._byId[model.cid]; - var id = this.modelId(model.attributes, model.idAttribute); - if (id != null) delete this._byId[id]; - - if (!options.silent) { - options.index = index; - model.trigger('remove', model, this, options); - } - - removed.push(model); - this._removeReference(model, options); - } - return removed; - }, - - // 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, model.idAttribute); - 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, model.idAttribute); - 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 (model) { - if ((event === 'add' || event === 'remove') && collection !== this) return; - if (event === 'destroy') this.remove(model, options); - if (event === 'change') { - var prevId = this.modelId(model.previousAttributes(), model.idAttribute); - var id = this.modelId(model.attributes, model.idAttribute); - if (prevId !== id) { - if (prevId != null) delete this._byId[prevId]; - if (id != null) this._byId[id] = model; - } - } - } - this.trigger.apply(this, arguments); - } - - }); - - // Defining an @@iterator method implements JavaScript's Iterable protocol. - // In modern ES2015 browsers, this value is found at Symbol.iterator. - /* global Symbol */ - var $$iterator = typeof Symbol === 'function' && Symbol.iterator; - if ($$iterator) { - Collection.prototype[$$iterator] = Collection.prototype.values; - } - - // CollectionIterator - // ------------------ - - // A CollectionIterator implements JavaScript's Iterator protocol, allowing the - // use of `for of` loops in modern browsers and interoperation between - // Backbone.Collection and other JavaScript functions and third-party libraries - // which can operate on Iterables. - var CollectionIterator = function(collection, kind) { - this._collection = collection; - this._kind = kind; - this._index = 0; - }; - - // This "enum" defines the three possible kinds of values which can be emitted - // by a CollectionIterator that correspond to the values(), keys() and entries() - // methods on Collection, respectively. - var ITERATOR_VALUES = 1; - var ITERATOR_KEYS = 2; - var ITERATOR_KEYSVALUES = 3; - - // All Iterators should themselves be Iterable. - if ($$iterator) { - CollectionIterator.prototype[$$iterator] = function() { - return this; - }; - } - - CollectionIterator.prototype.next = function() { - if (this._collection) { - - // Only continue iterating if the iterated collection is long enough. - if (this._index < this._collection.length) { - var model = this._collection.at(this._index); - this._index++; - - // Construct a value depending on what kind of values should be iterated. - var value; - if (this._kind === ITERATOR_VALUES) { - value = model; - } else { - var id = this._collection.modelId(model.attributes); - if (this._kind === ITERATOR_KEYS) { - value = id; - } else { // ITERATOR_KEYSVALUES - value = [id, model]; - } - } - return {value: value, done: false}; - } - - // Once exhausted, remove the reference to the collection so future - // calls to the next method always return done. - this._collection = void 0; - } - - return {value: void 0, done: true}; - }; - - // 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'); - this.preinitialize.apply(this, arguments); - _.extend(this, _.pick(options, viewOptions)); - this._ensureElement(); - this.initialize.apply(this, arguments); - }; - - // Cached regex to split keys for `delegate`. - var delegateEventSplitter = /^(\S+)\s*(.*)$/; - - // List of view options to be set as properties. - 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); - }, - - // preinitialize is an empty function by default. You can override it with a function - // or object. preinitialize will run before any instantiation logic is run in the View - preinitialize: function(){}, - - // 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], method.bind(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); - } - - }); - - // 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. - var addMethod = function(base, length, method, attribute) { - switch (length) { - case 1: return function() { - return base[method](this[attribute]); - }; - case 2: return function(value) { - return base[method](this[attribute], value); - }; - case 3: return function(iteratee, context) { - return base[method](this[attribute], cb(iteratee, this), context); - }; - case 4: return function(iteratee, defaultVal, context) { - return base[method](this[attribute], cb(iteratee, this), defaultVal, context); - }; - default: return function() { - var args = slice.call(arguments); - args.unshift(this[attribute]); - return base[method].apply(base, args); - }; - } - }; - - var addUnderscoreMethods = function(Class, base, methods, attribute) { - _.each(methods, function(length, method) { - if (base[method]) Class.prototype[method] = addMethod(base, length, method, attribute); - }); - }; - - // 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); - }; - }; - - // 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: 0, - foldl: 0, inject: 0, reduceRight: 0, foldr: 0, find: 3, detect: 3, filter: 3, - 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, - head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3, - without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3, - isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3, - sortBy: 3, indexBy: 3, findIndex: 3, findLastIndex: 3}; - - - // Underscore methods that we want to implement on the Model, mapped to the - // number of arguments they take. - 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 `Collection#models`. - - _.each([ - [Collection, collectionMethods, 'models'], - [Model, modelMethods, 'attributes'] - ], function(config) { - var Base = config[0], - methods = config[1], - attribute = config[2]; - - Base.mixin = function(obj) { - var mappings = _.reduce(_.functions(obj), function(memo, name) { - memo[name] = 0; - return memo; - }, {}); - addUnderscoreMethods(Base, obj, mappings, attribute); - }; - - addUnderscoreMethods(Base, _, methods, attribute); - }); - - // 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 = {}); - this.preinitialize.apply(this, arguments); - 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, { - - // preinitialize is an empty function by default. You can override it with a function - // or object. preinitialize will run before any instantiation logic is run in the Router. - preinitialize: function(){}, - - // 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 = []; - this.checkUrl = this.checkUrl.bind(this); - - // 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 rootPath = path.slice(0, this.root.length - 1) + '/'; - return rootPath === 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; - this._hasHashChange = 'onhashchange' in window && (document.documentMode === void 0 || document.documentMode > 7); - 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 rootPath = this.root.slice(0, -1) || '/'; - this.location.replace(rootPath + '#' + 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); - return _.some(this.handlers, function(handler) { - 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 rootPath = this.root; - if (fragment === '' || fragment.charAt(0) === '?') { - rootPath = rootPath.slice(0, -1) || '/'; - } - var url = rootPath + fragment; - - // Strip the fragment of the query and hash for matching. - fragment = fragment.replace(pathStripper, ''); - - // Decode for matching. - var decodedFragment = this.decodeFragment(fragment); - - if (this.fragment === decodedFragment) return; - this.fragment = decodedFragment; - - // 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`'s constructor function and add the prototype properties. - child.prototype = _.create(parent.prototype, protoProps); - child.prototype.constructor = child; - - // 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; -}); diff --git a/docs/Collection.html b/docs/Collection.html new file mode 100644 index 000000000..58768975c --- /dev/null +++ b/docs/Collection.html @@ -0,0 +1,1522 @@ + + + + + Collection.js + + + + + +
+
+ + + +
    + +
  • +
    +

    Collection.js

    +
    +
  • + + + +
  • +
    + +
    + +
    + +
    + +
    import { slice, splice, wrapError } from './utils';
    +import extend from './extend';
    +import { Model } from './Model';
    +import { sync } from './sync';
    +import _ from 'lodash';
    +import { mixinMethods } from './mixin-methods';
    +import { Events } from './events';
    + +
  • + + +
  • +
    + +
    + +
    +

    Collection

    + +
    + +
  • + + +
  • +
    + +
    + +
    + +
    + +
  • + + +
  • +
    + +
    + +
    +

    Defining an @@iterator method implements JavaScript’s Iterable protocol. +In modern ES2015 browsers, this value is found at Symbol.iterator.

    + +
    + +
    /* global Symbol */
    +export const $$iterator = typeof Symbol === 'function' && Symbol.iterator;
    + +
  • + + +
  • +
    + +
    + +
    +

    If models tend to represent a single row of data, a 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.

    + +
    + +
  • + + +
  • +
    + +
    + +
    +

    Default options for Collection#set.

    + +
    + +
    const setOptions = { add: true, remove: true, merge: true };
    +const addOptions = { add: true, remove: false };
    + +
  • + + +
  • +
    + +
    + +
    +

    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.

    + +
    + +
    export const Collection = /*#__PURE__*/ (() => {
    + +
  • + + +
  • +
    + +
    + +
    +

    eslint-disable-next-line no-shadow

    + +
    + +
      const Collection = class Collection {
    +    static extend = extend;
    +
    +    constructor(models, options = {}) {
    +      this.preinitialize.apply(this, arguments);
    +      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, { silent: true, ...options });
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    The JSON representation of a Collection is an array of the +models’ attributes.

    + +
    + +
        toJSON(options) {
    +      return this.map(function (model) {
    +        return model.toJSON(options);
    +      });
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Proxy sync by default.

    + +
    + +
        sync(...args) {
    +      return sync.apply(this, args);
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Add a model, or list of models to the set. models may be Models or raw +JavaScript objects to be converted to Models, or any combination of the +two.

    + +
    + +
        add(models, options) {
    +      return this.set(models, { merge: false, ...options, ...addOptions });
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Remove a model, or a list of models from the set.

    + +
    + +
        remove(models, options) {
    +      options = { ...options };
    +      const singular = !Array.isArray(models);
    +      models = singular ? [models] : models.slice();
    +      const removed = this._removeModels(models, options);
    +      if (!options.silent && removed.length) {
    +        options.changes = { added: [], merged: [], removed: 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(models, options) {
    +      if (models == null) return;
    +
    +      options = { ...setOptions, ...options };
    +      if (options.parse && !this._isModel(models)) {
    +        models = this.parse(models, options) || [];
    +      }
    +
    +      const singular = !Array.isArray(models);
    +      models = singular ? [models] : models.slice();
    +
    +      let at = options.at;
    +      if (at != null) at = +at;
    +      if (at > this.length) at = this.length;
    +      if (at < 0) at += this.length + 1;
    +
    +      const set = [];
    +      const toAdd = [];
    +      const toMerge = [];
    +      const toRemove = [];
    +      const modelMap = {};
    +
    +      const add = options.add;
    +      const merge = options.merge;
    +      const remove = options.remove;
    +
    +      let sort = false;
    +      const sortable = this.comparator && at == null && options.sort !== false;
    +      const sortAttr = typeof this.comparator === 'string' ? this.comparator : null;
    + +
  • + + +
  • +
    + +
    + +
    +

    Turn bare objects into model references, and prevent invalid models +from being added.

    + +
    + +
          let model, i;
    +      for (i = 0; i < models.length; i++) {
    +        model = models[i];
    + +
  • + + +
  • +
    + +
    + +
    +

    If a duplicate is found, prevent it from being added and +optionally merge it into the existing model.

    + +
    + +
            const existing = this.get(model);
    +        if (existing) {
    +          if (merge && model !== existing) {
    +            let attrs = this._isModel(model) ? model.attributes : model;
    +            if (options.parse) attrs = existing.parse(attrs, options);
    +            existing.set(attrs, options);
    +            toMerge.push(existing);
    +            if (sortable && !sort) sort = existing.hasChanged(sortAttr);
    +          }
    +          if (!modelMap[existing.cid]) {
    +            modelMap[existing.cid] = true;
    +            set.push(existing);
    +          }
    +          models[i] = existing;
    + +
  • + + +
  • +
    + +
    + +
    +

    If this is a new, valid model, push it to the toAdd list.

    + +
    + +
            } else if (add) {
    +          model = models[i] = this._prepareModel(model, options);
    +          if (model) {
    +            toAdd.push(model);
    +            this._addReference(model, options);
    +            modelMap[model.cid] = true;
    +            set.push(model);
    +          }
    +        }
    +      }
    + +
  • + + +
  • +
    + +
    + +
    +

    Remove stale models.

    + +
    + +
          if (remove) {
    +        for (i = 0; i < this.length; i++) {
    +          model = this.models[i];
    +          if (!modelMap[model.cid]) toRemove.push(model);
    +        }
    +        if (toRemove.length) this._removeModels(toRemove, options);
    +      }
    + +
  • + + +
  • +
    + +
    + +
    +

    See if sorting is needed, update length and splice in new models.

    + +
    + +
          let orderChanged = false;
    +      const replace = !sortable && add && remove;
    +      if (set.length && replace) {
    +        orderChanged =
    +          this.length !== set.length || _.some(this.models, (m, index) => m !== set[index]);
    +        this.models.length = 0;
    +        splice(this.models, set, 0);
    +        this.length = this.models.length;
    +      } else if (toAdd.length) {
    +        if (sortable) sort = true;
    +        splice(this.models, toAdd, at == null ? this.length : at);
    +        this.length = this.models.length;
    +      }
    + +
  • + + +
  • +
    + +
    + +
    +

    Silently sort the collection if appropriate.

    + +
    + +
          if (sort) this.sort({ silent: true });
    + +
  • + + +
  • +
    + +
    + +
    +

    Unless silenced, it’s time to fire all appropriate add/sort/update events.

    + +
    + +
          if (!options.silent) {
    +        for (i = 0; i < toAdd.length; i++) {
    +          if (at != null) options.index = at + i;
    +          model = toAdd[i];
    +          model.trigger('add', model, this, options);
    +        }
    +        if (sort || orderChanged) this.trigger('sort', this, options);
    +        if (toAdd.length || toRemove.length || toMerge.length) {
    +          options.changes = {
    +            added: toAdd,
    +            removed: toRemove,
    +            merged: toMerge,
    +          };
    +          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(models, options) {
    +      options = { ...options };
    +      for (let i = 0; i < this.models.length; i++) {
    +        this._removeReference(this.models[i], options);
    +      }
    +      options.previousModels = this.models;
    +      this._reset();
    +      models = this.add(models, { silent: true, ...options });
    +      if (!options.silent) this.trigger('reset', this, options);
    +      return models;
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Add a model to the end of the collection.

    + +
    + +
        push(model, options) {
    +      return this.add(model, { at: this.length, ...options });
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Remove a model from the end of the collection.

    + +
    + +
        pop(options) {
    +      const model = this.at(this.length - 1);
    +      return this.remove(model, options);
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Add a model to the beginning of the collection.

    + +
    + +
        unshift(model, options) {
    +      return this.add(model, { at: 0, ...options });
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Remove a model from the beginning of the collection.

    + +
    + +
        shift(options) {
    +      const model = this.at(0);
    +      return this.remove(model, options);
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Slice out a sub-array of models from the collection.

    + +
    + +
        slice(...args) {
    +      return slice.apply(this.models, args);
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Get a model from the set by id, cid, model object with id or cid +properties, or an attributes object that is transformed through modelId.

    + +
    + +
        get(obj) {
    +      if (obj == null) return void 0;
    +      return (
    +        this._byId[obj] ||
    +        this._byId[this.modelId(this._isModel(obj) ? obj.attributes : obj, obj.idAttribute)] ||
    +        (obj.cid && this._byId[obj.cid])
    +      );
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Returns true if the model is in the collection.

    + +
    + +
        has(obj) {
    +      return this.get(obj) != null;
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Get the model at the given index.

    + +
    + +
        at(index) {
    +      if (index < 0) index += this.length;
    +      return this.models[index];
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Return models with matching attributes. Useful for simple cases of +filter.

    + +
    + +
        where(attrs, first) {
    +      return this[first ? 'find' : 'filter'](attrs);
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Return the first model with matching attributes. Useful for simple cases +of find.

    + +
    + +
        findWhere(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(options = {}) {
    +      let comparator = this.comparator;
    +      if (!comparator) throw new Error('Cannot sort a set without a comparator');
    +
    +      const length = comparator.length;
    +      if (typeof comparator === 'function') comparator = comparator.bind(this);
    + +
  • + + +
  • +
    + +
    + +
    +

    Run sort based on type of comparator.

    + +
    + +
          if (length === 1 || typeof comparator === 'string') {
    +        this.models = this.sortBy(comparator);
    +      } else {
    +        this.models.sort(comparator);
    +      }
    +      if (!options.silent) this.trigger('sort', this, options);
    +      return this;
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Pluck an attribute from each model in the collection.

    + +
    + +
        pluck(attr) {
    +      return this.map(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(options) {
    +      options = { parse: true, ...options };
    +      const success = options.success;
    +      const collection = this;
    +      options.success = function (resp) {
    +        const 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(model, options) {
    +      options = { ...options };
    +      const wait = options.wait;
    +      model = this._prepareModel(model, options);
    +      if (!model) return false;
    +      if (!wait) this.add(model, options);
    +      const collection = this;
    +      const success = options.success;
    +      options.success = function (m, resp, callbackOpts) {
    +        if (wait) collection.add(m, callbackOpts);
    +        if (success) success.call(callbackOpts.context, m, 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(resp, options) {
    +      return resp;
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Create a new collection with an identical list of models as this one.

    + +
    + +
        clone() {
    +      return new this.constructor(this.models, {
    +        model: this.model,
    +        comparator: this.comparator,
    +      });
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Define how to uniquely identify models in the collection.

    + +
    + +
        modelId(attrs, idAttribute) {
    +      return attrs[idAttribute || this.model.prototype.idAttribute || 'id'];
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Get an iterator of all models in this collection.

    + +
    + +
        values() {
    +      return new CollectionIterator(this, ITERATOR_VALUES);
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Get an iterator of all model IDs in this collection.

    + +
    + +
        keys() {
    +      return new CollectionIterator(this, ITERATOR_KEYS);
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Get an iterator of all [ID, model] tuples in this collection.

    + +
    + +
        entries() {
    +      return new CollectionIterator(this, ITERATOR_KEYSVALUES);
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Private method to reset all internal state. Called when the collection +is first initialized or reset.

    + +
    + +
        _reset() {
    +      this.length = 0;
    +      this.models = [];
    +      this._byId = {};
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Prepare a hash of attributes (or other model) to be added to this +collection.

    + +
    + +
        _prepareModel(attrs, options) {
    +      if (this._isModel(attrs)) {
    +        if (!attrs.collection) attrs.collection = this;
    +        return attrs;
    +      }
    +      options = { ...options, collection: this };
    +      const 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(models, options) {
    +      const removed = [];
    +      for (let i = 0; i < models.length; i++) {
    +        const model = this.get(models[i]);
    +        if (!model) continue;
    +
    +        const index = this.indexOf(model);
    +        this.models.splice(index, 1);
    +        this.length--;
    + +
  • + + +
  • +
    + +
    + +
    +

    Remove references before triggering ‘remove’ event to prevent an +infinite loop. #3693

    + +
    + +
            delete this._byId[model.cid];
    +        const id = this.modelId(model.attributes, model.idAttribute);
    +        if (id != null) delete this._byId[id];
    +
    +        if (!options.silent) {
    +          options.index = index;
    +          model.trigger('remove', model, this, options);
    +        }
    +
    +        removed.push(model);
    +        this._removeReference(model, options);
    +      }
    +      return removed;
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Method for checking whether an object should be considered a model for +the purposes of adding to the collection.

    + +
    + +
        _isModel(model) {
    +      return model instanceof Model;
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Internal method to create a model’s ties to a collection.

    + +
    + +
        _addReference(model, options) {
    +      this._byId[model.cid] = model;
    +      const id = this.modelId(model.attributes, model.idAttribute);
    +      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(model, options) {
    +      delete this._byId[model.cid];
    +      const id = this.modelId(model.attributes, model.idAttribute);
    +      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(event, model, collection, options) {
    +      if (model) {
    +        if ((event === 'add' || event === 'remove') && collection !== this) return;
    +        if (event === 'destroy') this.remove(model, options);
    +        if (event === 'change') {
    +          const prevId = this.modelId(model.previousAttributes(), model.idAttribute);
    +          const id = this.modelId(model.attributes, model.idAttribute);
    +          if (prevId !== id) {
    +            if (prevId != null) delete this._byId[prevId];
    +            if (id != null) this._byId[id] = model;
    +          }
    +        }
    +      }
    +      this.trigger.apply(this, arguments);
    +    }
    +  };
    +
    +  const proto = Collection.prototype;
    +
    +  Object.assign(proto, Events);
    + +
  • + + +
  • +
    + +
    + +
    +

    The default model for a collection is just a Model. +This should be overridden in most cases.

    + +
    + +
      proto.model = Model;
    + +
  • + + +
  • +
    + +
    + +
    +

    preinitialize is an empty function by default. You can override it with a function +or object. preinitialize will run before any instantiation logic is run in the Collection.

    + +
    + +
      proto.preinitialize = _.noop;
    + +
  • + + +
  • +
    + +
    + +
    +

    Initialize is an empty function by default. Override it with your own +initialization logic.

    + +
    + +
      proto.initialize = _.noop;
    +
    +  if ($$iterator) {
    +    proto[$$iterator] = proto.values;
    +  }
    + +
  • + + +
  • +
    + +
    + +
    +

    Lodash methods that we want to implement on the Collection. 90% of the core +usefulness of Backbone Collections is actually implemented right here:

    + +
    + +
    +  mixinMethods(
    +    Collection,
    +    [
    +      ['chain', _.chain, 1],
    +      ['countBy', _.countBy, 3],
    +      ['difference', _.difference, 0],
    +      ['drop', _.drop, 3],
    +      ['each', _.each, 3],
    +      ['every', _.every, 3],
    +      ['filter', _.filter, 3],
    +      ['find', _.find, 3],
    +      ['findIndex', _.findIndex, 3],
    +      ['findLastIndex', _.findLastIndex, 3],
    +      ['first', _.first, 3],
    +      ['forEach', _.forEach, 3],
    +      ['groupBy', _.groupBy, 3],
    +      ['head', _.head, 3],
    +      ['includes', _.includes, 3],
    +      ['indexOf', _.indexOf, 3],
    +      ['initial', _.initial, 3],
    +      ['invoke', _.invoke, 0],
    +      ['isEmpty', _.isEmpty, 1],
    +      ['keyBy', _.keyBy, 3],
    +      ['last', _.last, 3],
    +      ['lastIndexOf', _.lastIndexOf, 3],
    +      ['map', _.map, 3],
    +      ['max', _.max, 3],
    +      ['min', _.min, 3],
    +      ['partition', _.partition, 3],
    +      ['reduce', _.reduce, 0],
    +      ['reduceRight', _.reduceRight, 0],
    +      ['reject', _.reject, 3],
    +      ['sample', _.sample, 3],
    +      ['shuffle', _.shuffle, 1],
    +      ['size', _.size, 1],
    +      ['some', _.some, 3],
    +      ['sortBy', _.sortBy, 3],
    +      ['tail', _.tail, 3],
    +      ['take', _.take, 3],
    +      ['toArray', _.toArray, 1],
    +      ['without', _.without, 0],
    + +
  • + + +
  • +
    + +
    + +
    +

    Translated, roughly, from underscore:

    + +
    + +
          ['pairs', _.entries, 1],
    +    ],
    +    'models'
    +  );
    + +
  • + + +
  • +
    + +
    + +
    +

    Translated, roughly, from underscore:

    + +
    + +
      proto.all = proto.every;
    +  proto.any = proto.some;
    +  proto.collect = proto.map;
    +  proto.contains = proto.include = proto.includes;
    +  proto.detect = proto.find;
    +  proto.foldl = proto.inject = proto.reduce;
    +  proto.foldr = proto.reduceRight;
    +  proto.indexBy = proto.keyBy;
    +  proto.rest = proto.tail;
    +  proto.select = proto.filter;
    +
    +  return Collection;
    +})();
    + +
  • + + +
  • +
    + +
    + +
    +

    CollectionIterator

    + +
    + +
  • + + +
  • +
    + +
    + +
    + +
    + +
  • + + +
  • +
    + +
    + +
    +

    This “enum” defines the three possible kinds of values which can be emitted +by a CollectionIterator that correspond to the values(), keys() and entries() +methods on Collection, respectively.

    + +
    + +
    const ITERATOR_VALUES = 1;
    +const ITERATOR_KEYS = 2;
    +const ITERATOR_KEYSVALUES = 3;
    + +
  • + + +
  • +
    + +
    + +
    +

    A CollectionIterator implements JavaScript’s Iterator protocol, allowing the +use of for of loops in modern browsers and interoperation between +Collection and other JavaScript functions and third-party libraries which can +operate on Iterables.

    + +
    + +
    const CollectionIterator = /*#__PURE__*/ (() => {
    + +
  • + + +
  • +
    + +
    + +
    +

    eslint-disable-next-line no-shadow

    + +
    + +
      const CollectionIterator = class CollectionIterator {
    +    _index = 0;
    +
    +    constructor(collection, kind) {
    +      this._collection = collection;
    +      this._kind = kind;
    +    }
    +
    +    next() {
    +      if (this._collection) {
    + +
  • + + +
  • +
    + +
    + +
    +

    Only continue iterating if the iterated collection is long enough.

    + +
    + +
            if (this._index < this._collection.length) {
    +          const model = this._collection.at(this._index);
    +          this._index++;
    + +
  • + + +
  • +
    + +
    + +
    +

    Construct a value depending on what kind of values should be iterated.

    + +
    + +
              let value;
    +          if (this._kind === ITERATOR_VALUES) {
    +            value = model;
    +          } else {
    +            const id = this._collection.modelId(model.attributes);
    +            if (this._kind === ITERATOR_KEYS) {
    +              value = id;
    +            } else {
    + +
  • + + +
  • +
    + +
    + +
    +

    ITERATOR_KEYSVALUES

    + +
    + +
                  value = [id, model];
    +            }
    +          }
    +          return { value: value, done: false };
    +        }
    + +
  • + + +
  • +
    + +
    + +
    +

    Once exhausted, remove the reference to the collection so future +calls to the next method always return done.

    + +
    + +
            this._collection = void 0;
    +      }
    +
    +      return { value: void 0, done: true };
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    All Iterators should themselves be Iterable.

    + +
    + +
        static [$$iterator || '$$iterator']() {
    +      return this;
    +    }
    +  };
    + +
  • + + +
  • +
    + +
    + +
    +

    All Iterators should themselves be Iterable.

    + +
    + +
      if ($$iterator) {
    +    CollectionIterator.prototype[$$iterator] = function () {
    +      return this;
    +    };
    +  }
    +
    +  return CollectionIterator;
    +})();
    + +
  • + +
+
+ + diff --git a/docs/Model.html b/docs/Model.html new file mode 100644 index 000000000..d99f10e97 --- /dev/null +++ b/docs/Model.html @@ -0,0 +1,1056 @@ + + + + + Model.js + + + + + +
+
+ + + +
    + +
  • +
    +

    Model.js

    +
    +
  • + + + +
  • +
    + +
    + +
    + +
    + +
    import _ from 'lodash';
    +import { urlError, wrapError } from './utils';
    +import extend from './extend';
    +import { sync } from './sync';
    +import { mixinMethods } from './mixin-methods';
    +import { Events } from './events';
    + +
  • + + +
  • +
    + +
    + +
    +

    Model

    + +
    + +
  • + + +
  • +
    + +
    + +
    + +
    + +
  • + + +
  • +
    + +
    + +
    +

    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.

    + +
    + +
    export const Model = /*#__PURE__*/ (() => {
    +  const Model = class Model {
    +    static extend = extend;
    +
    +    constructor(attributes, options) {
    +      let attrs = attributes || {};
    +      options || (options = {});
    +      this.preinitialize.apply(this, arguments);
    +      this.cid = _.uniqueId(this.cidPrefix);
    +      this.attributes = {};
    +      if (options.collection) this.collection = options.collection;
    +      if (options.parse) attrs = this.parse(attrs, options) || {};
    +      const defaults = _.result(this, 'defaults');
    +      attrs = _.defaults({ ...defaults, ...attrs }, defaults);
    +      this.set(attrs, options);
    +      this.changed = {};
    +      this.initialize.apply(this, arguments);
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Return a copy of the model’s attributes object.

    + +
    + +
        toJSON(options) {
    +      return { ...this.attributes };
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Proxy Backbone.sync by default – but override this if you need +custom syncing semantics for this particular model.

    + +
    + +
        sync(...args) {
    +      return sync.apply(this, args);
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Get the value of an attribute.

    + +
    + +
        get(attr) {
    +      return _.get(this.attributes, [attr]);
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Get the HTML-escaped value of an attribute.

    + +
    + +
        escape(attr) {
    +      return _.escape(this.get(attr));
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Returns true if the attribute contains a value that is not null +or undefined.

    + +
    + +
        has(attr) {
    +      return this.get(attr) != null;
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Special-cased proxy to underscore’s _.matches method.

    + +
    + +
        matches(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(key, val, options) {
    +      if (key == null) return this;
    + +
  • + + +
  • +
    + +
    + +
    +

    Handle both "key", value and {key: value} -style arguments.

    + +
    + +
          let 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.

    + +
    + +
          const unset = options.unset;
    +      const silent = options.silent;
    +      const changes = [];
    +      const changing = this._changing;
    +      this._changing = true;
    +
    +      if (!changing) {
    +        this._previousAttributes = { ...this.attributes };
    +        this.changed = {};
    +      }
    +
    +      const current = this.attributes;
    +      const changed = this.changed;
    +      const prev = this._previousAttributes;
    + +
  • + + +
  • +
    + +
    + +
    +

    For each set attribute, update or delete the current value.

    + +
    + +
          for (const 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);
    +      }
    + +
  • + + +
  • +
    + +
    + +
    +

    Update the id.

    + +
    + +
          if (this.idAttribute in attrs) this.id = this.get(this.idAttribute);
    + +
  • + + +
  • +
    + +
    + +
    +

    Trigger all relevant attribute changes.

    + +
    + +
          if (!silent) {
    +        if (changes.length) this._pending = options;
    +        for (let 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(attr, options) {
    +      return this.set(attr, void 0, { ...options, unset: true });
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Clear all attributes on the model, firing "change".

    + +
    + +
        clear(options) {
    +      const attrs = {};
    +      for (const key in this.attributes) attrs[key] = void 0;
    +      return this.set(attrs, { ...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(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(diff) {
    +      if (!diff) return this.hasChanged() ? { ...this.changed } : false;
    +      const old = this._changing ? this._previousAttributes : this.attributes;
    +      const changed = {};
    +      let hasChanged;
    +      for (const attr in diff) {
    +        const val = diff[attr];
    +        if (_.isEqual(old[attr], val)) continue;
    +        changed[attr] = val;
    +        hasChanged = true;
    +      }
    +      return hasChanged ? changed : false;
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Get the previous value of an attribute, recorded at the time the last +"change" event was fired.

    + +
    + +
        previous(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() {
    +      return { ...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(options) {
    +      options = { parse: true, ...options };
    +      const model = this;
    +      const success = options.success;
    +      options.success = function (resp) {
    +        const 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(key, val, options) {
    + +
  • + + +
  • +
    + +
    + +
    +

    Handle both "key", value and {key: value} -style arguments.

    + +
    + +
          let attrs;
    +      if (key == null || typeof key === 'object') {
    +        attrs = key;
    +        options = val;
    +      } else {
    +        (attrs = {})[key] = val;
    +      }
    +
    +      options = { validate: true, parse: true, ...options };
    +      const 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.

    + +
    + +
          const model = this;
    +      const success = options.success;
    +      const attributes = this.attributes;
    +      options.success = function (resp) {
    + +
  • + + +
  • +
    + +
    + +
    +

    Ensure attributes are restored during synchronous saves.

    + +
    + +
            model.attributes = attributes;
    +        let serverAttrs = options.parse ? model.parse(resp, options) : resp;
    +        if (wait) serverAttrs = { ...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 = { ...attributes, ...attrs };
    +
    +      const method = this.isNew() ? 'create' : options.patch ? 'patch' : 'update';
    +      if (method === 'patch' && !options.attrs) options.attrs = attrs;
    +      const 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(options) {
    +      options = { ...options };
    +      const model = this;
    +      const success = options.success;
    +      const wait = options.wait;
    +
    +      const 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);
    +      };
    +
    +      let 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 +the restful methods, override this to change the endpoint that will be +called.

    + +
    + +
        url() {
    +      const base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError();
    +      if (this.isNew()) return base;
    +      const 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(resp, options) {
    +      return resp;
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Create a new model with identical attributes to this one.

    + +
    + +
        clone() {
    +      return new this.constructor(this.attributes);
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    A model is new if it has never been saved to the server, and lacks an id.

    + +
    + +
        isNew() {
    +      return !this.has(this.idAttribute);
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Check if the model is currently in a valid state.

    + +
    + +
        isValid(options) {
    +      return this._validate({}, { ...options, validate: true });
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Run validation against the next complete set of model attributes, +returning true if all is well. Otherwise, fire an "invalid" event.

    + +
    + +
        _validate(attrs, options) {
    +      if (!options.validate || !this.validate) return true;
    +      attrs = { ...this.attributes, attrs };
    +      const error = (this.validationError = this.validate(attrs, options) || null);
    +      if (!error) return true;
    +      this.trigger('invalid', this, error, { ...options, validationError: error });
    +      return false;
    +    }
    +  };
    +
    +  const proto = Model.prototype;
    +  Object.assign(proto, Events);
    + +
  • + + +
  • +
    + +
    + +
    +

    A hash of attributes whose current and previous value differ.

    + +
    + +
      proto.changed = null;
    + +
  • + + +
  • +
    + +
    + +
    +

    The value returned during the last failed validation.

    + +
    + +
      proto.validationError = null;
    + +
  • + + +
  • +
    + +
    + +
    +

    The default name for the JSON id attribute is "id". MongoDB and CouchDB +users may want to set this to "_id".

    + +
    + +
      proto.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.

    + +
    + +
      proto.cidPrefix = 'c';
    + +
  • + + +
  • +
    + +
    + +
    +

    preinitialize is an empty function by default. You can override it with a +function or object. preinitialize will run before any instantiation logic +is run in the Model.

    + +
    + +
      proto.preinitialize = _.noop;
    + +
  • + + +
  • +
    + +
    + +
    +

    Initialize is an empty function by default. Override it with your own +initialization logic.

    + +
    + +
      proto.initialize = _.noop;
    + +
  • + + +
  • +
    + +
    + +
    +

    Lodash methods that we want to implement on the Model, mapped to the number +of arguments they take.

    + +
    + +
    +  mixinMethods(
    +    Model,
    +    [
    +      ['chain', _.chain, 1],
    +      ['entries', _.entries, 1],
    +      ['invert', _.invert, 1],
    +      ['isEmpty', _.isEmpty, 1],
    +      ['keys', _.keys, 1],
    +      ['omit', _.omit, 0],
    +      ['omitBy', _.omitBy, 1],
    +      ['pick', _.pick, 0],
    +      ['pickBy', _.pickBy, 1],
    +      ['values', _.values, 1],
    +    ],
    +    'attributes'
    +  );
    + +
  • + + +
  • +
    + +
    + +
    +

    Translated, roughly, from underscore:

    + +
    + +
      proto.pairs = proto.entries;
    +
    +  return Model;
    +})();
    + +
  • + +
+
+ + diff --git a/docs/backbone.html b/docs/backbone.html deleted file mode 100644 index 818fb9844..000000000 --- a/docs/backbone.html +++ /dev/null @@ -1,5439 +0,0 @@ - - - - - backbone.js - - - - - -
-
- -
    - -
  • -
    -

    backbone.js

    -
    -
  • - - - -
  • -
    - -
    - -
    -
    Backbone.js 1.4.0
    -
    -
    - -
  • - - -
  • -
    - -
    - -
    -
    (c) 2010-2019 Jeremy Ashkenas and DocumentCloud
    -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.

    - -
    - -
      var slice = Array.prototype.slice;
    - -
  • - - -
  • -
    - -
    - -
    -

    Current version of the library. Keep in sync with package.json.

    - -
    - -
      Backbone.VERSION = '1.4.0';
    - -
  • - - -
  • -
    - -
    - -
    -

    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;
    - -
  • - - -
  • -
    - -
    - -
    -

    Backbone.Events

    - -
    - -
  • - - -
  • -
    - -
    - -
    - -
    - -
  • - - -
  • -
    - -
    - -
    -

    A module that can be mixed in to any object in order to provide it with -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 -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+/;
    - -
  • - - -
  • -
    - -
    - -
    -

    A private global variable to share between listeners and listenees.

    - -
    - -
      var _listening;
    - -
  • - - -
  • -
    - -
    - -
    -

    Iterates over the standard event, callback (as well as the fancy multiple -space-separated events "change blur", callback and jQuery-style event -maps {event: callback}).

    - -
    - -
      var eventsApi = function(iteratee, events, name, callback, opts) {
    -    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++) {
    -        events = eventsApi(iteratee, events, names[i], name[names[i]], opts);
    -      }
    -    } else if (name && eventSplitter.test(name)) {
    - -
  • - - -
  • -
    - -
    - -
    -

    Handle space-separated event names by delegating them individually.

    - -
    - -
          for (names = name.split(eventSplitter); i < names.length; i++) {
    -        events = iteratee(events, names[i], callback, opts);
    -      }
    -    } else {
    - -
  • - - -
  • -
    - -
    - -
    -

    Finally, standard events.

    - -
    - -
          events = iteratee(events, name, callback, opts);
    -    }
    -    return events;
    -  };
    - -
  • - - -
  • -
    - -
    - -
    -

    Bind an event to a callback function. Passing "all" will bind -the callback to all events fired.

    - -
    - -
      Events.on = function(name, callback, context) {
    -    this._events = eventsApi(onApi, this._events || {}, name, callback, {
    -      context: context,
    -      ctx: this,
    -      listening: _listening
    -    });
    -
    -    if (_listening) {
    -      var listeners = this._listeners || (this._listeners = {});
    -      listeners[_listening.id] = _listening;
    - -
  • - - -
  • -
    - -
    - -
    -

    Allow the listening to use a counter, instead of tracking -callbacks for library interop

    - -
    - -
          _listening.interop = false;
    -    }
    -
    -    return this;
    -  };
    - -
  • - - -
  • -
    - -
    - -
    -

    Inversion-of-control versions of on. Tell this object to listen to -an event in another object… keeping track of what it’s listening to -for easier unbinding later.

    - -
    - -
      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 = _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) {
    -      this._listenId || (this._listenId = _.uniqueId('l'));
    -      listening = _listening = listeningTo[id] = new Listening(this, obj);
    -    }
    - -
  • - - -
  • -
    - -
    - -
    -

    Bind callbacks on obj.

    - -
    - -
        var error = tryCatchOn(obj, name, callback, this);
    -    _listening = void 0;
    -
    -    if (error) throw error;
    - -
  • - - -
  • -
    - -
    - -
    -

    If the target obj is not Backbone.Events, track events manually.

    - -
    - -
        if (listening.interop) listening.on(name, callback);
    -
    -    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;
    -  };
    - -
  • - - -
  • -
    - -
    - -
    -

    An try-catch guarded #on function, to prevent poisoning the global -_listening variable.

    - -
    - -
      var tryCatchOn = function(obj, name, callback, context) {
    -    try {
    -      obj.on(name, callback, context);
    -    } catch (e) {
    -      return e;
    -    }
    -  };
    - -
  • - - -
  • -
    - -
    - -
    -

    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 (listening.interop) listening.off(name, callback);
    -    }
    -    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 context = options.context, listeners = options.listeners;
    -    var i = 0, names;
    - -
  • - - -
  • -
    - -
    - -
    -

    Delete all event listeners and “drop” events.

    - -
    - -
        if (!name && !context && !callback) {
    -      for (names = _.keys(listeners); i < names.length; i++) {
    -        listeners[names[i]].cleanup();
    -      }
    -      return;
    -    }
    -
    -    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;
    - -
  • - - -
  • -
    - -
    - -
    -

    Find any remaining events.

    - -
    - -
          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 {
    -          var listening = handler.listening;
    -          if (listening) listening.off(name, callback);
    -        }
    -      }
    - -
  • - - -
  • -
    - -
    - -
    -

    Replace events if there are any remaining. Otherwise, clean up.

    - -
    - -
          if (remaining.length) {
    -        events[name] = remaining;
    -      } else {
    -        delete events[name];
    -      }
    -    }
    -
    -    return events;
    -  };
    - -
  • - - -
  • -
    - -
    - -
    -

    Bind an event to only be triggered a single time. After the first time -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.

    - -
    - -
      Events.once = function(name, callback, context) {
    - -
  • - - -
  • -
    - -
    - -
    -

    Map the event into a {event: once} object.

    - -
    - -
        var events = eventsApi(onceMap, {}, name, callback, this.off.bind(this));
    -    if (typeof name === 'string' && context == null) callback = void 0;
    -    return this.on(events, callback, 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, this.stopListening.bind(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, callback, 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;
    -    }
    -  };
    - -
  • - - -
  • -
    - -
    - -
    -

    A listening class that tracks and cleans up memory bindings -when all callbacks have been offed.

    - -
    - -
      var Listening = function(listener, obj) {
    -    this.id = listener._listenId;
    -    this.listener = listener;
    -    this.obj = obj;
    -    this.interop = true;
    -    this.count = 0;
    -    this._events = void 0;
    -  };
    -
    -  Listening.prototype.on = Events.on;
    - -
  • - - -
  • -
    - -
    - -
    -

    Offs a callback (or several). -Uses an optimized counter if the listenee uses Backbone.Events. -Otherwise, falls back to manual tracking to support events -library interop.

    - -
    - -
      Listening.prototype.off = function(name, callback) {
    -    var cleanup;
    -    if (this.interop) {
    -      this._events = eventsApi(offApi, this._events, name, callback, {
    -        context: void 0,
    -        listeners: void 0
    -      });
    -      cleanup = !this._events;
    -    } else {
    -      this.count--;
    -      cleanup = this.count === 0;
    -    }
    -    if (cleanup) this.cleanup();
    -  };
    - -
  • - - -
  • -
    - -
    - -
    -

    Cleans up memory bindings between the listener and the listenee.

    - -
    - -
      Listening.prototype.cleanup = function() {
    -    delete this.listener._listeningTo[this.obj._listenId];
    -    if (!this.interop) delete this.obj._listeners[this.id];
    -  };
    - -
  • - - -
  • -
    - -
    - -
    -

    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.preinitialize.apply(this, arguments);
    -    this.cid = _.uniqueId(this.cidPrefix);
    -    this.attributes = {};
    -    if (options.collection) this.collection = options.collection;
    -    if (options.parse) attrs = this.parse(attrs, options) || {};
    -    var defaults = _.result(this, 'defaults');
    -    attrs = _.defaults(_.extend({}, defaults, attrs), 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',
    - -
  • - - -
  • -
    - -
    - -
    -

    preinitialize is an empty function by default. You can override it with a function -or object. preinitialize will run before any instantiation logic is run in the Model.

    - -
    - -
        preinitialize: function(){},
    - -
  • - - -
  • -
    - -
    - -
    -

    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;
    -      }
    - -
  • - - -
  • -
    - -
    - -
    -

    Update the id.

    - -
    - -
          if (this.idAttribute in attrs) this.id = this.get(this.idAttribute);
    - -
  • - - -
  • -
    - -
    - -
    -

    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 = {};
    -      var hasChanged;
    -      for (var attr in diff) {
    -        var val = diff[attr];
    -        if (_.isEqual(old[attr], val)) continue;
    -        changed[attr] = val;
    -        hasChanged = true;
    -      }
    -      return hasChanged ? 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({}, _.extend({}, options, {validate: true}));
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    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;
    -    }
    -
    -  });
    - -
  • - - -
  • -
    - -
    - -
    -

    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 = {});
    -    this.preinitialize.apply(this, arguments);
    -    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};
    - -
  • - - -
  • -
    - -
    - -
    -

    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;
    -    var i;
    -    for (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];
    -  };
    - -
  • - - -
  • -
    - -
    - -
    -

    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,
    - -
  • - - -
  • -
    - -
    - -
    -

    preinitialize is an empty function by default. You can override it with a function -or object. preinitialize will run before any instantiation logic is run in the Collection.

    - -
    - -
        preinitialize: function(){},
    - -
  • - - -
  • -
    - -
    - -
    -

    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);
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    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.

    - -
    - -
        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] : models.slice();
    -      var removed = this._removeModels(models, options);
    -      if (!options.silent && removed.length) {
    -        options.changes = {added: [], merged: [], removed: 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) {
    -      if (models == null) return;
    -
    -      options = _.extend({}, setOptions, options);
    -      if (options.parse && !this._isModel(models)) {
    -        models = this.parse(models, options) || [];
    -      }
    -
    -      var singular = !_.isArray(models);
    -      models = singular ? [models] : models.slice();
    -
    -      var at = options.at;
    -      if (at != null) at = +at;
    -      if (at > this.length) at = this.length;
    -      if (at < 0) at += this.length + 1;
    -
    -      var set = [];
    -      var toAdd = [];
    -      var toMerge = [];
    -      var toRemove = [];
    -      var modelMap = {};
    -
    -      var add = options.add;
    -      var merge = options.merge;
    -      var remove = options.remove;
    -
    -      var sort = false;
    -      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.

    - -
    - -
          var model, i;
    -      for (i = 0; i < models.length; i++) {
    -        model = models[i];
    - -
  • - - -
  • -
    - -
    - -
    -

    If a duplicate is found, prevent it from being added and -optionally merge it into the existing model.

    - -
    - -
            var existing = this.get(model);
    -        if (existing) {
    -          if (merge && model !== existing) {
    -            var attrs = this._isModel(model) ? model.attributes : model;
    -            if (options.parse) attrs = existing.parse(attrs, options);
    -            existing.set(attrs, options);
    -            toMerge.push(existing);
    -            if (sortable && !sort) sort = existing.hasChanged(sortAttr);
    -          }
    -          if (!modelMap[existing.cid]) {
    -            modelMap[existing.cid] = true;
    -            set.push(existing);
    -          }
    -          models[i] = existing;
    - -
  • - - -
  • -
    - -
    - -
    -

    If this is a new, valid model, push it to the toAdd list.

    - -
    - -
            } else if (add) {
    -          model = models[i] = this._prepareModel(model, options);
    -          if (model) {
    -            toAdd.push(model);
    -            this._addReference(model, options);
    -            modelMap[model.cid] = true;
    -            set.push(model);
    -          }
    -        }
    -      }
    - -
  • - - -
  • -
    - -
    - -
    -

    Remove stale models.

    - -
    - -
          if (remove) {
    -        for (i = 0; i < this.length; i++) {
    -          model = this.models[i];
    -          if (!modelMap[model.cid]) toRemove.push(model);
    -        }
    -        if (toRemove.length) this._removeModels(toRemove, options);
    -      }
    - -
  • - - -
  • -
    - -
    - -
    -

    See if sorting is needed, update length and splice in new models.

    - -
    - -
          var orderChanged = false;
    -      var replace = !sortable && add && remove;
    -      if (set.length && replace) {
    -        orderChanged = this.length !== set.length || _.some(this.models, function(m, index) {
    -          return m !== set[index];
    -        });
    -        this.models.length = 0;
    -        splice(this.models, set, 0);
    -        this.length = this.models.length;
    -      } else if (toAdd.length) {
    -        if (sortable) sort = true;
    -        splice(this.models, toAdd, at == null ? this.length : at);
    -        this.length = this.models.length;
    -      }
    - -
  • - - -
  • -
    - -
    - -
    -

    Silently sort the collection if appropriate.

    - -
    - -
          if (sort) this.sort({silent: true});
    - -
  • - - -
  • -
    - -
    - -
    -

    Unless silenced, it’s time to fire all appropriate add/sort/update events.

    - -
    - -
          if (!options.silent) {
    -        for (i = 0; i < toAdd.length; i++) {
    -          if (at != null) options.index = at + i;
    -          model = toAdd[i];
    -          model.trigger('add', model, this, options);
    -        }
    -        if (sort || orderChanged) this.trigger('sort', this, options);
    -        if (toAdd.length || toRemove.length || toMerge.length) {
    -          options.changes = {
    -            added: toAdd,
    -            removed: toRemove,
    -            merged: toMerge
    -          };
    -          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, cid, model object with id or cid -properties, or an attributes object that is transformed through modelId.

    - -
    - -
        get: function(obj) {
    -      if (obj == null) return void 0;
    -      return this._byId[obj] ||
    -        this._byId[this.modelId(this._isModel(obj) ? obj.attributes : obj)] ||
    -        obj.cid && this._byId[obj.cid];
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    Returns true if the model is in the collection.

    - -
    - -
        has: function(obj) {
    -      return this.get(obj) != null;
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    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) {
    -      return this[first ? 'find' : 'filter'](attrs);
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    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) {
    -      var comparator = this.comparator;
    -      if (!comparator) throw new Error('Cannot sort a set without a comparator');
    -      options || (options = {});
    -
    -      var length = comparator.length;
    -      if (_.isFunction(comparator)) comparator = comparator.bind(this);
    - -
  • - - -
  • -
    - -
    - -
    -

    Run sort based on type of comparator.

    - -
    - -
          if (length === 1 || _.isString(comparator)) {
    -        this.models = this.sortBy(comparator);
    -      } else {
    -        this.models.sort(comparator);
    -      }
    -      if (!options.silent) this.trigger('sort', this, options);
    -      return this;
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    Pluck an attribute from each model in the collection.

    - -
    - -
        pluck: function(attr) {
    -      return this.map(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(m, resp, callbackOpts) {
    -        if (wait) collection.add(m, callbackOpts);
    -        if (success) success.call(callbackOpts.context, m, 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'];
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    Get an iterator of all models in this collection.

    - -
    - -
        values: function() {
    -      return new CollectionIterator(this, ITERATOR_VALUES);
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    Get an iterator of all model IDs in this collection.

    - -
    - -
        keys: function() {
    -      return new CollectionIterator(this, ITERATOR_KEYS);
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    Get an iterator of all [ID, model] tuples in this collection.

    - -
    - -
        entries: function() {
    -      return new CollectionIterator(this, ITERATOR_KEYSVALUES);
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    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--;
    - -
  • - - -
  • -
    - -
    - -
    -

    Remove references before triggering ‘remove’ event to prevent an -infinite loop. #3693

    - -
    - -
            delete this._byId[model.cid];
    -        var id = this.modelId(model.attributes);
    -        if (id != null) delete this._byId[id];
    -
    -        if (!options.silent) {
    -          options.index = index;
    -          model.trigger('remove', model, this, options);
    -        }
    -
    -        removed.push(model);
    -        this._removeReference(model, options);
    -      }
    -      return removed;
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    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 (model) {
    -        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);
    -    }
    -
    -  });
    - -
  • - - -
  • -
    - -
    - -
    -

    Defining an @@iterator method implements JavaScript’s Iterable protocol. -In modern ES2015 browsers, this value is found at Symbol.iterator.

    - -
    - -
      /* global Symbol */
    -  var $$iterator = typeof Symbol === 'function' && Symbol.iterator;
    -  if ($$iterator) {
    -    Collection.prototype[$$iterator] = Collection.prototype.values;
    -  }
    - -
  • - - -
  • -
    - -
    - -
    -

    CollectionIterator

    - -
    - -
  • - - -
  • -
    - -
    - -
    - -
    - -
  • - - -
  • -
    - -
    - -
    -

    A CollectionIterator implements JavaScript’s Iterator protocol, allowing the -use of for of loops in modern browsers and interoperation between -Backbone.Collection and other JavaScript functions and third-party libraries -which can operate on Iterables.

    - -
    - -
      var CollectionIterator = function(collection, kind) {
    -    this._collection = collection;
    -    this._kind = kind;
    -    this._index = 0;
    -  };
    - -
  • - - -
  • -
    - -
    - -
    -

    This “enum” defines the three possible kinds of values which can be emitted -by a CollectionIterator that correspond to the values(), keys() and entries() -methods on Collection, respectively.

    - -
    - -
      var ITERATOR_VALUES = 1;
    -  var ITERATOR_KEYS = 2;
    -  var ITERATOR_KEYSVALUES = 3;
    - -
  • - - -
  • -
    - -
    - -
    -

    All Iterators should themselves be Iterable.

    - -
    - -
      if ($$iterator) {
    -    CollectionIterator.prototype[$$iterator] = function() {
    -      return this;
    -    };
    -  }
    -
    -  CollectionIterator.prototype.next = function() {
    -    if (this._collection) {
    - -
  • - - -
  • -
    - -
    - -
    -

    Only continue iterating if the iterated collection is long enough.

    - -
    - -
          if (this._index < this._collection.length) {
    -        var model = this._collection.at(this._index);
    -        this._index++;
    - -
  • - - -
  • -
    - -
    - -
    -

    Construct a value depending on what kind of values should be iterated.

    - -
    - -
            var value;
    -        if (this._kind === ITERATOR_VALUES) {
    -          value = model;
    -        } else {
    -          var id = this._collection.modelId(model.attributes);
    -          if (this._kind === ITERATOR_KEYS) {
    -            value = id;
    -          } else { // ITERATOR_KEYSVALUES
    -            value = [id, model];
    -          }
    -        }
    -        return {value: value, done: false};
    -      }
    - -
  • - - -
  • -
    - -
    - -
    -

    Once exhausted, remove the reference to the collection so future -calls to the next method always return done.

    - -
    - -
          this._collection = void 0;
    -    }
    -
    -    return {value: void 0, done: true};
    -  };
    - -
  • - - -
  • -
    - -
    - -
    -

    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');
    -    this.preinitialize.apply(this, arguments);
    -    _.extend(this, _.pick(options, viewOptions));
    -    this._ensureElement();
    -    this.initialize.apply(this, arguments);
    -  };
    - -
  • - - -
  • -
    - -
    - -
    -

    Cached regex to split keys for delegate.

    - -
    - -
      var delegateEventSplitter = /^(\S+)\s*(.*)$/;
    - -
  • - - -
  • -
    - -
    - -
    -

    List of view options to be set as properties.

    - -
    - -
      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);
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    preinitialize is an empty function by default. You can override it with a function -or object. preinitialize will run before any instantiation logic is run in the View

    - -
    - -
        preinitialize: function(){},
    - -
  • - - -
  • -
    - -
    - -
    -

    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], method.bind(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);
    -    }
    -
    -  });
    - -
  • - - -
  • -
    - -
    - -
    -

    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.

    - -
    - -
      var addMethod = function(base, length, method, attribute) {
    -    switch (length) {
    -      case 1: return function() {
    -        return base[method](this[attribute]);
    -      };
    -      case 2: return function(value) {
    -        return base[method](this[attribute], value);
    -      };
    -      case 3: return function(iteratee, context) {
    -        return base[method](this[attribute], cb(iteratee, this), context);
    -      };
    -      case 4: return function(iteratee, defaultVal, context) {
    -        return base[method](this[attribute], cb(iteratee, this), defaultVal, context);
    -      };
    -      default: return function() {
    -        var args = slice.call(arguments);
    -        args.unshift(this[attribute]);
    -        return base[method].apply(base, args);
    -      };
    -    }
    -  };
    -
    -  var addUnderscoreMethods = function(Class, base, methods, attribute) {
    -    _.each(methods, function(length, method) {
    -      if (base[method]) Class.prototype[method] = addMethod(base, length, method, attribute);
    -    });
    -  };
    - -
  • - - -
  • -
    - -
    - -
    -

    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);
    -    };
    -  };
    - -
  • - - -
  • -
    - -
    - -
    -

    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: 0,
    -    foldl: 0, inject: 0, reduceRight: 0, foldr: 0, find: 3, detect: 3, filter: 3,
    -    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,
    -    head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3,
    -    without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3,
    -    isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3,
    -    sortBy: 3, indexBy: 3, findIndex: 3, findLastIndex: 3};
    - -
  • - - -
  • -
    - -
    - -
    -

    Underscore methods that we want to implement on the Model, mapped to the -number of arguments they take.

    - -
    - -
      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 Collection#models.

    - -
    - -
    -  _.each([
    -    [Collection, collectionMethods, 'models'],
    -    [Model, modelMethods, 'attributes']
    -  ], function(config) {
    -    var Base = config[0],
    -        methods = config[1],
    -        attribute = config[2];
    -
    -    Base.mixin = function(obj) {
    -      var mappings = _.reduce(_.functions(obj), function(memo, name) {
    -        memo[name] = 0;
    -        return memo;
    -      }, {});
    -      addUnderscoreMethods(Base, obj, mappings, attribute);
    -    };
    -
    -    addUnderscoreMethods(Base, _, methods, attribute);
    -  });
    - -
  • - - -
  • -
    - -
    - -
    -

    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 = {});
    -    this.preinitialize.apply(this, arguments);
    -    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, {
    - -
  • - - -
  • -
    - -
    - -
    -

    preinitialize is an empty function by default. You can override it with a function -or object. preinitialize will run before any instantiation logic is run in the Router.

    - -
    - -
        preinitialize: function(){},
    - -
  • - - -
  • -
    - -
    - -
    -

    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 and real URLs, or -onhashchange -and URL fragments. If the browser supports neither (old IE, natch), -falls back to polling.

    - -
    - -
      var History = Backbone.History = function() {
    -    this.handlers = [];
    -    this.checkUrl = this.checkUrl.bind(this);
    - -
  • - - -
  • -
    - -
    - -
    -

    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 rootPath = path.slice(0, this.root.length - 1) + '/';
    -      return rootPath === 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;
    -      this._hasHashChange   = 'onhashchange' in window && (document.documentMode === void 0 || document.documentMode > 7);
    -      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 rootPath = this.root.slice(0, -1) || '/';
    -          this.location.replace(rootPath + '#' + 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);
    -      return _.some(this.handlers, function(handler) {
    -        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 rootPath = this.root;
    -      if (fragment === '' || fragment.charAt(0) === '?') {
    -        rootPath = rootPath.slice(0, -1) || '/';
    -      }
    -      var url = rootPath + fragment;
    - -
  • - - -
  • -
    - -
    - -
    -

    Strip the fragment of the query and hash for matching.

    - -
    - -
          fragment = fragment.replace(pathStripper, '');
    - -
  • - - -
  • -
    - -
    - -
    -

    Decode for matching.

    - -
    - -
          var decodedFragment = this.decodeFragment(fragment);
    -
    -      if (this.fragment === decodedFragment) return;
    -      this.fragment = decodedFragment;
    - -
  • - - -
  • -
    - -
    - -
    -

    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‘s constructor function and add the prototype properties.

    - -
    - -
        child.prototype = _.create(parent.prototype, protoProps);
    -    child.prototype.constructor = child;
    - -
  • - - -
  • -
    - -
    - -
    -

    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;
    -});
    - -
  • - -
-
- - diff --git a/docs/backbone.localStorage.html b/docs/backbone.localStorage.html deleted file mode 100644 index 42c42c46c..000000000 --- a/docs/backbone.localStorage.html +++ /dev/null @@ -1,464 +0,0 @@ - - - - - backbone.localStorage.js - - - - - -
-
- - - -
    - -
  • -
    -

    backbone.localStorage.js

    -
    -
  • - - - -
  • -
    - -
    - -
    - -
    - -
    /**
    - * Backbone localStorage Adapter
    - * Version 1.1.0
    - *
    - * https://github.com/jeromegn/Backbone.localStorage
    - */
    -(function (root, factory) {
    -   if (typeof define === "function" && define.amd) {
    - -
  • - - -
  • -
    - -
    - -
    -

    AMD. Register as an anonymous module.

    - -
    - -
          define(["underscore","backbone"], function(_, Backbone) {
    - -
  • - - -
  • -
    - -
    - -
    -

    Use global variables if the locals are undefined.

    - -
    - -
            return factory(_ || root._, Backbone || root.Backbone);
    -      });
    -   } else {
    - -
  • - - -
  • -
    - -
    - -
    -

    RequireJS isn’t being used. Assume underscore and backbone are loaded in script tags

    - -
    - -
          factory(_, Backbone);
    -   }
    -}(this, function(_, Backbone) {
    - -
  • - - -
  • -
    - -
    - -
    -

    A simple module to replace Backbone.sync with localStorage-based -persistence. Models are given GUIDS, and saved into a JSON object. Simple -as that.

    - -
    - -
  • - - -
  • -
    - -
    - -
    -

    Hold reference to Underscore.js and Backbone.js in the closure in order -to make things work even if they are removed from the global namespace

    - -
    - -
  • - - -
  • -
    - -
    - -
    -

    Generate four random hex digits.

    - -
    - -
    function S4() {
    -   return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
    -};
    - -
  • - - -
  • -
    - -
    - -
    -

    Generate a pseudo-GUID by concatenating random hexadecimal.

    - -
    - -
    function guid() {
    -   return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
    -};
    - -
  • - - -
  • -
    - -
    - -
    -

    Our Store is represented by a single JS object in localStorage. Create it -with a meaningful name, like the name you’d give a table. -window.Store is deprecated, use Backbone.LocalStorage instead

    - -
    - -
    Backbone.LocalStorage = window.Store = function(name) {
    -  this.name = name;
    -  var store = this.localStorage().getItem(this.name);
    -  this.records = (store && store.split(",")) || [];
    -};
    -
    -_.extend(Backbone.LocalStorage.prototype, {
    - -
  • - - -
  • -
    - -
    - -
    -

    Save the current state of the Store to localStorage.

    - -
    - -
      save: function() {
    -    this.localStorage().setItem(this.name, this.records.join(","));
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    Add a model, giving it a (hopefully)-unique GUID, if it doesn’t already -have an id of it’s own.

    - -
    - -
      create: function(model) {
    -    if (!model.id) {
    -      model.id = guid();
    -      model.set(model.idAttribute, model.id);
    -    }
    -    this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model));
    -    this.records.push(model.id.toString());
    -    this.save();
    -    return this.find(model);
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    Update a model by replacing its copy in this.data.

    - -
    - -
      update: function(model) {
    -    this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model));
    -    if (!_.include(this.records, model.id.toString()))
    -      this.records.push(model.id.toString()); this.save();
    -    return this.find(model);
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    Retrieve a model from this.data by id.

    - -
    - -
      find: function(model) {
    -    return this.jsonData(this.localStorage().getItem(this.name+"-"+model.id));
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    Return the array of all models currently in storage.

    - -
    - -
      findAll: function() {
    -    return _(this.records).chain()
    -      .map(function(id){
    -        return this.jsonData(this.localStorage().getItem(this.name+"-"+id));
    -      }, this)
    -      .compact()
    -      .value();
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    Delete a model from this.data, returning it.

    - -
    - -
      destroy: function(model) {
    -    if (model.isNew())
    -      return false
    -    this.localStorage().removeItem(this.name+"-"+model.id);
    -    this.records = _.reject(this.records, function(id){
    -      return id === model.id.toString();
    -    });
    -    this.save();
    -    return model;
    -  },
    -
    -  localStorage: function() {
    -    return localStorage;
    -  },
    - -
  • - - -
  • -
    - -
    - -
    -

    fix for “illegal access” error on Android when JSON.parse is passed null

    - -
    - -
      jsonData: function (data) {
    -      return data && JSON.parse(data);
    -  }
    -
    -});
    - -
  • - - -
  • -
    - -
    - -
    -

    localSync delegate to the model or collection’s -localStorage property, which should be an instance of Store. -window.Store.sync and Backbone.localSync is deprectated, use Backbone.LocalStorage.sync instead

    - -
    - -
    Backbone.LocalStorage.sync = window.Store.sync = Backbone.localSync = function(method, model, options) {
    -  var store = model.localStorage || model.collection.localStorage;
    -
    -  var resp, errorMessage, syncDfd = $.Deferred && $.Deferred(); //If $ is having Deferred - use it.
    -
    -  try {
    -
    -    switch (method) {
    -      case "read":
    -        resp = model.id != undefined ? store.find(model) : store.findAll();
    -        break;
    -      case "create":
    -        resp = store.create(model);
    -        break;
    -      case "update":
    -        resp = store.update(model);
    -        break;
    -      case "delete":
    -        resp = store.destroy(model);
    -        break;
    -    }
    -
    -  } catch(error) {
    -    if (error.code === DOMException.QUOTA_EXCEEDED_ERR && window.localStorage.length === 0)
    -      errorMessage = "Private browsing is unsupported";
    -    else
    -      errorMessage = error.message;
    -  }
    -
    -  if (resp) {
    -    model.trigger("sync", model, resp, options);
    -    if (options && options.success)
    -      options.success(resp);
    -    if (syncDfd)
    -      syncDfd.resolve(resp);
    -
    -  } else {
    -    errorMessage = errorMessage ? errorMessage
    -                                : "Record Not Found";
    -
    -    if (options && options.error)
    -      options.error(errorMessage);
    -    if (syncDfd)
    -      syncDfd.reject(errorMessage);
    -  }
    - -
  • - - -
  • -
    - -
    - -
    -

    add compatibility with $.ajax -always execute callback for success and error

    - -
    - -
      if (options && options.complete) options.complete(resp);
    -
    -  return syncDfd && syncDfd.promise();
    -};
    -
    -Backbone.ajaxSync = Backbone.sync;
    -
    -Backbone.getSyncMethod = function(model) {
    -  if(model.localStorage || (model.collection && model.collection.localStorage)) {
    -    return Backbone.localSync;
    -  }
    -
    -  return Backbone.ajaxSync;
    -};
    - -
  • - - -
  • -
    - -
    - -
    -

    Override ‘Backbone.sync’ to default to localSync, -the original ‘Backbone.sync’ is still available in ‘Backbone.ajaxSync’

    - -
    - -
    Backbone.sync = function(method, model, options) {
    -  return Backbone.getSyncMethod(model).apply(this, [method, model, options]);
    -};
    -
    -return Backbone.LocalStorage;
    -}));
    - -
  • - -
-
- - diff --git a/docs/core.html b/docs/core.html new file mode 100644 index 000000000..032a47654 --- /dev/null +++ b/docs/core.html @@ -0,0 +1,106 @@ + + + + + core.js + + + + + +
+
+ + + +
    + +
  • +
    +

    core.js

    +
    +
  • + + + +
  • +
    + +
    + +
    + +
    + +
    export { Model } from './Model';
    +export { Collection } from './Collection';
    +export { ajax, sync } from './sync';
    +export { version as VERSION } from '../package.json';
    + +
  • + +
+
+ + diff --git a/docs/events.html b/docs/events.html new file mode 100644 index 000000000..20a0f261a --- /dev/null +++ b/docs/events.html @@ -0,0 +1,917 @@ + + + + + events.js + + + + + +
+
+ + + +
    + +
  • +
    +

    events.js

    +
    +
  • + + + +
  • +
    + +
    + +
    + +
    + +
    import _ from 'lodash';
    +import { keys, keysIn } from './utils';
    + +
  • + + +
  • +
    + +
    + +
    +

    Events

    + +
    + +
  • + + +
  • +
    + +
    + +
    + +
    + +
  • + + +
  • +
    + +
    + +
    +

    A module that can be mixed in to any object in order to provide it with 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 succession.

    +
    var object = {};
    +_.extend(object, Events);
    +object.on('expand', function(){ alert('expanded'); });
    +object.trigger('expand');
    + +
    + +
  • + + +
  • +
    + +
    + +
    +

    Regular expression used to split event strings.

    + +
    + +
    const eventSplitter = /\s+/;
    + +
  • + + +
  • +
    + +
    + +
    +

    A private singleton variable to share between listeners and listenees.

    + +
    + +
    let _listening;
    + +
  • + + +
  • +
    + +
    + +
    +

    Iterates over the standard event, callback (as well as the fancy multiple +space-separated events "change blur", callback and jQuery-style event maps +{event: callback}).

    + +
    + +
    function eventsApi(iteratee, events, name, callback, opts) {
    +  if (name && typeof name === 'object') {
    + +
  • + + +
  • +
    + +
    + +
    +

    Handle event maps.

    + +
    + +
        if (callback !== void 0 && 'context' in opts && opts.context === void 0)
    +      opts.context = callback;
    +    for (const key of keys(name)) {
    +      events = eventsApi(iteratee, events, key, name[key], opts);
    +    }
    +  } else if (name && eventSplitter.test(name)) {
    + +
  • + + +
  • +
    + +
    + +
    +

    Handle space-separated event names by delegating them individually.

    + +
    + +
        for (const key of name.split(eventSplitter)) {
    +      events = iteratee(events, key, callback, opts);
    +    }
    +  } else {
    + +
  • + + +
  • +
    + +
    + +
    +

    Finally, standard events.

    + +
    + +
        events = iteratee(events, name, callback, opts);
    +  }
    +  return events;
    +}
    + +
  • + + +
  • +
    + +
    + +
    +

    The reducing API that adds a callback to the events object.

    + +
    + +
    function onApi(events, name, callback, options) {
    +  if (callback) {
    +    const handlers = events[name] || (events[name] = []);
    +    const 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;
    +}
    + +
  • + + +
  • +
    + +
    + +
    +

    The reducing API that removes a callback from the events object.

    + +
    + +
    function offApi(events, name, callback, options) {
    +  if (!events) return;
    +
    +  const context = options.context,
    +    listeners = options.listeners;
    + +
  • + + +
  • +
    + +
    + +
    +

    Delete all event listeners and “drop” events.

    + +
    + +
      if (!name && !context && !callback) {
    +    for (const key of keys(listeners)) {
    +      listeners[key].cleanup();
    +    }
    +    return;
    +  }
    +
    +  const names = name ? [name] : keys(events);
    +  for (const key of names) {
    +    const handlers = events[key];
    + +
  • + + +
  • +
    + +
    + +
    +

    Bail out if there are no events stored.

    + +
    + +
        if (!handlers) break;
    + +
  • + + +
  • +
    + +
    + +
    +

    Find any remaining events.

    + +
    + +
        const remaining = [];
    +    for (let j = 0; j < handlers.length; j++) {
    +      const handler = handlers[j];
    +      if (
    +        (callback && callback !== handler.callback && callback !== handler.callback._callback) ||
    +        (context && context !== handler.context)
    +      ) {
    +        remaining.push(handler);
    +      } else {
    +        const listening = handler.listening;
    +        if (listening) listening.off(key, callback);
    +      }
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Replace events if there are any remaining. Otherwise, clean up.

    + +
    + +
        if (remaining.length) {
    +      events[key] = remaining;
    +    } else {
    +      delete events[key];
    +    }
    +  }
    +
    +  return events;
    +}
    + +
  • + + +
  • +
    + +
    + +
    +

    An try-catch guarded #on function, to prevent poisoning the global +_listening variable.

    + +
    + +
    function tryCatchOn(obj, name, callback, context) {
    +  try {
    +    obj.on(name, callback, context);
    +  } catch (e) {
    +    return e;
    +  }
    +}
    + +
  • + + +
  • +
    + +
    + +
    +

    Reduces the event callbacks into a map of {event: onceWrapper}. offer +unbinds the onceWrapper after it has been called.

    + +
    + +
    function onceMap(map, name, callback, offer) {
    +  if (callback) {
    +    const once = (map[name] = _.once(function (...args) {
    +      offer(name, once);
    +      callback.apply(this, args);
    +    }));
    +    once._callback = callback;
    +  }
    +  return map;
    +}
    + +
  • + + +
  • +
    + +
    + +
    +

    Handles triggering the appropriate event callbacks.

    + +
    + +
    function triggerApi(objEvents, name, callback, args) {
    +  if (objEvents) {
    +    const events = objEvents[name];
    +    let 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).

    + +
    + +
    function triggerEvents(events, args) {
    +  let ev,
    +    i = -1;
    +  const 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;
    +  }
    +}
    +
    +export const Events = /*#__PURE__*/ (() => {
    + +
  • + + +
  • +
    + +
    + +
    +

    eslint-disable-next-line no-shadow

    + +
    + +
      const Events = {
    + +
  • + + +
  • +
    + +
    + +
    +

    Bind an event to a callback function. Passing "all" will bind the +callback to all events fired.

    + +
    + +
        on(name, callback, context) {
    +      this._events = eventsApi(onApi, this._events || {}, name, callback, {
    +        context: context,
    +        ctx: this,
    +        listening: _listening,
    +      });
    +
    +      if (_listening) {
    +        const listeners = this._listeners || (this._listeners = {});
    +        listeners[_listening.id] = _listening;
    + +
  • + + +
  • +
    + +
    + +
    +

    Allow the listening to use a counter, instead of tracking callbacks +for library interop

    + +
    + +
            _listening.interop = false;
    +      }
    +
    +      return this;
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    Inversion-of-control versions of on. Tell this object to listen to an +event in another object… keeping track of what it’s listening to for +easier unbinding later.

    + +
    + +
        listenTo(obj, name, callback) {
    +      if (!obj) return this;
    +      const id = obj._listenId || (obj._listenId = _.uniqueId('l'));
    +      const listeningTo = this._listeningTo || (this._listeningTo = Object.create(null));
    +      let listening = (_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) {
    +        this._listenId || (this._listenId = _.uniqueId('l'));
    +        listening = _listening = listeningTo[id] = new Listening(this, obj);
    +      }
    + +
  • + + +
  • +
    + +
    + +
    +

    Bind callbacks on obj.

    + +
    + +
          const error = tryCatchOn(obj, name, callback, this);
    +      _listening = void 0;
    +
    +      if (error) throw error;
    + +
  • + + +
  • +
    + +
    + +
    +

    If the target obj is not Backbone.Events, track events manually.

    + +
    + +
          if (listening.interop) listening.on(name, callback);
    +
    +      return this;
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    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.

    + +
    + +
        off(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.

    + +
    + +
        stopListening(obj, name, callback) {
    +      const listeningTo = this._listeningTo;
    +      if (!listeningTo) return this;
    +
    +      const ids = obj ? [obj._listenId] : keysIn(listeningTo);
    +      for (let i = 0; i < ids.length; i++) {
    +        const 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 (listening.interop) listening.off(name, callback);
    +      }
    +      if (_.isEmpty(listeningTo)) this._listeningTo = void 0;
    +
    +      return this;
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    Bind an event to only be triggered a single time. After the first time +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.

    + +
    + +
        once(name, callback, context) {
    + +
  • + + +
  • +
    + +
    + +
    +

    Map the event into a {event: once} object.

    + +
    + +
          const events = eventsApi(onceMap, {}, name, callback, this.off.bind(this));
    +      if (typeof name === 'string' && context == null) callback = void 0;
    +      return this.on(events, callback, context);
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

    Inversion-of-control versions of once.

    + +
    + +
        listenToOnce(obj, name, callback) {
    + +
  • + + +
  • +
    + +
    + +
    +

    Map the event into a {event: once} object.

    + +
    + +
          const events = eventsApi(onceMap, {}, name, callback, this.stopListening.bind(this, obj));
    +      return this.listenTo(obj, events);
    +    },
    + +
  • + + +
  • +
    + +
    + +
    +

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

    + +
    + +
        trigger(name, ...args) {
    +      if (!this._events) return this;
    +
    +      eventsApi(triggerApi, this._events, name, void 0, args);
    +      return this;
    +    },
    +  };
    + +
  • + + +
  • +
    + +
    + +
    +

    Aliases for backwards compatibility.

    + +
    + +
      Events.bind = Events.on;
    +  Events.unbind = Events.off;
    +
    +  return Events;
    +})();
    + +
  • + + +
  • +
    + +
    + +
    +

    A listening class that tracks and cleans up memory bindings when all +callbacks have been offed.

    + +
    + +
    const Listening = /*#__PURE__*/ (() => {
    + +
  • + + +
  • +
    + +
    + +
    +

    eslint-disable-next-line no-shadow

    + +
    + +
      const Listening = class Listening {
    +    constructor(listener, obj) {
    +      this.id = listener._listenId;
    +      this.listener = listener;
    +      this.obj = obj;
    +      this.interop = true;
    +      this.count = 0;
    +      this._events = void 0;
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Offs a callback (or several). +Uses an optimized counter if the listenee uses Backbone.Events. +Otherwise, falls back to manual tracking to support events library +interop.

    + +
    + +
        off(name, callback) {
    +      let cleanup;
    +      if (this.interop) {
    +        this._events = eventsApi(offApi, this._events, name, callback, {
    +          context: void 0,
    +          listeners: void 0,
    +        });
    +        cleanup = !this._events;
    +      } else {
    +        this.count--;
    +        cleanup = this.count === 0;
    +      }
    +      if (cleanup) this.cleanup();
    +    }
    + +
  • + + +
  • +
    + +
    + +
    +

    Cleans up memory bindings between the listener and the listenee.

    + +
    + +
        cleanup() {
    +      delete this.listener._listeningTo[this.obj._listenId];
    +      if (!this.interop) delete this.obj._listeners[this.id];
    +    }
    +  };
    +
    +  Listening.prototype.on = Events.on;
    +
    +  return Listening;
    +})();
    + +
  • + +
+
+ + diff --git a/docs/extend.html b/docs/extend.html new file mode 100644 index 000000000..57c5b9bd6 --- /dev/null +++ b/docs/extend.html @@ -0,0 +1,234 @@ + + + + + extend.js + + + + + +
+
+ + + +
    + +
  • +
    +

    extend.js

    +
    +
  • + + + +
  • +
    + +
    + +
    + +
    + +
    import _ from 'lodash';
    + +
  • + + +
  • +
    + +
    + +
    +

    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.

    + +
    + +
    export default function extend(protoProps, staticProps) {
    +  const parent = this;
    +  let 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;
    + +
  • + + +
  • +
    + +
    + +
    +

    Set the prototype chain to inherit from parent, without calling +parent‘s constructor function and add the prototype properties.

    + +
    + +
        child.prototype = _.create(parent.prototype, protoProps);
    +    child.prototype.constructor = child;
    +  } else {
    +    child = class extends parent {};
    + +
  • + + +
  • +
    + +
    + +
    +

    Add the prototype properties.

    + +
    + +
        Object.assign(child, protoProps);
    +  }
    + +
  • + + +
  • +
    + +
    + +
    +

    Add static properties to the constructor function, if supplied.

    + +
    + +
      _.extend(child, parent, staticProps);
    + +
  • + + +
  • +
    + +
    + +
    +

    Set a convenience property in case the parent’s prototype is needed +later.

    + +
    + +
      child.__super__ = parent.prototype;
    +
    +  return child;
    +}
    + +
  • + +
+
+ + diff --git a/docs/fetch.html b/docs/fetch.html new file mode 100644 index 000000000..392719317 --- /dev/null +++ b/docs/fetch.html @@ -0,0 +1,201 @@ + + + + + fetch.js + + + + + +
+
+ + + +
    + +
  • +
    +

    fetch.js

    +
    +
  • + + + +
  • +
    + +
    + +
    +

    Backbone.Fetch.js 1.0.0

    + +
    + +
  • + + +
  • +
    + +
    + +
    + +
    + +
  • + + +
  • +
    + +
    + +
    +
    (c) 2016 Adam Krebs
    +Backbone.Fetch may be freely distributed under the MIT license.
    +For all details and documentation:
    +https://github.com/akre54/Backbone.Fetch
    + +
    + +
    +import _ from 'lodash';
    +
    +function stringifyGETParams(url, data) {
    +  const parsed = new URL(url);
    +  for (const key in data) {
    +    if (!_.has(data, key)) continue;
    +    const value = data[key];
    +    if (value == null) continue;
    +    parsed.searchParams.append(key, value);
    +  }
    +  return parsed.href;
    +}
    +
    +function getData(response, dataType) {
    +  return dataType === 'json' && response.status !== 204 ? response.json() : response.text();
    +}
    +
    +export default function ajax(options) {
    +  let url = options.url;
    +  if (options.type === 'GET' && options.data !== null && typeof options.data === 'object') {
    +    url = stringifyGETParams(url, options.data);
    +    delete options.data;
    +  } else {
    + +
  • + + +
  • +
    + +
    + +
    +

    For consistent errors when the url isn’t valid.

    + +
    + +
        new URL(url);
    +  }
    +
    +  _.defaults(options, {
    +    method: options.type,
    +    headers: _.defaults(options.headers || {}, {
    +      Accept: 'application/json',
    +      'Content-Type': 'application/json',
    +    }),
    +    body: options.data,
    +  });
    +
    +  return fetch(url, options)
    +    .then((response) => {
    +      const promise =
    +        options.type === 'HEAD' ? Promise.resolve(null) : getData(response, options.dataType);
    +
    +      if (response.ok) return promise;
    +
    +      const error = new Error(response.statusText);
    +      return promise.then((responseData) => {
    +        error.response = response;
    +        error.responseData = responseData;
    +        if (options.error) options.error(error);
    +        throw error;
    +      });
    +    })
    +    .then((responseData) => {
    +      if (options.success) options.success(responseData);
    +      return responseData;
    +    });
    +}
    + +
  • + +
+
+ + diff --git a/docs/flags.html b/docs/flags.html new file mode 100644 index 000000000..f4fe86eec --- /dev/null +++ b/docs/flags.html @@ -0,0 +1,126 @@ + + + + + flags.js + + + + + +
+
+ + + +
    + +
  • +
    +

    flags.js

    +
    +
  • + + + +
  • +
    + +
    + +
    +

    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. +eslint-disable-next-line prefer-const

    + +
    + +
    export let 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. +eslint-disable-next-line prefer-const

    + +
    + +
    export let emulateJSON = false;
    + +
  • + +
+
+ + diff --git a/docs/mixin-methods.html b/docs/mixin-methods.html new file mode 100644 index 000000000..5664ef1f1 --- /dev/null +++ b/docs/mixin-methods.html @@ -0,0 +1,177 @@ + + + + + mixin-methods.js + + + + + +
+
+ + + +
    + +
  • +
    +

    mixin-methods.js

    +
    +
  • + + + +
  • +
    + +
    + +
    + +
    + +
    import _ from 'lodash';
    + +
  • + + +
  • +
    + +
    + +
    +

    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.

    + +
    + +
    export function mixinMethod(method, length, 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) {
    +        return method(this[attribute], cb(iteratee, this), context);
    +      };
    +    case 4:
    +      return function (iteratee, defaultVal, context) {
    +        return method(this[attribute], cb(iteratee, this), defaultVal, context);
    +      };
    +    default:
    +      return function (...args) {
    +        args.unshift(this[attribute]);
    +        return method.apply(null, args);
    +      };
    +  }
    +}
    +
    +export function mixinMethods(Class, methods, attribute) {
    +  for (const [name, method, length] of Object.entries(methods)) {
    +    Class.prototype[name] = mixinMethod(method, length, attribute);
    +  }
    +}
    + +
  • + + +
  • +
    + +
    + +
    +

    Support collection.sortBy('attr') and collection.findWhere({id: 1}).

    + +
    + +
    function cb(iteratee, instance) {
    +  if (typeof iteratee === 'function') return iteratee;
    +  if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee);
    +  if (typeof iteratee === 'string') return (model) => model.get(iteratee);
    +  return iteratee;
    +}
    +
    +function modelMatcher(attrs) {
    +  const matcher = _.matches(attrs);
    +  return (model) => matcher(model.attributes);
    +}
    + +
  • + +
+
+ + diff --git a/docs/sync.html b/docs/sync.html new file mode 100644 index 000000000..66391022f --- /dev/null +++ b/docs/sync.html @@ -0,0 +1,368 @@ + + + + + sync.js + + + + + +
+
+ + + +
    + +
  • +
    +

    sync.js

    +
    +
  • + + + +
  • +
    + +
    + +
    + +
    + +
    import { urlError } from './utils';
    +import * as flags from './flags';
    +import _ from 'lodash';
    +import ajaxFetch from './fetch';
    + +
  • + + +
  • +
    + +
    + +
    +

    Map from CRUD to HTTP for our default Backbone.sync implementation.

    + +
    + +
    const methodMap = Object.assign(Object.create(null), {
    +  create: 'POST',
    +  update: 'PUT',
    +  patch: 'PATCH',
    +  delete: 'DELETE',
    +  read: 'GET',
    +});
    + +
  • + + +
  • +
    + +
    + +
    +

    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.

    + +
    + +
    export function sync(method, model, options) {
    +  const type = methodMap[method];
    + +
  • + + +
  • +
    + +
    + +
    +

    Default options, unless specified.

    + +
    + +
      _.defaults(options || (options = {}), {
    +    emulateHTTP: flags.emulateHTTP,
    +    emulateJSON: flags.emulateJSON,
    +  });
    + +
  • + + +
  • +
    + +
    + +
    +

    Default JSON-request options.

    + +
    + +
      const params = { 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;
    +    const 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.

    + +
    + +
      const 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.

    + +
    + +
      const xhr = (options.xhr = ajax(_.extend(params, options)));
    +  model.trigger('request', model, xhr, options);
    +  return xhr;
    +}
    + +
  • + + +
  • +
    + +
    + +
    +

    Set the default implementation of Backbone.ajax to proxy through to $. +Override this if you’d like to use a different library. +eslint-disable-next-line prefer-const

    + +
    + +
    export let ajax = ajaxFetch;
    + +
  • + +
+
+ + diff --git a/docs/todos.html b/docs/todos.html deleted file mode 100644 index 1ac4e52b4..000000000 --- a/docs/todos.html +++ /dev/null @@ -1,791 +0,0 @@ - - - - - todos.js - - - - - -
-
- - - -
    - -
  • -
    -

    todos.js

    -
    -
  • - - - -
  • -
    - -
    - -
    -

    An example Backbone application contributed by -Jérôme Gravel-Niquet. This demo uses a simple -LocalStorage adapter -to persist Backbone models within your browser.

    - -
    - -
  • - - -
  • -
    - -
    - -
    -

    Load the application once the DOM is ready, using jQuery.ready:

    - -
    - -
    $(function(){
    - -
  • - - -
  • -
    - -
    - -
    -

    Todo Model

    - -
    - -
  • - - -
  • -
    - -
    - -
    - -
    - -
  • - - -
  • -
    - -
    - -
    -

    Our basic Todo model has title, order, and done attributes.

    - -
    - -
      var Todo = Backbone.Model.extend({
    - -
  • - - -
  • -
    - -
    - -
    -

    Default attributes for the todo item.

    - -
    - -
        defaults: function() {
    -      return {
    -        title: "empty todo...",
    -        order: Todos.nextOrder(),
    -        done: false
    -      };
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    Toggle the done state of this todo item.

    - -
    - -
        toggle: function() {
    -      this.save({done: !this.get("done")});
    -    }
    -
    -  });
    - -
  • - - -
  • -
    - -
    - -
    -

    Todo Collection

    - -
    - -
  • - - -
  • -
    - -
    - -
    - -
    - -
  • - - -
  • -
    - -
    - -
    -

    The collection of todos is backed by localStorage instead of a remote -server.

    - -
    - -
      var TodoList = Backbone.Collection.extend({
    - -
  • - - -
  • -
    - -
    - -
    -

    Reference to this collection’s model.

    - -
    - -
        model: Todo,
    - -
  • - - -
  • -
    - -
    - -
    -

    Save all of the todo items under the "todos-backbone" namespace.

    - -
    - -
        localStorage: new Backbone.LocalStorage("todos-backbone"),
    - -
  • - - -
  • -
    - -
    - -
    -

    Filter down the list of all todo items that are finished.

    - -
    - -
        done: function() {
    -      return this.where({done: true});
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    Filter down the list to only todo items that are still not finished.

    - -
    - -
        remaining: function() {
    -      return this.where({done: false});
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    We keep the Todos in sequential order, despite being saved by unordered -GUID in the database. This generates the next order number for new items.

    - -
    - -
        nextOrder: function() {
    -      if (!this.length) return 1;
    -      return this.last().get('order') + 1;
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    Todos are sorted by their original insertion order.

    - -
    - -
        comparator: 'order'
    -
    -  });
    - -
  • - - -
  • -
    - -
    - -
    -

    Create our global collection of Todos.

    - -
    - -
      var Todos = new TodoList;
    - -
  • - - -
  • -
    - -
    - -
    -

    Todo Item View

    - -
    - -
  • - - -
  • -
    - -
    - -
    - -
    - -
  • - - -
  • -
    - -
    - -
    -

    The DOM element for a todo item…

    - -
    - -
      var TodoView = Backbone.View.extend({
    - -
  • - - -
  • -
    - -
    - -
    -

    … is a list tag.

    - -
    - -
        tagName:  "li",
    - -
  • - - -
  • -
    - -
    - -
    -

    Cache the template function for a single item.

    - -
    - -
        template: _.template($('#item-template').html()),
    - -
  • - - -
  • -
    - -
    - -
    -

    The DOM events specific to an item.

    - -
    - -
        events: {
    -      "click .toggle"   : "toggleDone",
    -      "dblclick .view"  : "edit",
    -      "click a.destroy" : "clear",
    -      "keypress .edit"  : "updateOnEnter",
    -      "blur .edit"      : "close"
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    The TodoView listens for changes to its model, re-rendering. Since there’s -a one-to-one correspondence between a Todo and a TodoView in this -app, we set a direct reference on the model for convenience.

    - -
    - -
        initialize: function() {
    -      this.listenTo(this.model, 'change', this.render);
    -      this.listenTo(this.model, 'destroy', this.remove);
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    Re-render the titles of the todo item.

    - -
    - -
        render: function() {
    -      this.$el.html(this.template(this.model.toJSON()));
    -      this.$el.toggleClass('done', this.model.get('done'));
    -      this.input = this.$('.edit');
    -      return this;
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    Toggle the "done" state of the model.

    - -
    - -
        toggleDone: function() {
    -      this.model.toggle();
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    Switch this view into "editing" mode, displaying the input field.

    - -
    - -
        edit: function() {
    -      this.$el.addClass("editing");
    -      this.input.focus();
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    Close the "editing" mode, saving changes to the todo.

    - -
    - -
        close: function() {
    -      var value = this.input.val();
    -      if (!value) {
    -        this.clear();
    -      } else {
    -        this.model.save({title: value});
    -        this.$el.removeClass("editing");
    -      }
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    If you hit enter, we’re through editing the item.

    - -
    - -
        updateOnEnter: function(e) {
    -      if (e.keyCode == 13) this.close();
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    Remove the item, destroy the model.

    - -
    - -
        clear: function() {
    -      this.model.destroy();
    -    }
    -
    -  });
    - -
  • - - -
  • -
    - -
    - -
    -

    The Application

    - -
    - -
  • - - -
  • -
    - -
    - -
    - -
    - -
  • - - -
  • -
    - -
    - -
    -

    Our overall AppView is the top-level piece of UI.

    - -
    - -
      var AppView = Backbone.View.extend({
    - -
  • - - -
  • -
    - -
    - -
    -

    Instead of generating a new element, bind to the existing skeleton of -the App already present in the HTML.

    - -
    - -
        el: $("#todoapp"),
    - -
  • - - -
  • -
    - -
    - -
    -

    Our template for the line of statistics at the bottom of the app.

    - -
    - -
        statsTemplate: _.template($('#stats-template').html()),
    - -
  • - - -
  • -
    - -
    - -
    -

    Delegated events for creating new items, and clearing completed ones.

    - -
    - -
        events: {
    -      "keypress #new-todo":  "createOnEnter",
    -      "click #clear-completed": "clearCompleted",
    -      "click #toggle-all": "toggleAllComplete"
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    At initialization we bind to the relevant events on the Todos -collection, when items are added or changed. Kick things off by -loading any preexisting todos that might be saved in localStorage.

    - -
    - -
        initialize: function() {
    -
    -      this.input = this.$("#new-todo");
    -      this.allCheckbox = this.$("#toggle-all")[0];
    -
    -      this.listenTo(Todos, 'add', this.addOne);
    -      this.listenTo(Todos, 'reset', this.addAll);
    -      this.listenTo(Todos, 'all', this.render);
    -
    -      this.footer = this.$('footer');
    -      this.main = $('#main');
    -
    -      Todos.fetch();
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    Re-rendering the App just means refreshing the statistics – the rest -of the app doesn’t change.

    - -
    - -
        render: function() {
    -      var done = Todos.done().length;
    -      var remaining = Todos.remaining().length;
    -
    -      if (Todos.length) {
    -        this.main.show();
    -        this.footer.show();
    -        this.footer.html(this.statsTemplate({done: done, remaining: remaining}));
    -      } else {
    -        this.main.hide();
    -        this.footer.hide();
    -      }
    -
    -      this.allCheckbox.checked = !remaining;
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    Add a single todo item to the list by creating a view for it, and -appending its element to the <ul>.

    - -
    - -
        addOne: function(todo) {
    -      var view = new TodoView({model: todo});
    -      this.$("#todo-list").append(view.render().el);
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    Add all items in the Todos collection at once.

    - -
    - -
        addAll: function() {
    -      Todos.each(this.addOne, this);
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    If you hit return in the main input field, create new Todo model, -persisting it to localStorage.

    - -
    - -
        createOnEnter: function(e) {
    -      if (e.keyCode != 13) return;
    -      if (!this.input.val()) return;
    -
    -      Todos.create({title: this.input.val()});
    -      this.input.val('');
    -    },
    - -
  • - - -
  • -
    - -
    - -
    -

    Clear all done todo items, destroying their models.

    - -
    - -
        clearCompleted: function() {
    -      _.invoke(Todos.done(), 'destroy');
    -      return false;
    -    },
    -
    -    toggleAllComplete: function () {
    -      var done = this.allCheckbox.checked;
    -      Todos.each(function (todo) { todo.save({'done': done}); });
    -    }
    -
    -  });
    - -
  • - - -
  • -
    - -
    - -
    -

    Finally, we kick things off by creating the App.

    - -
    - -
      var App = new AppView;
    -
    -});
    - -
  • - -
-
- - diff --git a/docs/utils.html b/docs/utils.html new file mode 100644 index 000000000..b4895cabb --- /dev/null +++ b/docs/utils.html @@ -0,0 +1,180 @@ + + + + + utils.js + + + + + +
+
+ + + +
    + +
  • +
    +

    utils.js

    +
    +
  • + + + +
  • +
    + +
    + +
    +

    Create a local reference to common methods we’ll want to use later.

    + +
    + +
    export const slice = Array.prototype.slice;
    +export const hasOwn = Object.prototype.hasOwnProperty;
    + +
  • + + +
  • +
    + +
    + +
    +

    Splices insert into array at index at.

    + +
    + +
    export function splice(array, insert, at) {
    +  at = Math.min(Math.max(at, 0), array.length);
    +  const tail = Array(array.length - at);
    +  const length = insert.length;
    +  let i;
    +  for (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];
    +}
    + +
  • + + +
  • +
    + +
    + +
    +

    Throw an error when a URL is needed, and none is supplied.

    + +
    + +
    export function urlError() {
    +  throw new Error('A "url" property or function must be specified');
    +}
    + +
  • + + +
  • +
    + +
    + +
    +

    Wrap an optional error callback with a fallback error event.

    + +
    + +
    export function wrapError(model, options) {
    +  const { error } = options;
    +  options.error = function (resp) {
    +    if (error) error.call(options.context, model, resp, options);
    +    model.trigger('error', model, resp, options);
    +  };
    +}
    +
    +export function* keys(obj) {
    +  for (const key in obj) {
    +    if (hasOwn.call(obj, key)) {
    +      yield key;
    +    }
    +  }
    +}
    +
    +export function* keysIn(obj) {
    +  for (const key in obj) {
    +    yield key;
    +  }
    +}
    + +
  • + +
+
+ + diff --git a/examples/backbone.localStorage.js b/examples/backbone.localStorage.js deleted file mode 100644 index 4ca7fe0e2..000000000 --- a/examples/backbone.localStorage.js +++ /dev/null @@ -1,184 +0,0 @@ -/** - * Backbone localStorage Adapter - * Version 1.1.0 - * - * https://github.com/jeromegn/Backbone.localStorage - */ -(function (root, factory) { - if (typeof define === "function" && define.amd) { - // AMD. Register as an anonymous module. - define(["underscore","backbone"], function(_, Backbone) { - // Use global variables if the locals are undefined. - return factory(_ || root._, Backbone || root.Backbone); - }); - } else { - // RequireJS isn't being used. Assume underscore and backbone are loaded in script tags - factory(_, Backbone); - } -}(this, function(_, Backbone) { -// A simple module to replace `Backbone.sync` with *localStorage*-based -// persistence. Models are given GUIDS, and saved into a JSON object. Simple -// as that. - -// Hold reference to Underscore.js and Backbone.js in the closure in order -// to make things work even if they are removed from the global namespace - -// Generate four random hex digits. -function S4() { - return (((1+Math.random())*0x10000)|0).toString(16).substring(1); -}; - -// Generate a pseudo-GUID by concatenating random hexadecimal. -function guid() { - return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4()); -}; - -// Our Store is represented by a single JS object in *localStorage*. Create it -// with a meaningful name, like the name you'd give a table. -// window.Store is deprecated, use Backbone.LocalStorage instead -Backbone.LocalStorage = window.Store = function(name) { - this.name = name; - var store = this.localStorage().getItem(this.name); - this.records = (store && store.split(",")) || []; -}; - -_.extend(Backbone.LocalStorage.prototype, { - - // Save the current state of the **Store** to *localStorage*. - save: function() { - this.localStorage().setItem(this.name, this.records.join(",")); - }, - - // Add a model, giving it a (hopefully)-unique GUID, if it doesn't already - // have an id of it's own. - create: function(model) { - if (!model.id) { - model.id = guid(); - model.set(model.idAttribute, model.id); - } - this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model)); - this.records.push(model.id.toString()); - this.save(); - return this.find(model); - }, - - // Update a model by replacing its copy in `this.data`. - update: function(model) { - this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model)); - if (!_.include(this.records, model.id.toString())) - this.records.push(model.id.toString()); this.save(); - return this.find(model); - }, - - // Retrieve a model from `this.data` by id. - find: function(model) { - return this.jsonData(this.localStorage().getItem(this.name+"-"+model.id)); - }, - - // Return the array of all models currently in storage. - findAll: function() { - return _(this.records).chain() - .map(function(id){ - return this.jsonData(this.localStorage().getItem(this.name+"-"+id)); - }, this) - .compact() - .value(); - }, - - // Delete a model from `this.data`, returning it. - destroy: function(model) { - if (model.isNew()) - return false - this.localStorage().removeItem(this.name+"-"+model.id); - this.records = _.reject(this.records, function(id){ - return id === model.id.toString(); - }); - this.save(); - return model; - }, - - localStorage: function() { - return localStorage; - }, - - // fix for "illegal access" error on Android when JSON.parse is passed null - jsonData: function (data) { - return data && JSON.parse(data); - } - -}); - -// localSync delegate to the model or collection's -// *localStorage* property, which should be an instance of `Store`. -// window.Store.sync and Backbone.localSync is deprectated, use Backbone.LocalStorage.sync instead -Backbone.LocalStorage.sync = window.Store.sync = Backbone.localSync = function(method, model, options) { - var store = model.localStorage || model.collection.localStorage; - - var resp, errorMessage, syncDfd = $.Deferred && $.Deferred(); //If $ is having Deferred - use it. - - try { - - switch (method) { - case "read": - resp = model.id != undefined ? store.find(model) : store.findAll(); - break; - case "create": - resp = store.create(model); - break; - case "update": - resp = store.update(model); - break; - case "delete": - resp = store.destroy(model); - break; - } - - } catch(error) { - if (error.code === DOMException.QUOTA_EXCEEDED_ERR && window.localStorage.length === 0) - errorMessage = "Private browsing is unsupported"; - else - errorMessage = error.message; - } - - if (resp) { - model.trigger("sync", model, resp, options); - if (options && options.success) - options.success(resp); - if (syncDfd) - syncDfd.resolve(resp); - - } else { - errorMessage = errorMessage ? errorMessage - : "Record Not Found"; - - if (options && options.error) - options.error(errorMessage); - if (syncDfd) - syncDfd.reject(errorMessage); - } - - // add compatibility with $.ajax - // always execute callback for success and error - if (options && options.complete) options.complete(resp); - - return syncDfd && syncDfd.promise(); -}; - -Backbone.ajaxSync = Backbone.sync; - -Backbone.getSyncMethod = function(model) { - if(model.localStorage || (model.collection && model.collection.localStorage)) { - return Backbone.localSync; - } - - return Backbone.ajaxSync; -}; - -// Override 'Backbone.sync' to default to localSync, -// the original 'Backbone.sync' is still available in 'Backbone.ajaxSync' -Backbone.sync = function(method, model, options) { - return Backbone.getSyncMethod(model).apply(this, [method, model, options]); -}; - -return Backbone.LocalStorage; -})); diff --git a/examples/todos/destroy.png b/examples/todos/destroy.png deleted file mode 100644 index bb5703f04..000000000 Binary files a/examples/todos/destroy.png and /dev/null differ diff --git a/examples/todos/index.html b/examples/todos/index.html deleted file mode 100644 index e737fd2c2..000000000 --- a/examples/todos/index.html +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - Backbone.js Todos - - - - - -
- -
-

Todos

- -
- -
- - -
    -
    - - - -
    - -
    - Double-click to edit a todo. -
    - -
    - Created by -
    - Jérôme Gravel-Niquet. -
    Rewritten by: TodoMVC. -
    - - - - - - - - - - - - - - - - diff --git a/examples/todos/todos.css b/examples/todos/todos.css deleted file mode 100644 index 35bdb056f..000000000 --- a/examples/todos/todos.css +++ /dev/null @@ -1,211 +0,0 @@ -html, -body { - margin: 0; - padding: 0; -} - -body { - font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif; - line-height: 1.4em; - background: #eeeeee; - color: #333333; - width: 520px; - margin: 0 auto; - -webkit-font-smoothing: antialiased; -} - -#todoapp { - background: #fff; - padding: 20px; - margin-bottom: 40px; - -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0; - -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0; - -ms-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0; - -o-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0; - box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0; - -webkit-border-radius: 0 0 5px 5px; - -moz-border-radius: 0 0 5px 5px; - -ms-border-radius: 0 0 5px 5px; - -o-border-radius: 0 0 5px 5px; - border-radius: 0 0 5px 5px; -} - -#todoapp h1 { - font-size: 36px; - font-weight: bold; - text-align: center; - padding: 0 0 10px 0; -} - -#todoapp input[type="text"] { - width: 466px; - font-size: 24px; - font-family: inherit; - line-height: 1.4em; - border: 0; - outline: none; - padding: 6px; - border: 1px solid #999999; - -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; - -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; - -ms-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; - -o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; - box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; -} - -#todoapp input::-webkit-input-placeholder { - font-style: italic; -} - -#main { - display: none; -} - -#todo-list { - margin: 10px 0; - padding: 0; - list-style: none; -} - -#todo-list li { - padding: 18px 20px 18px 0; - position: relative; - font-size: 24px; - border-bottom: 1px solid #cccccc; -} - -#todo-list li:last-child { - border-bottom: none; -} - -#todo-list li.done label { - color: #777777; - text-decoration: line-through; -} - -#todo-list .destroy { - position: absolute; - right: 5px; - top: 20px; - display: none; - cursor: pointer; - width: 20px; - height: 20px; - background: url(destroy.png) no-repeat; -} - -#todo-list li:hover .destroy { - display: block; -} - -#todo-list .destroy:hover { - background-position: 0 -20px; -} - -#todo-list li.editing { - border-bottom: none; - margin-top: -1px; - padding: 0; -} - -#todo-list li.editing:last-child { - margin-bottom: -1px; -} - -#todo-list li.editing .edit { - display: block; - width: 444px; - padding: 13px 15px 14px 20px; - margin: 0; -} - -#todo-list li.editing .view { - display: none; -} - -#todo-list li .view label { - word-break: break-word; -} - -#todo-list li .edit { - display: none; -} - -#todoapp footer { - display: none; - margin: 0 -20px -20px -20px; - overflow: hidden; - color: #555555; - background: #f4fce8; - border-top: 1px solid #ededed; - padding: 0 20px; - line-height: 37px; - -webkit-border-radius: 0 0 5px 5px; - -moz-border-radius: 0 0 5px 5px; - -ms-border-radius: 0 0 5px 5px; - -o-border-radius: 0 0 5px 5px; - border-radius: 0 0 5px 5px; -} - -#clear-completed { - float: right; - line-height: 20px; - text-decoration: none; - background: rgba(0, 0, 0, 0.1); - color: #555555; - font-size: 11px; - margin-top: 8px; - margin-bottom: 8px; - padding: 0 10px 1px; - cursor: pointer; - -webkit-border-radius: 12px; - -moz-border-radius: 12px; - -ms-border-radius: 12px; - -o-border-radius: 12px; - border-radius: 12px; - -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0; - -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0; - -ms-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0; - -o-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0; - box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0; -} - -#clear-completed:hover { - background: rgba(0, 0, 0, 0.15); - -webkit-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0; - -moz-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0; - -ms-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0; - -o-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0; - box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0; -} - -#clear-completed:active { - position: relative; - top: 1px; -} - -#todo-count span { - font-weight: bold; -} - -#instructions { - margin: 10px auto; - color: #777777; - text-shadow: rgba(255, 255, 255, 0.8) 0 1px 0; - text-align: center; -} - -#instructions a { - color: #336699; -} - -#credits { - margin: 30px auto; - color: #999; - text-shadow: rgba(255, 255, 255, 0.8) 0 1px 0; - text-align: center; -} - -#credits a { - color: #888; -} diff --git a/examples/todos/todos.js b/examples/todos/todos.js deleted file mode 100644 index 91e55c313..000000000 --- a/examples/todos/todos.js +++ /dev/null @@ -1,234 +0,0 @@ -// An example Backbone application contributed by -// [Jérôme Gravel-Niquet](http://jgn.me/). This demo uses a simple -// [LocalStorage adapter](backbone.localStorage.html) -// to persist Backbone models within your browser. - -// Load the application once the DOM is ready, using `jQuery.ready`: -$(function(){ - - // Todo Model - // ---------- - - // Our basic **Todo** model has `title`, `order`, and `done` attributes. - var Todo = Backbone.Model.extend({ - - // Default attributes for the todo item. - defaults: function() { - return { - title: "empty todo...", - order: Todos.nextOrder(), - done: false - }; - }, - - // Toggle the `done` state of this todo item. - toggle: function() { - this.save({done: !this.get("done")}); - } - - }); - - // Todo Collection - // --------------- - - // The collection of todos is backed by *localStorage* instead of a remote - // server. - var TodoList = Backbone.Collection.extend({ - - // Reference to this collection's model. - model: Todo, - - // Save all of the todo items under the `"todos-backbone"` namespace. - localStorage: new Backbone.LocalStorage("todos-backbone"), - - // Filter down the list of all todo items that are finished. - done: function() { - return this.where({done: true}); - }, - - // Filter down the list to only todo items that are still not finished. - remaining: function() { - return this.where({done: false}); - }, - - // We keep the Todos in sequential order, despite being saved by unordered - // GUID in the database. This generates the next order number for new items. - nextOrder: function() { - if (!this.length) return 1; - return this.last().get('order') + 1; - }, - - // Todos are sorted by their original insertion order. - comparator: 'order' - - }); - - // Create our global collection of **Todos**. - var Todos = new TodoList; - - // Todo Item View - // -------------- - - // The DOM element for a todo item... - var TodoView = Backbone.View.extend({ - - //... is a list tag. - tagName: "li", - - // Cache the template function for a single item. - template: _.template($('#item-template').html()), - - // The DOM events specific to an item. - events: { - "click .toggle" : "toggleDone", - "dblclick .view" : "edit", - "click a.destroy" : "clear", - "keypress .edit" : "updateOnEnter", - "blur .edit" : "close" - }, - - // The TodoView listens for changes to its model, re-rendering. Since there's - // a one-to-one correspondence between a **Todo** and a **TodoView** in this - // app, we set a direct reference on the model for convenience. - initialize: function() { - this.listenTo(this.model, 'change', this.render); - this.listenTo(this.model, 'destroy', this.remove); - }, - - // Re-render the titles of the todo item. - render: function() { - this.$el.html(this.template(this.model.toJSON())); - this.$el.toggleClass('done', this.model.get('done')); - this.input = this.$('.edit'); - return this; - }, - - // Toggle the `"done"` state of the model. - toggleDone: function() { - this.model.toggle(); - }, - - // Switch this view into `"editing"` mode, displaying the input field. - edit: function() { - this.$el.addClass("editing"); - this.input.focus(); - }, - - // Close the `"editing"` mode, saving changes to the todo. - close: function() { - var value = this.input.val(); - if (!value) { - this.clear(); - } else { - this.model.save({title: value}); - this.$el.removeClass("editing"); - } - }, - - // If you hit `enter`, we're through editing the item. - updateOnEnter: function(e) { - if (e.keyCode == 13) this.close(); - }, - - // Remove the item, destroy the model. - clear: function() { - this.model.destroy(); - } - - }); - - // The Application - // --------------- - - // Our overall **AppView** is the top-level piece of UI. - var AppView = Backbone.View.extend({ - - // Instead of generating a new element, bind to the existing skeleton of - // the App already present in the HTML. - el: $("#todoapp"), - - // Our template for the line of statistics at the bottom of the app. - statsTemplate: _.template($('#stats-template').html()), - - // Delegated events for creating new items, and clearing completed ones. - events: { - "keypress #new-todo": "createOnEnter", - "click #clear-completed": "clearCompleted", - "click #toggle-all": "toggleAllComplete" - }, - - // At initialization we bind to the relevant events on the `Todos` - // collection, when items are added or changed. Kick things off by - // loading any preexisting todos that might be saved in *localStorage*. - initialize: function() { - - this.input = this.$("#new-todo"); - this.allCheckbox = this.$("#toggle-all")[0]; - - this.listenTo(Todos, 'add', this.addOne); - this.listenTo(Todos, 'reset', this.addAll); - this.listenTo(Todos, 'all', this.render); - - this.footer = this.$('footer'); - this.main = $('#main'); - - Todos.fetch(); - }, - - // Re-rendering the App just means refreshing the statistics -- the rest - // of the app doesn't change. - render: function() { - var done = Todos.done().length; - var remaining = Todos.remaining().length; - - if (Todos.length) { - this.main.show(); - this.footer.show(); - this.footer.html(this.statsTemplate({done: done, remaining: remaining})); - } else { - this.main.hide(); - this.footer.hide(); - } - - this.allCheckbox.checked = !remaining; - }, - - // Add a single todo item to the list by creating a view for it, and - // appending its element to the `