From 5a42ab8fae0d0f67005ad59b8f78922e13bcf2b3 Mon Sep 17 00:00:00 2001 From: jeroensen Date: Fri, 3 Jan 2014 10:11:16 +0100 Subject: [PATCH 1/3] Added nested model / collection + changed validation on changed attributes Added nested model / collection validations as suggested in https://github.com/thedersen/backbone.validation/issues/109. Also modified validation on change. Switched for on change validation to only changed attribute in stead of all attributes to prevent triggering of non modified attributes (having default not correct not modified values). --- dist/backbone-validation-amd.js | 29 +++++++- dist/backbone-validation.js | 29 +++++++- docs/backbone-validation.html | 116 ++++++++++++++++---------------- src/backbone-validation.js | 29 +++++++- 4 files changed, 139 insertions(+), 64 deletions(-) diff --git a/dist/backbone-validation-amd.js b/dist/backbone-validation-amd.js index fd05e0e9..8929b600 100644 --- a/dist/backbone-validation-amd.js +++ b/dist/backbone-validation-amd.js @@ -242,7 +242,10 @@ allAttrs = _.extend({}, validatedAttrs, model.attributes, attrs), changedAttrs = flatten(attrs || allAttrs), - result = validateModel(model, allAttrs); + // Only validate the changed attributes on change (set), + // when save is being called, all attriubutes will be present + // in the changedAttrs property. (original: allAttrs) + result = validateModel(model, changedAttrs); model._isValid = result.isValid; @@ -434,7 +437,9 @@ number: '{0} must be a number', email: '{0} must be a valid email', url: '{0} must be a valid url', - inlinePattern: '{0} is invalid' + inlinePattern: '{0} is invalid', + validModel: '{0} must be a validated model', + validCollection: '{0} must be a validated collection' }; // Label formatters @@ -627,6 +632,26 @@ if (!hasValue(value) || !value.toString().match(defaultPatterns[pattern] || pattern)) { return this.format(defaultMessages[pattern] || defaultMessages.inlinePattern, this.formatLabel(attr, model), pattern); } + }, + + // Model validator + // Validates that a (nested) model is valid as defined by it's own validations + validModel: function (value, attr, customValue, model) { + if (value && !value.isValid(true)) { + return this.format(defaultMessages.validModel, this.formatLabel(attr, model)); + } + }, + + // Collection validator + // Validates that a (nested) collection of models is valid as defined by their own validations + validCollection: function (value, attr, customValue, model) { + var errors = value.map(function (entry) { + return entry.isValid(true); + }); + + if (_.indexOf(errors, false) !== -1) { + return this.format(defaultMessages.validCollection, this.formatLabel(attr, model)); + } } }; }()); diff --git a/dist/backbone-validation.js b/dist/backbone-validation.js index bd781b92..97033a55 100644 --- a/dist/backbone-validation.js +++ b/dist/backbone-validation.js @@ -235,7 +235,10 @@ Backbone.Validation = (function(_){ allAttrs = _.extend({}, validatedAttrs, model.attributes, attrs), changedAttrs = flatten(attrs || allAttrs), - result = validateModel(model, allAttrs); + // Only validate the changed attributes on change (set), + // when save is being called, all attriubutes will be present + // in the changedAttrs property. (original: allAttrs) + result = validateModel(model, changedAttrs); model._isValid = result.isValid; @@ -427,7 +430,9 @@ Backbone.Validation = (function(_){ number: '{0} must be a number', email: '{0} must be a valid email', url: '{0} must be a valid url', - inlinePattern: '{0} is invalid' + inlinePattern: '{0} is invalid', + validModel: '{0} must be a validated model', + validCollection: '{0} must be a validated collection' }; // Label formatters @@ -620,6 +625,26 @@ Backbone.Validation = (function(_){ if (!hasValue(value) || !value.toString().match(defaultPatterns[pattern] || pattern)) { return this.format(defaultMessages[pattern] || defaultMessages.inlinePattern, this.formatLabel(attr, model), pattern); } + }, + + // Model validator + // Validates that a (nested) model is valid as defined by it's own validations + validModel: function (value, attr, customValue, model) { + if (value && !value.isValid(true)) { + return this.format(defaultMessages.validModel, this.formatLabel(attr, model)); + } + }, + + // Collection validator + // Validates that a (nested) collection of models is valid as defined by their own validations + validCollection: function (value, attr, customValue, model) { + var errors = value.map(function (entry) { + return entry.isValid(true); + }); + + if (_.indexOf(errors, false) !== -1) { + return this.format(defaultMessages.validCollection, this.formatLabel(attr, model)); + } } }; }()); diff --git a/docs/backbone-validation.html b/docs/backbone-validation.html index 2ca0d530..5d961477 100644 --- a/docs/backbone-validation.html +++ b/docs/backbone-validation.html @@ -46,7 +46,7 @@

backbone-validation.js

