Adding backbone.js validations framework
							parent
							
								
									3402a3e463
								
							
						
					
					
						commit
						c45991b561
					
				|  | @ -5,6 +5,7 @@ | |||
| <script type="text/javascript" src="resources/bootstrap2/js/bootstrap.min.js"></script> | ||||
| <script type="text/javascript" src="resources/js/underscore-min.js"></script> | ||||
| <script type="text/javascript" src="resources/js/backbone-min.js"></script> | ||||
| <script type="text/javascript" src="resources/js/backbone.validations.js"></script> | ||||
| <script type="text/javascript" src="resources/js/app.js"></script> | ||||
| 
 | ||||
| </body> | ||||
|  |  | |||
|  | @ -0,0 +1,286 @@ | |||
| // Copyright (C) 2011 Neal Stewart
 | ||||
| //
 | ||||
| // Permission is hereby granted, free of charge, to any person obtaining a copy of
 | ||||
| // this software and associated documentation files (the "Software"), to deal in
 | ||||
| // the Software without restriction, including without limitation the rights to
 | ||||
| // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 | ||||
| // of the Software, and to permit persons to whom the Software is furnished to do
 | ||||
| // so, subject to the following conditions:
 | ||||
| //
 | ||||
| // The above copyright notice and this permission notice shall be included in all
 | ||||
| // copies or substantial portions of the Software.
 | ||||
| //
 | ||||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | ||||
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | ||||
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | ||||
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | ||||
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | ||||
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | ||||
| // SOFTWARE.
 | ||||
| 
 | ||||
| 
 | ||||
