// Define a "push" method for array if not exist
// Based on code from http://prototype.conio.net/
if (!Array.prototype.push) {
Array.prototype.push = function () {
var startLength = this.length;
for (var i = 0; i < arguments.length; i++) {
this[startLength + i] = arguments[i];
}
return this.length;
}
}
// Define a "apply" method for prototype if not exist
// Based on code from http://prototype.conio.net/
if (!Function.prototype.apply) {
Function.prototype.apply = function (object, parameters) {
var parameterStrings = [];
if (!object) {
object = window;
}
if (!parameters) {
parameters = [];
}
for (var i = 0; i < parameters.length; i++) {
parameterStrings[i] = 'parameters[' + i + ']';
}
object.__apply__ = this;
var result = eval('object.__apply__(' + parameterStrings.join(', ') + ')');
object.__apply__ = null;
return result;
}
}
/**
* @class
* @param {string} name The form id in the page
* @param {string} formObjectName the full qualified object name on server side, mandatory for AJAX validation
* @param {JSValidator.Rule[]} rules The JSON rule array
* @param {JSON} config The Extra config for override default config
* @property {JSValidator.Form} form The binding form
**/
var JSValidator = function (name, formObjectName, rules, config) {
this.name = name;
this.objectName = formObjectName;
this.config = config;
this.rules = rules;
this.form = this._findForm(name); //Attach form to the validator
};
/**
* Default conf for the validator
* @static
* @type {Object}
*/
JSValidator.defaultConf = {
errorLocalMessageTemplate: "<span class='{{class}}'>{{message}}</span>", // template for field message, {{class}} and {{message}} are mandatory
errorGlobalMessageTemplate: "<span class='{{class}}'>{{message}}</span>", // template for global messages, {{class}} and {{message}} are mandatory
ajaxValidateFieldURL: 0, // URI of the ajax validate service
ajaxValidateFieldParams: function (objectName, fieldName, fieldvalue, constaints) {
/*
* this method is the default builder for the parameters sent to the AJAX validate field service.
* key: represent the key of the parameter
* value: represent his value
* you can override this method to your needs, add params, remove params
*/
return {
objectName: objectName,
fieldName: fieldName,
fieldValue: fieldvalue,
constraints: constaints
}
},
debug: false
};
/**
* Validator utils methods, for realise commons tasks
* @static
* @class
* @private
*/
JSValidator.Utils = {
/**
* Utils for make ajax request easily with native JavaScript Code
* @namespace
* @private
*/
_ajax: {
/**
* Use the right AJAX object type depending on the browser
* @function
* @private
*/
x: function () {
try {
return new ActiveXObject('Msxml2.XMLHTTP');
} catch (e1) {
try {
return new ActiveXObject('Microsoft.XMLHTTP');
} catch (e2) {
return new XMLHttpRequest();
}
}
},
/**
*
* @param {string} url URL to send the request
* @param {function} callback Callback to process onSuccess
* @param {string} method POST/GET
* @param {JSON} data Data to send
* @param {boolean} sync
* @function
* @private
*/
send: function (url, callback, method, data, sync) {
var x = this.x();
x.open(method, url, sync);
x.onreadystatechange = function () {
if (x.readyState == 4) {
callback(x.responseText);
}
};
if (method == 'POST') {
x.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
}
x.send(data);
},
/**
* Ajax GET method, all the parameters in data are add to the URL query string.
* @param {String} url URL to send the request
* @param {JSON} data Data to send
* @param {function} callback Callback to process onSuccess
* @param {boolean} sync
* @function
*/
doGet: function (url, data, callback, sync) {
var query = [];
for (var key in data) {
query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]));
}
this.send(url + '?' + query.join('&'), callback, 'GET', null, sync);
},
/**
* Ajax POST method, all the parameters in data are send in the request
* @param {String} url URL to send the request
* @param {JSON} data Data to send
* @param {function} callback Callback to process onSuccess
* @param {boolean} sync
* @function
*/
doPost: function (url, data, callback, sync) {
var query = [];
for (var key in data) {
query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]));
}
this.send(url, callback, 'POST', query.join('&'), sync);
}
},
/**
* Method used for bind event to a specific element
* @param {Element} element HTML element
* @param {String} type Event type (change/keypress/keydown/...)
* @param {function} fn Callback function
* @param {boolean} propagation
* @private
*/
_bindEvent: function (element, type, fn, propagation) {
if (element.addEventListener) {
element.addEventListener(type, fn, propagation);
} else if (element.attachEvent) {
element.attachEvent('on' + type, fn);
}
},
/**
* Method use for bind all the field elements of a validator Field to a specific element
* @param {JSValidator.Field} field
* @param {String} type Event type (change/keypress/keydown/...)
* @param {function} callback
* @param {boolean} propagation
* @private
*/
_bindFieldToEvent: function (field, type, callback, propagation) {
for (var i = 0; i < field.fieldElements.length; i++) {
var fieldElement = field.fieldElements[i];
this._bindElementToEvent(field, fieldElement, type, callback, propagation);
}
},
/**
* Bind a specific event to an HTML element
* @param {JSValidator.Field[]} fields
* @param {HTMLElement} element
* @param {String} type Event type (change/keypress/keydown/...)
* @param {function} callback
* @param {boolean} propagation
* @private
*/
_bindElementToEvent: function (fields, element, type, callback, propagation) {
// create a proxy callback for encapsulate the event object
var fn = function (event) {
callback(event, fields);
};
this._bindEvent(element, type, fn, propagation);
},
/**
* method return only rules that need to be validated in AJAX
* @param {JSValidator.Rule[]} rules
* @returns {JSValidator.Rule[]}
* @private
*/
_getAjaxableInRules: function (rules) {
var ajaxables = [];
rules.forEach(function (rule) {
if (rule.params.ajaxable) {
ajaxables.push(rule);
}
});
return ajaxables;
},
/**
* method return only rules that need to be validated client side
* @param {JSValidator.Rule[]} rules
* @returns {JSValidator.Rule[]}
* @private
*/
_getDefaultInRules: function (rules) {
var defaults = [];
rules.forEach(function (rule) {
if (!rule.params.ajaxable) {
defaults.push(rule);
}
});
return defaults;
},
/**
* method for build an error line
* @param {JSValidator.Field} field
* @param ruleViolation
* @param global
* @returns {String}
* @private
*/
_buildErrorLine: function (field, ruleViolation, global) {
if (!ruleViolation.params.message) {
return "";
}
var error = global ? field.validator._getProp("errorGlobalMessageTemplate")
: field.validator._getProp("errorLocalMessageTemplate");
error = error.replace("{{class}}", this._buildErrorClassName(field, ruleViolation.constraint));
error = error.replace("{{message}}", ruleViolation.params.message);
return error;
},
//
/**
* Build the class name replaced in error messages for a specific JSValidator.Field and constraint name
* @param {JSValidator.Field} field
* @param {String} constraint
* @returns {string}
* @private
*/
_buildErrorClassName: function (field, constraint) {
return field.name + "_" + constraint + "_error"
}
};
// Validator API
JSValidator.prototype = {
/**
* Logger for JSValidator, log only if prop debug is set at TRUE
* @param {String} msg Message
*/
log: function (msg) {
if (this._getProp("debug")) {
console.log(msg);
}
},
/**
* Method for find and bind the form to the current validator instance
* @param {String} name Form id in the page
* @returns {JSValidator.Form}
* @private
*/
_findForm: function (name) {
var element = document.getElementById(name);
if (!element || element.tagName.toLowerCase() != 'form') {
element = document.getElementById(name + 'JSValidator');
if (!element || element.tagName.toLowerCase() != 'script') {
throw 'unable to find form with ID \'' + name + '\' or script element with ID \'' + name + 'JSValidator\'';
}
}
var foundElement = element;
while (element && element.tagName.toLowerCase() != 'form') {
element = element.parentNode;
}
if (!element) {
throw 'unable to find FORM element enclosing element with ID \'' + foundElement.id + '\'';
}
return new JSValidator.Form(element, this);
},
/**
* Getter for the form binded to the current validator
* @returns {JSValidator.Form}
*/
getForm: function () {
return this.form;
},
/**
* Getter for a Field with name
* @param {String} fieldName
* @returns {JSValidator.Field}
*/
getFieldWithName: function (fieldName) {
return this.form.getFieldWithName(fieldName);
},
/**
* Getter for all the form Fields
* @returns {JSValidator.Field[]}
*/
getFields: function () {
return this.form.getFields();
},
/**
* Getter for a prop from it's propName,
* check first in custom conf before check in default conf
* @returns {Object}
* @private
*/
_getProp: function (propName) {
if (this.config && this.config[propName]) {
return this.config[propName];
} else {
return JSValidator.defaultConf[propName];
}
},
/**
* SCOPE ELEMENT API: bind validation to external HTML Element
* @param {String} elementId
* @param {String} eventType Event type (keypress, click, submit, etc.)
* @param {boolean} preventDefaultOnFail True: Cancel event there has failed rules
* @returns {*}
*/
bindValidationToElement: function (elementId, eventType, preventDefaultOnFail) {
var element = document.getElementById(elementId);
if (element) {
return new JSValidator.Element(element, eventType, preventDefaultOnFail, this).actions;
} else {
this.log("can't find element with Id: " + elementId);
}
}
};
/**
* @class
* @param {HTMLFormElement} formElement The HTML form element
* @param {JSValidator} validator The current validator
* @property {JSValidator.Field[]} fields All the form Fields
*/
JSValidator.Form = function (formElement, validator) {
this.formElement = formElement;
this.validator = validator;
this.fields = this._findFields();
};
JSValidator.Form.prototype = {
/**
* Get the value of a specific field
* @param {String} fieldName
* @returns {String|String[]}
*/
getValue: function (fieldName) {
return this.getFieldWithName(fieldName).getValue();
},
/**
* Getter for a Field with name
* @param {String} fieldName
* @returns {JSValidator.Field}
*/
getFieldWithName: function (fieldName) {
var fields = this.getFields();
for (var i = 0; i < fields.length; i++) {
if (fields[i].name == fieldName) {
return fields[i];
}
}
return null;
},
/**
* Getter for all the form Fields
* @returns {JSValidator.Field[]}
*/
getFields: function () {
return this.fields;
},
/**
* Find and bind fields to the current form object
* @returns {JSValidator.Field[]}
* @private
*/
_findFields: function () {
var instance = this;
var fields = [];
var tagElements = this.formElement.elements;
var inputNames = [];
for (var i = 0; i < tagElements.length; i++) {
if (tagElements[i].tagName.toLowerCase() != "fieldset" &&
tagElements[i].name && !inputNames[tagElements[i].name]) {
inputNames[tagElements[i].name] = true;
var field = new JSValidator.Field(document.getElementsByName(tagElements[i].name), instance.validator);
if (field._hasValidationRules()) {
fields.push(field);
}
}
}
return fields;
},
/**
* Proxy function for execute actions on Form API
* @param {Event} event
* @param {JSValidator.RuleViolation} ruleViolations
* @param {Stirng} actionFnName Action function name to run
* @private
*/
_doAction: function (event, ruleViolations, actionFnName) {
if (this.actions[actionFnName]) {
this.actions[actionFnName](event, ruleViolations);
}
},
/**
* Method for add global actions on fields
* @param {function} fn Function to add behind the action
* @param {String} actionsFnName Action function name to run
* @private
*/
_addGlobalProcess: function (fn, actionsFnName) {
var instance = this;
var fields = instance.getFields();
var newGlobalActions = new JSValidator.Field.FieldActions();
newGlobalActions[actionsFnName](fn);
fields.forEach(function (field) {
var globalActions = field._getActionsForActionKey("always");
if (globalActions) {
globalActions[actionsFnName](fn);
} else {
field._addActionsToEventType("always", newGlobalActions);
}
});
},
/**
* FORM SCOPE API: add global prevalidation process (on all the fields)
* @param {function} fn The callback to execute behind the action
* @returns {JSValidator.Form}
*/
addFieldsPreValidationProcess: function (fn) {
this._addGlobalProcess(fn, "addPreValidationProcess");
return this;
},
/**
* FORM SCOPE API: add global postvalidation process before messages display (on all the fields)
* @param {function} fn The callback to execute behind the action
* @returns {JSValidator.Form}
*/
addFieldsPostValidationBeforeMessageProcess: function (fn) {
this._addGlobalProcess(fn, "addPostValidationBeforeMessageProcess");
return this;
},
/**
* FORM SCOPE API: add global postvalidation process after messages display (on all the fields)
* @param {function} fn The callback to execute behind the action
* @returns {JSValidator.Form}
*/
addFieldsPostValidationAfterMessageProcess: function (fn) {
this._addGlobalProcess(fn, "addPostValidationAfterMessageProcess");
return this;
}
};
/**
*
* @param {HTMLElement} element
* @param {String} eventType Event type (keypress, submit, click, etc.)
* @param {boolean} preventDefaultOnFail True: cancel event if there has failed rules
* @class
*/
JSValidator.Element = function (element, eventType, preventDefaultOnFail) {
this.element = element;
this.eventType = eventType;
this.preventDefaultOnFail = preventDefaultOnFail;
this.actions = new JSValidator.Element.ElementActions(this);
};
JSValidator.Element.prototype = {
/**
* Start point to get FIELD API from ELEMENT API
* @param {JSValidator.Field|JSValidator.Field[]} targetField
* @returns {JSValidator.Field.FieldActions}
* @private
*/
_bindFields: function (targetField) {
var instance = this;
var actionKey = instance.eventType + "." + instance.element.id;
var targetFields = targetField instanceof Array ? targetField : [targetField];
var actions = new JSValidator.Field.FieldActions();
JSValidator.Utils._bindElementToEvent(targetFields, instance.element, instance.eventType, function (event, fields) {
var ruleViolationsByField = [];
var validateFieldTemp = 0;
// Do preValidation
instance._doAction(event, null, "preValidationProcess");
fields.forEach(function (field) {
field._addActionsToEventType(actionKey, actions);
var validate = true;
field._doValidateField(event, field, actionKey, function (ruleViolations) {
validateFieldTemp++;
if (ruleViolations.length > 0) {
validate = false;
ruleViolationsByField.push(new JSValidator.FieldViolation(field, ruleViolations))
}
if (validateFieldTemp == targetFields.length) {
// Do postValidation
instance._doAction(event, ruleViolationsByField, "postValidationProcess");
// if errors don't send the form
if (ruleViolationsByField.length > 0 && instance.preventDefaultOnFail) {
event.preventDefault();
}
}
});
});
}, false);
return actions;
},
/**
* Proxy method for execute Element actions
* @param {String} event
* @param {JSValidator.RuleViolation[]} ruleViolations
* @param actionFnName
* @private
*/
_doAction: function (event, ruleViolations, actionFnName) {
if (this.actions[actionFnName]) {
this.actions[actionFnName](event, ruleViolations);
}
}
};
/**
* Element specific actions after the validation was binded to a specific event.
* @class
*/
JSValidator.Element.ElementActions = function (element) {
this.element = element;
};
JSValidator.Element.ElementActions.prototype = {
/**
* ELEMENT API: Add pre validation process
* This is the first end point of the validation process on the element event
* @param {function} fn The callback to execute behind the action
* @returns {JSValidator.Element.ElementActions}
*/
addPreValidationProcess: function (fn) {
this.preValidationProcess = fn;
return this;
},
/**
* ELEMENT API: Add post validation process
* This is the last end point of the validation process on the element event
* @param {function} fn The callback to execute behind the action
* @returns {JSValidator.Element.ElementActions}
*/
addPostValidationProcess: function (fn) {
this.postValidationProcess = fn;
return this;
},
/**
* ELEMENT API: Start point to get FIELD API from ELEMENT API
* bind a group of fields to the element validation event, returning FIELD API for define specific fields actions
* @param {JSValidator.Field|JSValidator.Field[]} targetField
* @returns {JSValidator.Field.FieldActions}
*/
bindFields: function (targetField) {
return this.element._bindFields(targetField);
}
};
/**
* @class
* @param fieldElements All the html fields elements that match the current Field name
* @param {JSValidator} validator The current validator
* @property {String} name Field html name
* @property {String} tagName Field html tag name
* @property {String} type Field html type
* @property {Array} fieldElements all the html fields fot the current field name
*/
JSValidator.Field = function (fieldElements, validator) {
this.validator = validator;
this.name = fieldElements[0].name;
this.tagName = fieldElements[0].tagName.toLowerCase();
this.type = fieldElements[0].type.toLowerCase();
this.fieldElements = fieldElements;
this.actions = [];
// init fields value getters
if (JSValidator.Field.ValueGetters[this.tagName]) {
this.getValue = JSValidator.Field.ValueGetters[this.tagName];
} else if (this.tagName == 'input') {
switch (this.type) {
case 'submit':
case 'hidden':
case 'password':
case 'text':
this.getValue = JSValidator.Field.ValueGetters['textarea'];
break
case 'checkbox':
this.getValue = JSValidator.Field.ValueGetters['checkbox'];
break
case 'radio':
this.getValue = JSValidator.Field.ValueGetters['radio'];
break
default:
throw 'unexpected input field type \'' + this.type + '\'';
}
} else {
throw 'unexpected form field tag name \'' + this.tagName + '\'';
}
};
JSValidator.Field.prototype = {
/**
* Field API: Bind the current field validation to an event
* @param {String} type Event type (keypress, keyup, change, etc.)
* @returns {JSValidator.Field.FieldActions}
*/
bindValidationToEvent: function (type) {
var instance = this;
var actions = new JSValidator.Field.FieldActions();
var atype = 0;
type.trim().split(",").forEach(function (theType) {
atype = theType;
instance._addActionsToEventType(theType, actions);
JSValidator.Utils._bindFieldToEvent(instance, theType, instance._initFieldValidation, false);
});
if (atype) {
return instance._getActionsForActionKey(atype);
}
},
/**
* Method for execute all conditions process bind to the event on a given event
* @param {Event} event
* @returns {boolean}
* @private
*/
_executeConditions: function (actionsKey) {
var instance = this;
try {
var conditions = instance._getActionsForActionKey(actionsKey);
if (conditions && conditions.length > 0) {
instance.validator.log("Execute validation conditions");
instance._getActionsForActionKey(actionsKey).conditions.forEach(function (condition) {
if (!condition(event, instance)) {
throw "conditionFailed";
}
});
}
} catch (err) {
if (err == "conditionFailed") {
instance.validator.log("Conditions failed");
return false;
} else {
throw err;
}
}
return true;
},
/**
* Getter for all the JSValidator.Rule associate to the current Field
* @returns {JSValidator.Rule[]}
* @private
*/
_getFieldRules: function () {
var instance = this;
var rules = [];
for (var i = 0; i < instance.validator.rules.length; i++) {
if (instance.validator.rules[i].field == instance.name) {
var rule = instance.validator.rules[i];
rule.form = instance.validator.form;
rules.push(rule);
}
}
return rules;
},
/**
* Check if the current have JSValidator.Rule
* @returns {boolean}
* @private
*/
_hasValidationRules: function () {
return this._getFieldRules().length > 0;
},
/**
* Run the validation for all the JSValidator.Rule on the current field
* @param {function} callback
* @private
*/
_doValidateRules: function (callback) {
var instance = this;
var rules = this._getFieldRules();
if (rules.length > 0) {
instance._validateRules(rules, callback);
} else {
instance.validator.log('Unable to find validation rules for field "' + instance.name + '"');
}
},
/**
* Execute the validation for all the given JSValidator.Rule
* @param {JSValidator.Rule[]} rules
* @param {function} validationCallBack Callback to execute after the validation
* @private
*/
_validateRules: function (rules, validationCallBack) {
var instance = this;
var ruleViolations = [];
// Validate default rules
var defaultRules = JSValidator.Utils._getDefaultInRules(rules);
defaultRules.forEach(function (defaultRule) {
instance.validator.log('Validating rule [' + defaultRule.constraintName + '] ' +
'for field [' + defaultRule.field + ']');
if (!defaultRule.validate(this)) {
instance.validator.log('Failed');
ruleViolations.push(new JSValidator.RuleViolation(defaultRule));
} else {
instance.validator.log('Passed');
}
});
// Validate ajax rules
var ajaxServiceURL = instance.validator._getProp("ajaxValidateFieldURL");
var ajaxRules = JSValidator.Utils._getAjaxableInRules(rules);
if (ajaxServiceURL && ajaxRules.length > 0) {
var constraints = [];
ajaxRules.forEach(function (ajaxRule) {
constraints.push(ajaxRule.constraintName);
});
var data = instance.validator._getProp("ajaxValidateFieldParams")(
instance.validator.objectName
, ajaxRules[0].field
, instance.getValue()
, constraints.join(","));
instance.validator.log('AJAX Validating rules ' +
'for field [' + ajaxRules[0].field + ']');
JSValidator.Utils._ajax.doPost(ajaxServiceURL, data, function (data) {
if (data) {
ruleViolations = ruleViolations.concat(JSON.parse(data));
if (ruleViolations.length > 0) {
instance.validator.log('Failed');
} else {
instance.validator.log('Passed');
}
}
if (validationCallBack) {
validationCallBack(ruleViolations);
}
})
} else if (!ajaxServiceURL && ajaxRules.length > 0) {
instance.validator.log('Unable to validates rules in AJAX, ' +
'no service URL provide in validator config');
} else {
if (validationCallBack) {
validationCallBack(ruleViolations);
}
}
},
/**
* Method for init the validation on a given field
* @param {Event} event
* @param {JSValidator.Field} field
* @private
*/
_initFieldValidation: function (event, field) {
if (field._getActionsForEvent(event).validationTimeoutDelay
&& !isNaN(field._getActionsForEvent(event).validationTimeoutDelay)) {
clearInterval(field._getActionsForEvent(event).validationTimeout);
field._getActionsForEvent(event).validationTimeout =
setTimeout(function () {
field._doValidateField(event, field, event.type);
},
field._getActionsForEvent(event).validationTimeoutDelay);
} else {
field._doValidateField(event, field, event.type);
}
},
/**
* Proxy method for execute actions allowed on Field API
* @param {Event} event
* @param {JSValidator.Field} field
* @param {JSValidator.RuleViolation[]} ruleViolations The rule violations to send to the action callback
* @param {String} actionFnName The action to run
* @private
*/
_doAction: function (event, field, ruleViolations, actionFnName, actionsKey) {
var globalAction = field._getActionsForActionKey("always");
if (globalAction && globalAction[actionFnName]) {
globalAction[actionFnName](event, field, ruleViolations);
}
if (field._getActionsForActionKey(actionsKey)[actionFnName]) {
field._getActionsForActionKey(actionsKey)[actionFnName](event, field, ruleViolations);
}
},
/**
* Main method for run an entire Field validation
* @param {Event} event
* @param {JSValidator.Field} field
* @param {function} callback Callback function execute at the really end of the validation process
* @private
*/
_doValidateField: function (event, field, actionsKey, callback) {
var instance = this;
instance.validator.log("Start validating field:" + field.name);
// Do conditions
if (!field._hasValidationRules() || !field._executeConditions(actionsKey)) {
if (callback) {
callback([]);
}
return true;
}
field._doAction(event, field, null, "preValidationProcess", actionsKey);
//Do validation
field._doValidateRules(function (ruleViolations) {
// Post validation process
field._doAction(event, field, ruleViolations,
"postValidationProcessBeforeMessage", actionsKey);
// Display error messages
field._updateErrorMessages(ruleViolations);
// Post validation process
field._doAction(event, field, ruleViolations, "postValidationProcessAfterMessage", actionsKey);
if (callback) {
callback(ruleViolations);
}
});
},
/**
* Main method for update error messages for the current field
* @param {JSValidator.RuleViolation[]} ruleViolations
* @private
*/
_updateErrorMessages: function (ruleViolations) {
this._updateLocalErrorMessages(ruleViolations);
this._updateGlobalErrorMessages(ruleViolations);
},
/**
* method for update globals errors messages
* @param {JSValidator.RuleViolation[]} ruleViolations
* @private
*/
_updateGlobalErrorMessages: function (ruleViolations) {
var instance = this;
var errorContainer = document.getElementById(instance.validator.form.formElement.getAttribute("id")
+ "_errors");
if (errorContainer) {
var newErrorContainer = errorContainer.cloneNode(true);
// Clean errors related to this field in global container
var fieldRules = instance._getFieldRules();
fieldRules.forEach(function (rule) {
var errorLineToDelete = newErrorContainer.getElementsByClassName(
JSValidator.Utils._buildErrorClassName(instance, rule.constraintName))
if (errorLineToDelete.length > 0) {
newErrorContainer.removeChild(errorLineToDelete[0]);
}
});
// Add errors related to this field
ruleViolations.forEach(function (ruleViolation) {
newErrorContainer.innerHTML += JSValidator.Utils._buildErrorLine(instance, ruleViolation, true);
});
errorContainer.parentNode.replaceChild(newErrorContainer, errorContainer);
}
},
/**
* method for local errors messages
* @param {JSValidator.RuleViolation[]} ruleViolations
* @private
*/
_updateLocalErrorMessages: function (ruleViolations) {
var instance = this;
var errorContainer = document.getElementById(instance.name + "_error");
if (errorContainer) {
var newErrorContainer = errorContainer.cloneNode(false);
ruleViolations.forEach(function (ruleViolation) {
newErrorContainer.innerHTML += JSValidator.Utils._buildErrorLine(instance, ruleViolation, false);
});
errorContainer.parentNode.replaceChild(newErrorContainer, errorContainer);
}
},
/**
* set actions on the current field for a given event type
* @param {String} type Event type
* @param {JSValidator.Field.FieldActions} actions
* @private
*/
_addActionsToEventType: function (type, actions) {
this.actions[type] = actions;
},
/**
* get actions on the current field for a given event
* @param {Event} event
* @private
*/
_getActionsForEvent: function (event) {
return this._getActionsForActionKey(event.type)
},
/**
* get actions on the current field for a given event type
* @param {String} eventType
* @private
*/
_getActionsForActionKey: function (eventType) {
return this.actions[eventType];
}
};
/**
* Field specific actions after the validation was binded to a given event.
* @class
*/
JSValidator.Field.FieldActions = function () {
};
JSValidator.Field.FieldActions.prototype = {
/**
* Field API: Add a condition process to the validation
* @param {function} condition The condition function to add (this function must return a boolean)
* @returns {JSValidator.Field.FieldActions}
*/
addValidationCondition: function (condition) {
if (!this.conditions) {
this.conditions = [];
}
this.conditions.push(condition);
return this;
},
/**
* Field API: Add a pre validation process to the validation
* @param {function} fn
* @returns {JSValidator.Field.FieldActions}
*/
addPreValidationProcess: function (fn) {
this.preValidationProcess = fn;
return this;
},
/**
* Field API: Add a post validation process before message was printed to the validation
* @param {function} fn
* @returns {JSValidator.Field.FieldActions}
*/
addPostValidationBeforeMessageProcess: function (fn) {
this.postValidationProcessBeforeMessage = fn;
return this;
},
/**
* Field API: Add a post validation process after message was printed to the validation
* @param {function} fn
* @returns {JSValidator.Field.FieldActions}
*/
addPostValidationAfterMessageProcess: function (fn) {
this.postValidationProcessAfterMessage = fn;
return this;
},
/**
* Field API: Set a delay to the validation
* @param {Number} delay Delay in ms
* @returns {JSValidator.Field.FieldActions}
*/
setValidationDelay: function (delay) {
this.validationTimeoutDelay = delay;
return this;
}
};
/**
* Value getter for Fields
* @static
* @class
* @private
*/
JSValidator.Field.ValueGetters = {
radio: function () {
var value = null;
for (var i = 0; i < this.fieldElements.length; i++) {
if (this.fieldElements[i].checked) {
value = this.fieldElements[i].value;
}
}
return value;
},
checkbox: function () {
var value = [];
for (var i = 0; i < this.fieldElements.length; i++) {
if (this.fieldElements[i].checked) {
value.push(this.fieldElements[i].value);
}
}
return value;
},
textarea: function () {
if (this.fieldElements.length == 1) {
return this.fieldElements[0].value;
} else if (this.fieldElements.length > 1) {
var arrayValue = [];
for (var i = 0; i < this.fieldElements.length; i++) {
var fieldElement = this.fieldElements[i];
arrayValue.push(fieldElement.value);
}
return arrayValue;
}
return null
},
select: function () {
var value = null;
if (this.fieldElements[0].type == 'select-one') {
value = this.fieldElements[0].value;
} else if (this.fieldElements[0].type == 'select-multiple') {
value = [];
for (var i = 0; i < this.fieldElements[0].options.length; i++) {
var option = this.fieldElements[0].options[i];
if (option.selected) {
value.push(option.value)
}
}
}
return value
}
};
/**
* Representation of a rule
* @param {String} field Field name
* @param {String} constraintName Constraint name, used to retrieve function to execute
* @param {JSON} params
* @class
*/
JSValidator.Rule = function (field, constraintName, params) {
this.field = field;
this.params = params;
this.constraintName = constraintName;
};
/**
* Representation of a rule violation
* @param {JSValidator.Rule} rule
* @property {String} constraint Constraint name
* @property {JSON} params Constraint params (message, min, max, etc.)
* @class
*/
JSValidator.RuleViolation = function (rule) {
this.constraint = rule.constraintName;
this.params = JSON.parse(JSON.stringify(rule.params));
};
/**
* Representation of a field violation
* @param {JSValidator.Field} field
* @param {JSValidator.RuleViolation[]} ruleViolations
* @property {String} field Field name
* @property {JSValidator.RuleViolation[]} ruleViolations
* @class
*/
JSValidator.FieldViolation = function (field, ruleViolations) {
this.field = field.name;
this.ruleViolations = ruleViolations;
};
JSValidator.Rule.prototype = {
/**
* Validate the current JSValidator.Rule
* @param {JSValidator} validator
* @returns {boolean}
*/
validate: function (validator) {
var f = this[this.constraintName];
if (!f || typeof f != 'function') {
return true;
}
return f(this.getPropertyValue(this.field), this.params, this.field, validator);
},
/**
* Get the rule error message
* @returns {String}
*/
getErrorMessage: function () {
return (this.params.message || 'Invalid value for ' + this.field);
},
/**
* Get the field value
* @param {String} propertyName Field name
* @returns {String|String[]}
*/
getPropertyValue: function (propertyName) {
return this.form.getValue(propertyName)
},
/**
* Assert the given value has Length (throm error)
* @param {String|String[]} value
* @private
*/
_assertHasLength: function (value) {
if (!value.length) {
throw 'value \'' + value + '\' does not have length';
}
},
/**
* Assert the given value has the given Length (throm error)
* @param {String|String[]} value
* @param {Number} length
* @private
*/
_assertLength: function (value, length) {
this._assertHasLength(value);
if (value.length != length) {
throw 'value\'s length != \'' + length + '\'';
}
},
/**
* Constraint: AssertFalse
* @param {String|String[]} value
* @param {JSON} params
* @returns {boolean}
*/
AssertFalse: function (value, params) {
return (value == 'false');
},
/**
* Constraint: AssertTrue
* @param {String|String[]} value
* @param {JSON} params
* @returns {boolean}
*/
AssertTrue: function (value, params) {
return (value == 'true');
},
/**
* Constraint: DecimalMax
* @param {String|String[]} value
* @param {JSON} params
* @returns {boolean}
*/
DecimalMax: function (value, params) {
var valid = true;
if (value) {
var valueNumber = new Number(value).valueOf();
if (isNaN(valueNumber)) {
valid = false;
} else {
valid = valueNumber <= new Number(params.value).valueOf();
}
}
return valid;
},
/**
* Constraint: DecimalMin
* @param {String|String[]} value
* @param {JSON} params
* @returns {boolean}
*/
DecimalMin: function (value, params) {
var valid = true;
if (value) {
var valueNumber = new Number(value).valueOf();
if (isNaN(valueNumber)) {
valid = false;
} else {
valid = valueNumber >= new Number(params.value).valueOf();
}
}
return valid;
},
/**
* Constraint: Digits
* @param {String|String[]} value
* @param {JSON} params
* @returns {boolean}
*/
Digits: function (value, params) {
var valid = true;
if (value) {
var valueNumber = new Number(value).valueOf();
if (isNaN(valueNumber)) {
valid = false;
} else {
var valueNumberString = valueNumber.toString();
var numberParts = valueNumberString.split('.');
if (params.integer && numberParts[0].length > params.integer) {
valid = false;
}
if (valid && params.fraction && numberParts.length > 1 && numberParts[1].length > params.fraction) {
valid = false;
}
}
}
return valid;
},
/**
* Constraint: Max
* @param {String|String[]} value
* @param {JSON} params
* @returns {boolean}
*/
Max: function (value, params) {
var valid = true;
if (value) {
var valueNumber = new Number(value).valueOf();
if (isNaN(valueNumber)) {
valid = false;
} else {
valid = valueNumber <= new Number(params.value).valueOf();
}
}
return valid;
},
/**
* Constraint: Min
* @param {String|String[]} value
* @param {JSON} params
* @returns {boolean}
*/
Min: function (value, params) {
var valid = true;
if (value) {
var valueNumber = new Number(value).valueOf();
if (isNaN(valueNumber)) {
valid = false;
} else {
valid = valueNumber >= new Number(params.value).valueOf();
}
}
return valid;
},
/**
* Constraint: NotNull
* @param {String|String[]} value
* @param {JSON} params
* @returns {boolean}
*/
NotNull: function (value, params) {
return (value && value.toString().length > 0);
},
/**
* Constraint: Null
* @param {String|String[]} value
* @param {JSON} params
* @returns {boolean}
*/
Null: function (value, params) {
return (!value || value.toString().length == 0);
},
/**
* Constraint: Pattern
* @param {String|String[]} value
* @param {JSON} params
* @returns {boolean}
*/
Pattern: function (value, params) {
var valid = true;
if (value) {
var caseInsensitive = false;
if (params.flag && params.flag.length > 0) {
for (var flagIndex = 0; flagIndex < params.flag.length; flagIndex++) {
if (params.flag[flagIndex] == 'CASE_INSENSITIVE') {
caseInsensitive = true;
break;
}
}
}
var regularExpression = caseInsensitive ? new RegExp(params.regexp, 'i') : new RegExp(params.regexp);
valid = value.search(regularExpression) > -1;
}
return valid;
},
/**
* Constraint: Size
* @param {String|String[]} value
* @param {JSON} params
* @returns {boolean}
*/
Size: function (value, params) {
var valid = true;
if (value) {
var valueLength = value.length;
if (params.min && valueLength < params.min) {
valid = false;
}
if (valid && params.max && valueLength > params.max) {
valid = false;
}
}
return valid;
},
/**
* Constraint: Future
* @param {String|String[]} value
* @param {JSON} params
* @param {String} fieldName
* @param {JSON} config
* @returns {boolean}
*/
Future: function (value, params, fieldName, validator) {
var valid = true;
if (value) {
var dateFormat = (validator.config[fieldName] && validator.config[fieldName].dateFormat ? validator.config[fieldName].dateFormat : JSValidator.DateParser.defaultFormat);
try {
var dateValue = JSValidator.DateParser.parseDate(dateFormat, value);
valid = dateValue && dateValue.getTime() > new Date().getTime();
} catch (e) {
validator.log(e);
}
}
return valid;
},
/**
* Constraint: Past
* @param {String|String[]} value
* @param {JSON} params
* @param {String} fieldName
* @param {JSON} config
* @returns {boolean}
*/
Past: function (value, params, fieldName, validator) {
var valid = true;
if (value) {
var dateFormat = (validator.config[fieldName] && validator.config[fieldName].dateFormat ? validator.config[fieldName].dateFormat : JSValidator.DateParser.defaultFormat);
try {
var dateValue = JSValidator.DateParser.parseDate(dateFormat, value);
valid = dateValue && dateValue.getTime() < new Date().getTime();
} catch (e) {
validator.log(e);
}
}
return valid;
},
// Hibernate Validator validations
/**
* Constraint: Email
* @param {String|String[]} value
* @param {JSON} params
* @returns {boolean}
*/
Email: function (value, params) {
return (!value || value.search(JSValidator.Rule.emailPattern) > -1);
},
/**
* Constraint: Length
* @param {String|String[]} value
* @param {JSON} params
* @returns {boolean}
*/
Length: function (value, params) {
var valid = true;
if (value) {
var valueLength = value.toString().length;
if (params.min && valueLength < params.min) {
valid = false;
}
if (valid && params.max && valueLength > params.max) {
valid = false;
}
}
return valid;
},
/**
* Constraint: NotEmpty
* @param {String|String[]} value
* @param {JSON} params
* @returns {boolean}
*/
NotEmpty: function (value, params) {
return (value && value.toString().search(/\w+/) > -1);
},
/**
* Constraint: Range
* @param {String|String[]} value
* @param {JSON} params
* @returns {boolean}
*/
Range: function (value, params) {
var valid = true;
if (value) {
var valueNumber = new Number(value).valueOf();
if (isNaN(valueNumber)) {
valid = false;
} else {
if (params.min && valueNumber < params.min) {
valid = false;
}
if (valid && params.max && valueNumber > params.max) {
valid = false;
}
}
}
return valid;
}
};
// email validation regular expressions, from Hibernate Validator EmailValidator
JSValidator.Rule.emailPatternAtom = '[^\x00-\x1F^\\(^\\)^\\<^\\>^\\@^\\,^\\;^\\:^\\^\"^\\.^\\[^\\]^\\s]';
JSValidator.Rule.emailPatternDomain = JSValidator.Rule.emailPatternAtom + '+(\\.' + JSValidator.Rule.emailPatternAtom + '+)*';
JSValidator.Rule.emailPatternIPDomain = '\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\]';
JSValidator.Rule.emailPattern = new RegExp(
"^" + JSValidator.Rule.emailPatternAtom + "+(\\." + JSValidator.Rule.emailPatternAtom + "+)*@("
+ JSValidator.Rule.emailPatternDomain
+ "|"
+ JSValidator.Rule.emailPatternIPDomain
+ ")$", 'i');
/**
* Very simple Date parsing utility, for @Future/@Past validation.
* Provide a date format in the tag body in a JSON object, keyed on
* field name, e.g.:
*
* { fieldName : { dateFormat : 'y/M/d' } }
*
* Only supports numerical values for days and months. At most one
* occurrence of each character is allowed (e.g. 'y' but not 'yy'
* or 'yyyy' or 'y y').
*
* The 'y' year format character is required, other characters are
* optional, and dates parsed will get default values for fields not
* represented in the format string.
*
* If fewer than four numbers are used for the year then the year
* will be set according to the browser defaults.
* @class
* @private
*/
JSValidator.DateParser = {
defaultFormat: 'M/d/y',
formatChars: {
// this order avoids errors with regex replace calls later on
'd': { regexp: '\\d{1,2}' }, // day of month
'm': { regexp: '\\d{1,2}' }, // minute of hour
'M': { regexp: '\\d{1,2}' }, // month of year
'a': { regexp: '[aApP][mM]+' }, // AM/PM, required for 12-hour time
'y': { regexp: '\\d{1,4}' }, // year, required
'h': { regexp: '\\d{1,2}' }, // 12-hour hour, requires 'a'
'H': { regexp: '\\d{1,2}' }, // 24-hour hour, cannot be used with 'a'
's': { regexp: '\\d{1,2}' } // second of minute
},
parseDate: function (dateFormat, dateValue) {
var parsedDate = null;
if (!dateFormat || dateFormat.search(/\w/) < 0) {
throw('date format must not be blank');
}
if (dateFormat.search(/y/) < 0) {
throw('date format must at least contain year character ("y")');
}
if (dateFormat.indexOf('h') > -1 && dateFormat.indexOf('a') < 0) {
throw('date format must contain AM/PM ("a") if using 12-hour hours ("h")');
}
if (dateFormat.indexOf('H') > -1 && dateFormat.indexOf('a') > -1) {
throw('date format must not contain AM/PM ("a") if using 24-hour hours ("H")');
}
if (!dateValue || dateValue.search(/\w/) < 0) {
throw('date value must not be blank');
}
// create map of date piece name to index of capturing group
var formatChar;
var partOrderMap = {};
var partOrder = 1;
for (var i = 0; i < dateFormat.length; i++) {
var userFormatChar = dateFormat.charAt(i);
for (formatChar in this.formatChars) {
if (userFormatChar == formatChar) {
if (partOrderMap[formatChar]) {
throw('date format must not contain more than one of the same format character');
// } else if ((userFormatChar == 'h' && partOrderMap['H']) || (userFormatChar == 'H' && partOrderMap['h'])) {
// alert('date format must contain either \'h\' or \'H\', but not both');
}
partOrderMap[formatChar] = partOrder++;
}
}
}
// create regexp from date format
var dateRegExp = dateFormat;
for (formatChar in this.formatChars) {
dateRegExp = dateRegExp.replace(formatChar, '(' + this.formatChars[formatChar].regexp + ')');
}
dateRegExp = new RegExp(dateRegExp);
// run regexp
var matches = dateValue.match(dateRegExp);
if (!matches) {
// throw('date value does not match date format');
return null;
}
// create date pulling values from match array using map of piece name to capturing group indexes
var yearValue = Math.max(0, matches[partOrderMap['y']] || 0);
var monthValue = Math.max(0, (matches[partOrderMap['M']] || 0) - 1);
var dayValue = Math.max(1, matches[partOrderMap['d']] || 0);
var twelveHourValue = matches[partOrderMap['h']];
var ampmValue = matches[partOrderMap['a']];
var twentyFourHourValue = matches[partOrderMap['H']];
var hourValue;
if (twelveHourValue) {
hourValue = twelveHourValue % 12;
if (ampmValue.toLowerCase().indexOf('p') > -1) {
hourValue += 12;
}
} else {
hourValue = twentyFourHourValue || 0;
}
hourValue = Math.max(0, hourValue);
var minuteValue = Math.max(0, matches[partOrderMap['m']] || 0);
var secondValue = Math.max(0, matches[partOrderMap['s']] || 0);
parsedDate = new Date(yearValue, monthValue, dayValue, hourValue, minuteValue, secondValue);
return parsedDate;
}
};