From f3e74c1fde142fc70788cc7e935b7f1d945a8e3f Mon Sep 17 00:00:00 2001 From: thedersen Date: Tue, 7 Aug 2012 18:40:47 +0200 Subject: [PATCH 1/8] Updated readme with examples --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index ac901419..2c8e83ec 100644 --- a/README.md +++ b/README.md @@ -722,6 +722,10 @@ The message can contain placeholders for arguments that will be replaced: * `{1}` will be replaced with the allowed value configured in the validation (or the first one in a range validator) * `{2}` will be replaced with the second value in a range validator +## Examples + +Some examples can be found [here](http://thedersen.com/projects/backbone-validation/examples/). This is by far not complete, but I hope you get the idea. View source to see how it's made. + ## FAQ ### What gets validated when? From 5f8bfb277ef810395848e873f4e3b44a7b32a28f Mon Sep 17 00:00:00 2001 From: Thomas Pedersen Date: Tue, 14 Aug 2012 22:41:29 +0300 Subject: [PATCH 2/8] Update README.md --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 2c8e83ec..080df1d0 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,6 @@ A validation plugin for [Backbone.js](http://documentcloud.github.com/backbone) that validates both your model as well as form input. - - - ## Introduction Good client side validation is an important part of giving your users a great experience when they visit your site. Backbone provides a [validate](http://backbonejs.org/#Model-validate) method, but it is left undefined and it is up to you to override it with your custom validation logic. Too many times I have seen validation implemented as lots of nested ifs and elses. This quickly becomes a big mess. One other thing is that with libraries like Backbone, you hold your state in a Model, and don't tie it to the DOM. Still, when validating your models you probably want to inform your users about errors etc., which means modifying the DOM. From ccbec2569f9d11467374551ecd4a8ccd74af9f2c Mon Sep 17 00:00:00 2001 From: Justin Etheredge Date: Tue, 28 Aug 2012 17:39:11 -0300 Subject: [PATCH 3/8] Update src/backbone-validation.js Update validate method so that valid callbacks are executed before invalid callbacks. --- src/backbone-validation.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/backbone-validation.js b/src/backbone-validation.js index 289c49d3..a701d0a9 100644 --- a/src/backbone-validation.js +++ b/src/backbone-validation.js @@ -149,16 +149,22 @@ Backbone.Validation = (function(_){ model._isValid = result.isValid; // After validation is performed, loop through all changed attributes - // and call either the valid or invalid callback so the view is updated. + // and call the valid callbacks so the view is updated. + for(var attr in allAttrs) { + var invalid = result.invalidAttrs.hasOwnProperty(attr); + if(!invalid){ + opt.valid(view, attr, opt.selector); + } + } + + // After validation is performed, loop through all changed attributes + // and call the invalid callback so the view is updated. for(var attr in allAttrs) { var invalid = result.invalidAttrs.hasOwnProperty(attr), changed = changedAttrs.hasOwnProperty(attr); if(invalid && (changed || validateAll)){ opt.invalid(view, attr, result.invalidAttrs[attr], opt.selector); } - if(!invalid){ - opt.valid(view, attr, opt.selector); - } } // Trigger validated events. From 046f0acf4204e8512b0b6f53a91529482406fcf8 Mon Sep 17 00:00:00 2001 From: thedersen Date: Tue, 20 Nov 2012 21:51:53 +0100 Subject: [PATCH 4/8] Fixed typo in readme. Fixes #81 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 080df1d0..b77c21e9 100644 --- a/README.md +++ b/README.md @@ -637,7 +637,7 @@ The built-in patterns are: * number - Matches any number (e.g. -100.000,00) * email - Matches a valid email address (e.g. mail@example.com) -* url - Mathes any valid url (e.g. http://www.xample.com) +* url - Matches any valid url (e.g. http://www.example.com) * digits - Matches any digit(s) (i.e. 0-9) Specify any regular expression you like: From f9191c405ec4a53bd148c5e923c4a7ce5ccb7af7 Mon Sep 17 00:00:00 2001 From: thedersen Date: Tue, 20 Nov 2012 22:05:30 +0100 Subject: [PATCH 5/8] Fixed lint warnings --- src/backbone-validation.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/backbone-validation.js b/src/backbone-validation.js index a701d0a9..4650f0a6 100644 --- a/src/backbone-validation.js +++ b/src/backbone-validation.js @@ -150,22 +150,23 @@ Backbone.Validation = (function(_){ // After validation is performed, loop through all changed attributes // and call the valid callbacks so the view is updated. - for(var attr in allAttrs) { + _.each(_.keys(allAttrs), function(attr){ var invalid = result.invalidAttrs.hasOwnProperty(attr); if(!invalid){ opt.valid(view, attr, opt.selector); } - } + }); // After validation is performed, loop through all changed attributes // and call the invalid callback so the view is updated. - for(var attr in allAttrs) { + _.each(_.keys(allAttrs), function(attr){ var invalid = result.invalidAttrs.hasOwnProperty(attr), changed = changedAttrs.hasOwnProperty(attr); + if(invalid && (changed || validateAll)){ opt.invalid(view, attr, result.invalidAttrs[attr], opt.selector); } - } + }); // Trigger validated events. // Need to defer this so the model is actually updated before From 5ee680ff4ac64ec7d3e84de15aaf0fd052113785 Mon Sep 17 00:00:00 2001 From: thedersen Date: Tue, 20 Nov 2012 22:26:32 +0100 Subject: [PATCH 6/8] Does not invoke callbacks for attributes not validated. Fixes #75 --- src/backbone-validation.js | 7 ++++--- tests/general.js | 11 +++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/backbone-validation.js b/src/backbone-validation.js index 4650f0a6..22f0f4a6 100644 --- a/src/backbone-validation.js +++ b/src/backbone-validation.js @@ -142,7 +142,8 @@ Backbone.Validation = (function(_){ var model = this, validateAll = !attrs, opt = _.extend({}, options, setOptions), - allAttrs = _.extend(getValidatedAttrs(model), model.attributes, attrs), + validatedAttrs = getValidatedAttrs(model), + allAttrs = _.extend({}, validatedAttrs, model.attributes, attrs), changedAttrs = attrs || allAttrs, result = validateModel(model, allAttrs); @@ -150,7 +151,7 @@ Backbone.Validation = (function(_){ // After validation is performed, loop through all changed attributes // and call the valid callbacks so the view is updated. - _.each(_.keys(allAttrs), function(attr){ + _.each(_.keys(validatedAttrs), function(attr){ var invalid = result.invalidAttrs.hasOwnProperty(attr); if(!invalid){ opt.valid(view, attr, opt.selector); @@ -159,7 +160,7 @@ Backbone.Validation = (function(_){ // After validation is performed, loop through all changed attributes // and call the invalid callback so the view is updated. - _.each(_.keys(allAttrs), function(attr){ + _.each(_.keys(validatedAttrs), function(attr){ var invalid = result.invalidAttrs.hasOwnProperty(attr), changed = changedAttrs.hasOwnProperty(attr); diff --git a/tests/general.js b/tests/general.js index fe5303d4..d5286085 100644 --- a/tests/general.js +++ b/tests/general.js @@ -351,6 +351,17 @@ buster.testCase("Backbone.Validation", { assert.calledWith(this.valid, this.view, 'age'); assert.calledWith(this.valid, this.view, 'name'); + }, + + "callbacks are not called for unvalidated attributes": function(){ + + this.model.set({age: 1, name: 'name', someProp: 'some value'}, {silent:true}); + + this.model.validate(); + + assert.calledWith(this.valid, this.view, 'age'); + assert.calledWith(this.valid, this.view, 'name'); + refute.calledWith(this.valid, this.view, 'someProp'); } } }, From ca2ec79bd334f7d2d085fa90852be15b865144a0 Mon Sep 17 00:00:00 2001 From: thedersen Date: Tue, 20 Nov 2012 22:43:37 +0100 Subject: [PATCH 7/8] Labelformatter set to 'label' no longer crashes when no labels attribute is present on the model. Fixes #73 --- src/backbone-validation.js | 2 +- tests/labelFormatter.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/backbone-validation.js b/src/backbone-validation.js index 22f0f4a6..f17059ab 100644 --- a/src/backbone-validation.js +++ b/src/backbone-validation.js @@ -373,7 +373,7 @@ Backbone.Validation = (function(_){ // } // }); label: function(attrName, model) { - return model.labels[attrName] || defaultLabelFormatters.sentenceCase(attrName, model); + return (model.labels && model.labels[attrName]) || defaultLabelFormatters.sentenceCase(attrName, model); } }; diff --git a/tests/labelFormatter.js b/tests/labelFormatter.js index 2f6c3f92..a8d15e62 100644 --- a/tests/labelFormatter.js +++ b/tests/labelFormatter.js @@ -52,6 +52,21 @@ buster.testCase('Label formatters', { "returns sentence cased name when label is not found": function(){ assert.equals('Some attribute is required', this.model.preValidate('some_attribute', '')); + }, + + "returns sentence cased name when label attribute is not defined": function(){ + var Model = Backbone.Model.extend({ + validation: { + someAttribute: { + required: true + } + } + }); + + var model = new Model(); + _.extend(model, Backbone.Validation.mixin); + + assert.equals('Some attribute is required', model.preValidate('someAttribute', '')); } }, From ebce02d7123ac751707baa3d6675d0b6848c5456 Mon Sep 17 00:00:00 2001 From: thedersen Date: Tue, 20 Nov 2012 22:56:48 +0100 Subject: [PATCH 8/8] Bumped version to 0.6.3, updated readme and dist files --- README.md | 15 +++++++++++---- dist/backbone-validation-amd-min.js | 4 ++-- dist/backbone-validation-amd.js | 28 ++++++++++++++++++---------- dist/backbone-validation-min.js | 4 ++-- dist/backbone-validation.js | 28 ++++++++++++++++++---------- package.json | 2 +- src/backbone-validation.js | 2 +- 7 files changed, 53 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index b77c21e9..14ce0605 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Backbone.Validation v0.6.2 +# Backbone.Validation v0.6.3 A validation plugin for [Backbone.js](http://documentcloud.github.com/backbone) that validates both your model as well as form input. @@ -18,12 +18,12 @@ You can download the raw source from [GitHub](http://github.com/thedersen/backbo #### Standard builds -* Development: [backbone-validation.js](https://raw.github.com/thedersen/backbone.validation/master/dist/backbone-validation.js) *21.1kb* +* Development: [backbone-validation.js](https://raw.github.com/thedersen/backbone.validation/master/dist/backbone-validation.js) *21.5kb* * Production: [backbone-validation-min.js](https://raw.github.com/thedersen/backbone.validation/master/dist/backbone-validation-min.js) *2.5kb gzipped* #### AMD builds -* Development: [backbone-validation-amd.js](https://raw.github.com/thedersen/backbone.validation/master/dist/backbone-validation-amd.js) *21.4kb* +* Development: [backbone-validation-amd.js](https://raw.github.com/thedersen/backbone.validation/master/dist/backbone-validation-amd.js) *21.8kb* * Production: [backbone-validation-amd-min.js](https://raw.github.com/thedersen/backbone.validation/master/dist/backbone-validation-amd-min.js) *2.6kb gzipped* #### Node.js builds @@ -107,7 +107,7 @@ MyModel = Backbone.Model.extend({ The philosophy behind this way of using the plugin, is that you should be able to reuse your validation rules both to validate your model and to validate form input, as well as providing a simple way of notifying users about errors when they are populating forms. -Note that Backbone.Validation does not provide any automatic/two-way binding between your model and the view, that's up you to implement (you can for instance use [Backbone.ModelBinder](https://github.com/theironcook/Backbone.ModelBinder) or [Rivets.js](http://rivetsjs.com/)). +Note that Backbone.Validation does not provide any automatic/two-way binding between your model and the view, that's up you to implement (you can for instance use [Backbone.stickit](http://nytimes.github.com/backbone.stickit/). Before you can start using form validation, you need to bind your view. @@ -768,6 +768,13 @@ Basic behaviour: ## Release notes +#### v0.6.3 + +* Labelformatter set to 'label' no longer crashes when no `labels` attribute is present on the model +* Does not invoke callbacks for attributes that are not validated +* Valid callbacks are always called before invalid callbacks (Thanks to [Justin Etheredge](https://github.com/jetheredge)) +* Fixed typo in the readme + #### v0.6.2 * Fixed typo in the package.json (Thanks to [Patrick Scott](https://github.com/patrickleet)) diff --git a/dist/backbone-validation-amd-min.js b/dist/backbone-validation-amd-min.js index a6f82658..647c5d88 100644 --- a/dist/backbone-validation-amd-min.js +++ b/dist/backbone-validation-amd-min.js @@ -1,8 +1,8 @@ -// Backbone.Validation v0.6.2 +// Backbone.Validation v0.6.3 // // Copyright (c) 2011-2012 Thomas Pedersen // Distributed under MIT License // // Documentation and full license available at: // http://thedersen.com/projects/backbone-validation -(function(a){typeof exports=="object"?module.exports=a(require("backbone"),require("underscore")):typeof define=="function"&&define.amd&&define(["backbone","underscore"],a)})(function(a,b){return a.Validation=function(a){"use strict";var b={forceUpdate:!1,selector:"name",labelFormatter:"sentenceCase",valid:Function.prototype,invalid:Function.prototype},c=function(){var c=function(b){return a.reduce(a.keys(b.validation||{}),function(a,b){return a[b]=void 0,a},{})},e=function(b,c){var d=b.validation?b.validation[c]||{}:{};if(a.isFunction(d)||a.isString(d))d={fn:d};return a.isArray(d)||(d=[d]),a.reduce(d,function(b,c){return a.each(a.without(a.keys(c),"msg"),function(a){b.push({fn:h[a],val:c[a],msg:c.msg})}),b},[])},f=function(b,c,d,f){return a.reduce(e(b,c),function(a,e){var g=e.fn.call(h,d,c,e.val,b,f);return g===!1||a===!1?!1:g&&!a?e.msg||g:a},"")},g=function(b,c){var d,e,g={},h=!0,i=a.clone(c);for(e in c)d=f(b,e,c[e],i),d&&(g[e]=d,h=!1);return{invalidAttrs:g,isValid:h}},i=function(b,d){return{preValidate:function(b,c){return f(this,b,c,a.extend({},this.attributes))},isValid:function(b){if(a.isString(b))return!f(this,b,this.get(b),a.extend({},this.attributes));if(a.isArray(b)){for(var c=0;c0)return m.invalidAttrs}}},j=function(b,c,d){a.extend(c,i(b,d))},k=function(a){delete a.validate,delete a.preValidate,delete a.isValid},l=function(a){j(this.view,a,this.options)},m=function(a){k(a)};return{version:"0.6.2",configure:function(c){a.extend(b,c)},bind:function(c,e){var f=c.model,g=c.collection;e=a.extend({},b,d,e);if(typeof f=="undefined"&&typeof g=="undefined")throw"Before you execute the binding your view must have a model or a collection.\nSee http://thedersen.com/projects/backbone-validation/#using-form-model-validation for more information.";f&&j(c,f,e),g&&(g.each(function(a){j(c,a,e)}),g.bind("add",l,{view:c,options:e}),g.bind("remove",m))},unbind:function(a){var b=a.model,c=a.collection;b&&k(a.model),c&&(c.each(function(a){k(a)}),c.unbind("add",l),c.unbind("remove",m))},mixin:i(null,b)}}(),d=c.callbacks={valid:function(a,b,c){a.$("["+c+"~="+b+"]").removeClass("invalid").removeAttr("data-error")},invalid:function(a,b,c,d){a.$("["+d+"~="+b+"]").addClass("invalid").attr("data-error",c)}},e=c.patterns={digits:/^\d+$/,number:/^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/,email:/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i,url:/^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i},f=c.messages={required:"{0} is required",acceptance:"{0} must be accepted",min:"{0} must be greater than or equal to {1}",max:"{0} must be less than or equal to {1}",range:"{0} must be between {1} and {2}",length:"{0} must be {1} characters",minLength:"{0} must be at least {1} characters",maxLength:"{0} must be at most {1} characters",rangeLength:"{0} must be between {1} and {2} characters",oneOf:"{0} must be one of: {1}",equalTo:"{0} must be the same as {1}",pattern:"{0} must be a valid {1}"},g=c.labelFormatters={none:function(a){return a},sentenceCase:function(a){return a.replace(/(?:^\w|[A-Z]|\b\w)/g,function(a,b){return b===0?a.toUpperCase():" "+a.toLowerCase()}).replace("_"," ")},label:function(a,b){return b.labels[a]||g.sentenceCase(a,b)}},h=c.validators=function(){var c=String.prototype.trim?function(a){return a===null?"":String.prototype.trim.call(a)}:function(a){var b=/^\s+/,c=/\s+$/;return a===null?"":a.toString().replace(b,"").replace(c,"")},d=function(a,c){return g[b.labelFormatter](a,c)},h=function(){var a=Array.prototype.slice.call(arguments),b=a.shift();return b.replace(/\{(\d+)\}/g,function(b,c){return typeof a[c]!="undefined"?a[c]:b})},i=function(b){return a.isNumber(b)||a.isString(b)&&b.match(e.number)},j=function(b){return!(a.isNull(b)||a.isUndefined(b)||a.isString(b)&&c(b)==="")};return{fn:function(b,c,d,e,f){return a.isString(d)&&(d=e[d]),d.call(e,b,c,f)},required:function(b,c,e,g,i){var k=a.isFunction(e)?e.call(g,b,c,i):e;if(!k&&!j(b))return!1;if(k&&!j(b))return h(f.required,d(c,g))},acceptance:function(b,c,e,g){if(b!=="true"&&(!a.isBoolean(b)||b===!1))return h(f.acceptance,d(c,g))},min:function(a,b,c,e){if(!i(a)||ac)return h(f.max,d(b,e),c)},range:function(a,b,c,e){if(!i(a)||ac[1])return h(f.range,d(b,e),c[0],c[1])},length:function(a,b,e,g){if(!j(a)||c(a).length!==e)return h(f.length,d(b,g),e)},minLength:function(a,b,e,g){if(!j(a)||c(a).lengthe)return h(f.maxLength,d(b,g),e)},rangeLength:function(a,b,e,g){if(!j(a)||c(a).lengthe[1])return h(f.rangeLength,d(b,g),e[0],e[1])},oneOf:function(b,c,e,g){if(!a.include(e,b))return h(f.oneOf,d(c,g),e.join(", "))},equalTo:function(a,b,c,e,g){if(a!==g[c])return h(f.equalTo,d(b,e),d(c,e))},pattern:function(a,b,c,g){if(!j(a)||!a.toString().match(e[c]||c))return h(f.pattern,d(b,g),c)}}}();return c}(b),a.Validation}); \ No newline at end of file +(function(e){typeof exports=="object"?module.exports=e(require("backbone"),require("underscore")):typeof define=="function"&&define.amd&&define(["backbone","underscore"],e)})(function(e,t){return e.Validation=function(e){"use strict";var t={forceUpdate:!1,selector:"name",labelFormatter:"sentenceCase",valid:Function.prototype,invalid:Function.prototype},n=function(){var n=function(t){return e.reduce(e.keys(t.validation||{}),function(e,t){return e[t]=void 0,e},{})},i=function(t,n){var r=t.validation?t.validation[n]||{}:{};if(e.isFunction(r)||e.isString(r))r={fn:r};return e.isArray(r)||(r=[r]),e.reduce(r,function(t,n){return e.each(e.without(e.keys(n),"msg"),function(e){t.push({fn:u[e],val:n[e],msg:n.msg})}),t},[])},s=function(t,n,r,s){return e.reduce(i(t,n),function(e,i){var o=i.fn.call(u,r,n,i.val,t,s);return o===!1||e===!1?!1:o&&!e?i.msg||o:e},"")},o=function(t,n){var r,i,o={},u=!0,a=e.clone(n);for(i in n)r=s(t,i,n[i],a),r&&(o[i]=r,u=!1);return{invalidAttrs:o,isValid:u}},a=function(t,r){return{preValidate:function(t,n){return s(this,t,n,e.extend({},this.attributes))},isValid:function(t){if(e.isString(t))return!s(this,t,this.get(t),e.extend({},this.attributes));if(e.isArray(t)){for(var n=0;n0)return p.invalidAttrs}}},f=function(t,n,r){e.extend(n,a(t,r))},l=function(e){delete e.validate,delete e.preValidate,delete e.isValid},c=function(e){f(this.view,e,this.options)},h=function(e){l(e)};return{version:"0.6.3",configure:function(n){e.extend(t,n)},bind:function(n,i){var s=n.model,o=n.collection;i=e.extend({},t,r,i);if(typeof s=="undefined"&&typeof o=="undefined")throw"Before you execute the binding your view must have a model or a collection.\nSee http://thedersen.com/projects/backbone-validation/#using-form-model-validation for more information.";s&&f(n,s,i),o&&(o.each(function(e){f(n,e,i)}),o.bind("add",c,{view:n,options:i}),o.bind("remove",h))},unbind:function(e){var t=e.model,n=e.collection;t&&l(e.model),n&&(n.each(function(e){l(e)}),n.unbind("add",c),n.unbind("remove",h))},mixin:a(null,t)}}(),r=n.callbacks={valid:function(e,t,n){e.$("["+n+"~="+t+"]").removeClass("invalid").removeAttr("data-error")},invalid:function(e,t,n,r){e.$("["+r+"~="+t+"]").addClass("invalid").attr("data-error",n)}},i=n.patterns={digits:/^\d+$/,number:/^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/,email:/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i,url:/^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i},s=n.messages={required:"{0} is required",acceptance:"{0} must be accepted",min:"{0} must be greater than or equal to {1}",max:"{0} must be less than or equal to {1}",range:"{0} must be between {1} and {2}",length:"{0} must be {1} characters",minLength:"{0} must be at least {1} characters",maxLength:"{0} must be at most {1} characters",rangeLength:"{0} must be between {1} and {2} characters",oneOf:"{0} must be one of: {1}",equalTo:"{0} must be the same as {1}",pattern:"{0} must be a valid {1}"},o=n.labelFormatters={none:function(e){return e},sentenceCase:function(e){return e.replace(/(?:^\w|[A-Z]|\b\w)/g,function(e,t){return t===0?e.toUpperCase():" "+e.toLowerCase()}).replace("_"," ")},label:function(e,t){return t.labels&&t.labels[e]||o.sentenceCase(e,t)}},u=n.validators=function(){var n=String.prototype.trim?function(e){return e===null?"":String.prototype.trim.call(e)}:function(e){var t=/^\s+/,n=/\s+$/;return e===null?"":e.toString().replace(t,"").replace(n,"")},r=function(e,n){return o[t.labelFormatter](e,n)},u=function(){var e=Array.prototype.slice.call(arguments),t=e.shift();return t.replace(/\{(\d+)\}/g,function(t,n){return typeof e[n]!="undefined"?e[n]:t})},a=function(t){return e.isNumber(t)||e.isString(t)&&t.match(i.number)},f=function(t){return!(e.isNull(t)||e.isUndefined(t)||e.isString(t)&&n(t)==="")};return{fn:function(t,n,r,i,s){return e.isString(r)&&(r=i[r]),r.call(i,t,n,s)},required:function(t,n,i,o,a){var l=e.isFunction(i)?i.call(o,t,n,a):i;if(!l&&!f(t))return!1;if(l&&!f(t))return u(s.required,r(n,o))},acceptance:function(t,n,i,o){if(t!=="true"&&(!e.isBoolean(t)||t===!1))return u(s.acceptance,r(n,o))},min:function(e,t,n,i){if(!a(e)||en)return u(s.max,r(t,i),n)},range:function(e,t,n,i){if(!a(e)||en[1])return u(s.range,r(t,i),n[0],n[1])},length:function(e,t,i,o){if(!f(e)||n(e).length!==i)return u(s.length,r(t,o),i)},minLength:function(e,t,i,o){if(!f(e)||n(e).lengthi)return u(s.maxLength,r(t,o),i)},rangeLength:function(e,t,i,o){if(!f(e)||n(e).lengthi[1])return u(s.rangeLength,r(t,o),i[0],i[1])},oneOf:function(t,n,i,o){if(!e.include(i,t))return u(s.oneOf,r(n,o),i.join(", "))},equalTo:function(e,t,n,i,o){if(e!==o[n])return u(s.equalTo,r(t,i),r(n,i))},pattern:function(e,t,n,o){if(!f(e)||!e.toString().match(i[n]||n))return u(s.pattern,r(t,o),n)}}}();return n}(t),e.Validation}); \ No newline at end of file diff --git a/dist/backbone-validation-amd.js b/dist/backbone-validation-amd.js index 24a92680..9eae23b2 100644 --- a/dist/backbone-validation-amd.js +++ b/dist/backbone-validation-amd.js @@ -1,4 +1,4 @@ -// Backbone.Validation v0.6.2 +// Backbone.Validation v0.6.3 // // Copyright (c) 2011-2012 Thomas Pedersen // Distributed under MIT License @@ -158,24 +158,32 @@ Backbone.Validation = (function(_){ var model = this, validateAll = !attrs, opt = _.extend({}, options, setOptions), - allAttrs = _.extend(getValidatedAttrs(model), model.attributes, attrs), + validatedAttrs = getValidatedAttrs(model), + allAttrs = _.extend({}, validatedAttrs, model.attributes, attrs), changedAttrs = attrs || allAttrs, result = validateModel(model, allAttrs); model._isValid = result.isValid; // After validation is performed, loop through all changed attributes - // and call either the valid or invalid callback so the view is updated. - for(var attr in allAttrs) { + // and call the valid callbacks so the view is updated. + _.each(_.keys(validatedAttrs), function(attr){ + var invalid = result.invalidAttrs.hasOwnProperty(attr); + if(!invalid){ + opt.valid(view, attr, opt.selector); + } + }); + + // After validation is performed, loop through all changed attributes + // and call the invalid callback so the view is updated. + _.each(_.keys(validatedAttrs), function(attr){ var invalid = result.invalidAttrs.hasOwnProperty(attr), changed = changedAttrs.hasOwnProperty(attr); + if(invalid && (changed || validateAll)){ opt.invalid(view, attr, result.invalidAttrs[attr], opt.selector); } - if(!invalid){ - opt.valid(view, attr, opt.selector); - } - } + }); // Trigger validated events. // Need to defer this so the model is actually updated before @@ -223,7 +231,7 @@ Backbone.Validation = (function(_){ return { // Current version of the library - version: '0.6.2', + version: '0.6.3', // Called to configure the default options configure: function(options) { @@ -381,7 +389,7 @@ Backbone.Validation = (function(_){ // } // }); label: function(attrName, model) { - return model.labels[attrName] || defaultLabelFormatters.sentenceCase(attrName, model); + return (model.labels && model.labels[attrName]) || defaultLabelFormatters.sentenceCase(attrName, model); } }; diff --git a/dist/backbone-validation-min.js b/dist/backbone-validation-min.js index 483d1641..9a1a31ec 100644 --- a/dist/backbone-validation-min.js +++ b/dist/backbone-validation-min.js @@ -1,8 +1,8 @@ -// Backbone.Validation v0.6.2 +// Backbone.Validation v0.6.3 // // Copyright (c) 2011-2012 Thomas Pedersen // Distributed under MIT License // // Documentation and full license available at: // http://thedersen.com/projects/backbone-validation -Backbone.Validation=function(a){"use strict";var b={forceUpdate:!1,selector:"name",labelFormatter:"sentenceCase",valid:Function.prototype,invalid:Function.prototype},c=function(){var c=function(b){return a.reduce(a.keys(b.validation||{}),function(a,b){return a[b]=void 0,a},{})},e=function(b,c){var d=b.validation?b.validation[c]||{}:{};if(a.isFunction(d)||a.isString(d))d={fn:d};return a.isArray(d)||(d=[d]),a.reduce(d,function(b,c){return a.each(a.without(a.keys(c),"msg"),function(a){b.push({fn:h[a],val:c[a],msg:c.msg})}),b},[])},f=function(b,c,d,f){return a.reduce(e(b,c),function(a,e){var g=e.fn.call(h,d,c,e.val,b,f);return g===!1||a===!1?!1:g&&!a?e.msg||g:a},"")},g=function(b,c){var d,e,g={},h=!0,i=a.clone(c);for(e in c)d=f(b,e,c[e],i),d&&(g[e]=d,h=!1);return{invalidAttrs:g,isValid:h}},i=function(b,d){return{preValidate:function(b,c){return f(this,b,c,a.extend({},this.attributes))},isValid:function(b){if(a.isString(b))return!f(this,b,this.get(b),a.extend({},this.attributes));if(a.isArray(b)){for(var c=0;c0)return m.invalidAttrs}}},j=function(b,c,d){a.extend(c,i(b,d))},k=function(a){delete a.validate,delete a.preValidate,delete a.isValid},l=function(a){j(this.view,a,this.options)},m=function(a){k(a)};return{version:"0.6.2",configure:function(c){a.extend(b,c)},bind:function(c,e){var f=c.model,g=c.collection;e=a.extend({},b,d,e);if(typeof f=="undefined"&&typeof g=="undefined")throw"Before you execute the binding your view must have a model or a collection.\nSee http://thedersen.com/projects/backbone-validation/#using-form-model-validation for more information.";f&&j(c,f,e),g&&(g.each(function(a){j(c,a,e)}),g.bind("add",l,{view:c,options:e}),g.bind("remove",m))},unbind:function(a){var b=a.model,c=a.collection;b&&k(a.model),c&&(c.each(function(a){k(a)}),c.unbind("add",l),c.unbind("remove",m))},mixin:i(null,b)}}(),d=c.callbacks={valid:function(a,b,c){a.$("["+c+"~="+b+"]").removeClass("invalid").removeAttr("data-error")},invalid:function(a,b,c,d){a.$("["+d+"~="+b+"]").addClass("invalid").attr("data-error",c)}},e=c.patterns={digits:/^\d+$/,number:/^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/,email:/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i,url:/^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i},f=c.messages={required:"{0} is required",acceptance:"{0} must be accepted",min:"{0} must be greater than or equal to {1}",max:"{0} must be less than or equal to {1}",range:"{0} must be between {1} and {2}",length:"{0} must be {1} characters",minLength:"{0} must be at least {1} characters",maxLength:"{0} must be at most {1} characters",rangeLength:"{0} must be between {1} and {2} characters",oneOf:"{0} must be one of: {1}",equalTo:"{0} must be the same as {1}",pattern:"{0} must be a valid {1}"},g=c.labelFormatters={none:function(a){return a},sentenceCase:function(a){return a.replace(/(?:^\w|[A-Z]|\b\w)/g,function(a,b){return b===0?a.toUpperCase():" "+a.toLowerCase()}).replace("_"," ")},label:function(a,b){return b.labels[a]||g.sentenceCase(a,b)}},h=c.validators=function(){var c=String.prototype.trim?function(a){return a===null?"":String.prototype.trim.call(a)}:function(a){var b=/^\s+/,c=/\s+$/;return a===null?"":a.toString().replace(b,"").replace(c,"")},d=function(a,c){return g[b.labelFormatter](a,c)},h=function(){var a=Array.prototype.slice.call(arguments),b=a.shift();return b.replace(/\{(\d+)\}/g,function(b,c){return typeof a[c]!="undefined"?a[c]:b})},i=function(b){return a.isNumber(b)||a.isString(b)&&b.match(e.number)},j=function(b){return!(a.isNull(b)||a.isUndefined(b)||a.isString(b)&&c(b)==="")};return{fn:function(b,c,d,e,f){return a.isString(d)&&(d=e[d]),d.call(e,b,c,f)},required:function(b,c,e,g,i){var k=a.isFunction(e)?e.call(g,b,c,i):e;if(!k&&!j(b))return!1;if(k&&!j(b))return h(f.required,d(c,g))},acceptance:function(b,c,e,g){if(b!=="true"&&(!a.isBoolean(b)||b===!1))return h(f.acceptance,d(c,g))},min:function(a,b,c,e){if(!i(a)||ac)return h(f.max,d(b,e),c)},range:function(a,b,c,e){if(!i(a)||ac[1])return h(f.range,d(b,e),c[0],c[1])},length:function(a,b,e,g){if(!j(a)||c(a).length!==e)return h(f.length,d(b,g),e)},minLength:function(a,b,e,g){if(!j(a)||c(a).lengthe)return h(f.maxLength,d(b,g),e)},rangeLength:function(a,b,e,g){if(!j(a)||c(a).lengthe[1])return h(f.rangeLength,d(b,g),e[0],e[1])},oneOf:function(b,c,e,g){if(!a.include(e,b))return h(f.oneOf,d(c,g),e.join(", "))},equalTo:function(a,b,c,e,g){if(a!==g[c])return h(f.equalTo,d(b,e),d(c,e))},pattern:function(a,b,c,g){if(!j(a)||!a.toString().match(e[c]||c))return h(f.pattern,d(b,g),c)}}}();return c}(_); \ No newline at end of file +Backbone.Validation=function(e){"use strict";var t={forceUpdate:!1,selector:"name",labelFormatter:"sentenceCase",valid:Function.prototype,invalid:Function.prototype},n=function(){var n=function(t){return e.reduce(e.keys(t.validation||{}),function(e,t){return e[t]=void 0,e},{})},i=function(t,n){var r=t.validation?t.validation[n]||{}:{};if(e.isFunction(r)||e.isString(r))r={fn:r};return e.isArray(r)||(r=[r]),e.reduce(r,function(t,n){return e.each(e.without(e.keys(n),"msg"),function(e){t.push({fn:u[e],val:n[e],msg:n.msg})}),t},[])},s=function(t,n,r,s){return e.reduce(i(t,n),function(e,i){var o=i.fn.call(u,r,n,i.val,t,s);return o===!1||e===!1?!1:o&&!e?i.msg||o:e},"")},o=function(t,n){var r,i,o={},u=!0,a=e.clone(n);for(i in n)r=s(t,i,n[i],a),r&&(o[i]=r,u=!1);return{invalidAttrs:o,isValid:u}},a=function(t,r){return{preValidate:function(t,n){return s(this,t,n,e.extend({},this.attributes))},isValid:function(t){if(e.isString(t))return!s(this,t,this.get(t),e.extend({},this.attributes));if(e.isArray(t)){for(var n=0;n0)return p.invalidAttrs}}},f=function(t,n,r){e.extend(n,a(t,r))},l=function(e){delete e.validate,delete e.preValidate,delete e.isValid},c=function(e){f(this.view,e,this.options)},h=function(e){l(e)};return{version:"0.6.3",configure:function(n){e.extend(t,n)},bind:function(n,i){var s=n.model,o=n.collection;i=e.extend({},t,r,i);if(typeof s=="undefined"&&typeof o=="undefined")throw"Before you execute the binding your view must have a model or a collection.\nSee http://thedersen.com/projects/backbone-validation/#using-form-model-validation for more information.";s&&f(n,s,i),o&&(o.each(function(e){f(n,e,i)}),o.bind("add",c,{view:n,options:i}),o.bind("remove",h))},unbind:function(e){var t=e.model,n=e.collection;t&&l(e.model),n&&(n.each(function(e){l(e)}),n.unbind("add",c),n.unbind("remove",h))},mixin:a(null,t)}}(),r=n.callbacks={valid:function(e,t,n){e.$("["+n+"~="+t+"]").removeClass("invalid").removeAttr("data-error")},invalid:function(e,t,n,r){e.$("["+r+"~="+t+"]").addClass("invalid").attr("data-error",n)}},i=n.patterns={digits:/^\d+$/,number:/^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/,email:/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i,url:/^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i},s=n.messages={required:"{0} is required",acceptance:"{0} must be accepted",min:"{0} must be greater than or equal to {1}",max:"{0} must be less than or equal to {1}",range:"{0} must be between {1} and {2}",length:"{0} must be {1} characters",minLength:"{0} must be at least {1} characters",maxLength:"{0} must be at most {1} characters",rangeLength:"{0} must be between {1} and {2} characters",oneOf:"{0} must be one of: {1}",equalTo:"{0} must be the same as {1}",pattern:"{0} must be a valid {1}"},o=n.labelFormatters={none:function(e){return e},sentenceCase:function(e){return e.replace(/(?:^\w|[A-Z]|\b\w)/g,function(e,t){return t===0?e.toUpperCase():" "+e.toLowerCase()}).replace("_"," ")},label:function(e,t){return t.labels&&t.labels[e]||o.sentenceCase(e,t)}},u=n.validators=function(){var n=String.prototype.trim?function(e){return e===null?"":String.prototype.trim.call(e)}:function(e){var t=/^\s+/,n=/\s+$/;return e===null?"":e.toString().replace(t,"").replace(n,"")},r=function(e,n){return o[t.labelFormatter](e,n)},u=function(){var e=Array.prototype.slice.call(arguments),t=e.shift();return t.replace(/\{(\d+)\}/g,function(t,n){return typeof e[n]!="undefined"?e[n]:t})},a=function(t){return e.isNumber(t)||e.isString(t)&&t.match(i.number)},f=function(t){return!(e.isNull(t)||e.isUndefined(t)||e.isString(t)&&n(t)==="")};return{fn:function(t,n,r,i,s){return e.isString(r)&&(r=i[r]),r.call(i,t,n,s)},required:function(t,n,i,o,a){var l=e.isFunction(i)?i.call(o,t,n,a):i;if(!l&&!f(t))return!1;if(l&&!f(t))return u(s.required,r(n,o))},acceptance:function(t,n,i,o){if(t!=="true"&&(!e.isBoolean(t)||t===!1))return u(s.acceptance,r(n,o))},min:function(e,t,n,i){if(!a(e)||en)return u(s.max,r(t,i),n)},range:function(e,t,n,i){if(!a(e)||en[1])return u(s.range,r(t,i),n[0],n[1])},length:function(e,t,i,o){if(!f(e)||n(e).length!==i)return u(s.length,r(t,o),i)},minLength:function(e,t,i,o){if(!f(e)||n(e).lengthi)return u(s.maxLength,r(t,o),i)},rangeLength:function(e,t,i,o){if(!f(e)||n(e).lengthi[1])return u(s.rangeLength,r(t,o),i[0],i[1])},oneOf:function(t,n,i,o){if(!e.include(i,t))return u(s.oneOf,r(n,o),i.join(", "))},equalTo:function(e,t,n,i,o){if(e!==o[n])return u(s.equalTo,r(t,i),r(n,i))},pattern:function(e,t,n,o){if(!f(e)||!e.toString().match(i[n]||n))return u(s.pattern,r(t,o),n)}}}();return n}(_); \ No newline at end of file diff --git a/dist/backbone-validation.js b/dist/backbone-validation.js index b295ea0c..6d1a6b53 100644 --- a/dist/backbone-validation.js +++ b/dist/backbone-validation.js @@ -1,4 +1,4 @@ -// Backbone.Validation v0.6.2 +// Backbone.Validation v0.6.3 // // Copyright (c) 2011-2012 Thomas Pedersen // Distributed under MIT License @@ -150,24 +150,32 @@ Backbone.Validation = (function(_){ var model = this, validateAll = !attrs, opt = _.extend({}, options, setOptions), - allAttrs = _.extend(getValidatedAttrs(model), model.attributes, attrs), + validatedAttrs = getValidatedAttrs(model), + allAttrs = _.extend({}, validatedAttrs, model.attributes, attrs), changedAttrs = attrs || allAttrs, result = validateModel(model, allAttrs); model._isValid = result.isValid; // After validation is performed, loop through all changed attributes - // and call either the valid or invalid callback so the view is updated. - for(var attr in allAttrs) { + // and call the valid callbacks so the view is updated. + _.each(_.keys(validatedAttrs), function(attr){ + var invalid = result.invalidAttrs.hasOwnProperty(attr); + if(!invalid){ + opt.valid(view, attr, opt.selector); + } + }); + + // After validation is performed, loop through all changed attributes + // and call the invalid callback so the view is updated. + _.each(_.keys(validatedAttrs), function(attr){ var invalid = result.invalidAttrs.hasOwnProperty(attr), changed = changedAttrs.hasOwnProperty(attr); + if(invalid && (changed || validateAll)){ opt.invalid(view, attr, result.invalidAttrs[attr], opt.selector); } - if(!invalid){ - opt.valid(view, attr, opt.selector); - } - } + }); // Trigger validated events. // Need to defer this so the model is actually updated before @@ -215,7 +223,7 @@ Backbone.Validation = (function(_){ return { // Current version of the library - version: '0.6.2', + version: '0.6.3', // Called to configure the default options configure: function(options) { @@ -373,7 +381,7 @@ Backbone.Validation = (function(_){ // } // }); label: function(attrName, model) { - return model.labels[attrName] || defaultLabelFormatters.sentenceCase(attrName, model); + return (model.labels && model.labels[attrName]) || defaultLabelFormatters.sentenceCase(attrName, model); } }; diff --git a/package.json b/package.json index bdd0ae8f..3c6d13be 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "backbone-validation", "title": "Backbone.Validation", - "version": "0.6.2", + "version": "0.6.3", "author": { "name": "Thomas Pedersen", "url": "http://thedersen.com/" diff --git a/src/backbone-validation.js b/src/backbone-validation.js index f17059ab..2812b1a5 100644 --- a/src/backbone-validation.js +++ b/src/backbone-validation.js @@ -215,7 +215,7 @@ Backbone.Validation = (function(_){ return { // Current version of the library - version: '0.6.2', + version: '0.6.3', // Called to configure the default options configure: function(options) {