| (function(Backbone) { | ||||
| // Premade Validators
 | ||||
|     Backbone.Validations = {}; | ||||
| 
 | ||||
|     var validators = { | ||||
|         "custom" : function(methodName, attributeName, model, valueToSet) { | ||||
|             return model[methodName](attributeName, valueToSet); | ||||
|         }, | ||||
| 
 | ||||
|         "required" : function(attributeName, model, valueToSet) { | ||||
|             var currentValue = model.get(attributeName); | ||||
|             var isNotAlreadySet = _.isUndefined(currentValue); | ||||
|             var isNotBeingSet = _.isUndefined(valueToSet); | ||||
|             if (_.isNull(valueToSet) || valueToSet === "" || (isNotBeingSet && isNotAlreadySet)) { | ||||
|                 return "required"; | ||||
|             } else { | ||||
|                 return false; | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         "in" : function(whitelist, attributeName, model, valueToSet) { | ||||
|             return _.include(whitelist, valueToSet) ? undefined : "in"; | ||||
|         }, | ||||
| 
 | ||||
|         "email" : function(type, attributeName, model, valueToSet) { | ||||
|             var emailRegex = new RegExp("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"); | ||||
| 
 | ||||
|             if (_.isString(valueToSet) && !valueToSet.match(emailRegex)) { | ||||
|                 return "email"; | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         "url" : function(type, attributeName, model, valueToSet) { | ||||
|             // taken from jQuery UI validation
 | ||||
|             var urlRegex = /^(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; | ||||
|             if (_.isString(valueToSet) && !valueToSet.match(urlRegex)) { | ||||
|                 return "url"; | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         "number" : function(type, attributeName, model, valueToSet) { | ||||
|             return isNaN(valueToSet) ? 'number' : undefined; | ||||
|         }, | ||||
| 
 | ||||
|         "pattern" : function(pattern, attributeName, model, valueToSet) { | ||||
|             if (_.isString(valueToSet)) { | ||||
|                 if (pattern.test(valueToSet)) { | ||||
|                     return false; | ||||
|                 } else { | ||||
|                     return "pattern"; | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         "min" : function(minimumValue, attributeName, model, valueToSet) { | ||||
|             if (valueToSet < minimumValue) { | ||||
|                 return "min"; | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         "max" : function(maximumValue, attributeName, model, valueToSet) { | ||||
|             if (valueToSet > maximumValue) { | ||||
|                 return "max"; | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         "minlength" : function(minlength, attributeName, model, valueToSet) { | ||||
|             if (_.isString(valueToSet)) { | ||||
|                 if (valueToSet.length < minlength) { return "minlength"; } | ||||
|             } | ||||
|         }, | ||||
| 
 | ||||
|         "maxlength" : function(maxlength, attributeName, model, valueToSet) { | ||||
|             if (_.isString(valueToSet)) { | ||||
|                 if (valueToSet.length > maxlength) { return "maxlength"; } | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     var customValidators = {}; | ||||
|     var getCustomValidator = function(name) { | ||||
|         var cv = customValidators[name]; | ||||
|         if (!cv) { throw "custom validator '"+name+"' could not be found."; } | ||||
|         return cv; | ||||
|     }; | ||||
| 
 | ||||
|     Backbone.Validations.addValidator = function(name, validator) { | ||||
|         if (validators.hasOwnProperty(name) || customValidators.hasOwnProperty(name)) { | ||||
|             throw "existing validator"; | ||||
|         } | ||||
|         customValidators[name] = validator; | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
|     /* | ||||
|      The newValidate method overrides validate in Backbone.Model. | ||||
|      It has the same interface as the validate function which you | ||||
|      would provide. | ||||
| 
 | ||||
|      The returned object looks like this: | ||||
| 
 | ||||
|      { | ||||
|      attributeName : ["required", "of", "errors"], | ||||
|      otherAttributeName: ["and", "so", "on"] | ||||
|      } | ||||
| 
 | ||||
|      */ | ||||
|     function newValidate(attributes) { | ||||
|         var errorsForAttribute, | ||||
|             errorHasOccured, | ||||
|             errors = {}; | ||||
| 
 | ||||
|         for (var attrName in this._attributeValidators) { | ||||
|             var valueToSet = attributes[attrName]; | ||||
|             var validateAttribute = this._attributeValidators[attrName]; | ||||
|             if (validateAttribute)  { | ||||
|                 errorsForAttribute = validateAttribute(this, valueToSet); | ||||
|             } | ||||
|             if (errorsForAttribute) { | ||||
|                 errorHasOccured = true; | ||||
|                 errors[attrName] = errorsForAttribute; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return errorHasOccured ? errors : false; | ||||
|     } | ||||
| 
 | ||||
|     function createMinValidator(attributeName, minimumValue) { | ||||
|         return _.bind(validators.min, null, minimumValue); | ||||
|     } | ||||
| 
 | ||||
|     function createMaxValidator(attributeName, maximumValue) { | ||||
|         return _.bind(validators.max, null, maximumValue); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /* createValidator takes in: | ||||
|      - the model | ||||
|      - the name of the attribute | ||||
|      - the type of validation | ||||
|      - the description of the validation | ||||
| 
 | ||||
|      returns a function that takes in: | ||||
|      - the value being set for the attribute | ||||
| 
 | ||||
|      and either returns nothing (undefined), | ||||
|      or the error name (string). | ||||
|      */ | ||||
|     function createValidator(attributeName, type, description) { | ||||
|         var validator, | ||||
|             validatorMethod, | ||||
|             customValidator; | ||||
| 
 | ||||
|         if (type === "type") { | ||||
|             type = description; | ||||
|         } | ||||
|         validator = validators[type]; | ||||
| 
 | ||||
|         if (!validator) { validator = getCustomValidator(type); } | ||||
| 
 | ||||
|         if (!validator) { throw "Improper validation type '"+type+"'" ; } | ||||
| 
 | ||||
|         if (type !== "required") { // doesn't need the description
 | ||||
|             validator = _.bind(validator, null, description, attributeName); | ||||
|         } else { | ||||
|             validator = _.bind(validator, null, attributeName); | ||||
|         } | ||||
| 
 | ||||
|         return validator; | ||||
|     } | ||||
| 
 | ||||
|     function createAttributeValidator(attributeName, attributeDescription) { | ||||
|         var validatorsForAttribute = [], | ||||
|             type, | ||||
|             desc; | ||||
| 
 | ||||
|         for (type in attributeDescription) { | ||||
|             desc = attributeDescription[type]; | ||||
|             validatorsForAttribute.push(createValidator(attributeName, type, desc)); | ||||
|         } | ||||
| 
 | ||||
|         return function(model, valueToSet, hasOverridenError, options) { | ||||
|             var validator, | ||||
|                 result, | ||||
|                 errors = []; | ||||
| 
 | ||||
|             for (var i = 0, length = validatorsForAttribute.length; i < length; i++) { | ||||
|                 validator = validatorsForAttribute[i]; | ||||
|                 result = validator(model, valueToSet); | ||||
|                 if (result) { | ||||
|                     if (_.isArray(result)) { | ||||
|                         errors = errors.concat(result); | ||||
|                     } else { | ||||
|                         errors.push(result); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (errors.length) { | ||||
|                 return errors; | ||||
|             } else { | ||||
|                 return false; | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     function createValidators(modelValidations) { | ||||
|         var attributeValidations, | ||||
|             attributeValidators = {}; | ||||
| 
 | ||||
|         for (var attrName in modelValidations) { | ||||
|             attributeValidations = modelValidations[attrName]; | ||||
|             attributeValidators[attrName] = createAttributeValidator(attrName, attributeValidations); | ||||
|         } | ||||
| 
 | ||||
|         return attributeValidators; | ||||
|     } | ||||
| 
 | ||||
|     var oldPerformValidation = Backbone.Model.prototype._performValidation; | ||||
|     function newPerformValidation(attrs, options) { | ||||
|         if (options.silent || !this.validate) return true; | ||||
|         var errors = this.validate(attrs); | ||||
|         if (errors) { | ||||
|             if (options.error) { | ||||
|                 options.error(this, errors, options); | ||||
|             } else { | ||||
|                 this.trigger('error', this, errors, options); | ||||
|                 _.each(errors, function(error, name) { | ||||
|                     this.trigger('error:' + name, this, errors, options); | ||||
|                 }, this); | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
| // save the old backbone
 | ||||
|     var oldModel = Backbone.Model; | ||||
| 
 | ||||
| // Constructor for our new Validations Model
 | ||||
|     Backbone.Validations.Model = Backbone.Model.extend({ | ||||
|         constructor : function() { | ||||
|             // if they pass an object, construct the new validations
 | ||||
|             if (typeof this.validate === "object" && this.validate !== null) { | ||||
|                 if (!this.constructor.prototype._attributeValidators) { | ||||
|                     this.constructor.prototype._attributeValidators = createValidators(this.validate); | ||||
|                     this.constructor.prototype.validate = newValidate; | ||||
|                     this.constructor.prototype._validate = newPerformValidation; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             oldModel.apply(this, arguments); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
| // Override Backbone.Model with our new Model
 | ||||
|     Backbone.Model = Backbone.Validations.Model; | ||||
| 
 | ||||
| 
 | ||||
| // Requisite noConflict
 | ||||
|     Backbone.Validations.Model.noConflict =  function() { | ||||
|         Backbone.Model = oldModel; | ||||
|     }; | ||||
| 
 | ||||
| }(Backbone)); | ||||
		Loading…
	
		Reference in New Issue
	
	 Michael Jett
						Michael Jett