Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Overhauling 'validate' -- Receives the computed new state of the attr…

…s, not just the delta. Now runs on model create, raising an exception if you try to 'new' and invalid model ... also runs even if changed silently.
  • Loading branch information...
commit ab164c450da4d5c883d5b2b2817fa6a416a4b55a 1 parent 71641fb
Jeremy Ashkenas jashkenas authored
9 backbone.js
@@ -161,7 +161,9 @@
161 161 this.attributes = {};
162 162 this._escapedAttributes = {};
163 163 this.cid = _.uniqueId('c');
164   - this.set(attributes, {silent: true});
  164 + if (!this.set(attributes, {silent: true})) {
  165 + throw new Error("Can't create an invalid model");
  166 + }
165 167 this._changed = false;
166 168 this._previousAttributes = _.clone(this.attributes);
167 169 this.initialize.apply(this, arguments);
@@ -225,7 +227,7 @@
225 227 var now = this.attributes, escaped = this._escapedAttributes;
226 228
227 229 // Run validation.
228   - if (!options.silent && this.validate && !this._performValidation(attrs, options)) return false;
  230 + if (this.validate && !this._performValidation(attrs, options)) return false;
229 231
230 232 // Check for changes of `id`.
231 233 if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
@@ -422,7 +424,8 @@
422 424 // if all is well. If a specific `error` callback has been passed,
423 425 // call that instead of firing the general `"error"` event.
424 426 _performValidation: function(attrs, options) {
425   - var error = this.validate(attrs, options);
  427 + var newAttrs = _.extend({}, this.attributes, attrs);
  428 + var error = this.validate(newAttrs, options);
426 429 if (error) {
427 430 if (options.error) {
428 431 options.error(this, error, options);
2  examples/todos/index.html
@@ -6,7 +6,7 @@
6 6 <link href="todos.css" media="all" rel="stylesheet" type="text/css"/>
7 7 <script src="../../test/vendor/json2.js"></script>
8 8 <script src="../../test/vendor/jquery-1.7.1.js"></script>
9   - <script src="../../test/vendor/underscore-1.2.4.js"></script>
  9 + <script src="../../test/vendor/underscore-1.3.1.js"></script>
10 10 <script src="../../backbone.js"></script>
11 11 <script src="../backbone-localstorage.js"></script>
12 12 <script src="todos.js"></script>
2  index.html
@@ -3061,7 +3061,7 @@ <h2 id="changelog">Change Log</h2>
3061 3061
3062 3062 </div>
3063 3063
3064   - <script src="test/vendor/underscore-1.2.4.js"></script>
  3064 + <script src="test/vendor/underscore-1.3.1.js"></script>
3065 3065 <script src="test/vendor/jquery-1.7.1.js"></script>
3066 3066 <script src="test/vendor/json2.js"></script>
3067 3067 <script src="backbone.js"></script>
15 test/collection.js
@@ -73,8 +73,9 @@ $(document).ready(function() {
73 73 var CustomSetCollection = Backbone.Collection.extend({
74 74 model: CustomSetModel
75 75 });
76   - var col = new CustomSetCollection([{ num_as_string: 2 }]);
77   - equal(col.length, 1);
  76 + raises(function(){
  77 + new CustomSetCollection([{ num_as_string: 2 }]);
  78 + });
78 79 });
79 80
80 81 test("Collection: update index when id changes", function() {
@@ -363,7 +364,9 @@ $(document).ready(function() {
363 364 model: ValidatingModel
364 365 });
365 366 var col = new ValidatingCollection();
366   - equal(col.create({"foo":"bar"}),false);
  367 + raises(function(){
  368 + equal(col.create({"foo":"bar"}),false);
  369 + });
367 370 });
368 371
369 372 test("Collection: a failing create runs the error callback", function() {
@@ -378,8 +381,9 @@ $(document).ready(function() {
378 381 var flag = false;
379 382 var callback = function(model, error) { flag = true; };
380 383 var col = new ValidatingCollection();
381   - col.create({"foo":"bar"}, { error: callback });
382   - equal(flag, true);
  384 + raises(function(){
  385 + col.create({"foo":"bar"}, { error: callback });
  386 + });
383 387 });
384 388
385 389 test("collection: initialize", function() {
@@ -456,6 +460,7 @@ $(document).ready(function() {
456 460 set: function(attrs) {
457 461 equal(attrs.prop, 'value');
458 462 equal(this.collection, col);
  463 + return this;
459 464 }
460 465 });
461 466 col.model = Model;
19 test/model.js
@@ -177,10 +177,10 @@ $(document).ready(function() {
177 177 ok(changeCount == 1, "Change count should NOT have incremented.");
178 178
179 179 a.validate = function(attrs) {
180   - equal(attrs.foo, void 0, 'ignore values when unsetting');
  180 + equal(attrs.foo, void 0, "don't ignore values when unsetting");
181 181 };
182 182 a.unset('foo');
183   - ok(a.get('foo') == null, "Foo should have changed");
  183 + equal(a.get('foo'), void 0, "Foo should have changed");
184 184 delete a.validate;
185 185 ok(changeCount == 2, "Change count should have incremented for unset.");
186 186
@@ -364,7 +364,7 @@ $(document).ready(function() {
364 364 var lastError;
365 365 var model = new Backbone.Model();
366 366 model.validate = function(attrs) {
367   - if (attrs.admin) return "Can't change admin status.";
  367 + if (attrs.admin != this.get('admin')) return "Can't change admin status.";
368 368 };
369 369 model.on('error', function(model, error) {
370 370 lastError = error;
@@ -374,23 +374,20 @@ $(document).ready(function() {
374 374 equal(model.get('a'), 100);
375 375 equal(lastError, undefined);
376 376 result = model.set({admin: true}, {silent: true});
377   - equal(lastError, undefined);
378   - equal(model.get('admin'), true);
  377 + equal(lastError, "Can't change admin status.");
  378 + equal(model.get('admin'), void 0);
379 379 result = model.set({a: 200, admin: true});
380 380 equal(result, false);
381 381 equal(model.get('a'), 100);
382   - equal(lastError, "Can't change admin status.");
383 382 });
384 383
385 384 test("Model: validate on unset and clear", function() {
386 385 var error;
387 386 var model = new Backbone.Model({name: "One"});
388 387 model.validate = function(attrs) {
389   - if ("name" in attrs) {
390   - if (!attrs.name) {
391   - error = true;
392   - return "No thanks.";
393   - }
  388 + if (!attrs.name) {
  389 + error = true;
  390 + return "No thanks.";
394 391 }
395 392 };
396 393 model.set({name: "Two"});
2  test/test-ender.html
@@ -7,7 +7,7 @@
7 7 <script type="text/javascript" src="vendor/ender-jeesh.js"></script>
8 8 <script type="text/javascript" src="vendor/qunit.js"></script>
9 9 <script type="text/javascript" src="vendor/jslitmus.js"></script>
10   - <script type="text/javascript" src="vendor/underscore-1.2.4.js"></script>
  10 + <script type="text/javascript" src="vendor/underscore-1.3.1.js"></script>
11 11 <script type="text/javascript" src="../backbone.js"></script>
12 12
13 13 <script type="text/javascript" src="events.js"></script>
2  test/test-zepto.html
@@ -7,7 +7,7 @@
7 7 <script type="text/javascript" src="vendor/zepto-0.6.js"></script>
8 8 <script type="text/javascript" src="vendor/qunit.js"></script>
9 9 <script type="text/javascript" src="vendor/jslitmus.js"></script>
10   - <script type="text/javascript" src="vendor/underscore-1.2.4.js"></script>
  10 + <script type="text/javascript" src="vendor/underscore-1.3.1.js"></script>
11 11 <script type="text/javascript" src="../backbone.js"></script>
12 12
13 13 <script type="text/javascript" src="events.js"></script>
2  test/test.html
@@ -10,7 +10,7 @@
10 10 QUnit.config.reorder = false;
11 11 </script>
12 12 <script type="text/javascript" src="vendor/jslitmus.js"></script>
13   - <script type="text/javascript" src="vendor/underscore-1.2.4.js"></script>
  13 + <script type="text/javascript" src="vendor/underscore-1.3.1.js"></script>
14 14 <script type="text/javascript" src="../backbone.js"></script>
15 15
16 16 <script type="text/javascript" src="noconflict.js"></script>
56 test/vendor/underscore-1.2.4.js → test/vendor/underscore-1.3.1.js
... ... @@ -1,4 +1,4 @@
1   -// Underscore.js 1.2.4
  1 +// Underscore.js 1.3.1
2 2 // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
3 3 // Underscore is freely distributable under the MIT license.
4 4 // Portions of Underscore are inspired or borrowed from Prototype,
@@ -48,26 +48,21 @@
48 48 // Create a safe reference to the Underscore object for use below.
49 49 var _ = function(obj) { return new wrapper(obj); };
50 50
51   - // Export the Underscore object for **Node.js** and **"CommonJS"**, with
52   - // backwards-compatibility for the old `require()` API. If we're not in
53   - // CommonJS, add `_` to the global object.
  51 + // Export the Underscore object for **Node.js**, with
  52 + // backwards-compatibility for the old `require()` API. If we're in
  53 + // the browser, add `_` as a global object via a string identifier,
  54 + // for Closure Compiler "advanced" mode.
54 55 if (typeof exports !== 'undefined') {
55 56 if (typeof module !== 'undefined' && module.exports) {
56 57 exports = module.exports = _;
57 58 }
58 59 exports._ = _;
59   - } else if (typeof define === 'function' && define.amd) {
60   - // Register as a named module with AMD.
61   - define('underscore', function() {
62   - return _;
63   - });
64 60 } else {
65   - // Exported as a string, for Closure Compiler "advanced" mode.
66 61 root['_'] = _;
67 62 }
68 63
69 64 // Current version.
70   - _.VERSION = '1.2.4';
  65 + _.VERSION = '1.3.1';
71 66
72 67 // Collection Functions
73 68 // --------------------
@@ -85,7 +80,7 @@
85 80 }
86 81 } else {
87 82 for (var key in obj) {
88   - if (hasOwnProperty.call(obj, key)) {
  83 + if (_.has(obj, key)) {
89 84 if (iterator.call(context, obj[key], key, obj) === breaker) return;
90 85 }
91 86 }
@@ -94,7 +89,7 @@
94 89
95 90 // Return the results of applying the iterator to each element.
96 91 // Delegates to **ECMAScript 5**'s native `map` if available.
97   - _.map = function(obj, iterator, context) {
  92 + _.map = _.collect = function(obj, iterator, context) {
98 93 var results = [];
99 94 if (obj == null) return results;
100 95 if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
@@ -511,7 +506,7 @@
511 506 hasher || (hasher = _.identity);
512 507 return function() {
513 508 var key = hasher.apply(this, arguments);
514   - return hasOwnProperty.call(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
  509 + return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
515 510 };
516 511 };
517 512
@@ -617,7 +612,7 @@
617 612 _.keys = nativeKeys || function(obj) {
618 613 if (obj !== Object(obj)) throw new TypeError('Invalid object');
619 614 var keys = [];
620   - for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key;
  615 + for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
621 616 return keys;
622 617 };
623 618
@@ -640,7 +635,7 @@
640 635 _.extend = function(obj) {
641 636 each(slice.call(arguments, 1), function(source) {
642 637 for (var prop in source) {
643   - if (source[prop] !== void 0) obj[prop] = source[prop];
  638 + obj[prop] = source[prop];
644 639 }
645 640 });
646 641 return obj;
@@ -738,17 +733,17 @@
738 733 if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
739 734 // Deep compare objects.
740 735 for (var key in a) {
741   - if (hasOwnProperty.call(a, key)) {
  736 + if (_.has(a, key)) {
742 737 // Count the expected number of properties.
743 738 size++;
744 739 // Deep compare each member.
745   - if (!(result = hasOwnProperty.call(b, key) && eq(a[key], b[key], stack))) break;
  740 + if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
746 741 }
747 742 }
748 743 // Ensure that both objects contain the same number of properties.
749 744 if (result) {
750 745 for (key in b) {
751   - if (hasOwnProperty.call(b, key) && !(size--)) break;
  746 + if (_.has(b, key) && !(size--)) break;
752 747 }
753 748 result = !size;
754 749 }
@@ -767,7 +762,7 @@
767 762 // An "empty" object has no enumerable own-properties.
768 763 _.isEmpty = function(obj) {
769 764 if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
770   - for (var key in obj) if (hasOwnProperty.call(obj, key)) return false;
  765 + for (var key in obj) if (_.has(obj, key)) return false;
771 766 return true;
772 767 };
773 768
@@ -793,7 +788,7 @@
793 788 };
794 789 if (!_.isArguments(arguments)) {
795 790 _.isArguments = function(obj) {
796   - return !!(obj && hasOwnProperty.call(obj, 'callee'));
  791 + return !!(obj && _.has(obj, 'callee'));
797 792 };
798 793 }
799 794
@@ -843,6 +838,11 @@
843 838 return obj === void 0;
844 839 };
845 840
  841 + // Has own property?
  842 + _.has = function(obj, key) {
  843 + return hasOwnProperty.call(obj, key);
  844 + };
  845 +
846 846 // Utility Functions
847 847 // -----------------
848 848
@@ -897,6 +897,12 @@
897 897 // guaranteed not to match.
898 898 var noMatch = /.^/;
899 899
  900 + // Within an interpolation, evaluation, or escaping, remove HTML escaping
  901 + // that had been previously added.
  902 + var unescape = function(code) {
  903 + return code.replace(/\\\\/g, '\\').replace(/\\'/g, "'");
  904 + };
  905 +
900 906 // JavaScript micro-templating, similar to John Resig's implementation.
901 907 // Underscore templating handles arbitrary delimiters, preserves whitespace,
902 908 // and correctly escapes quotes within interpolated code.
@@ -907,15 +913,13 @@
907 913 str.replace(/\\/g, '\\\\')
908 914 .replace(/'/g, "\\'")
909 915 .replace(c.escape || noMatch, function(match, code) {
910   - return "',_.escape(" + code.replace(/\\'/g, "'") + "),'";
  916 + return "',_.escape(" + unescape(code) + "),'";
911 917 })
912 918 .replace(c.interpolate || noMatch, function(match, code) {
913   - return "'," + code.replace(/\\'/g, "'") + ",'";
  919 + return "'," + unescape(code) + ",'";
914 920 })
915 921 .replace(c.evaluate || noMatch, function(match, code) {
916   - return "');" + code.replace(/\\'/g, "'")
917   - .replace(/[\r\n\t]/g, ' ')
918   - .replace(/\\\\/g, '\\') + ";__p.push('";
  922 + return "');" + unescape(code).replace(/[\r\n\t]/g, ' ') + ";__p.push('";
919 923 })
920 924 .replace(/\r/g, '\\r')
921 925 .replace(/\n/g, '\\n')

0 comments on commit ab164c4

Please sign in to comment.
Something went wrong with that request. Please try again.