-
Backbone.Validation = (function(_){
+            
Backbone.Validation = (function(_){
   'use strict';
@@ -124,7 +124,7 @@

Helper functions

-
    formatLabel: function(attrName, model) {
+            
    formatLabel: function(attrName, model) {
       return defaultLabelFormatters[defaultOptions.labelFormatter](attrName, model);
     },
@@ -142,10 +142,10 @@

Helper functions

-
    format: function() {
+            
    format: function() {
       var args = Array.prototype.slice.call(arguments),
           text = args.shift();
-      return text.replace(/\{(\d+)\}/g, function(match, number) {
+      return text.replace(/\{(\d+)\}/g, function(match, number) {
         return typeof args[number] !== 'undefined' ? args[number] : match;
       });
     }
@@ -180,7 +180,7 @@ 

Helper functions

into = into || {}; prefix = prefix || ''; - _.each(obj, function(val, key) { + _.each(obj, function(val, key) { if(obj.hasOwnProperty(key)) { if (val && typeof val === 'object' && !( val instanceof Array || @@ -225,7 +225,7 @@

Validation

-
  var Validation = (function(){
+
  var Validation = (function(){
@@ -242,8 +242,8 @@

Validation

-
    var getValidatedAttrs = function(model) {
-      return _.reduce(_.keys(_.result(model, 'validation') || {}), function(memo, key) {
+            
    var getValidatedAttrs = function(model) {
+      return _.reduce(_.keys(_.result(model, 'validation') || {}), function(memo, key) {
         memo[key] = void 0;
         return memo;
       }, {});
@@ -264,7 +264,7 @@ 

Validation

-
    var getValidators = function(model, attr) {
+            
    var getValidators = function(model, attr) {
       var attrValidationSet = model.validation ? _.result(model, 'validation')[attr] || {} : {};
@@ -318,8 +318,8 @@

Validation

-
      return _.reduce(attrValidationSet, function(memo, attrValidation) {
-        _.each(_.without(_.keys(attrValidation), 'msg'), function(validator) {
+            
      return _.reduce(attrValidationSet, function(memo, attrValidation) {
+        _.each(_.without(_.keys(attrValidation), 'msg'), function(validator) {
           memo.push({
             fn: defaultValidators[validator],
             val: attrValidation[validator],
@@ -346,7 +346,7 @@ 

Validation

-
    var validateAttr = function(model, attr, value, computed) {
+
    var validateAttr = function(model, attr, value, computed) {
@@ -363,7 +363,7 @@

Validation

-
      return _.reduce(getValidators(model, attr), function(memo, validator){
+
      return _.reduce(getValidators(model, attr), function(memo, validator){
@@ -407,14 +407,14 @@

Validation

-
    var validateModel = function(model, attrs) {
+            
    var validateModel = function(model, attrs) {
       var error,
           invalidAttrs = {},
           isValid = true,
           computed = _.clone(attrs),
           flattened = flatten(attrs);
 
-      _.each(flattened, function(val, attr) {
+      _.each(flattened, function(val, attr) {
         error = validateAttr(model, attr, val, computed);
         if (error) {
           invalidAttrs[attr] = error;
@@ -441,7 +441,7 @@ 

Validation

-
    var mixin = function(view, options) {
+            
    var mixin = function(view, options) {
       return {
@@ -458,13 +458,13 @@

Validation

-
        preValidate: function(attr, value) {
+            
        preValidate: function(attr, value) {
           var self = this,
               result = {},
               error;
 
           if(_.isObject(attr)){
-            _.each(attr, function(value, key) {
+            _.each(attr, function(value, key) {
               error = self.preValidate(key, value);
               if(error){
                 result[key] = error;
@@ -493,14 +493,14 @@ 

Validation

-
        isValid: function(option) {
+            
        isValid: function(option) {
           var flattened = flatten(this.attributes);
 
           if(_.isString(option)){
             return !validateAttr(this, option, flattened[option], _.extend({}, this.attributes));
           }
           if(_.isArray(option)){
-            return _.reduce(option, function(memo, attr) {
+            return _.reduce(option, function(memo, attr) {
               return memo && !validateAttr(this, attr, flattened[attr], _.extend({}, this.attributes));
             }, true, this);
           }
@@ -525,7 +525,7 @@ 

Validation

-
        validate: function(attrs, setOptions){
+            
        validate: function(attrs, setOptions){
           var model = this,
               validateAll = !attrs,
               opt = _.extend({}, options, setOptions),
@@ -551,7 +551,7 @@ 

Validation

-
          _.each(validatedAttrs, function(val, attr){
+            
          _.each(validatedAttrs, function(val, attr){
             var invalid = result.invalidAttrs.hasOwnProperty(attr);
             if(!invalid){
               opt.valid(view, attr, opt.selector);
@@ -572,7 +572,7 @@ 

Validation

-
          _.each(validatedAttrs, function(val, attr){
+            
          _.each(validatedAttrs, function(val, attr){
             var invalid = result.invalidAttrs.hasOwnProperty(attr),
                 changed = changedAttrs.hasOwnProperty(attr);
 
@@ -596,7 +596,7 @@ 

Validation

-
          _.defer(function() {
+            
          _.defer(function() {
             model.trigger('validated', model._isValid, model, result.invalidAttrs);
             model.trigger('validated:' + (model._isValid ? 'valid' : 'invalid'), model, result.invalidAttrs);
           });
@@ -636,7 +636,7 @@

Validation

-
    var bindModel = function(view, model, options) {
+            
    var bindModel = function(view, model, options) {
       _.extend(model, mixin(view, options));
     };
@@ -653,7 +653,7 @@

Validation

-
    var unbindModel = function(model) {
+            
    var unbindModel = function(model) {
       delete model.validate;
       delete model.preValidate;
       delete model.isValid;
@@ -673,7 +673,7 @@ 

Validation

-
    var collectionAdd = function(model) {
+            
    var collectionAdd = function(model) {
       bindModel(this.view, model, this.options);
     };
@@ -691,7 +691,7 @@

Validation

-
    var collectionRemove = function(model) {
+            
    var collectionRemove = function(model) {
       unbindModel(model);
     };
@@ -738,7 +738,7 @@

Validation

-
      configure: function(options) {
+            
      configure: function(options) {
         _.extend(defaultOptions, options);
       },
@@ -756,7 +756,7 @@

Validation

-
      bind: function(view, options) {
+            
      bind: function(view, options) {
         options = _.extend({}, defaultOptions, defaultCallbacks, options);
 
         var model = options.model || view.model,
@@ -771,7 +771,7 @@ 

Validation

bindModel(view, model, options); } else if(collection) { - collection.each(function(model){ + collection.each(function(model){ bindModel(view, model, options); }); collection.bind('add', collectionAdd, {view: view, options: options}); @@ -793,7 +793,7 @@

Validation

-
      unbind: function(view, options) {
+            
      unbind: function(view, options) {
         options = _.extend({}, options);
         var model = options.model || view.model,
             collection = options.collection || view.collection;
@@ -802,7 +802,7 @@ 

Validation

unbindModel(model); } if(collection) { - collection.each(function(model){ + collection.each(function(model){ unbindModel(model); }); collection.unbind('add', collectionAdd); @@ -870,7 +870,7 @@

Callbacks

-
    valid: function(view, attr, selector) {
+            
    valid: function(view, attr, selector) {
       view.$('[' + selector + '~="' + attr + '"]')
           .removeClass('invalid')
           .removeAttr('data-error');
@@ -891,7 +891,7 @@ 

Callbacks

-
    invalid: function(view, attr, error, selector) {
+            
    invalid: function(view, attr, error, selector) {
       view.$('[' + selector + '~="' + attr + '"]')
           .addClass('invalid')
           .attr('data-error', error);
@@ -1079,7 +1079,7 @@ 

Label formatters

-
    none: function(attrName) {
+            
    none: function(attrName) {
       return attrName;
     },
@@ -1096,8 +1096,8 @@

Label formatters

-
    sentenceCase: function(attrName) {
-      return attrName.replace(/(?:^\w|[A-Z]|\b\w)/g, function(match, index) {
+            
    sentenceCase: function(attrName) {
+      return attrName.replace(/(?:^\w|[A-Z]|\b\w)/g, function(match, index) {
         return index === 0 ? match.toUpperCase() : ' ' + match.toLowerCase();
       }).replace(/_/g, ' ');
     },
@@ -1126,7 +1126,7 @@

Label formatters

-
    label: function(attrName, model) {
+            
    label: function(attrName, model) {
       return (model.labels && model.labels[attrName]) || defaultLabelFormatters.sentenceCase(attrName, model);
     }
   };
@@ -1156,7 +1156,7 @@

Built in validators

-
  var defaultValidators = Validation.validators = (function(){
+
  var defaultValidators = Validation.validators = (function(){
@@ -1172,10 +1172,10 @@

Built in validators

    var trim = String.prototype.trim ?
-      function(text) {
+      function(text) {
         return text === null ? '' : String.prototype.trim.call(text);
       } :
-      function(text) {
+      function(text) {
         var trimLeft = /^\s+/,
             trimRight = /\s+$/;
 
@@ -1195,7 +1195,7 @@ 

Built in validators

-
    var isNumber = function(value){
+            
    var isNumber = function(value){
       return _.isNumber(value) || (_.isString(value) && value.match(defaultPatterns.number));
     };
@@ -1212,7 +1212,7 @@

Built in validators

-
    var hasValue = function(value) {
+            
    var hasValue = function(value) {
       return !(_.isNull(value) || _.isUndefined(value) || (_.isString(value) && trim(value) === '') || (_.isArray(value) && _.isEmpty(value)));
     };
 
@@ -1232,7 +1232,7 @@ 

Built in validators

-
      fn: function(value, attr, fn, model, computed) {
+            
      fn: function(value, attr, fn, model, computed) {
         if(_.isString(fn)){
           fn = model[fn];
         }
@@ -1254,7 +1254,7 @@ 

Built in validators

-
      required: function(value, attr, required, model, computed) {
+            
      required: function(value, attr, required, model, computed) {
         var isRequired = _.isFunction(required) ? required.call(model, value, attr, computed) : required;
         if(!isRequired && !hasValue(value)) {
           return false; // overrides all other validators
@@ -1279,7 +1279,7 @@ 

Built in validators

-
      acceptance: function(value, attr, accept, model) {
+            
      acceptance: function(value, attr, accept, model) {
         if(value !== 'true' && (!_.isBoolean(value) || value === false)) {
           return this.format(defaultMessages.acceptance, this.formatLabel(attr, model));
         }
@@ -1300,7 +1300,7 @@ 

Built in validators

-
      min: function(value, attr, minValue, model) {
+            
      min: function(value, attr, minValue, model) {
         if (!isNumber(value) || value < minValue) {
           return this.format(defaultMessages.min, this.formatLabel(attr, model), minValue);
         }
@@ -1321,7 +1321,7 @@ 

Built in validators

-
      max: function(value, attr, maxValue, model) {
+            
      max: function(value, attr, maxValue, model) {
         if (!isNumber(value) || value > maxValue) {
           return this.format(defaultMessages.max, this.formatLabel(attr, model), maxValue);
         }
@@ -1342,7 +1342,7 @@ 

Built in validators

-
      range: function(value, attr, range, model) {
+            
      range: function(value, attr, range, model) {
         if(!isNumber(value) || value < range[0] || value > range[1]) {
           return this.format(defaultMessages.range, this.formatLabel(attr, model), range[0], range[1]);
         }
@@ -1363,7 +1363,7 @@ 

Built in validators

-
      length: function(value, attr, length, model) {
+            
      length: function(value, attr, length, model) {
         if (!_.isString(value) || value.length !== length) {
           return this.format(defaultMessages.length, this.formatLabel(attr, model), length);
         }
@@ -1384,7 +1384,7 @@ 

Built in validators

-
      minLength: function(value, attr, minLength, model) {
+            
      minLength: function(value, attr, minLength, model) {
         if (!_.isString(value) || value.length < minLength) {
           return this.format(defaultMessages.minLength, this.formatLabel(attr, model), minLength);
         }
@@ -1405,7 +1405,7 @@ 

Built in validators

-
      maxLength: function(value, attr, maxLength, model) {
+            
      maxLength: function(value, attr, maxLength, model) {
         if (!_.isString(value) || value.length > maxLength) {
           return this.format(defaultMessages.maxLength, this.formatLabel(attr, model), maxLength);
         }
@@ -1426,7 +1426,7 @@ 

Built in validators

-
      rangeLength: function(value, attr, range, model) {
+            
      rangeLength: function(value, attr, range, model) {
         if (!_.isString(value) || value.length < range[0] || value.length > range[1]) {
           return this.format(defaultMessages.rangeLength, this.formatLabel(attr, model), range[0], range[1]);
         }
@@ -1447,7 +1447,7 @@ 

Built in validators

-
      oneOf: function(value, attr, values, model) {
+            
      oneOf: function(value, attr, values, model) {
         if(!_.include(values, value)){
           return this.format(defaultMessages.oneOf, this.formatLabel(attr, model), values.join(', '));
         }
@@ -1468,7 +1468,7 @@ 

Built in validators

-
      equalTo: function(value, attr, equalTo, model, computed) {
+            
      equalTo: function(value, attr, equalTo, model, computed) {
         if(value !== computed[equalTo]) {
           return this.format(defaultMessages.equalTo, this.formatLabel(attr, model), this.formatLabel(equalTo, model));
         }
@@ -1489,7 +1489,7 @@ 

Built in validators

-
      pattern: function(value, attr, pattern, model) {
+            
      pattern: function(value, attr, pattern, model) {
         if (!hasValue(value) || !value.toString().match(defaultPatterns[pattern] || pattern)) {
           return this.format(defaultMessages[pattern] || defaultMessages.inlinePattern, this.formatLabel(attr, model), pattern);
         }
@@ -1511,7 +1511,7 @@ 

Built in validators

-
  _.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 b5f11f81..2cba0cd8 100644
--- a/src/backbone-validation.js
+++ b/src/backbone-validation.js
@@ -228,7 +228,10 @@ Backbone.Validation = (function(_){
               allAttrs = _.extend({}, validatedAttrs, model.attributes, attrs),
               changedAttrs = flatten(attrs || allAttrs),
 
-              result = validateModel(model, allAttrs);
+              // Only validate the changed attributes on change (set),
+              // when save is being called, all attriubutes will be present
+              // in the changedAttrs property. (original: allAttrs)
+              result = validateModel(model, changedAttrs);
 
           model._isValid = result.isValid;
 
@@ -420,7 +423,9 @@ Backbone.Validation = (function(_){
     number: '{0} must be a number',
     email: '{0} must be a valid email',
     url: '{0} must be a valid url',
-    inlinePattern: '{0} is invalid'
+    inlinePattern: '{0} is invalid',
+    validModel: '{0} must be a validated model',
+    validCollection: '{0} must be a validated collection'
   };
 
   // Label formatters
@@ -613,6 +618,26 @@ Backbone.Validation = (function(_){
         if (!hasValue(value) || !value.toString().match(defaultPatterns[pattern] || pattern)) {
           return this.format(defaultMessages[pattern] || defaultMessages.inlinePattern, this.formatLabel(attr, model), pattern);
         }
+      },
+
+      // Model validator
+      // Validates that a (nested) model is valid as defined by it's own validations
+      validModel: function (value, attr, customValue, model) {
+          if (value && !value.isValid(true)) {
+              return this.format(defaultMessages.validModel, this.formatLabel(attr, model));
+          }
+      },
+
+      // Collection validator
+      // Validates that a (nested) collection of models is valid as defined by their own validations
+      validCollection: function (value, attr, customValue, model) {
+          var errors = value.map(function (entry) {
+              return entry.isValid(true);
+          });
+
+          if (_.indexOf(errors, false) !== -1) {
+            return this.format(defaultMessages.validCollection, this.formatLabel(attr, model));
+          }
       }
     };
   }());

From a6c10bd2e1e226333491d2aa16c5467cc9e34174 Mon Sep 17 00:00:00 2001
From: Soesah 
Date: Wed, 5 Feb 2014 13:49:00 +0100
Subject: [PATCH 2/3] Removed another change, focussing this branch on the
 child-model and child-collection validation

---
 dist/backbone-validation-amd-min.js | 2 +-
 dist/backbone-validation-amd.js     | 5 +----
 dist/backbone-validation-min.js     | 2 +-
 dist/backbone-validation.js         | 5 +----
 src/backbone-validation.js          | 5 +----
 5 files changed, 5 insertions(+), 14 deletions(-)

diff --git a/dist/backbone-validation-amd-min.js b/dist/backbone-validation-amd-min.js
index 6b59c18c..6671447f 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(b){"use strict";var c={forceUpdate:!1,selector:"name",labelFormatter:"sentenceCase",valid:Function.prototype,invalid:Function.prototype},d={formatLabel:function(a,b){return j[c.labelFormatter](a,b)},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})}},e=function(c,d,f){return d=d||{},f=f||"",b.each(c,function(b,g){c.hasOwnProperty(g)&&(!b||"object"!=typeof b||b instanceof Array||b instanceof Date||b instanceof RegExp||b instanceof a.Model||b instanceof a.Collection?d[f+g]=b:e(b,d,f+g+"."))}),d},f=function(){var a=function(a){return b.reduce(b.keys(b.result(a,"validation")||{}),function(a,b){return a[b]=void 0,a},{})},f=function(a,c){var d=a.validation?b.result(a,"validation")[c]||{}:{};return(b.isFunction(d)||b.isString(d))&&(d={fn:d}),b.isArray(d)||(d=[d]),b.reduce(d,function(a,c){return b.each(b.without(b.keys(c),"msg"),function(b){a.push({fn:k[b],val:c[b],msg:c.msg})}),a},[])},h=function(a,c,e,g){return b.reduce(f(a,c),function(f,h){var i=b.extend({},d,k),j=h.fn.call(i,e,c,h.val,a,g);return j===!1||f===!1?!1:j&&!f?b.result(h,"msg")||j:f},"")},i=function(a,c){var d,f={},g=!0,i=b.clone(c),j=e(c);return b.each(j,function(b,c){d=h(a,c,b,i),d&&(f[c]=d,g=!1)}),{invalidAttrs:f,isValid:g}},j=function(c,d){return{preValidate:function(a,c){var d,e=this,f={};return b.isObject(a)?(b.each(a,function(a,b){d=e.preValidate(b,a),d&&(f[b]=d)}),b.isEmpty(f)?void 0:f):h(this,a,c,b.extend({},this.attributes))},isValid:function(a){var c=e(this.attributes);return b.isString(a)?!h(this,a,c[a],b.extend({},this.attributes)):b.isArray(a)?b.reduce(a,function(a,d){return a&&!h(this,d,c[d],b.extend({},this.attributes))},!0,this):(a===!0&&this.validate(),this.validation?this._isValid:!0)},validate:function(f,g){var h=this,j=!f,k=b.extend({},d,g),l=a(h),m=b.extend({},l,h.attributes,f),n=e(f||m),o=i(h,m);return h._isValid=o.isValid,b.each(l,function(a,b){var d=o.invalidAttrs.hasOwnProperty(b);d||k.valid(c,b,k.selector)}),b.each(l,function(a,b){var d=o.invalidAttrs.hasOwnProperty(b),e=n.hasOwnProperty(b);d&&(e||j)&&k.invalid(c,b,o.invalidAttrs[b],k.selector)}),b.defer(function(){h.trigger("validated",h._isValid,h,o.invalidAttrs),h.trigger("validated:"+(h._isValid?"valid":"invalid"),h,o.invalidAttrs)}),!k.forceUpdate&&b.intersection(b.keys(o.invalidAttrs),b.keys(n)).length>0?o.invalidAttrs:void 0}}},l=function(a,c,d){b.extend(c,j(a,d))},m=function(a){delete a.validate,delete a.preValidate,delete a.isValid},n=function(a){l(this.view,a,this.options)},o=function(a){m(a)};return{version:"0.9.1",configure:function(a){b.extend(c,a)},bind:function(a,d){d=b.extend({},c,g,d);var e=d.model||a.model,f=d.collection||a.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?l(a,e,d):f&&(f.each(function(b){l(a,b,d)}),f.bind("add",n,{view:a,options:d}),f.bind("remove",o))},unbind:function(a,c){c=b.extend({},c);var d=c.model||a.model,e=c.collection||a.collection;d?m(d):e&&(e.each(function(a){m(a)}),e.unbind("add",n),e.unbind("remove",o))},mixin:j(null,c)}}(),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.validators=function(){var a=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(a){return b.isNumber(a)||b.isString(a)&&a.match(h.number)},d=function(c){return!(b.isNull(c)||b.isUndefined(c)||b.isString(c)&&""===a(c)||b.isArray(c)&&b.isEmpty(c))};return{fn:function(a,c,d,e,f){return b.isString(d)&&(d=e[d]),d.call(e,a,c,f)},required:function(a,c,e,f,g){var h=b.isFunction(e)?e.call(f,a,c,g):e;return h||d(a)?h&&!d(a)?this.format(i.required,this.formatLabel(c,f)):void 0:!1},acceptance:function(a,c,d,e){return"true"===a||b.isBoolean(a)&&a!==!1?void 0:this.format(i.acceptance,this.formatLabel(c,e))},min:function(a,b,d,e){return!c(a)||d>a?this.format(i.min,this.formatLabel(b,e),d):void 0},max:function(a,b,d,e){return!c(a)||a>d?this.format(i.max,this.formatLabel(b,e),d):void 0},range:function(a,b,d,e){return!c(a)||ad[1]?this.format(i.range,this.formatLabel(b,e),d[0],d[1]):void 0},length:function(a,c,d,e){return b.isString(a)&&a.length===d?void 0:this.format(i.length,this.formatLabel(c,e),d)},minLength:function(a,c,d,e){return!b.isString(a)||a.lengthd?this.format(i.maxLength,this.formatLabel(c,e),d):void 0},rangeLength:function(a,c,d,e){return!b.isString(a)||a.lengthd[1]?this.format(i.rangeLength,this.formatLabel(c,e),d[0],d[1]):void 0},oneOf:function(a,c,d,e){return b.include(d,a)?void 0:this.format(i.oneOf,this.formatLabel(c,e),d.join(", "))},equalTo:function(a,b,c,d,e){return a!==e[c]?this.format(i.equalTo,this.formatLabel(b,d),this.formatLabel(c,d)):void 0},pattern:function(a,b,c,e){return d(a)&&a.toString().match(h[c]||c)?void 0:this.format(i[c]||i.inlinePattern,this.formatLabel(b,e),c)}}}();return b.each(k,function(a,c){k[c]=b.bind(k[c],b.extend({},d,k))}),f}(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(b){"use strict";var c={forceUpdate:!1,selector:"name",labelFormatter:"sentenceCase",valid:Function.prototype,invalid:Function.prototype},d={formatLabel:function(a,b){return j[c.labelFormatter](a,b)},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})}},e=function(c,d,f){return d=d||{},f=f||"",b.each(c,function(b,g){c.hasOwnProperty(g)&&(b&&"object"==typeof b&&!(b instanceof Array||b instanceof Date||b instanceof RegExp||b instanceof a.Model||b instanceof a.Collection)?e(b,d,f+g+"."):d[f+g]=b)}),d},f=function(){var a=function(a){return b.reduce(b.keys(b.result(a,"validation")||{}),function(a,b){return a[b]=void 0,a},{})},f=function(a,c){var d=a.validation?b.result(a,"validation")[c]||{}:{};return(b.isFunction(d)||b.isString(d))&&(d={fn:d}),b.isArray(d)||(d=[d]),b.reduce(d,function(a,c){return b.each(b.without(b.keys(c),"msg"),function(b){a.push({fn:k[b],val:c[b],msg:c.msg})}),a},[])},h=function(a,c,e,g){return b.reduce(f(a,c),function(f,h){var i=b.extend({},d,k),j=h.fn.call(i,e,c,h.val,a,g);return j===!1||f===!1?!1:j&&!f?b.result(h,"msg")||j:f},"")},i=function(a,c){var d,f={},g=!0,i=b.clone(c),j=e(c);return b.each(j,function(b,c){d=h(a,c,b,i),d&&(f[c]=d,g=!1)}),{invalidAttrs:f,isValid:g}},j=function(c,d){return{preValidate:function(a,c){var d,e=this,f={};return b.isObject(a)?(b.each(a,function(a,b){d=e.preValidate(b,a),d&&(f[b]=d)}),b.isEmpty(f)?void 0:f):h(this,a,c,b.extend({},this.attributes))},isValid:function(a){var c=e(this.attributes);return b.isString(a)?!h(this,a,c[a],b.extend({},this.attributes)):b.isArray(a)?b.reduce(a,function(a,d){return a&&!h(this,d,c[d],b.extend({},this.attributes))},!0,this):(a===!0&&this.validate(),this.validation?this._isValid:!0)},validate:function(f,g){var h=this,j=!f,k=b.extend({},d,g),l=a(h),m=b.extend({},l,h.attributes,f),n=e(f||m),o=i(h,m);return h._isValid=o.isValid,b.each(l,function(a,b){var d=o.invalidAttrs.hasOwnProperty(b);d||k.valid(c,b,k.selector)}),b.each(l,function(a,b){var d=o.invalidAttrs.hasOwnProperty(b),e=n.hasOwnProperty(b);d&&(e||j)&&k.invalid(c,b,o.invalidAttrs[b],k.selector)}),b.defer(function(){h.trigger("validated",h._isValid,h,o.invalidAttrs),h.trigger("validated:"+(h._isValid?"valid":"invalid"),h,o.invalidAttrs)}),!k.forceUpdate&&b.intersection(b.keys(o.invalidAttrs),b.keys(n)).length>0?o.invalidAttrs:void 0}}},l=function(a,c,d){b.extend(c,j(a,d))},m=function(a){delete a.validate,delete a.preValidate,delete a.isValid},n=function(a){l(this.view,a,this.options)},o=function(a){m(a)};return{version:"0.9.1",configure:function(a){b.extend(c,a)},bind:function(a,d){d=b.extend({},c,g,d);var e=d.model||a.model,f=d.collection||a.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?l(a,e,d):f&&(f.each(function(b){l(a,b,d)}),f.bind("add",n,{view:a,options:d}),f.bind("remove",o))},unbind:function(a,c){c=b.extend({},c);var d=c.model||a.model,e=c.collection||a.collection;d?m(d):e&&(e.each(function(a){m(a)}),e.unbind("add",n),e.unbind("remove",o))},mixin:j(null,c)}}(),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",validModel:"{0} must be a validated model",validCollection:"{0} must be a validated collection"},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.validators=function(){var a=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(a){return b.isNumber(a)||b.isString(a)&&a.match(h.number)},d=function(c){return!(b.isNull(c)||b.isUndefined(c)||b.isString(c)&&""===a(c)||b.isArray(c)&&b.isEmpty(c))};return{fn:function(a,c,d,e,f){return b.isString(d)&&(d=e[d]),d.call(e,a,c,f)},required:function(a,c,e,f,g){var h=b.isFunction(e)?e.call(f,a,c,g):e;return h||d(a)?h&&!d(a)?this.format(i.required,this.formatLabel(c,f)):void 0:!1},acceptance:function(a,c,d,e){return"true"===a||b.isBoolean(a)&&a!==!1?void 0:this.format(i.acceptance,this.formatLabel(c,e))},min:function(a,b,d,e){return!c(a)||d>a?this.format(i.min,this.formatLabel(b,e),d):void 0},max:function(a,b,d,e){return!c(a)||a>d?this.format(i.max,this.formatLabel(b,e),d):void 0},range:function(a,b,d,e){return!c(a)||ad[1]?this.format(i.range,this.formatLabel(b,e),d[0],d[1]):void 0},length:function(a,c,d,e){return b.isString(a)&&a.length===d?void 0:this.format(i.length,this.formatLabel(c,e),d)},minLength:function(a,c,d,e){return!b.isString(a)||a.lengthd?this.format(i.maxLength,this.formatLabel(c,e),d):void 0},rangeLength:function(a,c,d,e){return!b.isString(a)||a.lengthd[1]?this.format(i.rangeLength,this.formatLabel(c,e),d[0],d[1]):void 0},oneOf:function(a,c,d,e){return b.include(d,a)?void 0:this.format(i.oneOf,this.formatLabel(c,e),d.join(", "))},equalTo:function(a,b,c,d,e){return a!==e[c]?this.format(i.equalTo,this.formatLabel(b,d),this.formatLabel(c,d)):void 0},pattern:function(a,b,c,e){return d(a)&&a.toString().match(h[c]||c)?void 0:this.format(i[c]||i.inlinePattern,this.formatLabel(b,e),c)},validModel:function(a,b,c,d){return a&&!a.isValid(!0)?this.format(i.validModel,this.formatLabel(b,d)):void 0},validCollection:function(a,c,d,e){var f=a.map(function(a){return a.isValid(!0)});return-1!==b.indexOf(f,!1)?this.format(i.validCollection,this.formatLabel(c,e)):void 0}}}();return b.each(k,function(a,c){k[c]=b.bind(k[c],b.extend({},d,k))}),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 c3597b7e..34587aa2 100644
--- a/dist/backbone-validation-amd.js
+++ b/dist/backbone-validation-amd.js
@@ -242,10 +242,7 @@
                 allAttrs = _.extend({}, validatedAttrs, model.attributes, attrs),
                 changedAttrs = flatten(attrs || allAttrs),
   
-                // Only validate the changed attributes on change (set),
-                // when save is being called, all attriubutes will be present
-                // in the changedAttrs property. (original: allAttrs)
-                result = validateModel(model, changedAttrs);
+                result = validateModel(model, allAttrs);
   
             model._isValid = result.isValid;
   
diff --git a/dist/backbone-validation-min.js b/dist/backbone-validation-min.js
index 4b62a7df..064424cd 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(a,f){b.hasOwnProperty(f)&&(!a||"object"!=typeof a||a instanceof Array||a instanceof Date||a instanceof RegExp||a instanceof Backbone.Model||a instanceof Backbone.Collection?c[e+f]=a:d(a,c,e+f+"."))}),c},e=function(){var e=function(b){return a.reduce(a.keys(a.result(b,"validation")||{}),function(a,b){return a[b]=void 0,a},{})},g=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:j[a],val:c[a],msg:c.msg})}),b},[])},h=function(b,d,e,f){return a.reduce(g(b,d),function(g,h){var i=a.extend({},c,j),k=h.fn.call(i,e,d,h.val,b,f);return k===!1||g===!1?!1:k&&!g?a.result(h,"msg")||k:g},"")},i=function(b,c){var e,f={},g=!0,i=a.clone(c),j=d(c);return a.each(j,function(a,c){e=h(b,c,a,i),e&&(f[c]=e,g=!1)}),{invalidAttrs:f,isValid:g}},k=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):h(this,b,c,a.extend({},this.attributes))},isValid:function(b){var c=d(this.attributes);return a.isString(b)?!h(this,b,c[b],a.extend({},this.attributes)):a.isArray(b)?a.reduce(b,function(b,d){return b&&!h(this,d,c[d],a.extend({},this.attributes))},!0,this):(b===!0&&this.validate(),this.validation?this._isValid:!0)},validate:function(f,g){var h=this,j=!f,k=a.extend({},c,g),l=e(h),m=a.extend({},l,h.attributes,f),n=d(f||m),o=i(h,m);return h._isValid=o.isValid,a.each(l,function(a,c){var d=o.invalidAttrs.hasOwnProperty(c);d||k.valid(b,c,k.selector)}),a.each(l,function(a,c){var d=o.invalidAttrs.hasOwnProperty(c),e=n.hasOwnProperty(c);d&&(e||j)&&k.invalid(b,c,o.invalidAttrs[c],k.selector)}),a.defer(function(){h.trigger("validated",h._isValid,h,o.invalidAttrs),h.trigger("validated:"+(h._isValid?"valid":"invalid"),h,o.invalidAttrs)}),!k.forceUpdate&&a.intersection(a.keys(o.invalidAttrs),a.keys(n)).length>0?o.invalidAttrs:void 0}}},l=function(b,c,d){a.extend(c,k(b,d))},m=function(a){delete a.validate,delete a.preValidate,delete a.isValid},n=function(a){l(this.view,a,this.options)},o=function(a){m(a)};return{version:"0.9.1",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?l(c,e,d):g&&(g.each(function(a){l(c,a,d)}),g.bind("add",n,{view:c,options:d}),g.bind("remove",o))},unbind:function(b,c){c=a.extend({},c);var d=c.model||b.model,e=c.collection||b.collection;d?m(d):e&&(e.each(function(a){m(a)}),e.unbind("add",n),e.unbind("remove",o))},mixin:k(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.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(j,function(b,d){j[d]=a.bind(j[d],a.extend({},c,j))}),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 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(a,f){b.hasOwnProperty(f)&&(a&&"object"==typeof a&&!(a instanceof Array||a instanceof Date||a instanceof RegExp||a instanceof Backbone.Model||a instanceof Backbone.Collection)?d(a,c,e+f+"."):c[e+f]=a)}),c},e=function(){var e=function(b){return a.reduce(a.keys(a.result(b,"validation")||{}),function(a,b){return a[b]=void 0,a},{})},g=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:j[a],val:c[a],msg:c.msg})}),b},[])},h=function(b,d,e,f){return a.reduce(g(b,d),function(g,h){var i=a.extend({},c,j),k=h.fn.call(i,e,d,h.val,b,f);return k===!1||g===!1?!1:k&&!g?a.result(h,"msg")||k:g},"")},i=function(b,c){var e,f={},g=!0,i=a.clone(c),j=d(c);return a.each(j,function(a,c){e=h(b,c,a,i),e&&(f[c]=e,g=!1)}),{invalidAttrs:f,isValid:g}},k=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):h(this,b,c,a.extend({},this.attributes))},isValid:function(b){var c=d(this.attributes);return a.isString(b)?!h(this,b,c[b],a.extend({},this.attributes)):a.isArray(b)?a.reduce(b,function(b,d){return b&&!h(this,d,c[d],a.extend({},this.attributes))},!0,this):(b===!0&&this.validate(),this.validation?this._isValid:!0)},validate:function(f,g){var h=this,j=!f,k=a.extend({},c,g),l=e(h),m=a.extend({},l,h.attributes,f),n=d(f||m),o=i(h,m);return h._isValid=o.isValid,a.each(l,function(a,c){var d=o.invalidAttrs.hasOwnProperty(c);d||k.valid(b,c,k.selector)}),a.each(l,function(a,c){var d=o.invalidAttrs.hasOwnProperty(c),e=n.hasOwnProperty(c);d&&(e||j)&&k.invalid(b,c,o.invalidAttrs[c],k.selector)}),a.defer(function(){h.trigger("validated",h._isValid,h,o.invalidAttrs),h.trigger("validated:"+(h._isValid?"valid":"invalid"),h,o.invalidAttrs)}),!k.forceUpdate&&a.intersection(a.keys(o.invalidAttrs),a.keys(n)).length>0?o.invalidAttrs:void 0}}},l=function(b,c,d){a.extend(c,k(b,d))},m=function(a){delete a.validate,delete a.preValidate,delete a.isValid},n=function(a){l(this.view,a,this.options)},o=function(a){m(a)};return{version:"0.9.1",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?l(c,e,d):g&&(g.each(function(a){l(c,a,d)}),g.bind("add",n,{view:c,options:d}),g.bind("remove",o))},unbind:function(b,c){c=a.extend({},c);var d=c.model||b.model,e=c.collection||b.collection;d?m(d):e&&(e.each(function(a){m(a)}),e.unbind("add",n),e.unbind("remove",o))},mixin:k(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",validModel:"{0} must be a validated model",validCollection:"{0} must be a validated collection"},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.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)},validModel:function(a,b,c,d){return a&&!a.isValid(!0)?this.format(h.validModel,this.formatLabel(b,d)):void 0},validCollection:function(b,c,d,e){var f=b.map(function(a){return a.isValid(!0)});return-1!==a.indexOf(f,!1)?this.format(h.validCollection,this.formatLabel(c,e)):void 0}}}();return a.each(j,function(b,d){j[d]=a.bind(j[d],a.extend({},c,j))}),e}(_);
\ No newline at end of file
diff --git a/dist/backbone-validation.js b/dist/backbone-validation.js
index 8b36b759..54363066 100644
--- a/dist/backbone-validation.js
+++ b/dist/backbone-validation.js
@@ -235,10 +235,7 @@ Backbone.Validation = (function(_){
               allAttrs = _.extend({}, validatedAttrs, model.attributes, attrs),
               changedAttrs = flatten(attrs || allAttrs),
 
-              // Only validate the changed attributes on change (set),
-              // when save is being called, all attriubutes will be present
-              // in the changedAttrs property. (original: allAttrs)
-              result = validateModel(model, changedAttrs);
+              result = validateModel(model, allAttrs);
 
           model._isValid = result.isValid;
 
diff --git a/src/backbone-validation.js b/src/backbone-validation.js
index 45c8beb0..3462da42 100644
--- a/src/backbone-validation.js
+++ b/src/backbone-validation.js
@@ -228,10 +228,7 @@ Backbone.Validation = (function(_){
               allAttrs = _.extend({}, validatedAttrs, model.attributes, attrs),
               changedAttrs = flatten(attrs || allAttrs),
 
-              // Only validate the changed attributes on change (set),
-              // when save is being called, all attriubutes will be present
-              // in the changedAttrs property. (original: allAttrs)
-              result = validateModel(model, changedAttrs);
+              result = validateModel(model, allAttrs);
 
           model._isValid = result.isValid;
 

From 168ea132596b5e2368bd323038a16839d78db90b Mon Sep 17 00:00:00 2001
From: Soesah 
Date: Wed, 5 Feb 2014 15:42:05 +0100
Subject: [PATCH 3/3] Added tests

---
 tests/validators/validCollection.js | 60 +++++++++++++++++++++++++++++
 tests/validators/validModel.js      | 50 ++++++++++++++++++++++++
 2 files changed, 110 insertions(+)
 create mode 100644 tests/validators/validCollection.js
 create mode 100644 tests/validators/validModel.js

diff --git a/tests/validators/validCollection.js b/tests/validators/validCollection.js
new file mode 100644
index 00000000..772a9314
--- /dev/null
+++ b/tests/validators/validCollection.js
@@ -0,0 +1,60 @@
+buster.testCase("validCollection validator", {
+    setUp: function() {
+        var that = this;
+        var ParentModel = Backbone.Model.extend({
+            validation: {
+                childCollection: {
+                    validCollection: true
+                }
+            }
+        });
+        var ChildModel = Backbone.Model.extend({
+            validation: {
+                name: {
+                    required: true
+                }
+            }
+        });
+        var ChildCollection = Backbone.Collection.extend({
+            model: ChildModel
+        });
+        var childCollection = new ChildCollection([{name:''}]);
+
+        Backbone.Validation.bind(new Backbone.View({collection: childCollection})); 
+        this.model = new ParentModel({childCollection:childCollection});
+        this.view = new Backbone.View({
+            model: this.model
+        });
+
+        Backbone.Validation.bind(this.view, {
+            valid: this.spy(),
+            invalid: this.spy()
+        });
+    },
+
+    "has default error message for string": function(done) {
+        this.model.bind('validated:invalid', function(model, error){
+            assert.equals({childCollection: 'Child collection must be a validated collection'}, error);
+            done();
+        });
+        this.model.isValid(true);
+    },
+    "has valid childCollection": function() {
+        this.model.get("childCollection").first().set("name", "Steve");
+        assert(this.model.isValid(true));
+    },
+    "has valid childCollection with multiple items": function() {
+        this.model.get("childCollection").first().set("name", "Steve");
+        this.model.get("childCollection").add({name:"Amy"});
+        this.model.get("childCollection").add({name:"John"});
+        assert(this.model.isValid(true));
+    },
+    "has invalid childCollection": function() {
+        refute(this.model.isValid(true));
+    },
+    "has invalid childCollection with multiple items": function() {
+        this.model.get("childCollection").add({name:"Amy"});
+        this.model.get("childCollection").add({name:"John"});
+        refute(this.model.isValid(true));
+    }
+});
\ No newline at end of file
diff --git a/tests/validators/validModel.js b/tests/validators/validModel.js
new file mode 100644
index 00000000..7b43682b
--- /dev/null
+++ b/tests/validators/validModel.js
@@ -0,0 +1,50 @@
+buster.testCase("validModel validator", {
+    setUp: function() {
+        var that = this;
+        var ParentModel = Backbone.Model.extend({
+            validation: {
+                childModel: {
+                    validModel: true
+                }
+            }
+        });
+        var ChildModel = Backbone.Model.extend({
+            validation: {
+                name: {
+                    required: true
+                }
+            }
+        });
+        this.childModel = new ChildModel();
+        this.childModel.set("name", '');
+        Backbone.Validation.bind(new Backbone.View({model: this.childModel})); // childModel requires a view for validation to work
+        this.model = new ParentModel({childModel:this.childModel});
+        this.view = new Backbone.View({
+            model: this.model
+        });
+
+        Backbone.Validation.bind(this.view, {
+            valid: this.spy(),
+            invalid: this.spy()
+        });
+    },
+
+    "has default error message for string": function(done) {
+        this.model.bind('validated:invalid', function(model, error){
+            assert.equals({childModel: 'Child model must be a validated model'}, error);
+            done();
+        });
+        this.model.isValid(true);
+    },
+    "has valid childModel": function() {
+        this.model.get("childModel").set("name", "Steve");
+        assert(this.model.isValid(true));
+    },
+    "has no childModel": function() {
+        this.model.set("childModel", null);
+        assert(this.model.isValid(true));
+    },
+    "has invalid childModel": function() {
+        refute(this.model.isValid(true));
+    }
+});
\ No newline at end of file