diff --git a/TODO b/TODO index 7e5e309a..54b34394 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,3 @@ -* Make Collection a Pseudo-array, a-la jQuery. - * Hook up data binding in the view. Both ways. Parallel to models. * Add simple validations. diff --git a/backbone.js b/backbone.js index 350bbeed..babd2439 100644 --- a/backbone.js +++ b/backbone.js @@ -35,10 +35,11 @@ // Backbone.Bindable // ----------------- - // A module that can be mixed in to any object in order to provide it with + // A module that can be mixed in to *any object* in order to provide it with // custom events. You may `bind` or `unbind` a callback function to an event; // `trigger`-ing an event fires all callbacks in succession. // + // var object = {}; // _.extend(object, Backbone.Bindable); // object.bind('expand', function(){ alert('expanded'); }); // object.trigger('expand'); @@ -160,14 +161,26 @@ // Set a hash of model attributes on the object, firing `changed` unless you // choose to silence it. set : function(attrs, options) { + + // Extract attributes and options. options || (options = {}); if (!attrs) return this; attrs = attrs._attributes || attrs; var now = this._attributes; - if ('id' in attrs) { - this.id = attrs.id; - if (this.collection) this.resource = this.collection.resource + '/' + this.id; + + // Run validation if `validate` is defined. + if (this.validate) { + var error = this.validate(attrs); + if (error) { + this.trigger('error', error); + return false; + } } + + // Check for changes of `id`. + if ('id' in attrs) this.id = attrs.id; + + // Update attributes. for (var attr in attrs) { var val = attrs[attr]; if (val === '') val = null; @@ -179,6 +192,8 @@ } } } + + // Fire the `change` event, if the model has been changed. if (!options.silent && this._changed) this.change(); return this; }, @@ -244,10 +259,10 @@ save : function(attrs, options) { attrs || (attrs = {}); options || (options = {}); - this.set(attrs, options); + if (!this.set(attrs, options)) return false; var model = this; var success = function(resp) { - model.set(resp.model); + if (!model.set(resp.model)) return false; if (options.success) options.success(model, resp); }; var method = this.isNew() ? 'POST' : 'PUT'; @@ -420,7 +435,7 @@ if (!(model instanceof Backbone.Model)) model = new this.model(model); model.collection = this; var success = function(model, resp) { - model.set(resp.model); + if (!model.set(resp.model)) return false; model.collection.add(model); if (options.success) options.success(model, resp); }; diff --git a/index.html b/index.html index 165a4943..17cb08be 100644 --- a/index.html +++ b/index.html @@ -35,7 +35,7 @@ ul.toc_section { font-size: 11px; line-height: 14px; - margin: 5px 0 20px 0; + margin: 5px 0 15px 0; padding-left: 0px; list-style-type: none; font-family: Lucida Grande; diff --git a/test/model.js b/test/model.js index 356a7a36..92b2e421 100644 --- a/test/model.js +++ b/test/model.js @@ -119,4 +119,23 @@ $(document).ready(function() { ok(_.isEqual(lastRequest[1], doc)); }); -}); \ No newline at end of file + test("model: validate", function() { + var lastError; + var model = new Backbone.Model(); + model.validate = function(attrs) { + if (attrs.admin) return "Can't change admin status."; + }; + model.bind('error', function(error) { + lastError = error; + }); + var result = model.set({a: 100}); + equals(result, model); + equals(model.get('a'), 100); + equals(lastError, undefined); + result = model.set({a: 200, admin: true}); + equals(result, false); + equals(model.get('a'), 100); + equals(lastError, "Can't change admin status."); + }); + +});