// // //var myObj.myConfig.configObjectName = { // rules:"required,email,minLength", // email:"@camelot,73,'a lot of text'", // minLength:"3", // sendMessage:"" // sendMessage:function(){} //}; // required,minLenght & maxLength,email & validateParent /** * go through a list of arguments, return the first one that validates * eval functions if are functions */ function ifAgain(){ var args = Array.prototype.slice.call(arguments); } var Validator = { /** * controls logging and errors * debug=true causes an exception when rule is missing * debug=false causes an log message when rule is missing * * also allows intercept of uncaught errors before form submits * ie: useful for debugging * * This will be automatically on for localhost development * Override this if you are developing on a remote server. * * DO NOT GO TO PRODUCTION WITH THIS SET TO TRUE */ debug: (document.location.hostname == "localhost")?true:false, submitting:false, validateInvisible:false, configAttributeName:"validator-config", /** * Exception used to allow a condition test rule to fail * and then stop all subsequent rules from running * @param message * @constructor */ StopValidationException:function(message){ this.message = message; this.name = "StopValidationException"; }, IgnoreRulesAndDisplayException:function(message){ this.message = message; this.name = "IgnoreRulesAndDisplayException"; }, /** * Regular expressions can be specified as validation rules */ regex:{ email:{ pattern:/^([\w]+)(.[\w]+)*@([\w]+)(.[\w]{2,3}){1,2}$/, message:"not a valid email address" }, ddmmyyyy:{ pattern:/^([0-9]){2}(\/|-){1}([0-9]){2}(\/|-){1}([0-9]){4}$/, message:"date must must be ddmmyyyy" } }, /** * single point for all validator messages * ie: can be overridden for different languages */ messages:{ form:"Please fix the errors in the form.", test:"this is a test error", required:"this field is required", minLength:"must be longer than %1" }, rules:{ required:function(element, args, errors, displayFunction){ var passesTest = JS.DOM.FORM.getValue(element).length > 0; return (passesTest)?{}:{required:Validator.messages.required}; }, notRequired:function(element, args, errors, displayFunction){ if(JS.DOM.FORM.getValue(element).length == 0){ throw new Validator.StopValidationException("condition failed"); } return {}; }, minLength:function(element, args, errors, displayFunction){ var len = args[0] || 1; var passes = JS.DOM.FORM.getValue(element).length >= len; return passes?{}:{minLength:JS.STRING.format(Validator.messages.minLength,len)}; } }, display:{ alertError:function(element,errors){ if(!_.isEmpty(errors)){ var errorStr = [ "This is the default error display handler:", JS.STRING.format("triggered by element[%1]with name[%2]",element.id,element.name), "To stop seeing this, set the 'errorDisplay' property in your config.", "A console log has been generated showing the element that caused this." ].join("\n"); for(e in errors){ errorStr += JS.STRING.format("test[%1]\n - fails with message[%2]\n",e,errors[e]); } alert(errorStr); } } }, runRegex:function(element, regexRuleName){ var value = JS.DOM.FORM.getValue(element); var regexRule = this.regex[regexRuleName]; var parses = regexRule.pattern.test(value); var errorObj = {}; errorObj[regexRuleName] = regexRule.message; return parses?{}:errorObj; }, /** * * @param ruleName * @param element * @return {*} */ runRule:function(element, ruleName, errors, displayFunction){ if(this.regex[ruleName]){ return this.runRegex(element, ruleName); }else{ var rule = (_.isFunction(ruleName))?ruleName:JS.OBJECT.getProperty(this.rules,ruleName); // handle missing rule if(!rule || !_.isFunction(rule)){ var msg = "ERROR with rule: " + ruleName; if(Validator.debug){ JS.log(msg,1); return JS.OBJECT.createFromArgPairs("ERROR",msg); }else{ return {}; } } // run rule // check ruleName is a String, as could be a Function var args = (_.isString(ruleName))?JS.DOM.DATA.getElementData(element,ruleName,this.configAttributeName, false):false; return rule(element, args, errors, displayFunction); } }, /** * Run all element rules * @param element * @param rules * @return [errors] */ runAllRules:function(element, rules, errors, displayFunction,continueOnError){ continueOnError = continueOnError || false; try{ for(num in rules){ var rule = rules[num]; if(!_.isEmpty(errors) && !continueOnError){ return errors; } if(rule instanceof Array){ errors = _.extend(errors, this.runAllRules(element, rule, errors, displayFunction, true)); }else{ errors = _.extend(errors, this.runRule(element, rule, errors, displayFunction)); } if(!_.isEmpty(errors) && !continueOnError){ return errors; } } }catch(e){ // a StopValidationException allows conditions to stop all other validations if(e.name != "StopValidationException"){ throw e; } } return errors; }, /** * Run a specific error display function * Get args for it if they exist * * @param element * @param displayName * @param errors * @return {*} */ runDisplay:function(element, displayName, errors){ var displayFunc = (_.isFunction(displayName))?displayName:JS.OBJECT.getProperty(this.display,displayName); JS.debug && JS.ASSERT.is(_.isFunction(displayFunc),true,"runDisplay:unable to find " + displayName); var args = (_.isString(displayName))?JS.DOM.DATA.getElementData(element,displayName,this.configAttributeName, false):false; return displayFunc(element, errors, args); }, /** * run all the error displays specified for the element * @param element * @param errors * @param displays */ runAllDisplays:function(element,displays,errors){ // iterate over displays for(num in displays){ var displayName = displays[num]; this.runDisplay(element,displayName,errors); } }, /** * parse validation rules * comma seperated means stop on failure * ampesand separated means continue on failure * eg: "required,email,minlength" or "required , email & minlength" * * @param rules * @return {*} */ parseRules:function(rules){ if(_.isString(rules)){ var firstCut = (rules)?rules.split(","):[]; var secondCut = _.map(firstCut,function(data){return data.indexOf("&") > -1?data.split("&"):data;}); rules = _.map(secondCut,JS.ARRAY.recursiveFunctionCallGenerator(JS.STRING.trim, _.map)); } return rules; }, /** * Parse display string from the config to an array * while allowing a config-object to specify an array directly, * eg: an array of functions * * @param displays * @return {*} */ parseDisplay:function(displays){ if(_.isString(displays)){ var firstCut = (displays)?displays.split(","):[]; displays = _.map(firstCut,JS.ARRAY.recursiveFunctionCallGenerator(JS.STRING.trim, _.map)); } return displays; }, validate:function(element){ JS.debug && JS.ASSERT.is( element && element.nodeType != undefined,true,"validate: element is not a node"); // var to only allow rules to run once, ie: when allowed == true var allowed = (allowed == undefined)?true:false; // get data-attr based rules var rules = this.parseRules(JS.DOM.DATA.getElementData(element,"rules",this.configAttributeName, false)); var displays = this.parseDisplay(JS.DOM.DATA.getElementData(element, "errorDisplay",this.configAttributeName, "alertError")); var errors = {}; // allow all rules and display to be skipped try{ // run rules errors = this.runAllRules(element, rules, errors, displays); // display result: ie: valid or errors this.runAllDisplays(element,displays,errors); }catch(e){ if(e.name != "IgnoreRulesAndDisplayException"){ throw e; } } // stop submit propagating return errors; }, /** * Validation event handler * @param event * @return {*} */ triggerValidation:function(event){ try{ var element = event.target; if(event.data && event.data.submitting){ Validator.submitting = true; } var errors = this.validate(element); Validator.submitting = false; return _.isEmpty(errors); }catch(e){ // this will catch and stop the form from submitting if there are errors if(Validator.debug && confirm("validation error.\n Do you want to debug now?\n ie: before the form submits?")){ debugger; return false; } throw e; } } }; // TODO: get config object rules first, as can then be overridden by tag attr? // keyup could be useful for AJAX validation, eg: username exists?