Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

backbone

Package Overview
Dependencies
Maintainers
1
Versions
31
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

backbone - npm Package Compare versions

Comparing version 0.9.1 to 0.9.2

.npmignore

65

backbone-min.js

@@ -1,2 +0,2 @@

// Backbone.js 0.9.1
// Backbone.js 0.9.2

@@ -7,32 +7,33 @@ // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.

// http://backbonejs.org
(function(){var i=this,r=i.Backbone,s=Array.prototype.slice,t=Array.prototype.splice,g;g="undefined"!==typeof exports?exports:i.Backbone={};g.VERSION="0.9.1";var f=i._;!f&&"undefined"!==typeof require&&(f=require("underscore"));var h=i.jQuery||i.Zepto||i.ender;g.setDomLibrary=function(a){h=a};g.noConflict=function(){i.Backbone=r;return this};g.emulateHTTP=!1;g.emulateJSON=!1;g.Events={on:function(a,b,c){for(var d,a=a.split(/\s+/),e=this._callbacks||(this._callbacks={});d=a.shift();){d=e[d]||(e[d]=
{});var f=d.tail||(d.tail=d.next={});f.callback=b;f.context=c;d.tail=f.next={}}return this},off:function(a,b,c){var d,e,f;if(a){if(e=this._callbacks)for(a=a.split(/\s+/);d=a.shift();)if(f=e[d],delete e[d],b&&f)for(;(f=f.next)&&f.next;)if(!(f.callback===b&&(!c||f.context===c)))this.on(d,f.callback,f.context)}else delete this._callbacks;return this},trigger:function(a){var b,c,d,e;if(!(d=this._callbacks))return this;e=d.all;for((a=a.split(/\s+/)).push(null);b=a.shift();)e&&a.push({next:e.next,tail:e.tail,
event:b}),(c=d[b])&&a.push({next:c.next,tail:c.tail});for(e=s.call(arguments,1);c=a.pop();){b=c.tail;for(d=c.event?[c.event].concat(e):e;(c=c.next)!==b;)c.callback.apply(c.context||this,d)}return this}};g.Events.bind=g.Events.on;g.Events.unbind=g.Events.off;g.Model=function(a,b){var c;a||(a={});b&&b.parse&&(a=this.parse(a));if(c=j(this,"defaults"))a=f.extend({},c,a);b&&b.collection&&(this.collection=b.collection);this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");if(!this.set(a,
{silent:!0}))throw Error("Can't create an invalid model");delete this._changed;this._previousAttributes=f.clone(this.attributes);this.initialize.apply(this,arguments)};f.extend(g.Model.prototype,g.Events,{idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},escape:function(a){var b;if(b=this._escapedAttributes[a])return b;b=this.attributes[a];return this._escapedAttributes[a]=f.escape(null==b?"":""+b)},has:function(a){return null!=
this.attributes[a]},set:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c||(c={});if(!d)return this;d instanceof g.Model&&(d=d.attributes);if(c.unset)for(e in d)d[e]=void 0;if(!this._validate(d,c))return!1;this.idAttribute in d&&(this.id=d[this.idAttribute]);var b=this.attributes,k=this._escapedAttributes,n=this._previousAttributes||{},h=this._setting;this._changed||(this._changed={});this._setting=!0;for(e in d)if(a=d[e],f.isEqual(b[e],a)||delete k[e],c.unset?delete b[e]:b[e]=
a,this._changing&&!f.isEqual(this._changed[e],a)&&(this.trigger("change:"+e,this,a,c),this._moreChanges=!0),delete this._changed[e],!f.isEqual(n[e],a)||f.has(b,e)!=f.has(n,e))this._changed[e]=a;h||(!c.silent&&this.hasChanged()&&this.change(c),this._setting=!1);return this},unset:function(a,b){(b||(b={})).unset=!0;return this.set(a,null,b)},clear:function(a){(a||(a={})).unset=!0;return this.set(f.clone(this.attributes),a)},fetch:function(a){var a=a?f.clone(a):{},b=this,c=a.success;a.success=function(d,
e,f){if(!b.set(b.parse(d,f),a))return!1;c&&c(b,d)};a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},save:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c=c?f.clone(c):{};c.wait&&(e=f.clone(this.attributes));a=f.extend({},c,{silent:!0});if(d&&!this.set(d,c.wait?a:c))return!1;var k=this,h=c.success;c.success=function(a,b,e){b=k.parse(a,e);c.wait&&(b=f.extend(d||{},b));if(!k.set(b,c))return!1;h?h(k,a):k.trigger("sync",k,a,c)};c.error=g.wrapError(c.error,
k,c);b=this.isNew()?"create":"update";b=(this.sync||g.sync).call(this,b,this,c);c.wait&&this.set(e,a);return b},destroy:function(a){var a=a?f.clone(a):{},b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};if(this.isNew())return d();a.success=function(e){a.wait&&d();c?c(b,e):b.trigger("sync",b,e,a)};a.error=g.wrapError(a.error,b,a);var e=(this.sync||g.sync).call(this,"delete",this,a);a.wait||d();return e},url:function(){var a=j(this.collection,"url")||j(this,"urlRoot")||o();return this.isNew()?
a:a+("/"==a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return null==this.id},change:function(a){if(this._changing||!this.hasChanged())return this;this._moreChanges=this._changing=!0;for(var b in this._changed)this.trigger("change:"+b,this,this._changed[b],a);for(;this._moreChanges;)this._moreChanges=!1,this.trigger("change",this,a);this._previousAttributes=f.clone(this.attributes);
delete this._changed;this._changing=!1;return this},hasChanged:function(a){return!arguments.length?!f.isEmpty(this._changed):this._changed&&f.has(this._changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this._changed):!1;var b,c=!1,d=this._previousAttributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return!arguments.length||!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},
isValid:function(){return!this.validate(this.attributes)},_validate:function(a,b){if(b.silent||!this.validate)return!0;var a=f.extend({},this.attributes,a),c=this.validate(a,b);if(!c)return!0;b&&b.error?b.error(this,c,b):this.trigger("error",this,c,b);return!1}});g.Collection=function(a,b){b||(b={});b.comparator&&(this.comparator=b.comparator);this._reset();this.initialize.apply(this,arguments);a&&this.reset(a,{silent:!0,parse:b.parse})};f.extend(g.Collection.prototype,g.Events,{model:g.Model,initialize:function(){},
toJSON:function(){return this.map(function(a){return a.toJSON()})},add:function(a,b){var c,d,e,g,h,i={},j={};b||(b={});a=f.isArray(a)?a.slice():[a];for(c=0,d=a.length;c<d;c++){if(!(e=a[c]=this._prepareModel(a[c],b)))throw Error("Can't add an invalid model to a collection");if(i[g=e.cid]||this._byCid[g]||null!=(h=e.id)&&(j[h]||this._byId[h]))throw Error("Can't add the same model to a collection twice");i[g]=j[h]=e}for(c=0;c<d;c++)(e=a[c]).on("all",this._onModelEvent,this),this._byCid[e.cid]=e,null!=
e.id&&(this._byId[e.id]=e);this.length+=d;t.apply(this.models,[null!=b.at?b.at:this.models.length,0].concat(a));this.comparator&&this.sort({silent:!0});if(b.silent)return this;for(c=0,d=this.models.length;c<d;c++)if(i[(e=this.models[c]).cid])b.index=c,e.trigger("add",e,this,b);return this},remove:function(a,b){var c,d,e,g;b||(b={});a=f.isArray(a)?a.slice():[a];for(c=0,d=a.length;c<d;c++)if(g=this.getByCid(a[c])||this.get(a[c]))delete this._byId[g.id],delete this._byCid[g.cid],e=this.indexOf(g),this.models.splice(e,
1),this.length--,b.silent||(b.index=e,g.trigger("remove",g,this,b)),this._removeReference(g);return this},get:function(a){return null==a?null:this._byId[null!=a.id?a.id:a]},getByCid:function(a){return a&&this._byCid[a.cid||a]},at:function(a){return this.models[a]},sort:function(a){a||(a={});if(!this.comparator)throw Error("Cannot sort a set without a comparator");var b=f.bind(this.comparator,this);1==this.comparator.length?this.models=this.sortBy(b):this.models.sort(b);a.silent||this.trigger("reset",
this,a);return this},pluck:function(a){return f.map(this.models,function(b){return b.get(a)})},reset:function(a,b){a||(a=[]);b||(b={});for(var c=0,d=this.models.length;c<d;c++)this._removeReference(this.models[c]);this._reset();this.add(a,{silent:!0,parse:b.parse});b.silent||this.trigger("reset",this,b);return this},fetch:function(a){a=a?f.clone(a):{};void 0===a.parse&&(a.parse=!0);var b=this,c=a.success;a.success=function(d,e,f){b[a.add?"add":"reset"](b.parse(d,f),a);c&&c(b,d)};a.error=g.wrapError(a.error,
b,a);return(this.sync||g.sync).call(this,"read",this,a)},create:function(a,b){var c=this,b=b?f.clone(b):{},a=this._prepareModel(a,b);if(!a)return!1;b.wait||c.add(a,b);var d=b.success;b.success=function(e,f){b.wait&&c.add(e,b);d?d(e,f):e.trigger("sync",a,f,b)};a.save(null,b);return a},parse:function(a){return a},chain:function(){return f(this.models).chain()},_reset:function(){this.length=0;this.models=[];this._byId={};this._byCid={}},_prepareModel:function(a,b){a instanceof g.Model?a.collection||
(a.collection=this):(b.collection=this,a=new this.model(a,b),a._validate(a.attributes,b)||(a=!1));return a},_removeReference:function(a){this==a.collection&&delete a.collection;a.off("all",this._onModelEvent,this)},_onModelEvent:function(a,b,c,d){("add"==a||"remove"==a)&&c!=this||("destroy"==a&&this.remove(b,d),b&&a==="change:"+b.idAttribute&&(delete this._byId[b.previous(b.idAttribute)],this._byId[b.id]=b),this.trigger.apply(this,arguments))}});f.each("forEach,each,map,reduce,reduceRight,find,detect,filter,select,reject,every,all,some,any,include,contains,invoke,max,min,sortBy,sortedIndex,toArray,size,first,initial,rest,last,without,indexOf,shuffle,lastIndexOf,isEmpty,groupBy".split(","),
function(a){g.Collection.prototype[a]=function(){return f[a].apply(f,[this.models].concat(f.toArray(arguments)))}});g.Router=function(a){a||(a={});a.routes&&(this.routes=a.routes);this._bindRoutes();this.initialize.apply(this,arguments)};var u=/:\w+/g,v=/\*\w+/g,w=/[-[\]{}()+?.,\\^$|#\s]/g;f.extend(g.Router.prototype,g.Events,{initialize:function(){},route:function(a,b,c){g.history||(g.history=new g.History);f.isRegExp(a)||(a=this._routeToRegExp(a));c||(c=this[b]);g.history.route(a,f.bind(function(d){d=
this._extractParameters(a,d);c&&c.apply(this,d);this.trigger.apply(this,["route:"+b].concat(d));g.history.trigger("route",this,b,d)},this));return this},navigate:function(a,b){g.history.navigate(a,b)},_bindRoutes:function(){if(this.routes){var a=[],b;for(b in this.routes)a.unshift([b,this.routes[b]]);b=0;for(var c=a.length;b<c;b++)this.route(a[b][0],a[b][1],this[a[b][1]])}},_routeToRegExp:function(a){a=a.replace(w,"\\$&").replace(u,"([^/]+)").replace(v,"(.*?)");return RegExp("^"+a+"$")},_extractParameters:function(a,
b){return a.exec(b).slice(1)}});g.History=function(){this.handlers=[];f.bindAll(this,"checkUrl")};var m=/^[#\/]/,x=/msie [\w.]+/,l=!1;f.extend(g.History.prototype,g.Events,{interval:50,getFragment:function(a,b){if(null==a)if(this._hasPushState||b){var a=window.location.pathname,c=window.location.search;c&&(a+=c)}else a=window.location.hash;a=decodeURIComponent(a);a.indexOf(this.options.root)||(a=a.substr(this.options.root.length));return a.replace(m,"")},start:function(a){if(l)throw Error("Backbone.history has already been started");
this.options=f.extend({},{root:"/"},this.options,a);this._wantsHashChange=!1!==this.options.hashChange;this._wantsPushState=!!this.options.pushState;this._hasPushState=!(!this.options.pushState||!window.history||!window.history.pushState);var a=this.getFragment(),b=document.documentMode;if(b=x.exec(navigator.userAgent.toLowerCase())&&(!b||7>=b))this.iframe=h('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow,this.navigate(a);this._hasPushState?h(window).bind("popstate",
this.checkUrl):this._wantsHashChange&&"onhashchange"in window&&!b?h(window).bind("hashchange",this.checkUrl):this._wantsHashChange&&(this._checkUrlInterval=setInterval(this.checkUrl,this.interval));this.fragment=a;l=!0;a=window.location;b=a.pathname==this.options.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),window.location.replace(this.options.root+"#"+this.fragment),!0;this._wantsPushState&&this._hasPushState&&b&&a.hash&&
(this.fragment=a.hash.replace(m,""),window.history.replaceState({},document.title,a.protocol+"//"+a.host+this.options.root+this.fragment));if(!this.options.silent)return this.loadUrl()},stop:function(){h(window).unbind("popstate",this.checkUrl).unbind("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);l=!1},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a==this.fragment&&this.iframe&&(a=this.getFragment(this.iframe.location.hash));
if(a==this.fragment||a==decodeURIComponent(this.fragment))return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(window.location.hash)},loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){if(!l)return!1;if(!b||!0===b)b={trigger:b};var c=(a||"").replace(m,"");this.fragment==c||this.fragment==decodeURIComponent(c)||(this._hasPushState?(0!=c.indexOf(this.options.root)&&(c=
this.options.root+c),this.fragment=c,window.history[b.replace?"replaceState":"pushState"]({},document.title,c)):this._wantsHashChange?(this.fragment=c,this._updateHash(window.location,c,b.replace),this.iframe&&c!=this.getFragment(this.iframe.location.hash)&&(b.replace||this.iframe.document.open().close(),this._updateHash(this.iframe.location,c,b.replace))):window.location.assign(this.options.root+a),b.trigger&&this.loadUrl(a))},_updateHash:function(a,b,c){c?a.replace(a.toString().replace(/(javascript:|#).*$/,
"")+"#"+b):a.hash=b}});g.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()};var y=/^(\S+)\s*(.*)$/,p="model,collection,el,id,attributes,className,tagName".split(",");f.extend(g.View.prototype,g.Events,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();return this},make:function(a,b,c){a=document.createElement(a);
b&&h(a).attr(b);c&&h(a).html(c);return a},setElement:function(a,b){this.$el=h(a);this.el=this.$el[0];!1!==b&&this.delegateEvents();return this},delegateEvents:function(a){if(a||(a=j(this,"events"))){this.undelegateEvents();for(var b in a){var c=a[b];f.isFunction(c)||(c=this[a[b]]);if(!c)throw Error('Event "'+a[b]+'" does not exist');var d=b.match(y),e=d[1],d=d[2],c=f.bind(c,this),e=e+(".delegateEvents"+this.cid);""===d?this.$el.bind(e,c):this.$el.delegate(d,e,c)}}},undelegateEvents:function(){this.$el.unbind(".delegateEvents"+
this.cid)},_configure:function(a){this.options&&(a=f.extend({},this.options,a));for(var b=0,c=p.length;b<c;b++){var d=p[b];a[d]&&(this[d]=a[d])}this.options=a},_ensureElement:function(){if(this.el)this.setElement(this.el,!1);else{var a=j(this,"attributes")||{};this.id&&(a.id=this.id);this.className&&(a["class"]=this.className);this.setElement(this.make(this.tagName,a),!1)}}});g.Model.extend=g.Collection.extend=g.Router.extend=g.View.extend=function(a,b){var c=z(this,a,b);c.extend=this.extend;return c};
var A={create:"POST",update:"PUT","delete":"DELETE",read:"GET"};g.sync=function(a,b,c){var d=A[a],e={type:d,dataType:"json"};c.url||(e.url=j(b,"url")||o());if(!c.data&&b&&("create"==a||"update"==a))e.contentType="application/json",e.data=JSON.stringify(b.toJSON());g.emulateJSON&&(e.contentType="application/x-www-form-urlencoded",e.data=e.data?{model:e.data}:{});if(g.emulateHTTP&&("PUT"===d||"DELETE"===d))g.emulateJSON&&(e.data._method=d),e.type="POST",e.beforeSend=function(a){a.setRequestHeader("X-HTTP-Method-Override",
d)};"GET"!==e.type&&!g.emulateJSON&&(e.processData=!1);return h.ajax(f.extend(e,c))};g.wrapError=function(a,b,c){return function(d,e){e=d===b?e:d;a?a(b,e,c):b.trigger("error",b,e,c)}};var q=function(){},z=function(a,b,c){var d;d=b&&b.hasOwnProperty("constructor")?b.constructor:function(){a.apply(this,arguments)};f.extend(d,a);q.prototype=a.prototype;d.prototype=new q;b&&f.extend(d.prototype,b);c&&f.extend(d,c);d.prototype.constructor=d;d.__super__=a.prototype;return d},j=function(a,b){return!a||!a[b]?
null:f.isFunction(a[b])?a[b]():a[b]},o=function(){throw Error('A "url" property or function must be specified');}}).call(this);
(function(){var l=this,y=l.Backbone,z=Array.prototype.slice,A=Array.prototype.splice,g;g="undefined"!==typeof exports?exports:l.Backbone={};g.VERSION="0.9.2";var f=l._;!f&&"undefined"!==typeof require&&(f=require("underscore"));var i=l.jQuery||l.Zepto||l.ender;g.setDomLibrary=function(a){i=a};g.noConflict=function(){l.Backbone=y;return this};g.emulateHTTP=!1;g.emulateJSON=!1;var p=/\s+/,k=g.Events={on:function(a,b,c){var d,e,f,g,j;if(!b)return this;a=a.split(p);for(d=this._callbacks||(this._callbacks=
{});e=a.shift();)f=(j=d[e])?j.tail:{},f.next=g={},f.context=c,f.callback=b,d[e]={tail:g,next:j?j.next:f};return this},off:function(a,b,c){var d,e,h,g,j,q;if(e=this._callbacks){if(!a&&!b&&!c)return delete this._callbacks,this;for(a=a?a.split(p):f.keys(e);d=a.shift();)if(h=e[d],delete e[d],h&&(b||c))for(g=h.tail;(h=h.next)!==g;)if(j=h.callback,q=h.context,b&&j!==b||c&&q!==c)this.on(d,j,q);return this}},trigger:function(a){var b,c,d,e,f,g;if(!(d=this._callbacks))return this;f=d.all;a=a.split(p);for(g=
z.call(arguments,1);b=a.shift();){if(c=d[b])for(e=c.tail;(c=c.next)!==e;)c.callback.apply(c.context||this,g);if(c=f){e=c.tail;for(b=[b].concat(g);(c=c.next)!==e;)c.callback.apply(c.context||this,b)}}return this}};k.bind=k.on;k.unbind=k.off;var o=g.Model=function(a,b){var c;a||(a={});b&&b.parse&&(a=this.parse(a));if(c=n(this,"defaults"))a=f.extend({},c,a);b&&b.collection&&(this.collection=b.collection);this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");this.changed={};this._silent=
{};this._pending={};this.set(a,{silent:!0});this.changed={};this._silent={};this._pending={};this._previousAttributes=f.clone(this.attributes);this.initialize.apply(this,arguments)};f.extend(o.prototype,k,{changed:null,_silent:null,_pending:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},escape:function(a){var b;if(b=this._escapedAttributes[a])return b;b=this.get(a);return this._escapedAttributes[a]=f.escape(null==
b?"":""+b)},has:function(a){return null!=this.get(a)},set:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c||(c={});if(!d)return this;d instanceof o&&(d=d.attributes);if(c.unset)for(e in d)d[e]=void 0;if(!this._validate(d,c))return!1;this.idAttribute in d&&(this.id=d[this.idAttribute]);var b=c.changes={},h=this.attributes,g=this._escapedAttributes,j=this._previousAttributes||{};for(e in d){a=d[e];if(!f.isEqual(h[e],a)||c.unset&&f.has(h,e))delete g[e],(c.silent?this._silent:
b)[e]=!0;c.unset?delete h[e]:h[e]=a;!f.isEqual(j[e],a)||f.has(h,e)!=f.has(j,e)?(this.changed[e]=a,c.silent||(this._pending[e]=!0)):(delete this.changed[e],delete this._pending[e])}c.silent||this.change(c);return this},unset:function(a,b){(b||(b={})).unset=!0;return this.set(a,null,b)},clear:function(a){(a||(a={})).unset=!0;return this.set(f.clone(this.attributes),a)},fetch:function(a){var a=a?f.clone(a):{},b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return!1;c&&c(b,d)};
a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},save:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c=c?f.clone(c):{};if(c.wait){if(!this._validate(d,c))return!1;e=f.clone(this.attributes)}a=f.extend({},c,{silent:!0});if(d&&!this.set(d,c.wait?a:c))return!1;var h=this,i=c.success;c.success=function(a,b,e){b=h.parse(a,e);if(c.wait){delete c.wait;b=f.extend(d||{},b)}if(!h.set(b,c))return false;i?i(h,a):h.trigger("sync",h,a,c)};c.error=g.wrapError(c.error,
h,c);b=this.isNew()?"create":"update";b=(this.sync||g.sync).call(this,b,this,c);c.wait&&this.set(e,a);return b},destroy:function(a){var a=a?f.clone(a):{},b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};if(this.isNew())return d(),!1;a.success=function(e){a.wait&&d();c?c(b,e):b.trigger("sync",b,e,a)};a.error=g.wrapError(a.error,b,a);var e=(this.sync||g.sync).call(this,"delete",this,a);a.wait||d();return e},url:function(){var a=n(this,"urlRoot")||n(this.collection,"url")||t();
return this.isNew()?a:a+("/"==a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return null==this.id},change:function(a){a||(a={});var b=this._changing;this._changing=!0;for(var c in this._silent)this._pending[c]=!0;var d=f.extend({},a.changes,this._silent);this._silent={};for(c in d)this.trigger("change:"+c,this,this.get(c),a);if(b)return this;for(;!f.isEmpty(this._pending);){this._pending=
{};this.trigger("change",this,a);for(c in this.changed)!this._pending[c]&&!this._silent[c]&&delete this.changed[c];this._previousAttributes=f.clone(this.attributes)}this._changing=!1;return this},hasChanged:function(a){return!arguments.length?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed):!1;var b,c=!1,d=this._previousAttributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return!arguments.length||
!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},isValid:function(){return!this.validate(this.attributes)},_validate:function(a,b){if(b.silent||!this.validate)return!0;var a=f.extend({},this.attributes,a),c=this.validate(a,b);if(!c)return!0;b&&b.error?b.error(this,c,b):this.trigger("error",this,c,b);return!1}});var r=g.Collection=function(a,b){b||(b={});b.model&&(this.model=b.model);b.comparator&&(this.comparator=b.comparator);
this._reset();this.initialize.apply(this,arguments);a&&this.reset(a,{silent:!0,parse:b.parse})};f.extend(r.prototype,k,{model:o,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},add:function(a,b){var c,d,e,g,i,j={},k={},l=[];b||(b={});a=f.isArray(a)?a.slice():[a];c=0;for(d=a.length;c<d;c++){if(!(e=a[c]=this._prepareModel(a[c],b)))throw Error("Can't add an invalid model to a collection");g=e.cid;i=e.id;j[g]||this._byCid[g]||null!=i&&(k[i]||this._byId[i])?
l.push(c):j[g]=k[i]=e}for(c=l.length;c--;)a.splice(l[c],1);c=0;for(d=a.length;c<d;c++)(e=a[c]).on("all",this._onModelEvent,this),this._byCid[e.cid]=e,null!=e.id&&(this._byId[e.id]=e);this.length+=d;A.apply(this.models,[null!=b.at?b.at:this.models.length,0].concat(a));this.comparator&&this.sort({silent:!0});if(b.silent)return this;c=0;for(d=this.models.length;c<d;c++)if(j[(e=this.models[c]).cid])b.index=c,e.trigger("add",e,this,b);return this},remove:function(a,b){var c,d,e,g;b||(b={});a=f.isArray(a)?
a.slice():[a];c=0;for(d=a.length;c<d;c++)if(g=this.getByCid(a[c])||this.get(a[c]))delete this._byId[g.id],delete this._byCid[g.cid],e=this.indexOf(g),this.models.splice(e,1),this.length--,b.silent||(b.index=e,g.trigger("remove",g,this,b)),this._removeReference(g);return this},push:function(a,b){a=this._prepareModel(a,b);this.add(a,b);return a},pop:function(a){var b=this.at(this.length-1);this.remove(b,a);return b},unshift:function(a,b){a=this._prepareModel(a,b);this.add(a,f.extend({at:0},b));return a},
shift:function(a){var b=this.at(0);this.remove(b,a);return b},get:function(a){return null==a?void 0:this._byId[null!=a.id?a.id:a]},getByCid:function(a){return a&&this._byCid[a.cid||a]},at:function(a){return this.models[a]},where:function(a){return f.isEmpty(a)?[]:this.filter(function(b){for(var c in a)if(a[c]!==b.get(c))return!1;return!0})},sort:function(a){a||(a={});if(!this.comparator)throw Error("Cannot sort a set without a comparator");var b=f.bind(this.comparator,this);1==this.comparator.length?
this.models=this.sortBy(b):this.models.sort(b);a.silent||this.trigger("reset",this,a);return this},pluck:function(a){return f.map(this.models,function(b){return b.get(a)})},reset:function(a,b){a||(a=[]);b||(b={});for(var c=0,d=this.models.length;c<d;c++)this._removeReference(this.models[c]);this._reset();this.add(a,f.extend({silent:!0},b));b.silent||this.trigger("reset",this,b);return this},fetch:function(a){a=a?f.clone(a):{};void 0===a.parse&&(a.parse=!0);var b=this,c=a.success;a.success=function(d,
e,f){b[a.add?"add":"reset"](b.parse(d,f),a);c&&c(b,d)};a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},create:function(a,b){var c=this,b=b?f.clone(b):{},a=this._prepareModel(a,b);if(!a)return!1;b.wait||c.add(a,b);var d=b.success;b.success=function(e,f){b.wait&&c.add(e,b);d?d(e,f):e.trigger("sync",a,f,b)};a.save(null,b);return a},parse:function(a){return a},chain:function(){return f(this.models).chain()},_reset:function(){this.length=0;this.models=[];this._byId=
{};this._byCid={}},_prepareModel:function(a,b){b||(b={});a instanceof o?a.collection||(a.collection=this):(b.collection=this,a=new this.model(a,b),a._validate(a.attributes,b)||(a=!1));return a},_removeReference:function(a){this==a.collection&&delete a.collection;a.off("all",this._onModelEvent,this)},_onModelEvent:function(a,b,c,d){("add"==a||"remove"==a)&&c!=this||("destroy"==a&&this.remove(b,d),b&&a==="change:"+b.idAttribute&&(delete this._byId[b.previous(b.idAttribute)],this._byId[b.id]=b),this.trigger.apply(this,
arguments))}});f.each("forEach,each,map,reduce,reduceRight,find,detect,filter,select,reject,every,all,some,any,include,contains,invoke,max,min,sortBy,sortedIndex,toArray,size,first,initial,rest,last,without,indexOf,shuffle,lastIndexOf,isEmpty,groupBy".split(","),function(a){r.prototype[a]=function(){return f[a].apply(f,[this.models].concat(f.toArray(arguments)))}});var u=g.Router=function(a){a||(a={});a.routes&&(this.routes=a.routes);this._bindRoutes();this.initialize.apply(this,arguments)},B=/:\w+/g,
C=/\*\w+/g,D=/[-[\]{}()+?.,\\^$|#\s]/g;f.extend(u.prototype,k,{initialize:function(){},route:function(a,b,c){g.history||(g.history=new m);f.isRegExp(a)||(a=this._routeToRegExp(a));c||(c=this[b]);g.history.route(a,f.bind(function(d){d=this._extractParameters(a,d);c&&c.apply(this,d);this.trigger.apply(this,["route:"+b].concat(d));g.history.trigger("route",this,b,d)},this));return this},navigate:function(a,b){g.history.navigate(a,b)},_bindRoutes:function(){if(this.routes){var a=[],b;for(b in this.routes)a.unshift([b,
this.routes[b]]);b=0;for(var c=a.length;b<c;b++)this.route(a[b][0],a[b][1],this[a[b][1]])}},_routeToRegExp:function(a){a=a.replace(D,"\\$&").replace(B,"([^/]+)").replace(C,"(.*?)");return RegExp("^"+a+"$")},_extractParameters:function(a,b){return a.exec(b).slice(1)}});var m=g.History=function(){this.handlers=[];f.bindAll(this,"checkUrl")},s=/^[#\/]/,E=/msie [\w.]+/;m.started=!1;f.extend(m.prototype,k,{interval:50,getHash:function(a){return(a=(a?a.location:window.location).href.match(/#(.*)$/))?a[1]:
""},getFragment:function(a,b){if(null==a)if(this._hasPushState||b){var a=window.location.pathname,c=window.location.search;c&&(a+=c)}else a=this.getHash();a.indexOf(this.options.root)||(a=a.substr(this.options.root.length));return a.replace(s,"")},start:function(a){if(m.started)throw Error("Backbone.history has already been started");m.started=!0;this.options=f.extend({},{root:"/"},this.options,a);this._wantsHashChange=!1!==this.options.hashChange;this._wantsPushState=!!this.options.pushState;this._hasPushState=
!(!this.options.pushState||!window.history||!window.history.pushState);var a=this.getFragment(),b=document.documentMode;if(b=E.exec(navigator.userAgent.toLowerCase())&&(!b||7>=b))this.iframe=i('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow,this.navigate(a);this._hasPushState?i(window).bind("popstate",this.checkUrl):this._wantsHashChange&&"onhashchange"in window&&!b?i(window).bind("hashchange",this.checkUrl):this._wantsHashChange&&(this._checkUrlInterval=setInterval(this.checkUrl,
this.interval));this.fragment=a;a=window.location;b=a.pathname==this.options.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),window.location.replace(this.options.root+"#"+this.fragment),!0;this._wantsPushState&&this._hasPushState&&b&&a.hash&&(this.fragment=this.getHash().replace(s,""),window.history.replaceState({},document.title,a.protocol+"//"+a.host+this.options.root+this.fragment));if(!this.options.silent)return this.loadUrl()},
stop:function(){i(window).unbind("popstate",this.checkUrl).unbind("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);m.started=!1},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a==this.fragment&&this.iframe&&(a=this.getFragment(this.getHash(this.iframe)));if(a==this.fragment)return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(this.getHash())},loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,
function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){if(!m.started)return!1;if(!b||!0===b)b={trigger:b};var c=(a||"").replace(s,"");this.fragment!=c&&(this._hasPushState?(0!=c.indexOf(this.options.root)&&(c=this.options.root+c),this.fragment=c,window.history[b.replace?"replaceState":"pushState"]({},document.title,c)):this._wantsHashChange?(this.fragment=c,this._updateHash(window.location,c,b.replace),this.iframe&&c!=this.getFragment(this.getHash(this.iframe))&&(b.replace||
this.iframe.document.open().close(),this._updateHash(this.iframe.location,c,b.replace))):window.location.assign(this.options.root+a),b.trigger&&this.loadUrl(a))},_updateHash:function(a,b,c){c?a.replace(a.toString().replace(/(javascript:|#).*$/,"")+"#"+b):a.hash=b}});var v=g.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()},F=/^(\S+)\s*(.*)$/,w="model,collection,el,id,attributes,className,tagName".split(",");
f.extend(v.prototype,k,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();return this},make:function(a,b,c){a=document.createElement(a);b&&i(a).attr(b);c&&i(a).html(c);return a},setElement:function(a,b){this.$el&&this.undelegateEvents();this.$el=a instanceof i?a:i(a);this.el=this.$el[0];!1!==b&&this.delegateEvents();return this},delegateEvents:function(a){if(a||(a=n(this,"events"))){this.undelegateEvents();
for(var b in a){var c=a[b];f.isFunction(c)||(c=this[a[b]]);if(!c)throw Error('Method "'+a[b]+'" does not exist');var d=b.match(F),e=d[1],d=d[2],c=f.bind(c,this),e=e+(".delegateEvents"+this.cid);""===d?this.$el.bind(e,c):this.$el.delegate(d,e,c)}}},undelegateEvents:function(){this.$el.unbind(".delegateEvents"+this.cid)},_configure:function(a){this.options&&(a=f.extend({},this.options,a));for(var b=0,c=w.length;b<c;b++){var d=w[b];a[d]&&(this[d]=a[d])}this.options=a},_ensureElement:function(){if(this.el)this.setElement(this.el,
!1);else{var a=n(this,"attributes")||{};this.id&&(a.id=this.id);this.className&&(a["class"]=this.className);this.setElement(this.make(this.tagName,a),!1)}}});o.extend=r.extend=u.extend=v.extend=function(a,b){var c=G(this,a,b);c.extend=this.extend;return c};var H={create:"POST",update:"PUT","delete":"DELETE",read:"GET"};g.sync=function(a,b,c){var d=H[a];c||(c={});var e={type:d,dataType:"json"};c.url||(e.url=n(b,"url")||t());if(!c.data&&b&&("create"==a||"update"==a))e.contentType="application/json",
e.data=JSON.stringify(b.toJSON());g.emulateJSON&&(e.contentType="application/x-www-form-urlencoded",e.data=e.data?{model:e.data}:{});if(g.emulateHTTP&&("PUT"===d||"DELETE"===d))g.emulateJSON&&(e.data._method=d),e.type="POST",e.beforeSend=function(a){a.setRequestHeader("X-HTTP-Method-Override",d)};"GET"!==e.type&&!g.emulateJSON&&(e.processData=!1);return i.ajax(f.extend(e,c))};g.wrapError=function(a,b,c){return function(d,e){e=d===b?e:d;a?a(b,e,c):b.trigger("error",b,e,c)}};var x=function(){},G=function(a,
b,c){var d;d=b&&b.hasOwnProperty("constructor")?b.constructor:function(){a.apply(this,arguments)};f.extend(d,a);x.prototype=a.prototype;d.prototype=new x;b&&f.extend(d.prototype,b);c&&f.extend(d,c);d.prototype.constructor=d;d.__super__=a.prototype;return d},n=function(a,b){return!a||!a[b]?null:f.isFunction(a[b])?a[b]():a[b]},t=function(){throw Error('A "url" property or function must be specified');}}).call(this);

@@ -1,2 +0,2 @@

// Backbone.js 0.9.1
// Backbone.js 0.9.2

@@ -35,3 +35,3 @@ // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.

// Current version of the library. Keep in sync with `package.json`.
Backbone.VERSION = '0.9.1';
Backbone.VERSION = '0.9.2';

@@ -75,2 +75,5 @@ // Require Underscore, if we're on the server, and it's not already present.

// Regular expression used to split event strings
var eventSplitter = /\s+/;
// A module that can be mixed in to *any object* in order to provide it with

@@ -85,20 +88,25 @@ // custom events. You may bind with `on` or remove with `off` callback functions

//
Backbone.Events = {
var Events = Backbone.Events = {
// Bind an event, specified by a string name, `ev`, to a `callback`
// Bind one or more space separated events, `events`, to a `callback`
// function. Passing `"all"` will bind the callback to all events fired.
on: function(events, callback, context) {
var ev;
events = events.split(/\s+/);
var calls = this._callbacks || (this._callbacks = {});
while (ev = events.shift()) {
// Create an immutable callback list, allowing traversal during
// modification. The tail is an empty object that will always be used
// as the next node.
var list = calls[ev] || (calls[ev] = {});
var tail = list.tail || (list.tail = list.next = {});
tail.callback = callback;
tail.context = context;
list.tail = tail.next = {};
var calls, event, node, tail, list;
if (!callback) return this;
events = events.split(eventSplitter);
calls = this._callbacks || (this._callbacks = {});
// Create an immutable callback list, allowing traversal during
// modification. The tail is an empty object that will always be used
// as the next node.
while (event = events.shift()) {
list = calls[event];
node = list ? list.tail : {};
node.next = tail = {};
node.context = context;
node.callback = callback;
calls[event] = {tail: tail, next: list ? list.next : node};
}
return this;

@@ -109,47 +117,63 @@ },

// with that function. If `callback` is null, removes all callbacks for the
// event. If `ev` is null, removes all bound callbacks for all events.
// event. If `events` is null, removes all bound callbacks for all events.
off: function(events, callback, context) {
var ev, calls, node;
if (!events) {
var event, calls, node, tail, cb, ctx;
// No events, or removing *all* events.
if (!(calls = this._callbacks)) return;
if (!(events || callback || context)) {
delete this._callbacks;
} else if (calls = this._callbacks) {
events = events.split(/\s+/);
while (ev = events.shift()) {
node = calls[ev];
delete calls[ev];
if (!callback || !node) continue;
// Create a new list, omitting the indicated event/context pairs.
while ((node = node.next) && node.next) {
if (node.callback === callback &&
(!context || node.context === context)) continue;
this.on(ev, node.callback, node.context);
return this;
}
// Loop through the listed events and contexts, splicing them out of the
// linked list of callbacks if appropriate.
events = events ? events.split(eventSplitter) : _.keys(calls);
while (event = events.shift()) {
node = calls[event];
delete calls[event];
if (!node || !(callback || context)) continue;
// Create a new list, omitting the indicated callbacks.
tail = node.tail;
while ((node = node.next) !== tail) {
cb = node.callback;
ctx = node.context;
if ((callback && cb !== callback) || (context && ctx !== context)) {
this.on(event, cb, ctx);
}
}
}
return this;
},
// Trigger an event, firing all bound callbacks. Callbacks are passed the
// same arguments as `trigger` is, apart from the event name.
// Listening for `"all"` passes the true event name as the first argument.
// 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: function(events) {
var event, node, calls, tail, args, all, rest;
if (!(calls = this._callbacks)) return this;
all = calls['all'];
(events = events.split(/\s+/)).push(null);
// Save references to the current heads & tails.
all = calls.all;
events = events.split(eventSplitter);
rest = slice.call(arguments, 1);
// For each event, walk through the linked list of callbacks twice,
// first to trigger the event, then to trigger any `"all"` callbacks.
while (event = events.shift()) {
if (all) events.push({next: all.next, tail: all.tail, event: event});
if (!(node = calls[event])) continue;
events.push({next: node.next, tail: node.tail});
}
// Traverse each list, stopping when the saved tail is reached.
rest = slice.call(arguments, 1);
while (node = events.pop()) {
tail = node.tail;
args = node.event ? [node.event].concat(rest) : rest;
while ((node = node.next) !== tail) {
node.callback.apply(node.context || this, args);
if (node = calls[event]) {
tail = node.tail;
while ((node = node.next) !== tail) {
node.callback.apply(node.context || this, rest);
}
}
if (node = all) {
tail = node.tail;
args = [event].concat(rest);
while ((node = node.next) !== tail) {
node.callback.apply(node.context || this, args);
}
}
}
return this;

@@ -161,4 +185,4 @@ }

// Aliases for backwards compatibility.
Backbone.Events.bind = Backbone.Events.on;
Backbone.Events.unbind = Backbone.Events.off;
Events.bind = Events.on;
Events.unbind = Events.off;

@@ -170,3 +194,3 @@ // Backbone.Model

// is automatically generated and assigned for you.
Backbone.Model = function(attributes, options) {
var Model = Backbone.Model = function(attributes, options) {
var defaults;

@@ -182,6 +206,10 @@ attributes || (attributes = {});

this.cid = _.uniqueId('c');
if (!this.set(attributes, {silent: true})) {
throw new Error("Can't create an invalid model");
}
delete this._changed;
this.changed = {};
this._silent = {};
this._pending = {};
this.set(attributes, {silent: true});
// Reset change tracking.
this.changed = {};
this._silent = {};
this._pending = {};
this._previousAttributes = _.clone(this.attributes);

@@ -192,4 +220,15 @@ this.initialize.apply(this, arguments);

// Attach all inheritable methods to the Model prototype.
_.extend(Backbone.Model.prototype, Backbone.Events, {
_.extend(Model.prototype, Events, {
// A hash of attributes whose current and previous value differ.
changed: null,
// A hash of attributes that have silently changed since the last time
// `change` was called. Will become pending attributes on the next call.
_silent: null,
// A hash of attributes that have changed since the last `'change'` event
// began.
_pending: null,
// The default name for the JSON `id` attribute is `"id"`. MongoDB and

@@ -204,3 +243,3 @@ // CouchDB users may want to set this to `"_id"`.

// Return a copy of the model's `attributes` object.
toJSON: function() {
toJSON: function(options) {
return _.clone(this.attributes);

@@ -218,3 +257,3 @@ },

if (html = this._escapedAttributes[attr]) return html;
var val = this.attributes[attr];
var val = this.get(attr);
return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);

@@ -226,3 +265,3 @@ },

has: function(attr) {
return this.attributes[attr] != null;
return this.get(attr) != null;
},

@@ -234,2 +273,4 @@

var attrs, attr, val;
// Handle both
if (_.isObject(key) || key == null) {

@@ -246,3 +287,3 @@ attrs = key;

if (!attrs) return this;
if (attrs instanceof Backbone.Model) attrs = attrs.attributes;
if (attrs instanceof Model) attrs = attrs.attributes;
if (options.unset) for (attr in attrs) attrs[attr] = void 0;

@@ -256,29 +297,33 @@

var changes = options.changes = {};
var now = this.attributes;
var escaped = this._escapedAttributes;
var prev = this._previousAttributes || {};
var alreadySetting = this._setting;
this._changed || (this._changed = {});
this._setting = true;
// Update attributes.
// For each `set` attribute...
for (attr in attrs) {
val = attrs[attr];
if (!_.isEqual(now[attr], val)) delete escaped[attr];
// If the new and current value differ, record the change.
if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
delete escaped[attr];
(options.silent ? this._silent : changes)[attr] = true;
}
// Update or delete the current value.
options.unset ? delete now[attr] : now[attr] = val;
if (this._changing && !_.isEqual(this._changed[attr], val)) {
this.trigger('change:' + attr, this, val, options);
this._moreChanges = true;
}
delete this._changed[attr];
// If the new and previous value differ, record the change. If not,
// then remove changes for this attribute.
if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
this._changed[attr] = val;
this.changed[attr] = val;
if (!options.silent) this._pending[attr] = true;
} else {
delete this.changed[attr];
delete this._pending[attr];
}
}
// Fire the `"change"` events, if the model has been changed.
if (!alreadySetting) {
if (!options.silent && this.hasChanged()) this.change(options);
this._setting = false;
}
// Fire the `"change"` events.
if (!options.silent) this.change(options);
return this;

@@ -321,2 +366,4 @@ },

var attrs, current;
// Handle both `("key", value)` and `({key: value})` -style calls.
if (_.isObject(key) || key == null) {

@@ -329,5 +376,11 @@ attrs = key;

}
options = options ? _.clone(options) : {};
options = options ? _.clone(options) : {};
if (options.wait) current = _.clone(this.attributes);
// If we're "wait"-ing to set changed attributes, validate early.
if (options.wait) {
if (!this._validate(attrs, options)) return false;
current = _.clone(this.attributes);
}
// Regular saves `set` attributes before persisting to the server.
var silentOptions = _.extend({}, options, {silent: true});

@@ -337,2 +390,5 @@ if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {

}
// After a successful server-side save, the client is (optionally)
// updated with the server-side state.
var model = this;

@@ -342,3 +398,6 @@ var success = options.success;

var serverAttrs = model.parse(resp, xhr);
if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
if (options.wait) {
delete options.wait;
serverAttrs = _.extend(attrs || {}, serverAttrs);
}
if (!model.set(serverAttrs, options)) return false;

@@ -351,2 +410,4 @@ if (success) {

};
// Finish configuring and sending the Ajax request.
options.error = Backbone.wrapError(options.error, model, options);

@@ -371,3 +432,7 @@ var method = this.isNew() ? 'create' : 'update';

if (this.isNew()) return triggerDestroy();
if (this.isNew()) {
triggerDestroy();
return false;
}
options.success = function(resp) {

@@ -381,2 +446,3 @@ if (options.wait) triggerDestroy();

};
options.error = Backbone.wrapError(options.error, model, options);

@@ -392,3 +458,3 @@ var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);

url: function() {
var base = getValue(this.collection, 'url') || getValue(this, 'urlRoot') || urlError();
var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
if (this.isNew()) return base;

@@ -418,14 +484,29 @@ return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);

change: function(options) {
if (this._changing || !this.hasChanged()) return this;
options || (options = {});
var changing = this._changing;
this._changing = true;
this._moreChanges = true;
for (var attr in this._changed) {
this.trigger('change:' + attr, this, this._changed[attr], options);
// Silent changes become pending changes.
for (var attr in this._silent) this._pending[attr] = true;
// Silent changes are triggered.
var changes = _.extend({}, options.changes, this._silent);
this._silent = {};
for (var attr in changes) {
this.trigger('change:' + attr, this, this.get(attr), options);
}
while (this._moreChanges) {
this._moreChanges = false;
if (changing) return this;
// Continue firing `"change"` events while there are pending changes.
while (!_.isEmpty(this._pending)) {
this._pending = {};
this.trigger('change', this, options);
// Pending and silent changes still remain.
for (var attr in this.changed) {
if (this._pending[attr] || this._silent[attr]) continue;
delete this.changed[attr];
}
this._previousAttributes = _.clone(this.attributes);
}
this._previousAttributes = _.clone(this.attributes);
delete this._changed;
this._changing = false;

@@ -438,4 +519,4 @@ return this;

hasChanged: function(attr) {
if (!arguments.length) return !_.isEmpty(this._changed);
return this._changed && _.has(this._changed, attr);
if (!arguments.length) return !_.isEmpty(this.changed);
return _.has(this.changed, attr);
},

@@ -450,3 +531,3 @@

changedAttributes: function(diff) {
if (!diff) return this.hasChanged() ? _.clone(this._changed) : false;
if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
var val, changed = false, old = this._previousAttributes;

@@ -479,5 +560,5 @@ for (var attr in diff) {

// Run validation against a set of incoming attributes, returning `true`
// if all is well. If a specific `error` callback has been passed,
// call that instead of firing the general `"error"` event.
// Run validation against the next complete set of model attributes,
// returning `true` if all is well. If a specific `error` callback has
// been passed, call that instead of firing the general `"error"` event.
_validate: function(attrs, options) {

@@ -504,4 +585,5 @@ if (options.silent || !this.validate) return true;

// its models in sort order, as they're added and removed.
Backbone.Collection = function(models, options) {
var Collection = Backbone.Collection = function(models, options) {
options || (options = {});
if (options.model) this.model = options.model;
if (options.comparator) this.comparator = options.comparator;

@@ -514,7 +596,7 @@ this._reset();

// Define the Collection's inheritable methods.
_.extend(Backbone.Collection.prototype, Backbone.Events, {
_.extend(Collection.prototype, Events, {
// The default model for a collection is just a **Backbone.Model**.
// This should be overridden in most cases.
model: Backbone.Model,
model: Model,

@@ -527,4 +609,4 @@ // Initialize is an empty function by default. Override it with your own

// models' attributes.
toJSON: function() {
return this.map(function(model){ return model.toJSON(); });
toJSON: function(options) {
return this.map(function(model){ return model.toJSON(options); });
},

@@ -535,3 +617,3 @@

add: function(models, options) {
var i, index, length, model, cid, id, cids = {}, ids = {};
var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
options || (options = {});

@@ -546,5 +628,7 @@ models = _.isArray(models) ? models.slice() : [models];

}
if (cids[cid = model.cid] || this._byCid[cid] ||
(((id = model.id) != null) && (ids[id] || this._byId[id]))) {
throw new Error("Can't add the same model to a collection twice");
cid = model.cid;
id = model.id;
if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
dups.push(i);
continue;
}

@@ -554,5 +638,11 @@ cids[cid] = ids[id] = model;

// Remove duplicates.
i = dups.length;
while (i--) {
models.splice(dups[i], 1);
}
// Listen to added models' events, and index models for lookup by
// `id` and by `cid`.
for (i = 0; i < length; i++) {
for (i = 0, length = models.length; i < length; i++) {
(model = models[i]).on('all', this._onModelEvent, this);

@@ -601,5 +691,33 @@ this._byCid[model.cid] = model;

// Add a model to the end of the collection.
push: function(model, options) {
model = this._prepareModel(model, options);
this.add(model, options);
return model;
},
// Remove a model from the end of the collection.
pop: function(options) {
var model = this.at(this.length - 1);
this.remove(model, options);
return model;
},
// Add a model to the beginning of the collection.
unshift: function(model, options) {
model = this._prepareModel(model, options);
this.add(model, _.extend({at: 0}, options));
return model;
},
// Remove a model from the beginning of the collection.
shift: function(options) {
var model = this.at(0);
this.remove(model, options);
return model;
},
// Get a model from the set by id.
get: function(id) {
if (id == null) return null;
if (id == null) return void 0;
return this._byId[id.id != null ? id.id : id];

@@ -618,2 +736,13 @@ },

// Return models with matching attributes. Useful for simple cases of `filter`.
where: function(attrs) {
if (_.isEmpty(attrs)) return [];
return this.filter(function(model) {
for (var key in attrs) {
if (attrs[key] !== model.get(key)) return false;
}
return true;
});
},
// Force the collection to re-sort itself. You don't need to call this under

@@ -650,3 +779,3 @@ // normal circumstances, as the set will maintain sort order as each item

this._reset();
this.add(models, {silent: true, parse: options.parse});
this.add(models, _.extend({silent: true}, options));
if (!options.silent) this.trigger('reset', this, options);

@@ -717,3 +846,4 @@ return this;

_prepareModel: function(model, options) {
if (!(model instanceof Backbone.Model)) {
options || (options = {});
if (!(model instanceof Model)) {
var attrs = model;

@@ -741,8 +871,8 @@ options.collection = this;

// in other collections are ignored.
_onModelEvent: function(ev, model, collection, options) {
if ((ev == 'add' || ev == 'remove') && collection != this) return;
if (ev == 'destroy') {
_onModelEvent: function(event, model, collection, options) {
if ((event == 'add' || event == 'remove') && collection != this) return;
if (event == 'destroy') {
this.remove(model, options);
}
if (model && ev === 'change:' + model.idAttribute) {
if (model && event === 'change:' + model.idAttribute) {
delete this._byId[model.previous(model.idAttribute)];

@@ -765,3 +895,3 @@ this._byId[model.id] = model;

_.each(methods, function(method) {
Backbone.Collection.prototype[method] = function() {
Collection.prototype[method] = function() {
return _[method].apply(_, [this.models].concat(_.toArray(arguments)));

@@ -776,3 +906,3 @@ };

// matched. Creating a new one sets its `routes` hash, if not set statically.
Backbone.Router = function(options) {
var Router = Backbone.Router = function(options) {
options || (options = {});

@@ -791,3 +921,3 @@ if (options.routes) this.routes = options.routes;

// Set up all inheritable **Backbone.Router** properties and methods.
_.extend(Backbone.Router.prototype, Backbone.Events, {
_.extend(Router.prototype, Events, {

@@ -805,3 +935,3 @@ // Initialize is an empty function by default. Override it with your own

route: function(route, name, callback) {
Backbone.history || (Backbone.history = new Backbone.History);
Backbone.history || (Backbone.history = new History);
if (!_.isRegExp(route)) route = this._routeToRegExp(route);

@@ -859,3 +989,3 @@ if (!callback) callback = this[name];

// browser does not support `onhashchange`, falls back to polling.
Backbone.History = function() {
var History = Backbone.History = function() {
this.handlers = [];

@@ -872,6 +1002,6 @@ _.bindAll(this, 'checkUrl');

// Has the history handling already been started?
var historyStarted = false;
History.started = false;
// Set up all inheritable **Backbone.History** properties and methods.
_.extend(Backbone.History.prototype, Backbone.Events, {
_.extend(History.prototype, Events, {

@@ -882,2 +1012,10 @@ // The default interval to poll for hash changes, if necessary, is

// Gets the true hash value. Cannot use location.hash directly due to bug
// in Firefox where location.hash will always be decoded.
getHash: function(windowOverride) {
var loc = windowOverride ? windowOverride.location : window.location;
var match = loc.href.match(/#(.*)$/);
return match ? match[1] : '';
},
// Get the cross-browser normalized URL fragment, either from the URL,

@@ -892,6 +1030,5 @@ // the hash, or the override.

} else {
fragment = window.location.hash;
fragment = this.getHash();
}
}
fragment = decodeURIComponent(fragment);
if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);

@@ -904,6 +1041,7 @@ return fragment.replace(routeStripper, '');

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?
if (historyStarted) throw new Error("Backbone.history has already been started");
this.options = _.extend({}, {root: '/'}, this.options, options);

@@ -916,2 +1054,3 @@ this._wantsHashChange = this.options.hashChange !== false;

var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
if (oldIE) {

@@ -935,3 +1074,2 @@ this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;

this.fragment = fragment;
historyStarted = true;
var loc = window.location;

@@ -951,3 +1089,3 @@ var atRoot = loc.pathname == this.options.root;

} else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
this.fragment = loc.hash.replace(routeStripper, '');
this.fragment = this.getHash().replace(routeStripper, '');
window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);

@@ -966,3 +1104,3 @@ }

clearInterval(this._checkUrlInterval);
historyStarted = false;
History.started = false;
},

@@ -980,6 +1118,6 @@

var current = this.getFragment();
if (current == this.fragment && this.iframe) current = this.getFragment(this.iframe.location.hash);
if (current == this.fragment || current == decodeURIComponent(this.fragment)) return false;
if (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe));
if (current == this.fragment) return false;
if (this.iframe) this.navigate(current);
this.loadUrl() || this.loadUrl(window.location.hash);
this.loadUrl() || this.loadUrl(this.getHash());
},

@@ -1007,8 +1145,8 @@

// route callback be fired (not usually desirable), or `replace: true`, if
// you which to modify the current URL without adding an entry to the history.
// you wish to modify the current URL without adding an entry to the history.
navigate: function(fragment, options) {
if (!historyStarted) return false;
if (!History.started) return false;
if (!options || options === true) options = {trigger: options};
var frag = (fragment || '').replace(routeStripper, '');
if (this.fragment == frag || this.fragment == decodeURIComponent(frag)) return;
if (this.fragment == frag) return;

@@ -1026,3 +1164,3 @@ // If pushState is available, we use it to set the fragment as a real URL.

this._updateHash(window.location, frag, options.replace);
if (this.iframe && (frag != this.getFragment(this.iframe.location.hash))) {
if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
// Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.

@@ -1058,3 +1196,3 @@ // When replace is true, we don't want this.

// if an existing element is not provided...
Backbone.View = function(options) {
var View = Backbone.View = function(options) {
this.cid = _.uniqueId('view');

@@ -1068,3 +1206,3 @@ this._configure(options || {});

// Cached regex to split keys for `delegate`.
var eventSplitter = /^(\S+)\s*(.*)$/;
var delegateEventSplitter = /^(\S+)\s*(.*)$/;

@@ -1075,3 +1213,3 @@ // List of view options to be merged as properties.

// Set up all inheritable **Backbone.View** properties and methods.
_.extend(Backbone.View.prototype, Backbone.Events, {
_.extend(View.prototype, Events, {

@@ -1120,3 +1258,4 @@ // The default `tagName` of a View's element is `"div"`.

setElement: function(element, delegate) {
this.$el = $(element);
if (this.$el) this.undelegateEvents();
this.$el = (element instanceof $) ? element : $(element);
this.el = this.$el[0];

@@ -1148,4 +1287,4 @@ if (delegate !== false) this.delegateEvents();

if (!_.isFunction(method)) method = this[events[key]];
if (!method) throw new Error('Event "' + events[key] + '" does not exist');
var match = key.match(eventSplitter);
if (!method) throw new Error('Method "' + events[key] + '" does not exist');
var match = key.match(delegateEventSplitter);
var eventName = match[1], selector = match[2];

@@ -1206,4 +1345,3 @@ method = _.bind(method, this);

// Set up inheritance for the model, collection, and view.
Backbone.Model.extend = Backbone.Collection.extend =
Backbone.Router.extend = Backbone.View.extend = extend;
Model.extend = Collection.extend = Router.extend = View.extend = extend;

@@ -1239,2 +1377,5 @@ // Backbone.sync

// Default options, unless specified.
options || (options = {});
// Default JSON-request options.

@@ -1241,0 +1382,0 @@ var params = {type: type, dataType: 'json'};

// An example Backbone application contributed by
// [Jérôme Gravel-Niquet](http://jgn.me/). This demo uses a simple
// [LocalStorage adapter](backbone-localstorage.html)
// [LocalStorage adapter](backbone-localstorage.js)
// to persist Backbone models within your browser.

@@ -12,16 +12,29 @@

// Our basic **Todo** model has `text`, `order`, and `done` attributes.
window.Todo = Backbone.Model.extend({
// Our basic **Todo** model has `title`, `order`, and `done` attributes.
var Todo = Backbone.Model.extend({
// Default attributes for a todo item.
// Default attributes for the todo item.
defaults: function() {
return {
done: false,
order: Todos.nextOrder()
title: "empty todo...",
order: Todos.nextOrder(),
done: false
};
},
// Ensure that each todo created has `title`.
initialize: function() {
if (!this.get("title")) {
this.set({"title": this.defaults.title});
}
},
// Toggle the `done` state of this todo item.
toggle: function() {
this.save({done: !this.get("done")});
},
// Remove this Todo from *localStorage* and delete its view.
clear: function() {
this.destroy();
}

@@ -36,3 +49,3 @@

// server.
window.TodoList = Backbone.Collection.extend({
var TodoList = Backbone.Collection.extend({

@@ -43,3 +56,3 @@ // Reference to this collection's model.

// Save all of the todo items under the `"todos"` namespace.
localStorage: new Store("todos"),
localStorage: new Store("todos-backbone"),

@@ -71,3 +84,3 @@ // Filter down the list of all todo items that are finished.

// Create our global collection of **Todos**.
window.Todos = new TodoList;
var Todos = new TodoList;

@@ -78,3 +91,3 @@ // Todo Item View

// The DOM element for a todo item...
window.TodoView = Backbone.View.extend({
var TodoView = Backbone.View.extend({

@@ -89,9 +102,12 @@ //... is a list tag.

events: {
"click .check" : "toggleDone",
"dblclick div.todo-text" : "edit",
"click span.todo-destroy" : "clear",
"keypress .todo-input" : "updateOnEnter"
"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.
// 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() {

@@ -102,18 +118,10 @@ this.model.bind('change', this.render, this);

// Re-render the contents of the todo item.
// Re-render the titles of the todo item.
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
this.setText();
this.$el.html(this.template(this.model.toJSON()));
this.$el.toggleClass('done', this.model.get('done'));
this.input = this.$('.edit');
return this;
},
// To avoid XSS (not that it would be harmful in this particular app),
// we use `jQuery.text` to set the contents of the todo item.
setText: function() {
var text = this.model.get('text');
this.$('.todo-text').text(text);
this.input = this.$('.todo-input');
this.input.bind('blur', _.bind(this.close, this)).val(text);
},
// Toggle the `"done"` state of the model.

@@ -126,3 +134,3 @@ toggleDone: function() {

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

@@ -133,4 +141,6 @@ },

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

@@ -143,10 +153,5 @@

// Remove this view from the DOM.
remove: function() {
$(this.el).remove();
},
// Remove the item, destroy the model.
clear: function() {
this.model.destroy();
this.model.clear();
}

@@ -160,3 +165,3 @@

// Our overall **AppView** is the top-level piece of UI.
window.AppView = Backbone.View.extend({
var AppView = Backbone.View.extend({

@@ -173,4 +178,4 @@ // Instead of generating a new element, bind to the existing skeleton of

"keypress #new-todo": "createOnEnter",
"keyup #new-todo": "showTooltip",
"click .todo-clear a": "clearCompleted"
"click #clear-completed": "clearCompleted",
"click #toggle-all": "toggleAllComplete"
},

@@ -182,8 +187,13 @@

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

@@ -195,7 +205,15 @@ },

render: function() {
this.$('#todo-stats').html(this.statsTemplate({
total: Todos.length,
done: Todos.done().length,
remaining: Todos.remaining().length
}));
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;
},

@@ -207,3 +225,3 @@

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

@@ -216,8 +234,9 @@

// If you hit return in the main input field, and there is text to save,
// create new **Todo** model persisting it to *localStorage*.
// If you hit return in the main input field, create new **Todo** model,
// persisting it to *localStorage*.
createOnEnter: function(e) {
var text = this.input.val();
if (!text || e.keyCode != 13) return;
Todos.create({text: text});
if (e.keyCode != 13) return;
if (!this.input.val()) return;
Todos.create({title: this.input.val()});
this.input.val('');

@@ -228,16 +247,9 @@ },

clearCompleted: function() {
_.each(Todos.done(), function(todo){ todo.destroy(); });
_.each(Todos.done(), function(todo){ todo.clear(); });
return false;
},
// Lazily show the tooltip that tells you to press `enter` to save
// a new todo item, after one second.
showTooltip: function(e) {
var tooltip = this.$(".ui-tooltip-top");
var val = this.input.val();
tooltip.fadeOut();
if (this.tooltipTimeout) clearTimeout(this.tooltipTimeout);
if (val == '' || val == this.input.attr('placeholder')) return;
var show = function(){ tooltip.show().fadeIn(); };
this.tooltipTimeout = _.delay(show, 1000);
toggleAllComplete: function () {
var done = this.allCheckbox.checked;
Todos.each(function (todo) { todo.save({'done': done}); });
}

@@ -248,4 +260,4 @@

// Finally, we kick things off by creating the **App**.
window.App = new AppView;
var App = new AppView;
});

@@ -13,3 +13,3 @@ {

"main" : "backbone.js",
"version" : "0.9.1"
"version" : "0.9.2"
}

@@ -6,7 +6,21 @@ $(document).ready(function() {

var a, b, c, d, e, col, otherCol;
module("Backbone.Collection", {
setup: function() {
Backbone.sync = function() {
lastRequest = _.toArray(arguments);
a = new Backbone.Model({id: 3, label: 'a'});
b = new Backbone.Model({id: 2, label: 'b'});
c = new Backbone.Model({id: 1, label: 'c'});
d = new Backbone.Model({id: 0, label: 'd'});
e = null;
col = new Backbone.Collection([a,b,c,d]);
otherCol = new Backbone.Collection();
Backbone.sync = function(method, model, options) {
lastRequest = {
method: method,
model: model,
options: options
};
};

@@ -21,10 +35,2 @@ },

var a = new Backbone.Model({id: 3, label: 'a'});
var b = new Backbone.Model({id: 2, label: 'b'});
var c = new Backbone.Model({id: 1, label: 'c'});
var d = new Backbone.Model({id: 0, label: 'd'});
var e = null;
var col = new Backbone.Collection([a,b,c,d]);
var otherCol = new Backbone.Collection();
test("Collection: new and sort", function() {

@@ -58,3 +64,3 @@ equal(col.first(), a, "a should be first");

var model = new MongoModel({_id: 100});
col.add(model);
col.push(model);
equal(col.get(100), model);

@@ -65,30 +71,2 @@ model.set({_id: 101});

test("Collection: add model with attributes modified by set", function() {
var CustomSetModel = Backbone.Model.extend({
defaults: {
number_as_string: null //presence of defaults forces extend
},
validate: function (attributes) {
if (!_.isString(attributes.num_as_string)) {
return 'fail';
}
},
set: function (attributes, options) {
if (attributes.num_as_string) {
attributes.num_as_string = attributes.num_as_string.toString();
}
Backbone.Model.prototype.set.call(this, attributes, options);
}
});
var CustomSetCollection = Backbone.Collection.extend({
model: CustomSetModel
});
raises(function(){
new CustomSetCollection([{ num_as_string: 2 }]);
});
});
test("Collection: update index when id changes", function() {

@@ -108,11 +86,12 @@ var col = new Backbone.Collection();

test("Collection: at", function() {
equal(col.at(2), b);
equal(col.at(2), c);
});
test("Collection: pluck", function() {
equal(col.pluck('label').join(' '), 'd c b a');
equal(col.pluck('label').join(' '), 'a b c d');
});
test("Collection: add", function() {
var added = opts = secondAdded = null;
var added, opts, secondAdded;
added = opts = secondAdded = null;
e = new Backbone.Model({id: 10, label : 'e'});

@@ -156,18 +135,11 @@ otherCol.add(e);

test("Collection: can't add model to collection twice", function() {
raises(function(){
// no id, same cid
var a2 = new Backbone.Model({label: a.label});
a2.cid = a.cid;
col.add(a2);
ok(false, "duplicate; expected add to fail");
}, "Can't add the same model to a collection twice");
var col = new Backbone.Collection([{id: 1}, {id: 2}, {id: 1}, {id: 2}, {id: 3}]);
equal(col.pluck('id').join(' '), '1 2 3');
});
test("Collection: can't add different model with same id to collection twice", function() {
raises(function(){
var col = new Backbone.Collection;
col.add({id: 101});
col.add({id: 101});
ok(false, "duplicate; expected add to fail");
}, "Can't add the same model to a collection twice");
var col = new Backbone.Collection;
col.unshift({id: 101});
col.add({id: 101});
equal(col.length, 1);
});

@@ -246,6 +218,7 @@

test("Collection: remove", function() {
var removed = otherRemoved = null;
var removed = null;
var otherRemoved = null;
col.bind('remove', function(model, col, options) {
removed = model.get('label');
equal(options.index, 4);
equal(options.index, 3);
});

@@ -255,9 +228,15 @@ otherCol.bind('remove', function(model, col, options) {

});
col.remove(e);
equal(removed, 'e');
equal(col.length, 4);
equal(col.first(), d);
col.remove(d);
equal(removed, 'd');
equal(col.length, 3);
equal(col.first(), a);
equal(otherRemoved, null);
});
test("Collection: shift and pop", function() {
var col = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]);
equal(col.shift().get('a'), 'a');
equal(col.pop().get('c'), 'c');
});
test("Collection: events are unbound on remove", function() {

@@ -358,8 +337,8 @@ var counter = 0;

col.fetch();
equal(lastRequest[0], 'read');
equal(lastRequest[1], col);
equal(lastRequest[2].parse, true);
equal(lastRequest.method, 'read');
equal(lastRequest.model, col);
equal(lastRequest.options.parse, true);
col.fetch({parse: false});
equal(lastRequest[2].parse, false);
equal(lastRequest.options.parse, false);
});

@@ -369,4 +348,4 @@

var model = col.create({label: 'f'}, {wait: true});
equal(lastRequest[0], 'create');
equal(lastRequest[1], model);
equal(lastRequest.method, 'create');
equal(lastRequest.model, model);
equal(model.get('label'), 'f');

@@ -416,10 +395,26 @@ equal(model.collection, col);

test("Collection: toJSON", function() {
equal(JSON.stringify(col), '[{"id":0,"label":"d"},{"id":1,"label":"c"},{"id":2,"label":"b"},{"id":3,"label":"a"}]');
equal(JSON.stringify(col), '[{"id":3,"label":"a"},{"id":2,"label":"b"},{"id":1,"label":"c"},{"id":0,"label":"d"}]');
});
test("Collection: where", function() {
var coll = new Backbone.Collection([
{a: 1},
{a: 1},
{a: 1, b: 2},
{a: 2, b: 2},
{a: 3}
]);
equal(coll.where({a: 1}).length, 3);
equal(coll.where({a: 2}).length, 1);
equal(coll.where({a: 3}).length, 1);
equal(coll.where({b: 1}).length, 0);
equal(coll.where({b: 2}).length, 2);
equal(coll.where({a: 1, b: 2}).length, 1);
});
test("Collection: Underscore methods", function() {
equal(col.map(function(model){ return model.get('label'); }).join(' '), 'd c b a');
equal(col.map(function(model){ return model.get('label'); }).join(' '), 'a b c d');
equal(col.any(function(model){ return model.id === 100; }), false);
equal(col.any(function(model){ return model.id === 0; }), true);
equal(col.indexOf(b), 2);
equal(col.indexOf(b), 1);
equal(col.size(), 4);

@@ -437,3 +432,3 @@ equal(col.rest().length, 3);

.value(),
[0, 4]);
[4, 0]);
});

@@ -452,10 +447,24 @@

equal(col.length, 4);
equal(col.last(), a);
equal(col.last(), d);
col.reset(_.map(models, function(m){ return m.attributes; }));
equal(resetCount, 3);
equal(col.length, 4);
ok(col.last() !== a);
ok(_.isEqual(col.last().attributes, a.attributes));
ok(col.last() !== d);
ok(_.isEqual(col.last().attributes, d.attributes));
});
test("Collection: reset passes caller options", function() {
var Model = Backbone.Model.extend({
initialize: function(attrs, options) {
this.model_parameter = options.model_parameter;
}
});
var col = new (Backbone.Collection.extend({ model: Model }))();
col.reset([{ astring: "green", anumber: 1 }, { astring: "blue", anumber: 2 }], { model_parameter: 'model parameter' });
equal(col.length, 2);
col.each(function(model) {
equal(model.model_parameter, 'model parameter');
});
});
test("Collection: trigger custom events on models", function() {

@@ -552,6 +561,22 @@ var fired = null;

var model = new Backbone.Model();
raises(function() { col.add([model, model]); });
raises(function() { col.add([{id: 1}, {id: 1}]); });
col.add([model, model]);
equal(col.length, 1);
col.add([{id: 1}, {id: 1}]);
equal(col.length, 2);
equal(col.last().id, 1);
});
test("#964 - collection.get return in consistent", function() {
var c = new Backbone.Collection();
ok(c.get(null) === undefined);
ok(c.get() === undefined);
});
test("#1112 - passing options.model sets collection.model", function() {
var Model = Backbone.Model.extend({});
var c = new Backbone.Collection([{id: 1}], {model: Model});
ok(c.model === Model);
ok(c.at(0) instanceof Model);
});
});

@@ -148,2 +148,24 @@ $(document).ready(function() {

test("if no callback is provided, `on` is a noop", function() {
_.extend({}, Backbone.Events).bind('test').trigger('test');
});
test("remove all events for a specific context", 4, function() {
var obj = _.extend({}, Backbone.Events);
obj.on('x y all', function() { ok(true); });
obj.on('x y all', function() { ok(false); }, obj);
obj.off(null, null, obj);
obj.trigger('x y');
});
test("remove all events for a specific callback", 4, function() {
var obj = _.extend({}, Backbone.Events);
var success = function() { ok(true); };
var fail = function() { ok(false); };
obj.on('x y all', success);
obj.on('x y all', fail);
obj.off(null, fail);
obj.trigger('x y');
});
});

@@ -11,7 +11,26 @@ $(document).ready(function() {

var proxy = Backbone.Model.extend();
var klass = Backbone.Collection.extend({
url : function() { return '/collection'; }
});
var doc, collection;
module("Backbone.Model", {
setup: function() {
Backbone.sync = function() {
lastRequest = _.toArray(arguments);
doc = new proxy({
id : '1-the-tempest',
title : "The Tempest",
author : "Bill Shakespeare",
length : 123
});
collection = new klass();
collection.add(doc);
Backbone.sync = function(method, model, options) {
lastRequest = {
method: method,
model: model,
options: options
};
sync.apply(this, arguments);

@@ -32,19 +51,2 @@ };

var attrs = {
id : '1-the-tempest',
title : "The Tempest",
author : "Bill Shakespeare",
length : 123
};
var proxy = Backbone.Model.extend();
var doc = new proxy(attrs);
var klass = Backbone.Collection.extend({
url : function() { return '/collection'; }
});
var collection = new klass();
collection.add(doc);
test("Model: initialize", function() {

@@ -84,2 +86,3 @@ var Model = Backbone.Model.extend({

test("Model: url", function() {
doc.urlRoot = null;
equal(doc.url(), '/collection/1-the-tempest');

@@ -89,3 +92,2 @@ doc.collection.url = '/collection/';

doc.collection = null;
doc.urlRoot = null;
raises(function() { doc.url(); });

@@ -119,5 +121,4 @@ doc.collection = collection;

test("Model: clone", function() {
attrs = { 'foo': 1, 'bar': 2, 'baz': 3};
a = new Backbone.Model(attrs);
b = a.clone();
var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
var b = a.clone();
equal(a.get('foo'), 1);

@@ -135,10 +136,7 @@ equal(a.get('bar'), 2);

test("Model: isNew", function() {
attrs = { 'foo': 1, 'bar': 2, 'baz': 3};
a = new Backbone.Model(attrs);
var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
ok(a.isNew(), "it should be new");
attrs = { 'foo': 1, 'bar': 2, 'baz': 3, 'id': -5 };
a = new Backbone.Model(attrs);
a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3, 'id': -5 });
ok(!a.isNew(), "any defined ID is legal, negative or positive");
attrs = { 'foo': 1, 'bar': 2, 'baz': 3, 'id': 0 };
a = new Backbone.Model(attrs);
a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3, 'id': 0 });
ok(!a.isNew(), "any defined ID is legal, including zero");

@@ -168,4 +166,3 @@ ok( new Backbone.Model({ }).isNew(), "is true when there is no id");

test("Model: has", function() {
attrs = {};
a = new Backbone.Model(attrs);
var a = new Backbone.Model();
equal(a.has("name"), false);

@@ -186,4 +183,3 @@ _([true, "Truth!", 1, false, '', 0]).each(function(value) {

expect(8);
attrs = {id: 'id', foo: 1, bar: 2, baz: 3};
a = new Backbone.Model(attrs);
var a = new Backbone.Model({id: 'id', foo: 1, bar: 2, baz: 3});
var changeCount = 0;

@@ -334,3 +330,3 @@ a.on("change:foo", function() { changeCount += 1; });

model.save();
ok(_.isEqual(lastRequest[1], model));
ok(_.isEqual(lastRequest.model, model));
});

@@ -369,4 +365,4 @@ model.set({lastName: 'Hicks'});

doc.save({title : "Henry V"});
equal(lastRequest[0], 'update');
ok(_.isEqual(lastRequest[1], doc));
equal(lastRequest.method, 'update');
ok(_.isEqual(lastRequest.model, doc));
});

@@ -383,6 +379,8 @@

test("Model: fetch", function() {
doc.fetch();
equal(lastRequest[0], 'read');
ok(_.isEqual(lastRequest[1], doc));
equal(lastRequest.method, 'read');
ok(_.isEqual(lastRequest.model, doc));
});

@@ -392,9 +390,11 @@

doc.destroy();
equal(lastRequest[0], 'delete');
ok(_.isEqual(lastRequest[1], doc));
equal(lastRequest.method, 'delete');
ok(_.isEqual(lastRequest.model, doc));
var newModel = new Backbone.Model;
equal(newModel.destroy(), false);
});
test("Model: non-persisted destroy", function() {
attrs = { 'foo': 1, 'bar': 2, 'baz': 3};
a = new Backbone.Model(attrs);
var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
a.sync = function() { throw "should not be called"; };

@@ -528,4 +528,4 @@ a.destroy();

a = new A();
b = new B({a: a});
var a = new A();
var b = new B({a: a});
a.set({state: 'hello'});

@@ -603,14 +603,6 @@ });

test("set/hasChanged object prototype props", function() {
var model = new Backbone.Model();
ok(!model.hasChanged('toString'));
model.set({toString: undefined});
model.unset('toString', {silent: true});
ok(model.hasChanged());
});
test("save with `wait` succeeds without `validate`", function() {
var model = new Backbone.Model();
model.save({x: 1}, {wait: true});
ok(lastRequest[1] === model);
ok(lastRequest.model === model);
});

@@ -640,3 +632,3 @@

equal(changed, 0);
lastRequest[2].success({});
lastRequest.options.success({});
equal(model.get('x'), 3);

@@ -646,11 +638,35 @@ equal(changed, 1);

test("nested `set` during `'change:attr'`", 1, function() {
test("`save` with `wait` results in correct attributes if success is called during sync", function() {
var changed = 0;
var model = new Backbone.Model({x: 1, y: 2});
model.sync = function(method, model, options) {
options.success();
};
model.on("change:x", function() { changed++; });
model.save({x: 3}, {wait: true});
equal(model.get('x'), 3);
equal(changed, 1);
});
test("save with wait validates attributes", 1, function() {
var model = new Backbone.Model();
model.on('change:x', function() { ok(true); });
model.on('change:y', function() {
model.set({x: true});
// only fires once
model.set({x: true});
model.validate = function() { ok(true); };
model.save({x: 1}, {wait: true});
});
test("nested `set` during `'change:attr'`", function() {
var events = [];
var model = new Backbone.Model();
model.on('all', function(event) { events.push(event); });
model.on('change', function() {
model.set({z: true}, {silent:true});
});
model.set({y: true});
model.on('change:x', function() {
model.set({y: true});
});
model.set({x: true});
deepEqual(events, ['change:y', 'change:x', 'change']);
events = [];
model.change();
deepEqual(events, ['change:z', 'change']);
});

@@ -673,3 +689,3 @@

test("nested `set` suring `'change'`", 3, function() {
test("nested `set` during `'change'`", 6, function() {
var count = 0;

@@ -681,10 +697,13 @@ var model = new Backbone.Model();

deepEqual(this.changedAttributes(), {x: true});
equal(model.previous('x'), undefined);
model.set({y: true});
break;
case 1:
deepEqual(this.changedAttributes(), {x: true, y: true});
deepEqual(this.changedAttributes(), {y: true});
equal(model.previous('x'), true);
model.set({z: true});
break;
case 2:
deepEqual(this.changedAttributes(), {x: true, y: true, z: true});
deepEqual(this.changedAttributes(), {z: true});
equal(model.previous('y'), true);
break;

@@ -698,2 +717,90 @@ default:

test("nested `'change'` with silent", 3, function() {
var count = 0;
var model = new Backbone.Model();
model.on('change:y', function() { ok(true); });
model.on('change', function() {
switch(count++) {
case 0:
deepEqual(this.changedAttributes(), {x: true});
model.set({y: true}, {silent: true});
break;
case 1:
deepEqual(this.changedAttributes(), {y: true, z: true});
break;
default:
ok(false);
}
});
model.set({x: true});
model.set({z: true});
});
test("nested `'change:attr'` with silent", 1, function() {
var model = new Backbone.Model();
model.on('change:y', function(){ ok(true); });
model.on('change', function() {
model.set({y: true}, {silent: true});
model.set({z: true});
});
model.set({x: true});
});
test("multiple nested changes with silent", 1, function() {
var model = new Backbone.Model();
model.on('change:x', function() {
model.set({y: 1}, {silent: true});
model.set({y: 2});
});
model.on('change:y', function(model, val) {
equal(val, 2);
});
model.set({x: true});
model.change();
});
test("multiple nested changes with silent", function() {
var changes = [];
var model = new Backbone.Model();
model.on('change:b', function(model, val) { changes.push(val); });
model.on('change', function() {
model.set({b: 1});
model.set({b: 2}, {silent: true});
});
model.set({b: 0});
deepEqual(changes, [0, 1, 1]);
model.change();
deepEqual(changes, [0, 1, 1, 2, 1]);
});
test("nested set multiple times", 1, function() {
var model = new Backbone.Model();
model.on('change:b', function() {
ok(true);
});
model.on('change:a', function() {
model.set({b: true});
model.set({b: true});
});
model.set({a: true});
});
test("Backbone.wrapError triggers `'error'`", 12, function() {
var resp = {};
var options = {};
var model = new Backbone.Model();
model.on('error', error);
var callback = Backbone.wrapError(null, model, options);
callback(model, resp);
callback(resp);
callback = Backbone.wrapError(error, model, options);
callback(model, resp);
callback(resp);
function error(_model, _resp, _options) {
ok(model === _model);
ok(resp === _resp);
ok(options === _options);
}
});
});
$(document).ready(function() {
module("Backbone.Router");
var router = null;
var lastRoute = null;
var lastArgs = [];
function onRoute(router, route, args) {
lastRoute = route;
lastArgs = args;
}
module("Backbone.Router", {
setup: function() {
Backbone.history = null;
router = new Router({testing: 101});
Backbone.history.interval = 9;
Backbone.history.start({pushState: false});
lastRoute = null;
lastArgs = [];
Backbone.history.on('route', onRoute);
},
teardown: function() {
Backbone.history.stop();
Backbone.history.off('route', onRoute);
}
});
var Router = Backbone.Router.extend({

@@ -14,2 +40,5 @@

"search/:query/p:page": "search",
"contacts": "contacts",
"contacts/new": "newContact",
"contacts/:id": "loadContact",
"splat/*args/end": "splat",

@@ -39,2 +68,14 @@ "*first/complex-:part/*rest": "complex",

contacts: function(){
this.contact = 'index';
},
newContact: function(){
this.contact = 'new';
},
loadContact: function(){
this.contact = 'load';
},
splat : function(args) {

@@ -63,15 +104,2 @@ this.args = args;

Backbone.history = null;
var router = new Router({testing: 101});
Backbone.history.interval = 9;
Backbone.history.start({pushState: false});
var lastRoute = null;
var lastArgs = [];
Backbone.history.bind('route', function(router, route, args) {
lastRoute = route;
lastArgs = args;
});
test("Router: initialize", function() {

@@ -113,2 +141,14 @@ equal(router.testing, 101);

test("Router: route precedence via navigate", 6, function(){
// check both 0.9.x and backwards-compatibility options
_.each([ { trigger: true }, true ], function( options ){
Backbone.history.navigate('contacts', options);
equal(router.contact, 'index');
Backbone.history.navigate('contacts/new', options);
equal(router.contact, 'new');
Backbone.history.navigate('contacts/foo', options);
equal(router.contact, 'load');
});
});
test("Router: doesn't fire routes to the same place twice", function() {

@@ -207,2 +247,38 @@ equal(router.count, 0);

test("#933, #908 - leading slash", function() {
var history = new Backbone.History();
history.options = {root: '/root'};
equal(history.getFragment('/root/foo'), 'foo');
history.options.root = '/root/';
equal(history.getFragment('/root/foo'), 'foo');
});
test("#1003 - History is started before navigate is called", function() {
var history = new Backbone.History();
history.navigate = function(){ ok(Backbone.History.started); };
Backbone.history.stop();
history.start();
});
test("Router: route callback gets passed non-decoded values", function() {
var route = 'has%2Fslash/complex-has%23hash/has%20space';
Backbone.history.navigate(route, {trigger: true});
equal(router.first, 'has%2Fslash');
equal(router.part, 'has%23hash');
equal(router.rest, 'has%20space');
});
asyncTest("Router: correctly handles URLs with % (#868)", 3, function() {
window.location.hash = 'search/fat%3A1.5%25';
setTimeout(function() {
window.location.hash = 'search/fat';
setTimeout(function() {
equal(router.query, 'fat');
equal(router.page, undefined);
equal(lastRoute, 'search');
start();
}, 50);
}, 50);
});
});

@@ -1,2 +0,2 @@

$(document).ready(function() {
$(document).ready(function(jQuery) {

@@ -14,15 +14,16 @@ // a mock object that looks sufficiently jQuery-ish

module("Backbone.setDomLibrary");
module("Backbone.setDomLibrary", {
teardown: function() {
Backbone.setDomLibrary(jQuery);
}
});
test('Changing jQuery library to custom library', function() {
Backbone.setDomLibrary(myLib);
var view = new Backbone.View(viewAttrs);
ok(view.$el.hasClass('test-setdomlibrary') === 'spam');
});
test('Changing jQuery library back to global jQuery', function() {
Backbone.setDomLibrary(jQuery);
var view = new Backbone.View(viewAttrs);
ok(view.$el.hasClass('test-setdomlibrary'));

@@ -29,0 +30,0 @@ });

@@ -6,8 +6,21 @@ $(document).ready(function() {

var Library = Backbone.Collection.extend({
url : function() { return '/library'; }
});
var library;
var attrs = {
title : "The Tempest",
author : "Bill Shakespeare",
length : 123
};
module("Backbone.sync", {
setup : function() {
library = new Library();
$.ajax = function(obj) {
lastRequest = obj;
};
library.create(attrs, {wait: false});
},

@@ -21,14 +34,2 @@

var Library = Backbone.Collection.extend({
url : function() { return '/library'; }
});
var library = new Library();
var attrs = {
title : "The Tempest",
author : "Bill Shakespeare",
length : 123
};
test("sync: read", function() {

@@ -50,3 +51,2 @@ library.fetch();

test("sync: create", function() {
library.create(attrs, {wait: false});
equal(lastRequest.url, '/library');

@@ -114,2 +114,3 @@ equal(lastRequest.type, 'POST');

test("sync: read model", function() {
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
library.first().fetch();

@@ -122,2 +123,3 @@ equal(lastRequest.url, '/library/2-the-tempest');

test("sync: destroy", function() {
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
library.first().destroy({wait: true});

@@ -130,2 +132,3 @@ equal(lastRequest.url, '/library/2-the-tempest');

test("sync: destroy with emulateHTTP", function() {
library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
Backbone.emulateHTTP = Backbone.emulateJSON = true;

@@ -140,3 +143,3 @@ library.first().destroy();

test("sync: urlError", function() {
model = new Backbone.Model();
var model = new Backbone.Model();
raises(function() {

@@ -149,2 +152,8 @@ model.fetch();

test("#1052 - `options` is optional.", function() {
var model = new Backbone.Model();
model.url = '/test';
Backbone.sync('create', model);
});
});
$(document).ready(function() {
module("Backbone.View");
var view;
var view = new Backbone.View({
id : 'test-view',
className : 'test-view'
module("Backbone.View", {
setup: function() {
view = new Backbone.View({
id : 'test-view',
className : 'test-view'
});
}
});

@@ -41,3 +47,4 @@

test("View: delegateEvents", function() {
var counter = counter2 = 0;
var counter = 0;
var counter2 = 0;
view.setElement(document.body);

@@ -75,3 +82,4 @@ view.increment = function(){ counter++; };

test("View: undelegateEvents", function() {
var counter = counter2 = 0;
var counter = 0;
var counter2 = 0;
view.setElement(document.body);

@@ -183,2 +191,22 @@ view.increment = function(){ counter++; };

test("#1048 - setElement uses provided object.", function() {
var $el = $('body');
var view = new Backbone.View({el: $el});
ok(view.$el === $el);
view.setElement($el = $($el));
ok(view.$el === $el);
});
test("#986 - Undelegate before changing element.", 1, function() {
var a = $('<button></button>');
var b = $('<button></button>');
var View = Backbone.View.extend({
events: {click: function(e) { ok(view.el === e.target); }}
});
var view = new View({el: a});
view.setElement(b);
a.trigger('click');
b.trigger('click');
});
});

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc