From dc5ae8ec3b914a494280354240896ef76a3fea39 Mon Sep 17 00:00:00 2001 From: jekuno Date: Mon, 14 Dec 2015 06:41:34 +0100 Subject: [PATCH] Adds support for dynamic values for all built in validators e.g. dynamic ranges for the range validator. --- dist/backbone-validation-amd-min.js | 2 +- dist/backbone-validation-amd.js | 85 ++++++++++++++++++++--------- dist/backbone-validation-min.js | 2 +- dist/backbone-validation.js | 85 ++++++++++++++++++++--------- src/backbone-validation.js | 85 ++++++++++++++++++++--------- 5 files changed, 182 insertions(+), 77 deletions(-) diff --git a/dist/backbone-validation-amd-min.js b/dist/backbone-validation-amd-min.js index d3a75333..6327284f 100644 --- a/dist/backbone-validation-amd-min.js +++ b/dist/backbone-validation-amd-min.js @@ -5,4 +5,4 @@ // // Documentation and full license available at: // http://thedersen.com/projects/backbone-validation -!function(a){"object"==typeof exports?module.exports=a(require("backbone"),require("underscore")):"function"==typeof define&&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={formatLabel:function(a,c){return i[b.labelFormatter](a,c)},format:function(){var a=Array.prototype.slice.call(arguments),b=a.shift();return b.replace(/\{(\d+)\}/g,function(b,c){return"undefined"!=typeof a[c]?a[c]:b})}},d=function(b,c,e){return c=c||{},e=e||"",a.each(b,function(f,g){b.hasOwnProperty(g)&&(f&&a.isArray(f)?a.forEach(f,function(a,b){d(a,c,e+g+"."+b+"."),c[e+g+"."+b]=a}):f&&"object"==typeof f&&f.constructor===Object&&d(f,c,e+g+"."),c[e+g]=f)}),c},e=function(){var e=function(b,c){return c=c||a.keys(a.result(b,"validation")||{}),a.reduce(c,function(a,b){return a[b]=void 0,a},{})},g=function(b,c){var d=b.attributes;return a.isFunction(d)?d=d(c):a.isString(d)&&a.isFunction(j[d])&&(d=j[d](c)),a.isArray(d)?d:void 0},h=function(b,c){var d=b.validation?a.result(b,"validation")[c]||{}:{};return(a.isFunction(d)||a.isString(d))&&(d={fn:d}),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:k[a],val:c[a],msg:c.msg})}),b},[])},i=function(b,d,e,f){return a.reduce(h(b,d),function(g,h){var i=a.extend({},c,k),j=h.fn.call(i,e,d,h.val,b,f);return j===!1||g===!1?!1:j&&!g?a.result(h,"msg")||j:g},"")},l=function(b,c,d){var e,f={},g=!0,h=a.clone(c);return a.each(d,function(a,c){e=i(b,c,a,h),e&&(f[c]=e,g=!1)}),{invalidAttrs:f,isValid:g}},m=function(b,c){return{preValidate:function(b,c){var d,e=this,f={};return a.isObject(b)?(a.each(b,function(a,b){d=e.preValidate(b,a),d&&(f[b]=d)}),a.isEmpty(f)?void 0:f):i(this,b,c,a.extend({},this.attributes))},isValid:function(e){var f,h,j,k;return e=e||g(c,b),a.isString(e)?h=[e]:a.isArray(e)&&(h=e),h&&(f=d(this.attributes),a.each(this.associatedViews,function(b){a.each(h,function(d){j=i(this,d,f[d],a.extend({},this.attributes)),j?(c.invalid(b,d,j,c.selector),k=k||{},k[d]=j):c.valid(b,d,c.selector)},this)},this)),e===!0&&(k=this.validate()),k&&this.trigger("invalid",this,k,{validationError:k}),h?!k:this.validation?this._isValid:!0},validate:function(f,h){var i=this,j=!f,k=a.extend({},c,h),m=e(i,g(c,b)),n=a.extend({},m,i.attributes,f),o=d(n),p=f?d(f):o,q=l(i,n,a.pick(o,a.keys(m)));return i._isValid=q.isValid,a.each(i.associatedViews,function(b){a.each(m,function(a,c){var d=q.invalidAttrs.hasOwnProperty(c),e=p.hasOwnProperty(c);d||k.valid(b,c,k.selector),d&&(e||j)&&k.invalid(b,c,q.invalidAttrs[c],k.selector)})}),a.defer(function(){i.trigger("validated",i._isValid,i,q.invalidAttrs),i.trigger("validated:"+(i._isValid?"valid":"invalid"),i,q.invalidAttrs)}),!k.forceUpdate&&a.intersection(a.keys(q.invalidAttrs),a.keys(p)).length>0?q.invalidAttrs:void 0}}},n=function(b,c,d){c.associatedViews?c.associatedViews.push(b):c.associatedViews=[b],a.extend(c,m(b,d))},o=function(b,c){c&&b.associatedViews&&b.associatedViews.length>1?b.associatedViews=a.without(b.associatedViews,c):(delete b.validate,delete b.preValidate,delete b.isValid,delete b.associatedViews)},p=function(a){n(this.view,a,this.options)},q=function(a){o(a)};return{version:"0.11.3",configure:function(c){a.extend(b,c)},bind:function(c,d){d=a.extend({},b,f,d);var e=d.model||c.model,g=d.collection||c.collection;if("undefined"==typeof e&&"undefined"==typeof g)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.";e?n(c,e,d):g&&(g.each(function(a){n(c,a,d)}),g.bind("add",p,{view:c,options:d}),g.bind("remove",q))},unbind:function(b,c){c=a.extend({},c);var d=c.model||b.model,e=c.collection||b.collection;d?o(d,b):e&&(e.each(function(a){o(a,b)}),e.unbind("add",p),e.unbind("remove",q))},mixin:m(null,b)}}(),f=e.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)}},g=e.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},h=e.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}",digits:"{0} must only contain digits",number:"{0} must be a number",email:"{0} must be a valid email",url:"{0} must be a valid url",inlinePattern:"{0} is invalid"},i=e.labelFormatters={none:function(a){return a},sentenceCase:function(a){return a.replace(/(?:^\w|[A-Z]|\b\w)/g,function(a,b){return 0===b?a.toUpperCase():" "+a.toLowerCase()}).replace(/_/g," ")},label:function(a,b){return b.labels&&b.labels[a]||i.sentenceCase(a,b)}},j=e.attributeLoaders={inputNames:function(a){var b=[];return a&&a.$("form [name]").each(function(){/^(?:input|select|textarea)$/i.test(this.nodeName)&&this.name&&"submit"!==this.type&&-1===b.indexOf(this.name)&&b.push(this.name)}),b}},k=e.validators=function(){var b=String.prototype.trim?function(a){return null===a?"":String.prototype.trim.call(a)}:function(a){var b=/^\s+/,c=/\s+$/;return null===a?"":a.toString().replace(b,"").replace(c,"")},c=function(b){return a.isNumber(b)||a.isString(b)&&b.match(g.number)},d=function(c){return!(a.isNull(c)||a.isUndefined(c)||a.isString(c)&&""===b(c)||a.isArray(c)&&a.isEmpty(c))};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,f,g){var i=a.isFunction(e)?e.call(f,b,c,g):e;return i||d(b)?i&&!d(b)?this.format(h.required,this.formatLabel(c,f)):void 0:!1},acceptance:function(b,c,d,e){return"true"===b||a.isBoolean(b)&&b!==!1?void 0:this.format(h.acceptance,this.formatLabel(c,e))},min:function(a,b,d,e){return!c(a)||d>a?this.format(h.min,this.formatLabel(b,e),d):void 0},max:function(a,b,d,e){return!c(a)||a>d?this.format(h.max,this.formatLabel(b,e),d):void 0},range:function(a,b,d,e){return!c(a)||ad[1]?this.format(h.range,this.formatLabel(b,e),d[0],d[1]):void 0},length:function(b,c,d,e){return a.isString(b)&&b.length===d?void 0:this.format(h.length,this.formatLabel(c,e),d)},minLength:function(b,c,d,e){return!a.isString(b)||b.lengthd?this.format(h.maxLength,this.formatLabel(c,e),d):void 0},rangeLength:function(b,c,d,e){return!a.isString(b)||b.lengthd[1]?this.format(h.rangeLength,this.formatLabel(c,e),d[0],d[1]):void 0},oneOf:function(b,c,d,e){return a.include(d,b)?void 0:this.format(h.oneOf,this.formatLabel(c,e),d.join(", "))},equalTo:function(a,b,c,d,e){return a!==e[c]?this.format(h.equalTo,this.formatLabel(b,d),this.formatLabel(c,d)):void 0},pattern:function(a,b,c,e){return d(a)&&a.toString().match(g[c]||c)?void 0:this.format(h[c]||h.inlinePattern,this.formatLabel(b,e),c)}}}();return a.each(k,function(b,d){k[d]=a.bind(k[d],a.extend({},c,k))}),e}(b),a.Validation}); \ No newline at end of file +!function(a){"object"==typeof exports?module.exports=a(require("backbone"),require("underscore")):"function"==typeof define&&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={formatLabel:function(a,c){return j[b.labelFormatter](a,c)},format:function(){var a=Array.prototype.slice.call(arguments),b=a.shift();return b.replace(/\{(\d+)\}/g,function(b,c){return"undefined"!=typeof a[c]?a[c]:b})}},d=function(b,c,e){return c=c||{},e=e||"",a.each(b,function(f,g){b.hasOwnProperty(g)&&(f&&a.isArray(f)?a.forEach(f,function(a,b){d(a,c,e+g+"."+b+"."),c[e+g+"."+b]=a}):f&&"object"==typeof f&&f.constructor===Object&&d(f,c,e+g+"."),c[e+g]=f)}),c},e=function(b,c,d,e,f){var g;return g=a.isFunction(b)?b.call(c,d,e,f):b},f=function(){var e=function(b,c){return c=c||a.keys(a.result(b,"validation")||{}),a.reduce(c,function(a,b){return a[b]=void 0,a},{})},f=function(b,c){var d=b.attributes;return a.isFunction(d)?d=d(c):a.isString(d)&&a.isFunction(k[d])&&(d=k[d](c)),a.isArray(d)?d:void 0},h=function(b,c){var d=b.validation?a.result(b,"validation")[c]||{}:{};return(a.isFunction(d)||a.isString(d))&&(d={fn:d}),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:l[a],val:c[a],msg:c.msg})}),b},[])},i=function(b,d,e,f){return a.reduce(h(b,d),function(g,h){var i=a.extend({},c,l),j=h.fn.call(i,e,d,h.val,b,f);return j===!1||g===!1?!1:j&&!g?a.result(h,"msg")||j:g},"")},j=function(b,c,d){var e,f={},g=!0,h=a.clone(c);return a.each(d,function(a,c){e=i(b,c,a,h),e&&(f[c]=e,g=!1)}),{invalidAttrs:f,isValid:g}},m=function(b,c){return{preValidate:function(b,c){var d,e=this,f={};return a.isObject(b)?(a.each(b,function(a,b){d=e.preValidate(b,a),d&&(f[b]=d)}),a.isEmpty(f)?void 0:f):i(this,b,c,a.extend({},this.attributes))},isValid:function(e){var g,h,j,k;return e=e||f(c,b),a.isString(e)?h=[e]:a.isArray(e)&&(h=e),h&&(g=d(this.attributes),a.each(this.associatedViews,function(b){a.each(h,function(d){j=i(this,d,g[d],a.extend({},this.attributes)),j?(c.invalid(b,d,j,c.selector),k=k||{},k[d]=j):c.valid(b,d,c.selector)},this)},this)),e===!0&&(k=this.validate()),k&&this.trigger("invalid",this,k,{validationError:k}),h?!k:this.validation?this._isValid:!0},validate:function(g,h){var i=this,k=!g,l=a.extend({},c,h),m=e(i,f(c,b)),n=a.extend({},m,i.attributes,g),o=d(n),p=g?d(g):o,q=j(i,n,a.pick(o,a.keys(m)));return i._isValid=q.isValid,a.each(i.associatedViews,function(b){a.each(m,function(a,c){var d=q.invalidAttrs.hasOwnProperty(c),e=p.hasOwnProperty(c);d||l.valid(b,c,l.selector),d&&(e||k)&&l.invalid(b,c,q.invalidAttrs[c],l.selector)})}),a.defer(function(){i.trigger("validated",i._isValid,i,q.invalidAttrs),i.trigger("validated:"+(i._isValid?"valid":"invalid"),i,q.invalidAttrs)}),!l.forceUpdate&&a.intersection(a.keys(q.invalidAttrs),a.keys(p)).length>0?q.invalidAttrs:void 0}}},n=function(b,c,d){c.associatedViews?c.associatedViews.push(b):c.associatedViews=[b],a.extend(c,m(b,d))},o=function(b,c){c&&b.associatedViews&&b.associatedViews.length>1?b.associatedViews=a.without(b.associatedViews,c):(delete b.validate,delete b.preValidate,delete b.isValid,delete b.associatedViews)},p=function(a){n(this.view,a,this.options)},q=function(a){o(a)};return{version:"0.11.3",configure:function(c){a.extend(b,c)},bind:function(c,d){d=a.extend({},b,g,d);var e=d.model||c.model,f=d.collection||c.collection;if("undefined"==typeof e&&"undefined"==typeof f)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.";e?n(c,e,d):f&&(f.each(function(a){n(c,a,d)}),f.bind("add",p,{view:c,options:d}),f.bind("remove",q))},unbind:function(b,c){c=a.extend({},c);var d=c.model||b.model,e=c.collection||b.collection;d?o(d,b):e&&(e.each(function(a){o(a,b)}),e.unbind("add",p),e.unbind("remove",q))},mixin:m(null,b)}}(),g=f.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)}},h=f.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},i=f.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}",digits:"{0} must only contain digits",number:"{0} must be a number",email:"{0} must be a valid email",url:"{0} must be a valid url",inlinePattern:"{0} is invalid"},j=f.labelFormatters={none:function(a){return a},sentenceCase:function(a){return a.replace(/(?:^\w|[A-Z]|\b\w)/g,function(a,b){return 0===b?a.toUpperCase():" "+a.toLowerCase()}).replace(/_/g," ")},label:function(a,b){return b.labels&&b.labels[a]||j.sentenceCase(a,b)}},k=f.attributeLoaders={inputNames:function(a){var b=[];return a&&a.$("form [name]").each(function(){/^(?:input|select|textarea)$/i.test(this.nodeName)&&this.name&&"submit"!==this.type&&-1===b.indexOf(this.name)&&b.push(this.name)}),b}},l=f.validators=function(){var b=String.prototype.trim?function(a){return null===a?"":String.prototype.trim.call(a)}:function(a){var b=/^\s+/,c=/\s+$/;return null===a?"":a.toString().replace(b,"").replace(c,"")},c=function(b){return a.isNumber(b)||a.isString(b)&&b.match(h.number)},d=function(c){return!(a.isNull(c)||a.isUndefined(c)||a.isString(c)&&""===b(c)||a.isArray(c)&&a.isEmpty(c))};return{fn:function(b,c,d,e,f){return a.isString(d)&&(d=e[d]),d.call(e,b,c,f)},required:function(a,b,c,f,g){var h=e(c,f,a,b,g);return h||d(a)?h&&!d(a)?this.format(i.required,this.formatLabel(b,f)):void 0:!1},acceptance:function(b,c,d,f,g){var h=e(d,f,b,c,g);return!h||"true"===b||a.isBoolean(b)&&b!==!1?void 0:this.format(i.acceptance,this.formatLabel(c,f))},min:function(a,b,d,f,g){var h=e(d,f,a,b,g);return!c(a)||h>a?this.format(i.min,this.formatLabel(b,f),h):void 0},max:function(a,b,d,f,g){var h=e(d,f,a,b,g);return!c(a)||a>h?this.format(i.max,this.formatLabel(b,f),h):void 0},range:function(a,b,d,f,g){var h=e(d,f,a,b,g);return!c(a)||ah[1]?this.format(i.range,this.formatLabel(b,f),h[0],h[1]):void 0},length:function(b,c,d,f,g){var h=e(d,f,b,c,g);return a.isString(b)&&b.length===h?void 0:this.format(i.length,this.formatLabel(c,f),h)},minLength:function(b,c,d,f,g){var h=e(d,f,b,c,g);return!a.isString(b)||b.lengthh?this.format(i.maxLength,this.formatLabel(c,f),h):void 0},rangeLength:function(b,c,d,f,g){var h=e(d,f,b,c,g);return!a.isString(b)||b.lengthh[1]?this.format(i.rangeLength,this.formatLabel(c,f),h[0],h[1]):void 0},oneOf:function(b,c,d,f,g){var h=e(d,f,b,c,g);return a.include(h,b)?void 0:this.format(i.oneOf,this.formatLabel(c,f),h.join(", "))},equalTo:function(a,b,c,d,f){var g=e(c,d,a,b,f);return a!==f[g]?this.format(i.equalTo,this.formatLabel(b,d),this.formatLabel(g,d)):void 0},pattern:function(a,b,c,f,g){var j=e(c,f,a,b,g);return d(a)&&a.toString().match(h[j]||j)?void 0:this.format(i[j]||i.inlinePattern,this.formatLabel(b,f),j)}}}();return a.each(l,function(b,d){l[d]=a.bind(l[d],a.extend({},c,l))}),f}(b),a.Validation}); \ No newline at end of file diff --git a/dist/backbone-validation-amd.js b/dist/backbone-validation-amd.js index 6f90e318..e1c1cc27 100644 --- a/dist/backbone-validation-amd.js +++ b/dist/backbone-validation-amd.js @@ -105,6 +105,18 @@ return into; }; + + // Returns plain value if any. Runs the provided function and returns its result otherwise. + var getFunctionParam = function (functionOrValue, model, value, attr, computed) { + var result; + if (_.isFunction(functionOrValue)) { + result = functionOrValue.call(model, value, attr, computed); + } else { + result = functionOrValue; + } + return result; + }; + // Validation // ---------- @@ -589,18 +601,18 @@ if(_.isString(fn)){ fn = model[fn]; } - return fn.call(model, value, attr, computed); + return fn.call(model, value, attr, computed); // makes model available as 'this' in the validation function }, // Required validator // Validates if the attribute is required or not - // This can be specified as either a boolean value or a function that returns a boolean value - required: function(value, attr, required, model, computed) { - var isRequired = _.isFunction(required) ? required.call(model, value, attr, computed) : required; - if(!isRequired && !hasValue(value)) { + // Whether the attribute is required can be specified as either a boolean value or a function that returns a boolean value + required: function(value, attr, requiredFnOrVal, model, computed) { + var required = getFunctionParam(requiredFnOrVal, model, value, attr, computed); + if (!required && !hasValue(value)) { return false; // overrides all other validators } - if (isRequired && !hasValue(value)) { + if (required && !hasValue(value)) { return this.format(defaultMessages.required, this.formatLabel(attr, model)); } }, @@ -608,8 +620,10 @@ // Acceptance validator // Validates that something has to be accepted, e.g. terms of use // `true` or 'true' are valid - acceptance: function(value, attr, accept, model) { - if(value !== 'true' && (!_.isBoolean(value) || value === false)) { + // Whether acceptance is required can be specified as either a boolean value or a function that returns a boolean value + acceptance: function(value, attr, acceptFnOrVal, model, computed) { + var accept = getFunctionParam(acceptFnOrVal, model, value, attr, computed); + if (accept && value !== 'true' && (!_.isBoolean(value) || value === false)) { return this.format(defaultMessages.acceptance, this.formatLabel(attr, model)); } }, @@ -617,7 +631,9 @@ // Min validator // Validates that the value has to be a number and equal to or greater than // the min value specified - min: function(value, attr, minValue, model) { + // The min value can be specified either directly or as a function which returns the value. + min: function(value, attr, minFnOrVal, model, computed) { + var minValue = getFunctionParam(minFnOrVal, model, value, attr, computed); if (!isNumber(value) || value < minValue) { return this.format(defaultMessages.min, this.formatLabel(attr, model), minValue); } @@ -626,7 +642,9 @@ // Max validator // Validates that the value has to be a number and equal to or less than // the max value specified - max: function(value, attr, maxValue, model) { + // The max value can be specified either directly or as a function which returns the value. + max: function(value, attr, maxFnOrVal, model, computed) { + var maxValue = getFunctionParam(maxFnOrVal, model, value, attr, computed); if (!isNumber(value) || value > maxValue) { return this.format(defaultMessages.max, this.formatLabel(attr, model), maxValue); } @@ -634,9 +652,11 @@ // Range validator // Validates that the value has to be a number and equal to or between - // the two numbers specified - range: function(value, attr, range, model) { - if(!isNumber(value) || value < range[0] || value > range[1]) { + // the two numbers specified as an array + // The range value can be specified either directly or as a function which returns the value. + range: function(value, attr, rangeFnOrVal, model, computed) { + var range = getFunctionParam(rangeFnOrVal, model, value, attr, computed); + if (!isNumber(value) || value < range[0] || value > range[1]) { return this.format(defaultMessages.range, this.formatLabel(attr, model), range[0], range[1]); } }, @@ -644,7 +664,9 @@ // Length validator // Validates that the value has to be a string with length equal to // the length value specified - length: function(value, attr, length, model) { + // The length value can be specified either directly or as a function which returns the value. + length: function(value, attr, lengthFnOrVal, model, computed) { + var length = getFunctionParam(lengthFnOrVal, model, value, attr, computed); if (!_.isString(value) || value.length !== length) { return this.format(defaultMessages.length, this.formatLabel(attr, model), length); } @@ -653,7 +675,9 @@ // Min length validator // Validates that the value has to be a string with length equal to or greater than // the min length value specified - minLength: function(value, attr, minLength, model) { + // The min length value can be specified either directly or as a function which returns the value. + minLength: function(value, attr, minLengthFnOrVal, model, computed) { + var minLength = getFunctionParam(minLengthFnOrVal, model, value, attr, computed); if (!_.isString(value) || value.length < minLength) { return this.format(defaultMessages.minLength, this.formatLabel(attr, model), minLength); } @@ -662,16 +686,20 @@ // Max length validator // Validates that the value has to be a string with length equal to or less than // the max length value specified - maxLength: function(value, attr, maxLength, model) { + // The max length value can be specified either directly or as a function which returns the value. + maxLength: function(value, attr, maxLengthFnOrVal, model, computed) { + var maxLength = getFunctionParam(maxLengthFnOrVal, model, value, attr, computed); if (!_.isString(value) || value.length > maxLength) { return this.format(defaultMessages.maxLength, this.formatLabel(attr, model), maxLength); } }, // Range length validator - // Validates that the value has to be a string and equal to or between - // the two numbers specified - rangeLength: function(value, attr, range, model) { + // Validates that the value has to be a string with a length equal to or between + // two numbers specified as an array + // The string length value can be specified either directly or as a function which returns the value. + rangeLength: function(value, attr, rangeFnOrVal, model, computed) { + var range = getFunctionParam(rangeFnOrVal, model, value, attr, computed); if (!_.isString(value) || value.length < range[0] || value.length > range[1]) { return this.format(defaultMessages.rangeLength, this.formatLabel(attr, model), range[0], range[1]); } @@ -680,8 +708,10 @@ // One of validator // Validates that the value has to be equal to one of the elements in // the specified array. Case sensitive matching - oneOf: function(value, attr, values, model) { - if(!_.include(values, value)){ + // The array of values can be specified either directly or as a function which returns the array. + oneOf: function(value, attr, fnOrValues, model, computed) { + var values = getFunctionParam(fnOrValues, model, value, attr, computed); + if (!_.include(values, value)) { return this.format(defaultMessages.oneOf, this.formatLabel(attr, model), values.join(', ')); } }, @@ -689,8 +719,11 @@ // Equal to validator // Validates that the value has to be equal to the value of the attribute // with the name specified - equalTo: function(value, attr, equalTo, model, computed) { - if(value !== computed[equalTo]) { + // The name of the corresponding attribute can be specified either directly or as a function which returns + // the attribute name. + equalTo: function(value, attr, equalToFnOrVal, model, computed) { + var equalTo = getFunctionParam(equalToFnOrVal, model, value, attr, computed); + if (value !== computed[equalTo]) { return this.format(defaultMessages.equalTo, this.formatLabel(attr, model), this.formatLabel(equalTo, model)); } }, @@ -698,7 +731,9 @@ // Pattern validator // Validates that the value has to match the pattern specified. // Can be a regular expression or the name of one of the built in patterns - pattern: function(value, attr, pattern, model) { + // The pattern can be specified either directly or as a function which returns the pattern. + pattern: function(value, attr, patternFnOrVal, model, computed) { + var pattern = getFunctionParam(patternFnOrVal, model, value, attr, computed); if (!hasValue(value) || !value.toString().match(defaultPatterns[pattern] || pattern)) { return this.format(defaultMessages[pattern] || defaultMessages.inlinePattern, this.formatLabel(attr, model), pattern); } @@ -708,7 +743,7 @@ // Set the correct context for all validators // when used from within a method validator - _.each(defaultValidators, function(validator, key){ + _.each(defaultValidators, function(validator, key) { defaultValidators[key] = _.bind(defaultValidators[key], _.extend({}, formatFunctions, defaultValidators)); }); diff --git a/dist/backbone-validation-min.js b/dist/backbone-validation-min.js index c7c264d5..657a2ee1 100644 --- a/dist/backbone-validation-min.js +++ b/dist/backbone-validation-min.js @@ -5,4 +5,4 @@ // // 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={formatLabel:function(a,c){return i[b.labelFormatter](a,c)},format:function(){var a=Array.prototype.slice.call(arguments),b=a.shift();return b.replace(/\{(\d+)\}/g,function(b,c){return"undefined"!=typeof a[c]?a[c]:b})}},d=function(b,c,e){return c=c||{},e=e||"",a.each(b,function(f,g){b.hasOwnProperty(g)&&(f&&a.isArray(f)?a.forEach(f,function(a,b){d(a,c,e+g+"."+b+"."),c[e+g+"."+b]=a}):f&&"object"==typeof f&&f.constructor===Object&&d(f,c,e+g+"."),c[e+g]=f)}),c},e=function(){var e=function(b,c){return c=c||a.keys(a.result(b,"validation")||{}),a.reduce(c,function(a,b){return a[b]=void 0,a},{})},g=function(b,c){var d=b.attributes;return a.isFunction(d)?d=d(c):a.isString(d)&&a.isFunction(j[d])&&(d=j[d](c)),a.isArray(d)?d:void 0},h=function(b,c){var d=b.validation?a.result(b,"validation")[c]||{}:{};return(a.isFunction(d)||a.isString(d))&&(d={fn:d}),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:k[a],val:c[a],msg:c.msg})}),b},[])},i=function(b,d,e,f){return a.reduce(h(b,d),function(g,h){var i=a.extend({},c,k),j=h.fn.call(i,e,d,h.val,b,f);return j===!1||g===!1?!1:j&&!g?a.result(h,"msg")||j:g},"")},l=function(b,c,d){var e,f={},g=!0,h=a.clone(c);return a.each(d,function(a,c){e=i(b,c,a,h),e&&(f[c]=e,g=!1)}),{invalidAttrs:f,isValid:g}},m=function(b,c){return{preValidate:function(b,c){var d,e=this,f={};return a.isObject(b)?(a.each(b,function(a,b){d=e.preValidate(b,a),d&&(f[b]=d)}),a.isEmpty(f)?void 0:f):i(this,b,c,a.extend({},this.attributes))},isValid:function(e){var f,h,j,k;return e=e||g(c,b),a.isString(e)?h=[e]:a.isArray(e)&&(h=e),h&&(f=d(this.attributes),a.each(this.associatedViews,function(b){a.each(h,function(d){j=i(this,d,f[d],a.extend({},this.attributes)),j?(c.invalid(b,d,j,c.selector),k=k||{},k[d]=j):c.valid(b,d,c.selector)},this)},this)),e===!0&&(k=this.validate()),k&&this.trigger("invalid",this,k,{validationError:k}),h?!k:this.validation?this._isValid:!0},validate:function(f,h){var i=this,j=!f,k=a.extend({},c,h),m=e(i,g(c,b)),n=a.extend({},m,i.attributes,f),o=d(n),p=f?d(f):o,q=l(i,n,a.pick(o,a.keys(m)));return i._isValid=q.isValid,a.each(i.associatedViews,function(b){a.each(m,function(a,c){var d=q.invalidAttrs.hasOwnProperty(c),e=p.hasOwnProperty(c);d||k.valid(b,c,k.selector),d&&(e||j)&&k.invalid(b,c,q.invalidAttrs[c],k.selector)})}),a.defer(function(){i.trigger("validated",i._isValid,i,q.invalidAttrs),i.trigger("validated:"+(i._isValid?"valid":"invalid"),i,q.invalidAttrs)}),!k.forceUpdate&&a.intersection(a.keys(q.invalidAttrs),a.keys(p)).length>0?q.invalidAttrs:void 0}}},n=function(b,c,d){c.associatedViews?c.associatedViews.push(b):c.associatedViews=[b],a.extend(c,m(b,d))},o=function(b,c){c&&b.associatedViews&&b.associatedViews.length>1?b.associatedViews=a.without(b.associatedViews,c):(delete b.validate,delete b.preValidate,delete b.isValid,delete b.associatedViews)},p=function(a){n(this.view,a,this.options)},q=function(a){o(a)};return{version:"0.11.3",configure:function(c){a.extend(b,c)},bind:function(c,d){d=a.extend({},b,f,d);var e=d.model||c.model,g=d.collection||c.collection;if("undefined"==typeof e&&"undefined"==typeof g)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.";e?n(c,e,d):g&&(g.each(function(a){n(c,a,d)}),g.bind("add",p,{view:c,options:d}),g.bind("remove",q))},unbind:function(b,c){c=a.extend({},c);var d=c.model||b.model,e=c.collection||b.collection;d?o(d,b):e&&(e.each(function(a){o(a,b)}),e.unbind("add",p),e.unbind("remove",q))},mixin:m(null,b)}}(),f=e.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)}},g=e.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},h=e.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}",digits:"{0} must only contain digits",number:"{0} must be a number",email:"{0} must be a valid email",url:"{0} must be a valid url",inlinePattern:"{0} is invalid"},i=e.labelFormatters={none:function(a){return a},sentenceCase:function(a){return a.replace(/(?:^\w|[A-Z]|\b\w)/g,function(a,b){return 0===b?a.toUpperCase():" "+a.toLowerCase()}).replace(/_/g," ")},label:function(a,b){return b.labels&&b.labels[a]||i.sentenceCase(a,b)}},j=e.attributeLoaders={inputNames:function(a){var b=[];return a&&a.$("form [name]").each(function(){/^(?:input|select|textarea)$/i.test(this.nodeName)&&this.name&&"submit"!==this.type&&-1===b.indexOf(this.name)&&b.push(this.name)}),b}},k=e.validators=function(){var b=String.prototype.trim?function(a){return null===a?"":String.prototype.trim.call(a)}:function(a){var b=/^\s+/,c=/\s+$/;return null===a?"":a.toString().replace(b,"").replace(c,"")},c=function(b){return a.isNumber(b)||a.isString(b)&&b.match(g.number)},d=function(c){return!(a.isNull(c)||a.isUndefined(c)||a.isString(c)&&""===b(c)||a.isArray(c)&&a.isEmpty(c))};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,f,g){var i=a.isFunction(e)?e.call(f,b,c,g):e;return i||d(b)?i&&!d(b)?this.format(h.required,this.formatLabel(c,f)):void 0:!1},acceptance:function(b,c,d,e){return"true"===b||a.isBoolean(b)&&b!==!1?void 0:this.format(h.acceptance,this.formatLabel(c,e))},min:function(a,b,d,e){return!c(a)||d>a?this.format(h.min,this.formatLabel(b,e),d):void 0},max:function(a,b,d,e){return!c(a)||a>d?this.format(h.max,this.formatLabel(b,e),d):void 0},range:function(a,b,d,e){return!c(a)||ad[1]?this.format(h.range,this.formatLabel(b,e),d[0],d[1]):void 0},length:function(b,c,d,e){return a.isString(b)&&b.length===d?void 0:this.format(h.length,this.formatLabel(c,e),d)},minLength:function(b,c,d,e){return!a.isString(b)||b.lengthd?this.format(h.maxLength,this.formatLabel(c,e),d):void 0},rangeLength:function(b,c,d,e){return!a.isString(b)||b.lengthd[1]?this.format(h.rangeLength,this.formatLabel(c,e),d[0],d[1]):void 0},oneOf:function(b,c,d,e){return a.include(d,b)?void 0:this.format(h.oneOf,this.formatLabel(c,e),d.join(", "))},equalTo:function(a,b,c,d,e){return a!==e[c]?this.format(h.equalTo,this.formatLabel(b,d),this.formatLabel(c,d)):void 0},pattern:function(a,b,c,e){return d(a)&&a.toString().match(g[c]||c)?void 0:this.format(h[c]||h.inlinePattern,this.formatLabel(b,e),c)}}}();return a.each(k,function(b,d){k[d]=a.bind(k[d],a.extend({},c,k))}),e}(_); \ No newline at end of file +Backbone.Validation=function(a){"use strict";var b={forceUpdate:!1,selector:"name",labelFormatter:"sentenceCase",valid:Function.prototype,invalid:Function.prototype},c={formatLabel:function(a,c){return j[b.labelFormatter](a,c)},format:function(){var a=Array.prototype.slice.call(arguments),b=a.shift();return b.replace(/\{(\d+)\}/g,function(b,c){return"undefined"!=typeof a[c]?a[c]:b})}},d=function(b,c,e){return c=c||{},e=e||"",a.each(b,function(f,g){b.hasOwnProperty(g)&&(f&&a.isArray(f)?a.forEach(f,function(a,b){d(a,c,e+g+"."+b+"."),c[e+g+"."+b]=a}):f&&"object"==typeof f&&f.constructor===Object&&d(f,c,e+g+"."),c[e+g]=f)}),c},e=function(b,c,d,e,f){var g;return g=a.isFunction(b)?b.call(c,d,e,f):b},f=function(){var e=function(b,c){return c=c||a.keys(a.result(b,"validation")||{}),a.reduce(c,function(a,b){return a[b]=void 0,a},{})},f=function(b,c){var d=b.attributes;return a.isFunction(d)?d=d(c):a.isString(d)&&a.isFunction(k[d])&&(d=k[d](c)),a.isArray(d)?d:void 0},h=function(b,c){var d=b.validation?a.result(b,"validation")[c]||{}:{};return(a.isFunction(d)||a.isString(d))&&(d={fn:d}),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:l[a],val:c[a],msg:c.msg})}),b},[])},i=function(b,d,e,f){return a.reduce(h(b,d),function(g,h){var i=a.extend({},c,l),j=h.fn.call(i,e,d,h.val,b,f);return j===!1||g===!1?!1:j&&!g?a.result(h,"msg")||j:g},"")},j=function(b,c,d){var e,f={},g=!0,h=a.clone(c);return a.each(d,function(a,c){e=i(b,c,a,h),e&&(f[c]=e,g=!1)}),{invalidAttrs:f,isValid:g}},m=function(b,c){return{preValidate:function(b,c){var d,e=this,f={};return a.isObject(b)?(a.each(b,function(a,b){d=e.preValidate(b,a),d&&(f[b]=d)}),a.isEmpty(f)?void 0:f):i(this,b,c,a.extend({},this.attributes))},isValid:function(e){var g,h,j,k;return e=e||f(c,b),a.isString(e)?h=[e]:a.isArray(e)&&(h=e),h&&(g=d(this.attributes),a.each(this.associatedViews,function(b){a.each(h,function(d){j=i(this,d,g[d],a.extend({},this.attributes)),j?(c.invalid(b,d,j,c.selector),k=k||{},k[d]=j):c.valid(b,d,c.selector)},this)},this)),e===!0&&(k=this.validate()),k&&this.trigger("invalid",this,k,{validationError:k}),h?!k:this.validation?this._isValid:!0},validate:function(g,h){var i=this,k=!g,l=a.extend({},c,h),m=e(i,f(c,b)),n=a.extend({},m,i.attributes,g),o=d(n),p=g?d(g):o,q=j(i,n,a.pick(o,a.keys(m)));return i._isValid=q.isValid,a.each(i.associatedViews,function(b){a.each(m,function(a,c){var d=q.invalidAttrs.hasOwnProperty(c),e=p.hasOwnProperty(c);d||l.valid(b,c,l.selector),d&&(e||k)&&l.invalid(b,c,q.invalidAttrs[c],l.selector)})}),a.defer(function(){i.trigger("validated",i._isValid,i,q.invalidAttrs),i.trigger("validated:"+(i._isValid?"valid":"invalid"),i,q.invalidAttrs)}),!l.forceUpdate&&a.intersection(a.keys(q.invalidAttrs),a.keys(p)).length>0?q.invalidAttrs:void 0}}},n=function(b,c,d){c.associatedViews?c.associatedViews.push(b):c.associatedViews=[b],a.extend(c,m(b,d))},o=function(b,c){c&&b.associatedViews&&b.associatedViews.length>1?b.associatedViews=a.without(b.associatedViews,c):(delete b.validate,delete b.preValidate,delete b.isValid,delete b.associatedViews)},p=function(a){n(this.view,a,this.options)},q=function(a){o(a)};return{version:"0.11.3",configure:function(c){a.extend(b,c)},bind:function(c,d){d=a.extend({},b,g,d);var e=d.model||c.model,f=d.collection||c.collection;if("undefined"==typeof e&&"undefined"==typeof f)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.";e?n(c,e,d):f&&(f.each(function(a){n(c,a,d)}),f.bind("add",p,{view:c,options:d}),f.bind("remove",q))},unbind:function(b,c){c=a.extend({},c);var d=c.model||b.model,e=c.collection||b.collection;d?o(d,b):e&&(e.each(function(a){o(a,b)}),e.unbind("add",p),e.unbind("remove",q))},mixin:m(null,b)}}(),g=f.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)}},h=f.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},i=f.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}",digits:"{0} must only contain digits",number:"{0} must be a number",email:"{0} must be a valid email",url:"{0} must be a valid url",inlinePattern:"{0} is invalid"},j=f.labelFormatters={none:function(a){return a},sentenceCase:function(a){return a.replace(/(?:^\w|[A-Z]|\b\w)/g,function(a,b){return 0===b?a.toUpperCase():" "+a.toLowerCase()}).replace(/_/g," ")},label:function(a,b){return b.labels&&b.labels[a]||j.sentenceCase(a,b)}},k=f.attributeLoaders={inputNames:function(a){var b=[];return a&&a.$("form [name]").each(function(){/^(?:input|select|textarea)$/i.test(this.nodeName)&&this.name&&"submit"!==this.type&&-1===b.indexOf(this.name)&&b.push(this.name)}),b}},l=f.validators=function(){var b=String.prototype.trim?function(a){return null===a?"":String.prototype.trim.call(a)}:function(a){var b=/^\s+/,c=/\s+$/;return null===a?"":a.toString().replace(b,"").replace(c,"")},c=function(b){return a.isNumber(b)||a.isString(b)&&b.match(h.number)},d=function(c){return!(a.isNull(c)||a.isUndefined(c)||a.isString(c)&&""===b(c)||a.isArray(c)&&a.isEmpty(c))};return{fn:function(b,c,d,e,f){return a.isString(d)&&(d=e[d]),d.call(e,b,c,f)},required:function(a,b,c,f,g){var h=e(c,f,a,b,g);return h||d(a)?h&&!d(a)?this.format(i.required,this.formatLabel(b,f)):void 0:!1},acceptance:function(b,c,d,f,g){var h=e(d,f,b,c,g);return!h||"true"===b||a.isBoolean(b)&&b!==!1?void 0:this.format(i.acceptance,this.formatLabel(c,f))},min:function(a,b,d,f,g){var h=e(d,f,a,b,g);return!c(a)||h>a?this.format(i.min,this.formatLabel(b,f),h):void 0},max:function(a,b,d,f,g){var h=e(d,f,a,b,g);return!c(a)||a>h?this.format(i.max,this.formatLabel(b,f),h):void 0},range:function(a,b,d,f,g){var h=e(d,f,a,b,g);return!c(a)||ah[1]?this.format(i.range,this.formatLabel(b,f),h[0],h[1]):void 0},length:function(b,c,d,f,g){var h=e(d,f,b,c,g);return a.isString(b)&&b.length===h?void 0:this.format(i.length,this.formatLabel(c,f),h)},minLength:function(b,c,d,f,g){var h=e(d,f,b,c,g);return!a.isString(b)||b.lengthh?this.format(i.maxLength,this.formatLabel(c,f),h):void 0},rangeLength:function(b,c,d,f,g){var h=e(d,f,b,c,g);return!a.isString(b)||b.lengthh[1]?this.format(i.rangeLength,this.formatLabel(c,f),h[0],h[1]):void 0},oneOf:function(b,c,d,f,g){var h=e(d,f,b,c,g);return a.include(h,b)?void 0:this.format(i.oneOf,this.formatLabel(c,f),h.join(", "))},equalTo:function(a,b,c,d,f){var g=e(c,d,a,b,f);return a!==f[g]?this.format(i.equalTo,this.formatLabel(b,d),this.formatLabel(g,d)):void 0},pattern:function(a,b,c,f,g){var j=e(c,f,a,b,g);return d(a)&&a.toString().match(h[j]||j)?void 0:this.format(i[j]||i.inlinePattern,this.formatLabel(b,f),j)}}}();return a.each(l,function(b,d){l[d]=a.bind(l[d],a.extend({},c,l))}),f}(_); \ No newline at end of file diff --git a/dist/backbone-validation.js b/dist/backbone-validation.js index e1d10e10..b5232416 100644 --- a/dist/backbone-validation.js +++ b/dist/backbone-validation.js @@ -98,6 +98,18 @@ Backbone.Validation = (function(_){ return into; }; + + // Returns plain value if any. Runs the provided function and returns its result otherwise. + var getFunctionParam = function (functionOrValue, model, value, attr, computed) { + var result; + if (_.isFunction(functionOrValue)) { + result = functionOrValue.call(model, value, attr, computed); + } else { + result = functionOrValue; + } + return result; + }; + // Validation // ---------- @@ -582,18 +594,18 @@ Backbone.Validation = (function(_){ if(_.isString(fn)){ fn = model[fn]; } - return fn.call(model, value, attr, computed); + return fn.call(model, value, attr, computed); // makes model available as 'this' in the validation function }, // Required validator // Validates if the attribute is required or not - // This can be specified as either a boolean value or a function that returns a boolean value - required: function(value, attr, required, model, computed) { - var isRequired = _.isFunction(required) ? required.call(model, value, attr, computed) : required; - if(!isRequired && !hasValue(value)) { + // Whether the attribute is required can be specified as either a boolean value or a function that returns a boolean value + required: function(value, attr, requiredFnOrVal, model, computed) { + var required = getFunctionParam(requiredFnOrVal, model, value, attr, computed); + if (!required && !hasValue(value)) { return false; // overrides all other validators } - if (isRequired && !hasValue(value)) { + if (required && !hasValue(value)) { return this.format(defaultMessages.required, this.formatLabel(attr, model)); } }, @@ -601,8 +613,10 @@ Backbone.Validation = (function(_){ // Acceptance validator // Validates that something has to be accepted, e.g. terms of use // `true` or 'true' are valid - acceptance: function(value, attr, accept, model) { - if(value !== 'true' && (!_.isBoolean(value) || value === false)) { + // Whether acceptance is required can be specified as either a boolean value or a function that returns a boolean value + acceptance: function(value, attr, acceptFnOrVal, model, computed) { + var accept = getFunctionParam(acceptFnOrVal, model, value, attr, computed); + if (accept && value !== 'true' && (!_.isBoolean(value) || value === false)) { return this.format(defaultMessages.acceptance, this.formatLabel(attr, model)); } }, @@ -610,7 +624,9 @@ Backbone.Validation = (function(_){ // Min validator // Validates that the value has to be a number and equal to or greater than // the min value specified - min: function(value, attr, minValue, model) { + // The min value can be specified either directly or as a function which returns the value. + min: function(value, attr, minFnOrVal, model, computed) { + var minValue = getFunctionParam(minFnOrVal, model, value, attr, computed); if (!isNumber(value) || value < minValue) { return this.format(defaultMessages.min, this.formatLabel(attr, model), minValue); } @@ -619,7 +635,9 @@ Backbone.Validation = (function(_){ // Max validator // Validates that the value has to be a number and equal to or less than // the max value specified - max: function(value, attr, maxValue, model) { + // The max value can be specified either directly or as a function which returns the value. + max: function(value, attr, maxFnOrVal, model, computed) { + var maxValue = getFunctionParam(maxFnOrVal, model, value, attr, computed); if (!isNumber(value) || value > maxValue) { return this.format(defaultMessages.max, this.formatLabel(attr, model), maxValue); } @@ -627,9 +645,11 @@ Backbone.Validation = (function(_){ // Range validator // Validates that the value has to be a number and equal to or between - // the two numbers specified - range: function(value, attr, range, model) { - if(!isNumber(value) || value < range[0] || value > range[1]) { + // the two numbers specified as an array + // The range value can be specified either directly or as a function which returns the value. + range: function(value, attr, rangeFnOrVal, model, computed) { + var range = getFunctionParam(rangeFnOrVal, model, value, attr, computed); + if (!isNumber(value) || value < range[0] || value > range[1]) { return this.format(defaultMessages.range, this.formatLabel(attr, model), range[0], range[1]); } }, @@ -637,7 +657,9 @@ Backbone.Validation = (function(_){ // Length validator // Validates that the value has to be a string with length equal to // the length value specified - length: function(value, attr, length, model) { + // The length value can be specified either directly or as a function which returns the value. + length: function(value, attr, lengthFnOrVal, model, computed) { + var length = getFunctionParam(lengthFnOrVal, model, value, attr, computed); if (!_.isString(value) || value.length !== length) { return this.format(defaultMessages.length, this.formatLabel(attr, model), length); } @@ -646,7 +668,9 @@ Backbone.Validation = (function(_){ // Min length validator // Validates that the value has to be a string with length equal to or greater than // the min length value specified - minLength: function(value, attr, minLength, model) { + // The min length value can be specified either directly or as a function which returns the value. + minLength: function(value, attr, minLengthFnOrVal, model, computed) { + var minLength = getFunctionParam(minLengthFnOrVal, model, value, attr, computed); if (!_.isString(value) || value.length < minLength) { return this.format(defaultMessages.minLength, this.formatLabel(attr, model), minLength); } @@ -655,16 +679,20 @@ Backbone.Validation = (function(_){ // Max length validator // Validates that the value has to be a string with length equal to or less than // the max length value specified - maxLength: function(value, attr, maxLength, model) { + // The max length value can be specified either directly or as a function which returns the value. + maxLength: function(value, attr, maxLengthFnOrVal, model, computed) { + var maxLength = getFunctionParam(maxLengthFnOrVal, model, value, attr, computed); if (!_.isString(value) || value.length > maxLength) { return this.format(defaultMessages.maxLength, this.formatLabel(attr, model), maxLength); } }, // Range length validator - // Validates that the value has to be a string and equal to or between - // the two numbers specified - rangeLength: function(value, attr, range, model) { + // Validates that the value has to be a string with a length equal to or between + // two numbers specified as an array + // The string length value can be specified either directly or as a function which returns the value. + rangeLength: function(value, attr, rangeFnOrVal, model, computed) { + var range = getFunctionParam(rangeFnOrVal, model, value, attr, computed); if (!_.isString(value) || value.length < range[0] || value.length > range[1]) { return this.format(defaultMessages.rangeLength, this.formatLabel(attr, model), range[0], range[1]); } @@ -673,8 +701,10 @@ Backbone.Validation = (function(_){ // One of validator // Validates that the value has to be equal to one of the elements in // the specified array. Case sensitive matching - oneOf: function(value, attr, values, model) { - if(!_.include(values, value)){ + // The array of values can be specified either directly or as a function which returns the array. + oneOf: function(value, attr, fnOrValues, model, computed) { + var values = getFunctionParam(fnOrValues, model, value, attr, computed); + if (!_.include(values, value)) { return this.format(defaultMessages.oneOf, this.formatLabel(attr, model), values.join(', ')); } }, @@ -682,8 +712,11 @@ Backbone.Validation = (function(_){ // Equal to validator // Validates that the value has to be equal to the value of the attribute // with the name specified - equalTo: function(value, attr, equalTo, model, computed) { - if(value !== computed[equalTo]) { + // The name of the corresponding attribute can be specified either directly or as a function which returns + // the attribute name. + equalTo: function(value, attr, equalToFnOrVal, model, computed) { + var equalTo = getFunctionParam(equalToFnOrVal, model, value, attr, computed); + if (value !== computed[equalTo]) { return this.format(defaultMessages.equalTo, this.formatLabel(attr, model), this.formatLabel(equalTo, model)); } }, @@ -691,7 +724,9 @@ Backbone.Validation = (function(_){ // Pattern validator // Validates that the value has to match the pattern specified. // Can be a regular expression or the name of one of the built in patterns - pattern: function(value, attr, pattern, model) { + // The pattern can be specified either directly or as a function which returns the pattern. + pattern: function(value, attr, patternFnOrVal, model, computed) { + var pattern = getFunctionParam(patternFnOrVal, model, value, attr, computed); if (!hasValue(value) || !value.toString().match(defaultPatterns[pattern] || pattern)) { return this.format(defaultMessages[pattern] || defaultMessages.inlinePattern, this.formatLabel(attr, model), pattern); } @@ -701,7 +736,7 @@ Backbone.Validation = (function(_){ // Set the correct context for all validators // when used from within a method validator - _.each(defaultValidators, function(validator, key){ + _.each(defaultValidators, function(validator, key) { defaultValidators[key] = _.bind(defaultValidators[key], _.extend({}, formatFunctions, defaultValidators)); }); diff --git a/src/backbone-validation.js b/src/backbone-validation.js index e4aa8de2..1f2dbfde 100644 --- a/src/backbone-validation.js +++ b/src/backbone-validation.js @@ -91,6 +91,18 @@ Backbone.Validation = (function(_){ return into; }; + + // Returns plain value if any. Runs the provided function and returns its result otherwise. + var getFunctionParam = function (functionOrValue, model, value, attr, computed) { + var result; + if (_.isFunction(functionOrValue)) { + result = functionOrValue.call(model, value, attr, computed); + } else { + result = functionOrValue; + } + return result; + }; + // Validation // ---------- @@ -575,18 +587,18 @@ Backbone.Validation = (function(_){ if(_.isString(fn)){ fn = model[fn]; } - return fn.call(model, value, attr, computed); + return fn.call(model, value, attr, computed); // makes model available as 'this' in the validation function }, // Required validator // Validates if the attribute is required or not - // This can be specified as either a boolean value or a function that returns a boolean value - required: function(value, attr, required, model, computed) { - var isRequired = _.isFunction(required) ? required.call(model, value, attr, computed) : required; - if(!isRequired && !hasValue(value)) { + // Whether the attribute is required can be specified as either a boolean value or a function that returns a boolean value + required: function(value, attr, requiredFnOrVal, model, computed) { + var required = getFunctionParam(requiredFnOrVal, model, value, attr, computed); + if (!required && !hasValue(value)) { return false; // overrides all other validators } - if (isRequired && !hasValue(value)) { + if (required && !hasValue(value)) { return this.format(defaultMessages.required, this.formatLabel(attr, model)); } }, @@ -594,8 +606,10 @@ Backbone.Validation = (function(_){ // Acceptance validator // Validates that something has to be accepted, e.g. terms of use // `true` or 'true' are valid - acceptance: function(value, attr, accept, model) { - if(value !== 'true' && (!_.isBoolean(value) || value === false)) { + // Whether acceptance is required can be specified as either a boolean value or a function that returns a boolean value + acceptance: function(value, attr, acceptFnOrVal, model, computed) { + var accept = getFunctionParam(acceptFnOrVal, model, value, attr, computed); + if (accept && value !== 'true' && (!_.isBoolean(value) || value === false)) { return this.format(defaultMessages.acceptance, this.formatLabel(attr, model)); } }, @@ -603,7 +617,9 @@ Backbone.Validation = (function(_){ // Min validator // Validates that the value has to be a number and equal to or greater than // the min value specified - min: function(value, attr, minValue, model) { + // The min value can be specified either directly or as a function which returns the value. + min: function(value, attr, minFnOrVal, model, computed) { + var minValue = getFunctionParam(minFnOrVal, model, value, attr, computed); if (!isNumber(value) || value < minValue) { return this.format(defaultMessages.min, this.formatLabel(attr, model), minValue); } @@ -612,7 +628,9 @@ Backbone.Validation = (function(_){ // Max validator // Validates that the value has to be a number and equal to or less than // the max value specified - max: function(value, attr, maxValue, model) { + // The max value can be specified either directly or as a function which returns the value. + max: function(value, attr, maxFnOrVal, model, computed) { + var maxValue = getFunctionParam(maxFnOrVal, model, value, attr, computed); if (!isNumber(value) || value > maxValue) { return this.format(defaultMessages.max, this.formatLabel(attr, model), maxValue); } @@ -620,9 +638,11 @@ Backbone.Validation = (function(_){ // Range validator // Validates that the value has to be a number and equal to or between - // the two numbers specified - range: function(value, attr, range, model) { - if(!isNumber(value) || value < range[0] || value > range[1]) { + // the two numbers specified as an array + // The range value can be specified either directly or as a function which returns the value. + range: function(value, attr, rangeFnOrVal, model, computed) { + var range = getFunctionParam(rangeFnOrVal, model, value, attr, computed); + if (!isNumber(value) || value < range[0] || value > range[1]) { return this.format(defaultMessages.range, this.formatLabel(attr, model), range[0], range[1]); } }, @@ -630,7 +650,9 @@ Backbone.Validation = (function(_){ // Length validator // Validates that the value has to be a string with length equal to // the length value specified - length: function(value, attr, length, model) { + // The length value can be specified either directly or as a function which returns the value. + length: function(value, attr, lengthFnOrVal, model, computed) { + var length = getFunctionParam(lengthFnOrVal, model, value, attr, computed); if (!_.isString(value) || value.length !== length) { return this.format(defaultMessages.length, this.formatLabel(attr, model), length); } @@ -639,7 +661,9 @@ Backbone.Validation = (function(_){ // Min length validator // Validates that the value has to be a string with length equal to or greater than // the min length value specified - minLength: function(value, attr, minLength, model) { + // The min length value can be specified either directly or as a function which returns the value. + minLength: function(value, attr, minLengthFnOrVal, model, computed) { + var minLength = getFunctionParam(minLengthFnOrVal, model, value, attr, computed); if (!_.isString(value) || value.length < minLength) { return this.format(defaultMessages.minLength, this.formatLabel(attr, model), minLength); } @@ -648,16 +672,20 @@ Backbone.Validation = (function(_){ // Max length validator // Validates that the value has to be a string with length equal to or less than // the max length value specified - maxLength: function(value, attr, maxLength, model) { + // The max length value can be specified either directly or as a function which returns the value. + maxLength: function(value, attr, maxLengthFnOrVal, model, computed) { + var maxLength = getFunctionParam(maxLengthFnOrVal, model, value, attr, computed); if (!_.isString(value) || value.length > maxLength) { return this.format(defaultMessages.maxLength, this.formatLabel(attr, model), maxLength); } }, // Range length validator - // Validates that the value has to be a string and equal to or between - // the two numbers specified - rangeLength: function(value, attr, range, model) { + // Validates that the value has to be a string with a length equal to or between + // two numbers specified as an array + // The string length value can be specified either directly or as a function which returns the value. + rangeLength: function(value, attr, rangeFnOrVal, model, computed) { + var range = getFunctionParam(rangeFnOrVal, model, value, attr, computed); if (!_.isString(value) || value.length < range[0] || value.length > range[1]) { return this.format(defaultMessages.rangeLength, this.formatLabel(attr, model), range[0], range[1]); } @@ -666,8 +694,10 @@ Backbone.Validation = (function(_){ // One of validator // Validates that the value has to be equal to one of the elements in // the specified array. Case sensitive matching - oneOf: function(value, attr, values, model) { - if(!_.include(values, value)){ + // The array of values can be specified either directly or as a function which returns the array. + oneOf: function(value, attr, fnOrValues, model, computed) { + var values = getFunctionParam(fnOrValues, model, value, attr, computed); + if (!_.include(values, value)) { return this.format(defaultMessages.oneOf, this.formatLabel(attr, model), values.join(', ')); } }, @@ -675,8 +705,11 @@ Backbone.Validation = (function(_){ // Equal to validator // Validates that the value has to be equal to the value of the attribute // with the name specified - equalTo: function(value, attr, equalTo, model, computed) { - if(value !== computed[equalTo]) { + // The name of the corresponding attribute can be specified either directly or as a function which returns + // the attribute name. + equalTo: function(value, attr, equalToFnOrVal, model, computed) { + var equalTo = getFunctionParam(equalToFnOrVal, model, value, attr, computed); + if (value !== computed[equalTo]) { return this.format(defaultMessages.equalTo, this.formatLabel(attr, model), this.formatLabel(equalTo, model)); } }, @@ -684,7 +717,9 @@ Backbone.Validation = (function(_){ // Pattern validator // Validates that the value has to match the pattern specified. // Can be a regular expression or the name of one of the built in patterns - pattern: function(value, attr, pattern, model) { + // The pattern can be specified either directly or as a function which returns the pattern. + pattern: function(value, attr, patternFnOrVal, model, computed) { + var pattern = getFunctionParam(patternFnOrVal, model, value, attr, computed); if (!hasValue(value) || !value.toString().match(defaultPatterns[pattern] || pattern)) { return this.format(defaultMessages[pattern] || defaultMessages.inlinePattern, this.formatLabel(attr, model), pattern); } @@ -694,7 +729,7 @@ Backbone.Validation = (function(_){ // Set the correct context for all validators // when used from within a method validator - _.each(defaultValidators, function(validator, key){ + _.each(defaultValidators, function(validator, key) { defaultValidators[key] = _.bind(defaultValidators[key], _.extend({}, formatFunctions, defaultValidators)); });