diff --git a/backbone.js b/backbone.js index 20e5fa6b..c219f516 100644 --- a/backbone.js +++ b/backbone.js @@ -8,42 +8,24 @@ // Initial Setup // ------------- - // The top-level namespace. - var Backbone = {}; + // The top-level namespace. All public Backbone classes and modules will + // be attached to this. Exported for both CommonJS and the browser. + var Backbone; + if (typeof exports !== 'undefined') { + Backbone = exports; + } else { + Backbone = this.Backbone = {}; + } - // Keep the version here in sync with `package.json`. + // Current version of the library. Keep in sync with `package.json`. Backbone.VERSION = '0.1.1'; - // Export for both CommonJS and the browser. - (typeof exports !== 'undefined' ? exports : this).Backbone = Backbone; - // Require Underscore, if we're on the server, and it's not already present. var _ = this._; if (!_ && (typeof require !== 'undefined')) _ = require("underscore")._; // For Backbone's purposes, jQuery owns the `$` variable. - var $ = this.$; - - // Helper function to correctly set up the prototype chain, for subclasses. - // Similar to `goog.inherits`, but uses a hash of prototype properties and - // class properties to be extended. - var inherits = function(parent, protoProps, classProps) { - var child = protoProps.hasOwnProperty('constructor') ? protoProps.constructor : - function(){ return parent.apply(this, arguments); }; - var ctor = function(){}; - ctor.prototype = parent.prototype; - child.prototype = new ctor(); - _.extend(child.prototype, protoProps); - if (classProps) _.extend(child, classProps); - child.prototype.constructor = child; - return child; - }; - - // Helper function to get a URL from a Model or Collection as a property - // or as a function. - var getUrl = function(object) { - return _.isFunction(object.url) ? object.url() : object.url; - }; + var $ = this.jQuery; // Backbone.Events // ----------------- @@ -97,11 +79,10 @@ // Listening for `"all"` passes the true event name as the first argument. trigger : function(ev) { var list, calls, i, l; - var calls = this._callbacks; if (!(calls = this._callbacks)) return this; if (list = calls[ev]) { for (i = 0, l = list.length; i < l; i++) { - list[i].apply(this, _.rest(arguments)); + list[i].apply(this, Array.prototype.slice.call(arguments, 1)); } } if (list = calls['all']) { @@ -154,7 +135,7 @@ // Extract attributes and options. options || (options = {}); if (!attrs) return this; - attrs = attrs.attributes || attrs; + if (attrs.attributes) attrs = attrs.attributes; var now = this.attributes; // Run validation if `validate` is defined. @@ -441,6 +422,7 @@ model.bind('all', this._boundOnModelEvent); this.length++; if (!options.silent) this.trigger('add', model); + return model; }, // Internal implementation of removing a single model from the set, updating @@ -456,6 +438,7 @@ model.unbind('all', this._boundOnModelEvent); this.length--; if (!options.silent) this.trigger('remove', model); + return model; }, // Internal method called every time a model in the set fires an event. @@ -607,6 +590,9 @@ 'read' : 'GET' }; + // Backbone.sync + // ------------- + // Override this function to change the manner in which Backbone persists // models to the server. You will be passed the type of request, and the // model in question. By default, uses jQuery to make a RESTful Ajax request @@ -627,4 +613,32 @@ }); }; + // Helpers + // ------- + + // Helper function to correctly set up the prototype chain, for subclasses. + // Similar to `goog.inherits`, but uses a hash of prototype properties and + // class properties to be extended. + var inherits = function(parent, protoProps, classProps) { + var child; + if (protoProps.hasOwnProperty('constructor')) { + child = protoProps.constructor; + } else { + child = function(){ return parent.apply(this, arguments); }; + } + var ctor = function(){}; + ctor.prototype = parent.prototype; + child.prototype = new ctor(); + _.extend(child.prototype, protoProps); + if (classProps) _.extend(child, classProps); + child.prototype.constructor = child; + return child; + }; + + // Helper function to get a URL from a Model or Collection as a property + // or as a function. + var getUrl = function(object) { + return _.isFunction(object.url) ? object.url() : object.url; + }; + })(); diff --git a/test/collection.js b/test/collection.js index fb9cec85..de5c974f 100644 --- a/test/collection.js +++ b/test/collection.js @@ -1,6 +1,6 @@ $(document).ready(function() { - module("Backbone collections"); + module("Backbone.Collection"); window.lastRequest = null; @@ -15,7 +15,7 @@ $(document).ready(function() { var e = null; var col = window.col = new Backbone.Collection([a,b,c,d]); - test("collections: new and sort", function() { + test("Collection: new and sort", function() { equals(col.first(), a, "a should be first"); equals(col.last(), d, "d should be last"); col.comparator = function(model) { return model.id; }; @@ -25,21 +25,21 @@ $(document).ready(function() { equals(col.length, 4); }); - test("collections: get, getByCid", function() { + test("Collection: get, getByCid", function() { equals(col.get(1), d); equals(col.get(3), b); equals(col.getByCid(col.first().cid), col.first()); }); - test("collections: at", function() { + test("Collection: at", function() { equals(col.at(2), b); }); - test("collections: pluck", function() { + test("Collection: pluck", function() { equals(col.pluck('label').join(' '), 'd c b a'); }); - test("collections: add", 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'}); @@ -49,7 +49,7 @@ $(document).ready(function() { equals(col.first(), e); }); - test("collections: remove", function() { + test("Collection: remove", function() { var removed = null; col.bind('remove', function(model){ removed = model.get('label'); }); col.remove(e); @@ -58,13 +58,13 @@ $(document).ready(function() { equals(col.first(), d); }); - test("collections: fetch", function() { + test("Collection: fetch", function() { col.fetch(); equals(lastRequest[0], 'read'); equals(lastRequest[1], col); }); - test("collections: create", function() { + test("Collection: create", function() { var model = col.create({label: 'f'}); equals(lastRequest[0], 'create'); equals(lastRequest[1], model); @@ -82,7 +82,7 @@ $(document).ready(function() { equals(coll.one, 1); }); - test("collections: Underscore methods", function() { + 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); @@ -97,7 +97,7 @@ $(document).ready(function() { equals(col.min(function(model){ return model.id; }).id, 1); }); - test("collections: refresh", function() { + test("Collection: refresh", function() { var refreshed = 0; var models = col.models; col.bind('refresh', function() { refreshed += 1; }); diff --git a/test/bindable.js b/test/events.js similarity index 84% rename from test/bindable.js rename to test/events.js index 1d92bfab..83808428 100644 --- a/test/bindable.js +++ b/test/events.js @@ -1,8 +1,8 @@ $(document).ready(function() { - module("Backbone bindable"); + module("Backbone.Events"); - test("bindable: bind and trigger", function() { + test("Events: bind and trigger", function() { var obj = { counter: 0 }; _.extend(obj,Backbone.Events); obj.bind('event', function() { obj.counter += 1; }); @@ -15,7 +15,7 @@ $(document).ready(function() { equals(obj.counter, 5, 'counter should be incremented five times.'); }); - test("bindable: bind, then unbind all functions", function() { + test("Events: bind, then unbind all functions", function() { var obj = { counter: 0 }; _.extend(obj,Backbone.Events); var callback = function() { obj.counter += 1; }; @@ -26,7 +26,7 @@ $(document).ready(function() { equals(obj.counter, 1, 'counter should have only been incremented once.'); }); - test("bindable: bind two callbacks, unbind only one", function() { + test("Events: bind two callbacks, unbind only one", function() { var obj = { counterA: 0, counterB: 0 }; _.extend(obj,Backbone.Events); var callback = function() { obj.counterA += 1; }; diff --git a/test/model.js b/test/model.js index f10e9145..cedb0050 100644 --- a/test/model.js +++ b/test/model.js @@ -1,6 +1,6 @@ $(document).ready(function() { - module("Backbone model"); + module("Backbone.Model"); // Variable to catch the last request. window.lastRequest = null; @@ -26,7 +26,7 @@ $(document).ready(function() { var collection = new klass(); collection.add(doc); - test("model: initialize", function() { + test("Model: initialize", function() { var Model = Backbone.Model.extend({ initialize: function() { this.one = 1; @@ -36,11 +36,11 @@ $(document).ready(function() { equals(model.one, 1); }); - test("model: url", function() { + test("Model: url", function() { equals(doc.url(), '/collection/1-the-tempest'); }); - test("model: clone", function() { + test("Model: clone", function() { attrs = { 'foo': 1, 'bar': 2, 'baz': 3}; a = new Backbone.Model(attrs); b = a.clone(); @@ -55,7 +55,7 @@ $(document).ready(function() { equals(b.get('foo'), 1, "Changing a parent attribute does not change the clone."); }); - test("model: isNew", function() { + test("Model: isNew", function() { attrs = { 'foo': 1, 'bar': 2, 'baz': 3}; a = new Backbone.Model(attrs); ok(a.isNew(), "it should be new"); @@ -63,12 +63,12 @@ $(document).ready(function() { ok(a.isNew(), "any defined ID is legal, negative or positive"); }); - test("model: get", function() { + test("Model: get", function() { equals(doc.get('title'), 'The Tempest'); equals(doc.get('author'), 'Bill Shakespeare'); }); - test("model: set and unset", function() { + test("Model: set and unset", function() { attrs = { 'foo': 1, 'bar': 2, 'baz': 3}; a = new Backbone.Model(attrs); var changeCount = 0; @@ -85,7 +85,7 @@ $(document).ready(function() { ok(changeCount == 2, "Change count should have incremented for unset."); }); - test("model: changed, hasChanged, changedAttributes, previous, previousAttributes", function() { + test("Model: changed, hasChanged, changedAttributes, previous, previousAttributes", function() { var model = new Backbone.Model({name : "Tim", age : 10}); model.bind('change', function() { ok(model.hasChanged('name'), 'name changed'); @@ -99,19 +99,19 @@ $(document).ready(function() { equals(model.get('name'), 'Rob'); }); - test("model: save", function() { + test("Model: save", function() { doc.save({title : "Henry V"}); equals(lastRequest[0], 'update'); ok(_.isEqual(lastRequest[1], doc)); }); - test("model: destroy", function() { + test("Model: destroy", function() { doc.destroy(); equals(lastRequest[0], 'delete'); ok(_.isEqual(lastRequest[1], doc)); }); - test("model: validate", function() { + test("Model: validate", function() { var lastError; var model = new Backbone.Model(); model.validate = function(attrs) { diff --git a/test/speed.js b/test/speed.js new file mode 100644 index 00000000..6cd0b66c --- /dev/null +++ b/test/speed.js @@ -0,0 +1,45 @@ +(function(){ + + var object = {}; + _.extend(object, Backbone.Events); + var fn = function(){}; + + JSLitmus.test('Events: bind + unbind', function() { + object.bind("event", fn); + object.unbind("event", fn); + }); + + object.bind('test:trigger', fn); + + JSLitmus.test('Events: trigger', function() { + object.trigger('test:trigger'); + }); + + object.bind('test:trigger2', fn); + object.bind('test:trigger2', fn); + + JSLitmus.test('Events: trigger 2 functions, passing 5 arguments', function() { + object.trigger('test:trigger2', 1, 2, 3, 4, 5); + }); + + var model = new Backbone.Model; + + JSLitmus.test('Model: set Math.random()', function() { + model.set({number: Math.random()}); + }); + + var eventModel = new Backbone.Model; + eventModel.bind('change', fn); + + JSLitmus.test('Model: set Math.random() with a change event', function() { + eventModel.set({number: Math.random()}); + }); + + var keyModel = new Backbone.Model; + keyModel.bind('change:number', fn); + + JSLitmus.test('Model: set Math.random() with a key-value observer', function() { + keyModel.set({number: Math.random()}); + }); + +})(); \ No newline at end of file diff --git a/test/test.html b/test/test.html index 4ecd3863..22d16f04 100644 --- a/test/test.html +++ b/test/test.html @@ -5,18 +5,23 @@ + - + +