Skip to content

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also .

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also .
...
  • 15 commits
  • 11 files changed
  • 7 commit comments
  • 3 contributors
Showing with 308 additions and 167 deletions.
  1. +23 −22 backbone-min.js
  2. +35 −17 backbone.js
  3. +106 −100 docs/backbone.html
  4. +4 −4 docs/docco.css
  5. BIN docs/images/backbone-mobile.png
  6. BIN docs/images/dc-workspace.png
  7. +1 −1 docs/jsl.conf
  8. +74 −5 index.html
  9. +1 −1 package.json
  10. +41 −17 test/collection.js
  11. +23 −0 test/model.js
View
45 backbone-min.js
@@ -1,26 +1,27 @@
-// Backbone.js 0.3.1
+// Backbone.js 0.3.2
// (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://documentcloud.github.com/backbone
-(function(){var e;e=typeof exports!=="undefined"?exports:this.Backbone={};e.VERSION="0.3.1";var f=this._;if(!f&&typeof require!=="undefined")f=require("underscore")._;var h=this.jQuery;e.emulateHTTP=false;e.emulateJSON=false;e.Events={bind:function(a,b){this._callbacks||(this._callbacks={});(this._callbacks[a]||(this._callbacks[a]=[])).push(b);return this},unbind:function(a,b){var c;if(a){if(c=this._callbacks)if(b){c=c[a];if(!c)return this;for(var d=0,g=c.length;d<g;d++)if(b===c[d]){c.splice(d,1);
-break}}else c[a]=[]}else this._callbacks={};return this},trigger:function(a){var b,c,d,g;if(!(c=this._callbacks))return this;if(b=c[a]){d=0;for(g=b.length;d<g;d++)b[d].apply(this,Array.prototype.slice.call(arguments,1))}if(b=c.all){d=0;for(g=b.length;d<g;d++)b[d].apply(this,arguments)}return this}};e.Model=function(a,b){this.attributes={};this.cid=f.uniqueId("c");this.set(a||{},{silent:true});this._previousAttributes=f.clone(this.attributes);if(b&&b.collection)this.collection=b.collection;this.initialize&&
-this.initialize(a,b)};f.extend(e.Model.prototype,e.Events,{_previousAttributes:null,_changed:false,toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},set:function(a,b){b||(b={});if(!a)return this;if(a.attributes)a=a.attributes;var c=this.attributes;if(!b.silent&&this.validate&&!this._performValidation(a,b))return false;if("id"in a)this.id=a.id;for(var d in a){var g=a[d];if(!f.isEqual(c[d],g)){c[d]=g;if(!b.silent){this._changed=true;this.trigger("change:"+
-d,this,g)}}}!b.silent&&this._changed&&this.change();return this},unset:function(a,b){b||(b={});var c={};c[a]=void 0;if(!b.silent&&this.validate&&!this._performValidation(c,b))return false;delete this.attributes[a];if(!b.silent){this._changed=true;this.trigger("change:"+a,this);this.change()}return this},clear:function(a){a||(a={});var b=this.attributes,c={};for(attr in b)c[attr]=void 0;if(!a.silent&&this.validate&&!this._performValidation(c,a))return false;this.attributes={};if(!a.silent){this._changed=
-true;for(attr in b)this.trigger("change:"+attr,this);this.change()}return this},fetch:function(a){a||(a={});var b=this,c=a.error&&f.bind(a.error,null,b);e.sync("read",this,function(d){if(!b.set(b.parse(d),a))return false;a.success&&a.success(b,d)},c);return this},save:function(a,b){a||(a={});b||(b={});if(!this.set(a,b))return false;var c=this,d=b.error&&f.bind(b.error,null,c),g=this.isNew()?"create":"update";e.sync(g,this,function(i){if(!c.set(c.parse(i),b))return false;b.success&&b.success(c,i)},
-d);return this},destroy:function(a){a||(a={});var b=this,c=a.error&&f.bind(a.error,null,b);e.sync("delete",this,function(d){b.collection&&b.collection.remove(b);a.success&&a.success(b,d)},c);return this},url:function(){var a=j(this.collection);if(this.isNew())return a;return a+"/"+this.id},parse:function(a){return a},clone:function(){return new this.constructor(this)},isNew:function(){return!this.id},change:function(){this.trigger("change",this);this._previousAttributes=f.clone(this.attributes);this._changed=
-false},hasChanged:function(a){if(a)return this._previousAttributes[a]!=this.attributes[a];return this._changed},changedAttributes:function(a){a||(a=this.attributes);var b=this._previousAttributes,c=false,d;for(d in a)if(!f.isEqual(b[d],a[d])){c=c||{};c[d]=a[d]}return c},previous:function(a){if(!a||!this._previousAttributes)return null;return this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},_performValidation:function(a,b){var c=this.validate(a);
-if(c){b.error?b.error(this,c):this.trigger("error",this,c);return false}return true}});e.Collection=function(a,b){b||(b={});if(b.comparator){this.comparator=b.comparator;delete b.comparator}this._boundOnModelEvent=f.bind(this._onModelEvent,this);this._reset();a&&this.refresh(a,{silent:true});this.initialize&&this.initialize(a,b)};f.extend(e.Collection.prototype,e.Events,{model:e.Model,toJSON:function(){return this.map(function(a){return a.toJSON()})},add:function(a,b){if(f.isArray(a))for(var c=0,
-d=a.length;c<d;c++)this._add(a[c],b);else this._add(a,b);return this},remove:function(a,b){if(f.isArray(a))for(var c=0,d=a.length;c<d;c++)this._remove(a[c],b);else this._remove(a,b);return this},get:function(a){return a&&this._byId[a.id!=null?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");this.models=this.sortBy(this.comparator);a.silent||this.trigger("refresh",
-this);return this},pluck:function(a){return f.map(this.models,function(b){return b.get(a)})},refresh:function(a,b){a||(a=[]);b||(b={});this._reset();this.add(a,{silent:true});b.silent||this.trigger("refresh",this);return this},fetch:function(a){a||(a={});var b=this,c=a.error&&f.bind(a.error,null,b);e.sync("read",this,function(d){b.refresh(b.parse(d));a.success&&a.success(b,d)},c);return this},create:function(a,b){var c=this;b||(b={});if(a instanceof e.Model)a.collection=c;else a=new this.model(a,
-{collection:c});return a.save(null,{success:function(d,g){c.add(d);b.success&&b.success(d,g)},error:b.error})},parse:function(a){return a},chain:function(){return f(this.models).chain()},_reset:function(){this.length=0;this.models=[];this._byId={};this._byCid={}},_add:function(a,b){b||(b={});a instanceof e.Model||(a=new this.model(a,{collection:this}));var c=this.getByCid(a);if(c)throw Error(["Can't add the same model to a set twice",c.id]);this._byId[a.id]=a;this._byCid[a.cid]=a;a.collection=this;
-this.models.splice(this.comparator?this.sortedIndex(a,this.comparator):this.length,0,a);a.bind("all",this._boundOnModelEvent);this.length++;b.silent||a.trigger("add",a,this);return a},_remove:function(a,b){b||(b={});a=this.getByCid(a);if(!a)return null;delete this._byId[a.id];delete this._byCid[a.cid];delete a.collection;this.models.splice(this.indexOf(a),1);this.length--;b.silent||a.trigger("remove",a,this);a.unbind("all",this._boundOnModelEvent);return a},_onModelEvent:function(a,b){if(a==="change:id"){delete this._byId[b.previous("id")];
-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","invoke","max","min","sortBy","sortedIndex","toArray","size","first","rest","last","without","indexOf","lastIndexOf","isEmpty"],function(a){e.Collection.prototype[a]=function(){return f[a].apply(f,[this.models].concat(f.toArray(arguments)))}});e.Controller=function(a){a||(a={});if(a.routes)this.routes=a.routes;
-this._bindRoutes();this.initialize&&this.initialize(a)};var o=/:([\w\d]+)/g,p=/\*([\w\d]+)/g;f.extend(e.Controller.prototype,e.Events,{route:function(a,b,c){e.history||(e.history=new e.History);f.isRegExp(a)||(a=this._routeToRegExp(a));e.history.route(a,f.bind(function(d){d=this._extractParameters(a,d);c.apply(this,d);this.trigger.apply(this,["route:"+b].concat(d))},this))},saveLocation:function(a){e.history.saveLocation(a)},_bindRoutes:function(){if(this.routes)for(var a in this.routes){var b=this.routes[a];
-this.route(a,b,this[b])}},_routeToRegExp:function(a){a=a.replace(o,"([^/]*)").replace(p,"(.*?)");return RegExp("^"+a+"$")},_extractParameters:function(a,b){return a.exec(b).slice(1)}});e.History=function(){this.handlers=[];this.fragment=this.getFragment();f.bindAll(this,"checkUrl")};var k=/^#*/;f.extend(e.History.prototype,{interval:50,getFragment:function(a){return(a||window.location).hash.replace(k,"")},start:function(){var a=document.documentMode;if(a=h.browser.msie&&a<7)this.iframe=h('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow;
-"onhashchange"in window&&!a?h(window).bind("hashchange",this.checkUrl):setInterval(this.checkUrl,this.interval);return this.loadUrl()},route:function(a,b){this.handlers.push({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();if(a==this.fragment&&this.iframe)a=this.getFragment(this.iframe.location);if(a==this.fragment||a==decodeURIComponent(this.fragment))return false;if(this.iframe)window.location.hash=this.iframe.location.hash=a;this.loadUrl()},loadUrl:function(){var a=this.fragment=
-this.getFragment();return f.any(this.handlers,function(b){if(b.route.test(a)){b.callback(a);return true}})},saveLocation:function(a){a=(a||"").replace(k,"");if(this.fragment!=a){window.location.hash=this.fragment=a;if(this.iframe&&a!=this.getFragment(this.iframe.location)){this.iframe.document.open().close();this.iframe.location.hash=a}}}});e.View=function(a){this._configure(a||{});this._ensureElement();this.delegateEvents();this.initialize&&this.initialize(a)};var l=function(a){return h(a,this.el)},
-q=/^(\w+)\s*(.*)$/;f.extend(e.View.prototype,e.Events,{tagName:"div",$:l,jQuery:l,render:function(){return this},remove:function(){h(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},delegateEvents:function(a){if(a||(a=this.events)){h(this.el).unbind();for(var b in a){var c=a[b],d=b.match(q),g=d[1];d=d[2];c=f.bind(this[c],this);d===""?h(this.el).bind(g,c):h(this.el).delegate(d,g,c)}}},_configure:function(a){if(this.options)a=
-f.extend({},this.options,a);if(a.model)this.model=a.model;if(a.collection)this.collection=a.collection;if(a.el)this.el=a.el;if(a.id)this.id=a.id;if(a.className)this.className=a.className;if(a.tagName)this.tagName=a.tagName;this.options=a},_ensureElement:function(){if(!this.el){var a={};if(this.id)a.id=this.id;if(this.className)a.className=this.className;this.el=this.make(this.tagName,a)}}});var m=function(a,b){var c=r(this,a,b);c.extend=m;return c};e.Model.extend=e.Collection.extend=e.Controller.extend=
-e.View.extend=m;var s={create:"POST",update:"PUT","delete":"DELETE",read:"GET"};e.sync=function(a,b,c,d){var g=s[a];a=a==="create"||a==="update"?JSON.stringify(b.toJSON()):null;b={url:j(b),type:g,contentType:"application/json",data:a,dataType:"json",processData:false,success:c,error:d};if(e.emulateJSON){b.contentType="application/x-www-form-urlencoded";b.processData=true;b.data=a?{model:a}:{}}if(e.emulateHTTP)if(g==="PUT"||g==="DELETE"){if(e.emulateJSON)b.data._method=g;b.type="POST";b.beforeSend=
-function(i){i.setRequestHeader("X-HTTP-Method-Override",g)}}h.ajax(b)};var n=function(){},r=function(a,b,c){var d;d=b&&b.hasOwnProperty("constructor")?b.constructor:function(){return a.apply(this,arguments)};n.prototype=a.prototype;d.prototype=new n;b&&f.extend(d.prototype,b);c&&f.extend(d,c);d.prototype.constructor=d;d.__super__=a.prototype;return d},j=function(a){if(!(a&&a.url))throw Error("A 'url' property or function must be specified");return f.isFunction(a.url)?a.url():a.url}})();
+(function(){var e;e=typeof exports!=="undefined"?exports:this.Backbone={};e.VERSION="0.3.2";var f=this._;if(!f&&typeof require!=="undefined")f=require("underscore")._;var h=this.jQuery;e.emulateHTTP=false;e.emulateJSON=false;e.Events={bind:function(a,b){this._callbacks||(this._callbacks={});(this._callbacks[a]||(this._callbacks[a]=[])).push(b);return this},unbind:function(a,b){var c;if(a){if(c=this._callbacks)if(b){c=c[a];if(!c)return this;for(var d=0,g=c.length;d<g;d++)if(b===c[d]){c.splice(d,1);
+break}}else c[a]=[]}else this._callbacks={};return this},trigger:function(a){var b,c,d,g;if(!(c=this._callbacks))return this;if(b=c[a]){d=0;for(g=b.length;d<g;d++)b[d].apply(this,Array.prototype.slice.call(arguments,1))}if(b=c.all){d=0;for(g=b.length;d<g;d++)b[d].apply(this,arguments)}return this}};e.Model=function(a,b){a||(a={});if(this.defaults)a=f.extend({},this.defaults,a);this.attributes={};this.cid=f.uniqueId("c");this.set(a,{silent:true});this._previousAttributes=f.clone(this.attributes);if(b&&
+b.collection)this.collection=b.collection;this.initialize(a,b)};f.extend(e.Model.prototype,e.Events,{_previousAttributes:null,_changed:false,initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},set:function(a,b){b||(b={});if(!a)return this;if(a.attributes)a=a.attributes;var c=this.attributes;if(!b.silent&&this.validate&&!this._performValidation(a,b))return false;if("id"in a)this.id=a.id;for(var d in a){var g=a[d];if(!f.isEqual(c[d],
+g)){c[d]=g;if(!b.silent){this._changed=true;this.trigger("change:"+d,this,g)}}}!b.silent&&this._changed&&this.change();return this},unset:function(a,b){b||(b={});var c={};c[a]=void 0;if(!b.silent&&this.validate&&!this._performValidation(c,b))return false;delete this.attributes[a];if(!b.silent){this._changed=true;this.trigger("change:"+a,this);this.change()}return this},clear:function(a){a||(a={});var b=this.attributes,c={};for(attr in b)c[attr]=void 0;if(!a.silent&&this.validate&&!this._performValidation(c,
+a))return false;this.attributes={};if(!a.silent){this._changed=true;for(attr in b)this.trigger("change:"+attr,this);this.change()}return this},fetch:function(a){a||(a={});var b=this,c=a.error&&f.bind(a.error,null,b);(this.sync||e.sync)("read",this,function(d){if(!b.set(b.parse(d),a))return false;a.success&&a.success(b,d)},c);return this},save:function(a,b){b||(b={});if(a&&!this.set(a,b))return false;var c=this,d=b.error&&f.bind(b.error,null,c),g=this.isNew()?"create":"update";(this.sync||e.sync)(g,
+this,function(i){if(!c.set(c.parse(i),b))return false;b.success&&b.success(c,i)},d);return this},destroy:function(a){a||(a={});var b=this,c=a.error&&f.bind(a.error,null,b);(this.sync||e.sync)("delete",this,function(d){b.collection&&b.collection.remove(b);a.success&&a.success(b,d)},c);return this},url:function(){var a=j(this.collection);if(this.isNew())return a;return a+(a.charAt(a.length-1)=="/"?"":"/")+this.id},parse:function(a){return a},clone:function(){return new this.constructor(this)},isNew:function(){return!this.id},
+change:function(){this.trigger("change",this);this._previousAttributes=f.clone(this.attributes);this._changed=false},hasChanged:function(a){if(a)return this._previousAttributes[a]!=this.attributes[a];return this._changed},changedAttributes:function(a){a||(a=this.attributes);var b=this._previousAttributes,c=false,d;for(d in a)if(!f.isEqual(b[d],a[d])){c=c||{};c[d]=a[d]}return c},previous:function(a){if(!a||!this._previousAttributes)return null;return this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},
+_performValidation:function(a,b){var c=this.validate(a);if(c){b.error?b.error(this,c):this.trigger("error",this,c);return false}return true}});e.Collection=function(a,b){b||(b={});if(b.comparator){this.comparator=b.comparator;delete b.comparator}this._boundOnModelEvent=f.bind(this._onModelEvent,this);this._reset();a&&this.refresh(a,{silent:true});this.initialize(a,b)};f.extend(e.Collection.prototype,e.Events,{model:e.Model,initialize:function(){},toJSON:function(){return this.map(function(a){return a.toJSON()})},
+add:function(a,b){if(f.isArray(a))for(var c=0,d=a.length;c<d;c++)this._add(a[c],b);else this._add(a,b);return this},remove:function(a,b){if(f.isArray(a))for(var c=0,d=a.length;c<d;c++)this._remove(a[c],b);else this._remove(a,b);return this},get:function(a){if(a==null)return null;return this._byId[a.id!=null?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");
+this.models=this.sortBy(this.comparator);a.silent||this.trigger("refresh",this);return this},pluck:function(a){return f.map(this.models,function(b){return b.get(a)})},refresh:function(a,b){a||(a=[]);b||(b={});this._reset();this.add(a,{silent:true});b.silent||this.trigger("refresh",this);return this},fetch:function(a){a||(a={});var b=this,c=a.error&&f.bind(a.error,null,b);(this.sync||e.sync)("read",this,function(d){b.refresh(b.parse(d));a.success&&a.success(b,d)},c);return this},create:function(a,
+b){var c=this;b||(b={});if(a instanceof e.Model)a.collection=c;else a=new this.model(a,{collection:c});return a.save(null,{success:function(d,g){c.add(d);b.success&&b.success(d,g)},error:b.error})},parse:function(a){return a},chain:function(){return f(this.models).chain()},_reset:function(){this.length=0;this.models=[];this._byId={};this._byCid={}},_add:function(a,b){b||(b={});a instanceof e.Model||(a=new this.model(a,{collection:this}));var c=this.getByCid(a);if(c)throw Error(["Can't add the same model to a set twice",
+c.id]);this._byId[a.id]=a;this._byCid[a.cid]=a;a.collection=this;this.models.splice(this.comparator?this.sortedIndex(a,this.comparator):this.length,0,a);a.bind("all",this._boundOnModelEvent);this.length++;b.silent||a.trigger("add",a,this);return a},_remove:function(a,b){b||(b={});a=this.getByCid(a)||this.get(a);if(!a)return null;delete this._byId[a.id];delete this._byCid[a.cid];delete a.collection;this.models.splice(this.indexOf(a),1);this.length--;b.silent||a.trigger("remove",a,this);a.unbind("all",
+this._boundOnModelEvent);return a},_onModelEvent:function(a,b){if(a==="change:id"){delete this._byId[b.previous("id")];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","invoke","max","min","sortBy","sortedIndex","toArray","size","first","rest","last","without","indexOf","lastIndexOf","isEmpty"],function(a){e.Collection.prototype[a]=function(){return f[a].apply(f,
+[this.models].concat(f.toArray(arguments)))}});e.Controller=function(a){a||(a={});if(a.routes)this.routes=a.routes;this._bindRoutes();this.initialize(a)};var o=/:([\w\d]+)/g,p=/\*([\w\d]+)/g;f.extend(e.Controller.prototype,e.Events,{initialize:function(){},route:function(a,b,c){e.history||(e.history=new e.History);f.isRegExp(a)||(a=this._routeToRegExp(a));e.history.route(a,f.bind(function(d){d=this._extractParameters(a,d);c.apply(this,d);this.trigger.apply(this,["route:"+b].concat(d))},this))},saveLocation:function(a){e.history.saveLocation(a)},
+_bindRoutes:function(){if(this.routes)for(var a in this.routes){var b=this.routes[a];this.route(a,b,this[b])}},_routeToRegExp:function(a){a=a.replace(o,"([^/]*)").replace(p,"(.*?)");return RegExp("^"+a+"$")},_extractParameters:function(a,b){return a.exec(b).slice(1)}});e.History=function(){this.handlers=[];this.fragment=this.getFragment();f.bindAll(this,"checkUrl")};var k=/^#*/;f.extend(e.History.prototype,{interval:50,getFragment:function(a){return(a||window.location).hash.replace(k,"")},start:function(){var a=
+document.documentMode;if(a=h.browser.msie&&(!a||a<=7))this.iframe=h('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow;"onhashchange"in window&&!a?h(window).bind("hashchange",this.checkUrl):setInterval(this.checkUrl,this.interval);return this.loadUrl()},route:function(a,b){this.handlers.push({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();if(a==this.fragment&&this.iframe)a=this.getFragment(this.iframe.location);if(a==this.fragment||a==decodeURIComponent(this.fragment))return false;
+if(this.iframe)window.location.hash=this.iframe.location.hash=a;this.loadUrl()},loadUrl:function(){var a=this.fragment=this.getFragment();return f.any(this.handlers,function(b){if(b.route.test(a)){b.callback(a);return true}})},saveLocation:function(a){a=(a||"").replace(k,"");if(this.fragment!=a){window.location.hash=this.fragment=a;if(this.iframe&&a!=this.getFragment(this.iframe.location)){this.iframe.document.open().close();this.iframe.location.hash=a}}}});e.View=function(a){this._configure(a||{});
+this._ensureElement();this.delegateEvents();this.initialize(a)};var l=function(a){return h(a,this.el)},q=/^(\w+)\s*(.*)$/;f.extend(e.View.prototype,e.Events,{tagName:"div",$:l,jQuery:l,initialize:function(){},render:function(){return this},remove:function(){h(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},delegateEvents:function(a){if(a||(a=this.events)){h(this.el).unbind();for(var b in a){var c=a[b],d=b.match(q),g=d[1];d=
+d[2];c=f.bind(this[c],this);d===""?h(this.el).bind(g,c):h(this.el).delegate(d,g,c)}}},_configure:function(a){if(this.options)a=f.extend({},this.options,a);if(a.model)this.model=a.model;if(a.collection)this.collection=a.collection;if(a.el)this.el=a.el;if(a.id)this.id=a.id;if(a.className)this.className=a.className;if(a.tagName)this.tagName=a.tagName;this.options=a},_ensureElement:function(){if(!this.el){var a={};if(this.id)a.id=this.id;if(this.className)a.className=this.className;this.el=this.make(this.tagName,
+a)}}});var m=function(a,b){var c=r(this,a,b);c.extend=m;return c};e.Model.extend=e.Collection.extend=e.Controller.extend=e.View.extend=m;var s={create:"POST",update:"PUT","delete":"DELETE",read:"GET"};e.sync=function(a,b,c,d){var g=s[a];a=a==="create"||a==="update"?JSON.stringify(b.toJSON()):null;b={url:j(b),type:g,contentType:"application/json",data:a,dataType:"json",processData:false,success:c,error:d};if(e.emulateJSON){b.contentType="application/x-www-form-urlencoded";b.processData=true;b.data=
+a?{model:a}:{}}if(e.emulateHTTP)if(g==="PUT"||g==="DELETE"){if(e.emulateJSON)b.data._method=g;b.type="POST";b.beforeSend=function(i){i.setRequestHeader("X-HTTP-Method-Override",g)}}h.ajax(b)};var n=function(){},r=function(a,b,c){var d;d=b&&b.hasOwnProperty("constructor")?b.constructor:function(){return a.apply(this,arguments)};n.prototype=a.prototype;d.prototype=new n;b&&f.extend(d.prototype,b);c&&f.extend(d,c);d.prototype.constructor=d;d.__super__=a.prototype;return d},j=function(a){if(!(a&&a.url))throw Error("A 'url' property or function must be specified");
+return f.isFunction(a.url)?a.url():a.url}})();
View
52 backbone.js
@@ -1,4 +1,4 @@
-// Backbone.js 0.3.1
+// Backbone.js 0.3.2
// (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
@@ -19,7 +19,7 @@
}
// Current version of the library. Keep in sync with `package.json`.
- Backbone.VERSION = '0.3.1';
+ Backbone.VERSION = '0.3.2';
// Require Underscore, if we're on the server, and it's not already present.
var _ = this._;
@@ -113,12 +113,14 @@
// Create a new model, with defined attributes. A client id (`cid`)
// is automatically generated and assigned for you.
Backbone.Model = function(attributes, options) {
+ attributes || (attributes = {});
+ if (this.defaults) attributes = _.extend({}, this.defaults, attributes);
this.attributes = {};
this.cid = _.uniqueId('c');
- this.set(attributes || {}, {silent : true});
+ this.set(attributes, {silent : true});
this._previousAttributes = _.clone(this.attributes);
if (options && options.collection) this.collection = options.collection;
- if (this.initialize) this.initialize(attributes, options);
+ this.initialize(attributes, options);
};
// Attach all inheritable methods to the Model prototype.
@@ -131,6 +133,10 @@
// Has the item been changed since the last `"change"` event?
_changed : false,
+ // 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() {
return _.clone(this.attributes);
@@ -228,25 +234,24 @@
if (options.success) options.success(model, resp);
};
var error = options.error && _.bind(options.error, null, model);
- Backbone.sync('read', this, success, error);
+ (this.sync || Backbone.sync)('read', this, success, error);
return this;
},
// 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(attrs, options) {
- attrs || (attrs = {});
options || (options = {});
- if (!this.set(attrs, options)) return false;
+ if (attrs && !this.set(attrs, options)) return false;
var model = this;
var success = function(resp) {
if (!model.set(model.parse(resp), options)) return false;
if (options.success) options.success(model, resp);
};
var error = options.error && _.bind(options.error, null, model);
var method = this.isNew() ? 'create' : 'update';
- Backbone.sync(method, this, success, error);
+ (this.sync || Backbone.sync)(method, this, success, error);
return this;
},
@@ -260,7 +265,7 @@
if (options.success) options.success(model, resp);
};
var error = options.error && _.bind(options.error, null, model);
- Backbone.sync('delete', this, success, error);
+ (this.sync || Backbone.sync)('delete', this, success, error);
return this;
},
@@ -270,7 +275,7 @@
url : function() {
var base = getUrl(this.collection);
if (this.isNew()) return base;
- return base + '/' + this.id;
+ return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + this.id;
},
// **parse** converts a response into the hash of attributes to be `set` on
@@ -368,7 +373,7 @@
this._boundOnModelEvent = _.bind(this._onModelEvent, this);
this._reset();
if (models) this.refresh(models, {silent: true});
- if (this.initialize) this.initialize(models, options);
+ this.initialize(models, options);
};
// Define the Collection's inheritable methods.
@@ -378,6 +383,10 @@
// This should be overridden in most cases.
model : Backbone.Model,
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize : function(){},
+
// The JSON representation of a Collection is an array of the
// models' attributes.
toJSON : function() {
@@ -412,7 +421,8 @@
// Get a model from the set by id.
get : function(id) {
- return id && this._byId[id.id != null ? id.id : id];
+ if (id == null) return null;
+ return this._byId[id.id != null ? id.id : id];
},
// Get a model from the set by client id.
@@ -462,7 +472,7 @@
if (options.success) options.success(collection, resp);
};
var error = options.error && _.bind(options.error, null, collection);
- Backbone.sync('read', this, success, error);
+ (this.sync || Backbone.sync)('read', this, success, error);
return this;
},
@@ -528,7 +538,7 @@
// hash indexes for `id` and `cid` lookups.
_remove : function(model, options) {
options || (options = {});
- model = this.getByCid(model);
+ model = this.getByCid(model) || this.get(model);
if (!model) return null;
delete this._byId[model.id];
delete this._byCid[model.cid];
@@ -575,7 +585,7 @@
options || (options = {});
if (options.routes) this.routes = options.routes;
this._bindRoutes();
- if (this.initialize) this.initialize(options);
+ this.initialize(options);
};
// Cached regular expressions for matching named param parts and splatted
@@ -586,6 +596,10 @@
// Set up all inheritable **Backbone.Controller** properties and methods.
_.extend(Backbone.Controller.prototype, Backbone.Events, {
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize : function(){},
+
// Manually bind a single named route to a callback. For example:
//
// this.route('search/:query/p:num', 'search', function(query, num) {
@@ -662,7 +676,7 @@
// an existing route, and `false` otherwise.
start : function() {
var docMode = document.documentMode;
- var oldIE = ($.browser.msie && docMode < 7);
+ var oldIE = ($.browser.msie && (!docMode || docMode <= 7));
if (oldIE) {
this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
}
@@ -733,7 +747,7 @@
this._configure(options || {});
this._ensureElement();
this.delegateEvents();
- if (this.initialize) this.initialize(options);
+ this.initialize(options);
};
// jQuery lookup, scoped to DOM elements within the current view.
@@ -756,6 +770,10 @@
$ : jQueryDelegate,
jQuery : jQueryDelegate,
+ // 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`.
View
206 docs/backbone.html
106 additions, 100 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
8 docs/docco.css
@@ -16,17 +16,17 @@ p {
margin: 0 0 15px 0;
}
h1, h2, h3, h4, h5, h6 {
- margin: 40px 0 15px 0;
+ margin: 0px 0 15px 0;
}
- h3, h4, h5, h6 {
- margin-top: 20px;
+ h1 {
+ margin-top: 40px;
}
#container {
position: relative;
}
#background {
position: fixed;
- top: 0; left: 575px; right: 0; bottom: 0;
+ top: 0; left: 525px; right: 0; bottom: 0;
background: #f5f5ff;
border-left: 1px solid #e5e5ee;
z-index: -1;
View
BIN docs/images/backbone-mobile.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN docs/images/dc-workspace.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
2 docs/jsl.conf
@@ -14,7 +14,7 @@
+missing_break_for_last_case # missing break statement for last case in switch
-comparison_type_conv # comparisons against null, 0, true, false, or an empty string allowing implicit type conversion (use === or !==)
-inc_dec_within_stmt # increment (++) and decrement (--) operators used as part of greater statement
-+useless_void # use of the void type may be unnecessary (void is always undefined)
+-useless_void # use of the void type may be unnecessary (void is always undefined)
+multiple_plus_minus # unknown order of operations for successive plus (e.g. x+++y) or minus (e.g. x---y) signs
+use_of_label # use of label
-block_without_braces # block statement without curly braces
View
79 index.html
@@ -144,7 +144,7 @@
<div id="sidebar" class="interface">
<a class="toc_title" href="#">
- Backbone.js <span class="version">(0.3.1)</span>
+ Backbone.js <span class="version">(0.3.2)</span>
</a>
<a class="toc_title" href="#Introduction">
@@ -173,6 +173,7 @@
<li>– <a href="#Model-id">id</a></li>
<li>– <a href="#Model-cid">cid</a></li>
<li>– <a href="#Model-attributes">attributes</a></li>
+ <li>– <a href="#Model-defaults">defaults</a></li>
<li>- <a href="#Model-toJSON">toJSON</a></li>
<li>– <a href="#Model-fetch">fetch</a></li>
<li>– <a href="#Model-save">save</a></li>
@@ -309,11 +310,11 @@ <h2 id="downloads">
<table>
<tr>
- <td><a href="backbone.js">Development Version (0.3.1)</a></td>
+ <td><a href="backbone.js">Development Version (0.3.2)</a></td>
<td><i>33kb, Uncompressed with Comments</i></td>
</tr>
<tr>
- <td><a href="backbone-min.js">Production Version (0.3.1)</a></td>
+ <td><a href="backbone-min.js">Production Version (0.3.2)</a></td>
<td><i>3.7kb, Packed and Gzipped</i></td>
</tr>
</table>
@@ -613,6 +614,26 @@ <h2 id="Model">Backbone.Model</h2>
them directly. If you'd like to retrieve and munge a copy of the model's
attributes, use <a href="#Model-toJSON">toJSON</a> instead.
</p>
+
+ <p id="Model-defaults">
+ <b class="header">defaults</b><code>model.defaults</code>
+ <br />
+ The <b>defaults</b> hash can be used to specify the default attributes
+ for your model. When creating an instance of the model, any unspecified
+ attributes will be set to their default value.
+ </p>
+
+<pre class="runnable">
+var Meal = Backbone.Model.extend({
+ defaults: {
+ "appetizer": "caesar salad",
+ "entree": "ravioli",
+ "dessert": "cheesecake"
+ }
+});
+
+alert("Dessert will be " + (new Meal).get('dessert'));
+</pre>
<p id="Model-toJSON">
<b class="header">toJSON</b><code>model.toJSON()</code>
@@ -786,6 +807,16 @@ <h2 id="Model">Backbone.Model</h2>
Override this if you need to work with a preexisting API, or better namespace
your responses.
</p>
+
+ <p>
+ If you're working with a Rails backend, you'll notice that Rails' default
+ <tt>to_json</tt> implementation includes a model's attributes under a
+ namespace. To disable this behavior for seamless Backbone integration, set:
+ </p>
+
+<pre>
+ActiveRecord::Base.include_root_in_json = false
+</pre>
<p id="Model-clone">
<b class="header">clone</b><code>model.clone()</code>
@@ -1465,12 +1496,17 @@ <h2 id="Sync">Backbone.sync</h2>
<pre>
def update
- account = Account.find(params[:id])
- account.update_attributes JSON.parse params[:model]
+ account = Account.find params[:id]
+ account.update_attributes params
render :json => account
end
</pre>
+ <p>
+ One more tip for Rails integration is to disable the default namespacing for
+ <tt>to_json</tt> calls on models by setting <tt>ActiveRecord::Base.include_root_in_json = false</tt>
+ </p>
+
<p id="Sync-emulateHTTP">
<b class="header">emulateHTTP</b><code>Backbone.emulateHTTP = true</code>
<br />
@@ -1752,10 +1788,43 @@ <h2 id="examples">Examples</h2>
<img src="docs/images/todos.png" alt="Todos" style="margin: 10px auto;" />
</a>
</div>
+
+ <p>
+ The <a href="http://www.documentcloud.org/">DocumentCloud</a> workspace
+ is built on Backbone.js, with <i>Documents</i>, <i>Projects</i>,
+ <i>Notes</i>, and <i>Accounts</i> all as Backbone models and collections.
+ </p>
+
+ <div style="text-align: center;">
+ <img src="docs/images/dc-workspace.png" alt="DocumentCloud Workspace" style="margin: 10px auto;" />
+ </div>
+
+ <p>
+ <a href="http://bennolan.com/">Ben Nolan</a> created
+ <a href="http://bennolan.com/2010/11/24/backbone-jquery-demo.html">an example "Backbone Mobile" application</a>, combining Backbone.js
+ with <a href="http://jquerymobile.com/">jQuery Mobile</a>. You can
+ <a href="http://bennolan.com/science/backbone-mobile/">try the app</a>
+ in your browser, or view the
+ <a href="https://github.com/bnolan/backbone-mobile">source code</a> on Github.
+ </p>
+
+ <div style="text-align: center;">
+ <a href="http://bennolan.com/science/backbone-mobile/">
+ <img src="docs/images/backbone-mobile.png" alt="Backbone Mobile" style="margin: 10px auto;" />
+ </a>
+ </div>
<h2 id="changelog">Change Log</h2>
<p>
+ <b class="header">0.3.2</b> &mdash; <small><i>Nov 23, 2010</i></small><br />
+ Bugfix for IE7 + iframe-based "hashchange" events. <tt>sync</tt> may now be
+ overridden on a per-model, or per-collection basis. Fixed recursion error
+ when calling <tt>save</tt> with no changed attributes, within a
+ <tt>"change"</tt> event.
+ </p>
+
+ <p>
<b class="header">0.3.1</b> &mdash; <small><i>Nov 15, 2010</i></small><br />
All <tt>"add"</tt> and <tt>"remove"</tt> events are now sent through the
model, so that views can listen for them without having to know about the
View
2 package.json
@@ -10,5 +10,5 @@
},
"lib" : ".",
"main" : "backbone.js",
- "version" : "0.3.1"
+ "version" : "0.3.2"
}
View
58 test/collection.js
@@ -8,10 +8,10 @@ $(document).ready(function() {
lastRequest = _.toArray(arguments);
};
- var a = new Backbone.Model({id: 4, label: 'a'});
- var b = new Backbone.Model({id: 3, label: 'b'});
- var c = new Backbone.Model({id: 2, label: 'c'});
- var d = new Backbone.Model({id: 1, label: 'd'});
+ 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 = window.col = new Backbone.Collection([a,b,c,d]);
@@ -26,21 +26,21 @@ $(document).ready(function() {
});
test("Collection: get, getByCid", function() {
- equals(col.get(1), d);
- equals(col.get(3), b);
+ equals(col.get(0), d);
+ equals(col.get(2), b);
equals(col.getByCid(col.first().cid), col.first());
});
test("Collection: update index when id changes", function() {
var col = new Backbone.Collection();
col.add([
- {id : 1, name : 'one'},
- {id : 2, name : 'two'}
+ {id : 0, name : 'one'},
+ {id : 1, name : 'two'}
]);
- var one = col.get(1);
+ var one = col.get(0);
equals(one.get('name'), 'one');
one.set({id : 101});
- equals(col.get(1), null);
+ equals(col.get(0), null);
equals(col.get(101).get('name'), 'one');
});
@@ -55,11 +55,11 @@ $(document).ready(function() {
test("Collection: add", function() {
var added = null;
col.bind('add', function(model){ added = model.get('label'); });
- e = new Backbone.Model({id: 0, label : 'e'});
+ e = new Backbone.Model({id: 10, label : 'e'});
col.add(e);
equals(added, 'e');
equals(col.length, 5);
- equals(col.first(), e);
+ equals(col.last(), e);
});
test("Collection: remove", function() {
@@ -70,6 +70,30 @@ $(document).ready(function() {
equals(col.length, 4);
equals(col.first(), d);
});
+
+ test("Collection: remove in multiple collections", function() {
+ var modelData = {
+ id : 5,
+ title : 'Othello'
+ };
+ var passed = false;
+ var e = new Backbone.Model(modelData);
+ var f = new Backbone.Model(modelData);
+ f.bind('remove', function() {
+ passed = true;
+ });
+ var colE = new Backbone.Collection([e]);
+ var colF = new Backbone.Collection([f]);
+ ok(e != f);
+ ok(colE.length == 1);
+ ok(colF.length == 1);
+ colE.remove(e);
+ equals(passed, false);
+ ok(colE.length == 0);
+ colF.remove(e);
+ ok(colF.length == 0);
+ equals(passed, true);
+ });
test("Collection: fetch", function() {
col.fetch();
@@ -96,27 +120,27 @@ $(document).ready(function() {
});
test("Collection: toJSON", function() {
- equals(JSON.stringify(col), '[{"id":1,"label":"d"},{"id":2,"label":"c"},{"id":3,"label":"b"},{"id":4,"label":"a"}]');
+ equals(JSON.stringify(col), '[{"id":0,"label":"d"},{"id":1,"label":"c"},{"id":2,"label":"b"},{"id":3,"label":"a"}]');
});
test("Collection: Underscore methods", function() {
equals(col.map(function(model){ return model.get('label'); }).join(' '), 'd c b a');
equals(col.any(function(model){ return model.id === 100; }), false);
- equals(col.any(function(model){ return model.id === 1; }), true);
+ equals(col.any(function(model){ return model.id === 0; }), true);
equals(col.indexOf(b), 2);
equals(col.size(), 4);
equals(col.rest().length, 3);
ok(!_.include(col.rest()), a);
ok(!_.include(col.rest()), d);
ok(!col.isEmpty());
ok(!_.include(col.without(d)), d);
- equals(col.max(function(model){ return model.id; }).id, 4);
- equals(col.min(function(model){ return model.id; }).id, 1);
+ equals(col.max(function(model){ return model.id; }).id, 3);
+ equals(col.min(function(model){ return model.id; }).id, 0);
same(col.chain()
.filter(function(o){ return o.id % 2 === 0; })
.map(function(o){ return o.id * 2; })
.value(),
- [4, 8]);
+ [0, 4]);
});
test("Collection: refresh", function() {
View
23 test/model.js
@@ -53,6 +53,8 @@ $(document).ready(function() {
test("Model: url", function() {
equals(doc.url(), '/collection/1-the-tempest');
+ doc.collection.url = '/collection/';
+ equals(doc.url(), '/collection/1-the-tempest');
doc.collection = null;
var failed = false;
try {
@@ -124,6 +126,18 @@ $(document).ready(function() {
equals(model.get('name'), undefined);
});
+ test("Model: defaults", function() {
+ var Defaulted = Backbone.Model.extend({
+ defaults: {
+ "one": 1,
+ "two": 2
+ }
+ });
+ var model = new Defaulted({two: null});
+ equals(model.get('one'), 1);
+ equals(model.get('two'), null);
+ });
+
test("Model: changed, hasChanged, changedAttributes, previous, previousAttributes", function() {
var model = new Backbone.Model({name : "Tim", age : 10});
model.bind('change', function() {
@@ -137,6 +151,15 @@ $(document).ready(function() {
model.change();
equals(model.get('name'), 'Rob');
});
+
+ test("Model: save within change event", function () {
+ var model = new Backbone.Model({firstName : "Taylor", lastName: "Swift"});
+ model.bind('change', function () {
+ model.save();
+ ok(_.isEqual(lastRequest[1], model));
+ });
+ model.set({lastName: 'Hicks'});
+ });
test("Model: save", function() {
doc.save({title : "Henry V"});

Showing you all comments on commits in this comparison.

@danigb
danigb commented on f3e961d Nov 22, 2010

nice one! thanks!

@collin
collin commented on f3e961d Dec 1, 2010

<3

@KrisJordan

This commit seems to cause other scenarios to regress. Specifically, because set calls validate and set is getting short wired when attrs is false, validate never runs on saves to Models instantiated with attributes. Because Collection.create's implementation relies on that scenario validation does not get triggered when Models are created using Collection.create even though the documentation implies otherwise (http://documentcloud.github.com/backbone/#Collection-create).

Examples here: https://gist.github.com/813138

On further inspection of set it now shortwire returns this with empty attrs before running validate so getting validate to run on Collection.create may just need a localized fix there.

@jashkenas
Owner

Thanks for the fix in your pull request for Collection#create ... which has been merged to master.

@timmywil

I just went through blame to comment on this style. For readability, you could do

if (!attributes) {
  attributes = {};
}

and both Closure Compiler and UglifyJS will compile it to

attributes||(attributes={});
@jashkenas
Owner

Backbone shouldn't depend on any compiler to run efficiently, but thanks.

@timmywil

In this case, it can be depended upon. It's pretty simple. Unless you disagree that it is more readable, there's no reason not to change it.

Something went wrong with that request. Please try again.