diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..67d713f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +node_modules +npm-debug.log diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..8fbfdca --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,137 @@ +module.exports = function (grunt) { + + grunt.initConfig({ + + pkg: grunt.file.readJSON('package.json'), + + banner: '/*!\n' + + ' * jQuery Form Validation\n' + + ' * Copyright (C) 2015 RunningCoder.org\n' + + ' * Licensed under the MIT license\n' + + ' *\n' + + ' * @author <%= pkg.author.name %>\n' + + ' * @version <%= pkg.version %> (<%= grunt.template.today("yyyy-mm-dd") %>)\n' + + ' * @link http://www.runningcoder.org/jqueryvalidation/\n' + + '*/\n', + + clean: { + dist: ["dist"] + }, + + copy: { + dist: { + files: [ + { + src: ['src/jquery.validation.js'], + dest: 'dist/jquery.validation.js' + }, + { + src: ['src/jquery.validation.js'], + dest: 'dist/jquery.validation.min.js' + } + ] + } + }, + + comments: { + dist: { + options: { + singleline: true, + multiline: true + }, + src: [ 'dist/jquery.validation.js'] + } + }, + + replace: { + banner: { + options: { + patterns: [ + { + match: /\/\*![\S\s]+?\*\/[\r\n]*/, + replacement: '<%= banner %>' + } + ] + }, + files: [ + { + src: ['src/jquery.validation.js'], + dest: 'src/jquery.validation.js' + } + ] + }, + removeDebug: { + options: { + patterns: [ + { + match: /\/\/\s?\{debug}[\s\S]*?\{\/debug}/g, + replacement: '' + } + ] + }, + files: [ + { + src: ['dist/jquery.validation.min.js'], + dest: 'dist/jquery.validation.min.js' + } + ] + }, + removeComments: { + options: { + patterns: [ + { + match: /\/\*[^!][\S\s]+?\*\//gm, + replacement: '' + } + ] + }, + files: [ + { + src: ['dist/jquery.validation.js'], + dest: 'dist/jquery.validation.js' + } + ] + } + }, + + jsbeautifier : { + files : ['dist/jquery.validation.js'], + options : { + } + }, + + uglify: { + dist: { + options: { + mangle: true, + compress: true, + banner: '<%= banner %>' + }, + files: { + 'dist/jquery.validation.min.js': ['dist/jquery.validation.min.js'] + } + } + + } + + }); + + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-contrib-copy'); + grunt.loadNpmTasks('grunt-stripcomments'); + grunt.loadNpmTasks('grunt-replace'); + grunt.loadNpmTasks("grunt-jsbeautifier"); + grunt.loadNpmTasks('grunt-contrib-uglify'); + + grunt.registerTask('default', [ + 'clean:dist', + 'replace:banner', + 'copy:dist', + 'comments', + 'replace:removeComments', + 'jsbeautifier', + 'replace:removeDebug', + 'uglify' + ]); + +}; diff --git a/README.md b/README.md index 92ff012..4ff9da1 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,18 @@ It is a simple clientside library that will save you a lot of time when it comes The jQuery form Validation plugin is released under the MIT License. The complete documentation, demo and further instructions can be found at www.runningcoder.org + +Documentation +====================== + +www.runningcoder.org/jqueryvalidation/documentation/ + +Demos +====================== + +www.runningcoder.org/jqueryvalidation/demo/ + +Patch Notes +====================== + +www.runningcoder.org/jqueryvalidation/version/ diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..5e42e12 --- /dev/null +++ b/bower.json @@ -0,0 +1,26 @@ +{ + "name": "html5-form-validation", + "version": "1.5.3", + "authors": [ + "Tom Bertrand" + ], + "description": "jQuery plugin that provides a client site form validation with builtin options and deep customization.", + "main": "jquery.validation.min.js", + "keywords": [ + "form", + "html5", + "validate", + "validation", + "input" + ], + "ignore": [], + "licenses": "MIT", + "homepage": "http://www.runningcoder.org/jqueryvalidation/", + "repository": { + "type": "git", + "url": "git://github.com/running-coder/jquery-form-validation.git" + }, + "dependencies": { + "jquery": ">=1.7.2" + } +} \ No newline at end of file diff --git a/dist/jquery.validation.js b/dist/jquery.validation.js new file mode 100644 index 0000000..0d16d61 --- /dev/null +++ b/dist/jquery.validation.js @@ -0,0 +1,1559 @@ +/*! + * jQuery Form Validation + * Copyright (C) 2015 RunningCoder.org + * Licensed under the MIT license + * + * @author Tom Bertrand + * @version 1.5.3 (2015-12-02) + * @link http://www.runningcoder.org/jqueryvalidation/ + */ +; +(function(window, document, $, undefined) { + + window.Validation = { + form: [], + labels: {}, + hasScrolled: false + }; + + if (typeof Object.preventExtensions !== "function") { + Object.preventExtensions = function(obj) { + return obj; + }; + } + var _rules = { + NOTEMPTY: /\S/, + INTEGER: /^\d+$/, + NUMERIC: /^\d+(?:[,\s]\d{3})*(?:\.\d+)?$/, + MIXED: /^[\w\s-]+$/, + NAME: /^['a-zãàáäâẽèéëêìíïîõòóöôùúüûñç\s-]+$/i, + NOSPACE: /^(?!\s)\S*$/, + TRIM: /^[^\s].*[^\s]$/, + DATE: /^\d{4}-\d{2}-\d{2}(\s\d{2}:\d{2}(:\d{2})?)?$/, + EMAIL: /^([^@]+?)@(([a-z0-9]-*)*[a-z0-9]+\.)+([a-z0-9]+)$/i, + URL: /^(https?:\/\/)?((([a-z0-9]-*)*[a-z0-9]+\.?)*([a-z0-9]+))(\/[\w?=\.-]*)*$/, + PHONE: /^(\()?\d{3}(\))?(-|\s)?\d{3}(-|\s)\d{4}$/, + OPTIONAL: /\S/, + COMPARISON: /^\s*([LV])\s*([<>]=?|==|!=)\s*([^<>=!]+?)\s*$/ + }; + + var _messages = { + 'default': '$ contain error(s).', + 'NOTEMPTY': '$ must not be empty.', + 'INTEGER': '$ must be an integer.', + 'NUMERIC': '$ must be numeric.', + 'MIXED': '$ must be letters or numbers (no special characters).', + 'NAME': '$ must not contain special characters.', + 'NOSPACE': '$ must not contain spaces.', + 'TRIM': '$ must not start or end with space character.', + 'DATE': '$ is not a valid with format YYYY-MM-DD.', + 'EMAIL': '$ is not valid.', + 'URL': '$ is not valid.', + 'PHONE': '$ is not a valid phone number.', + '<': '$ must be less than % characters.', + '<=': '$ must be less or equal to % characters.', + '>': '$ must be greater than % characters.', + '>=': '$ must be greater or equal to % characters.', + '==': '$ must be equal to %', + '!=': '$ must be different than %' + }; + + var _data = { + validation: 'data-validation', + validationMessage: 'data-validation-message', + regex: 'data-validation-regex', + regexReverse: 'data-validation-regex-reverse', + regexMessage: 'data-validation-regex-message', + group: 'data-validation-group', + label: 'data-validation-label', + errorList: 'data-error-list' + } + + var _options = { + submit: { + settings: { + form: null, + display: "inline", + insertion: "append", + allErrors: false, + trigger: "click", + button: "[type='submit']", + errorClass: "error", + errorListClass: "error-list", + errorListContainer: null, + errorTemplate: null, + inputContainer: null, + clear: "focusin", + scrollToError: false + }, + callback: { + onInit: null, + onValidate: null, + onError: null, + onBeforeSubmit: null, + onSubmit: null, + onAfterSubmit: null + } + }, + dynamic: { + settings: { + trigger: null, + delay: 300 + }, + callback: { + onSuccess: null, + onError: null, + onComplete: null + } + }, + rules: {}, + messages: {}, + labels: {}, + debug: false + }; + + var _supported = { + submit: { + settings: { + display: ["inline", "block"], + insertion: ["append", "prepend"], //"before", "insertBefore", "after", "insertAfter" + allErrors: [true, false], + clear: ["focusin", "keypress", false], + trigger: [ + "click", "dblclick", "focusout", + "hover", "mousedown", "mouseenter", + "mouseleave", "mousemove", "mouseout", + "mouseover", "mouseup", "toggle" + ] + } + }, + dynamic: { + settings: { + trigger: ["focusout", "keydown", "keypress", "keyup"] + } + }, + debug: [true, false] + }; + + var Validation = function(node, options) { + + var errors = [], + messages = {}, + formData = {}, + delegateSuffix = ".vd", // validation.delegate + resetSuffix = ".vr"; // validation.resetError + + window.Validation.hasScrolled = false; + + function extendRules() { + options.rules = $.extend( + true, {}, + _rules, + options.rules + ); + } + + function extendMessages() { + options.messages = $.extend( + true, {}, + _messages, + options.messages + ); + } + + function extendOptions() { + + if (!(options instanceof Object)) { + options = {}; + } + + var tpmOptions = Object.preventExtensions($.extend(true, {}, _options)); + + for (var method in options) { + + if (!options.hasOwnProperty(method) || method === "debug") { + continue; + } + + if (~["labels", "messages", "rules"].indexOf(method) && options[method] instanceof Object) { + tpmOptions[method] = options[method]; + continue; + } + + if (!_options[method] || !(options[method] instanceof Object)) { + options.debug && window.Debug.log({ + 'node': node, + 'function': 'extendOptions()', + 'arguments': '{' + method + ': ' + JSON.stringify(options[method]) + '}', + 'message': 'WARNING - ' + method + ' - invalid option' + }); + + continue; + } + + for (var type in options[method]) { + if (!options[method].hasOwnProperty(type)) { + continue; + } + + if (!_options[method][type] || !(options[method][type] instanceof Object)) { + options.debug && window.Debug.log({ + 'node': node, + 'function': 'extendOptions()', + 'arguments': '{' + type + ': ' + JSON.stringify(options[method][type]) + '}', + 'message': 'WARNING - ' + type + ' - invalid option' + }); + + continue; + } + + for (var option in options[method][type]) { + if (!options[method][type].hasOwnProperty(option)) { + continue; + } + + if (_supported[method] && + _supported[method][type] && + _supported[method][type][option] && + $.inArray(options[method][type][option], _supported[method][type][option]) === -1) { + options.debug && window.Debug.log({ + 'node': node, + 'function': 'extendOptions()', + 'arguments': '{' + option + ': ' + JSON.stringify(options[method][type][option]) + '}', + 'message': 'WARNING - ' + option.toString() + ': ' + JSON.stringify(options[method][type][option]) + ' - unsupported option' + }); + + delete options[method][type][option]; + } + + } + if (tpmOptions[method] && tpmOptions[method][type]) { + tpmOptions[method][type] = $.extend(Object.preventExtensions(tpmOptions[method][type]), options[method][type]); + } + } + } + if (options.debug && $.inArray(options.debug, _supported.debug !== -1)) { + tpmOptions.debug = options.debug; + } + if (tpmOptions.dynamic.settings.trigger) { + if (tpmOptions.dynamic.settings.trigger === "keypress" && tpmOptions.submit.settings.clear === "keypress") { + tpmOptions.dynamic.settings.trigger = "keydown"; + } + } + + options = tpmOptions; + + } + + function delegateDynamicValidation() { + + if (!options.dynamic.settings.trigger) { + return false; + } + options.debug && window.Debug.log({ + 'node': node, + 'function': 'delegateDynamicValidation()', + 'message': 'OK - Dynamic Validation activated on ' + node.length + ' form(s)' + }); + + if (!node.find('[' + _data.validation + '],[' + _data.regex + ']')[0]) { + options.debug && window.Debug.log({ + 'node': node, + 'function': 'delegateDynamicValidation()', + 'arguments': 'node.find([' + _data.validation + '],[' + _data.regex + '])', + 'message': 'ERROR - [' + _data.validation + '] not found' + }); + + return false; + } + + var event = options.dynamic.settings.trigger + delegateSuffix; + if (options.dynamic.settings.trigger !== "focusout") { + event += " change" + delegateSuffix + " paste" + delegateSuffix; + } + + $.each( + node.find('[' + _data.validation + '],[' + _data.regex + ']'), + function(index, input) { + + $(input).unbind(event).on(event, function(e) { + + if ($(this).is(':disabled')) { + return false; + } + + var input = this, + keyCode = e.keyCode || null; + + _typeWatch(function() { + + if (!validateInput(input)) { + + displayOneError(input.name); + _executeCallback(options.dynamic.callback.onError, [node, input, keyCode, errors[input.name]]); + + } else { + + _executeCallback(options.dynamic.callback.onSuccess, [node, input, keyCode]); + + } + + _executeCallback(options.dynamic.callback.onComplete, [node, input, keyCode]); + + }, options.dynamic.settings.delay); + + }); + } + ); + } + + function delegateValidation() { + + _executeCallback(options.submit.callback.onInit, [node]); + + var event = options.submit.settings.trigger + '.vd'; + options.debug && window.Debug.log({ + 'node': node, + 'function': 'delegateValidation()', + 'message': 'OK - Validation activated on ' + node.length + ' form(s)' + }); + + if (!node.find(options.submit.settings.button)[0]) { + options.debug && window.Debug.log({ + 'node': node, + 'function': 'delegateValidation()', + 'arguments': '{button: ' + options.submit.settings.button + '}', + 'message': 'ERROR - node.find("' + options.submit.settings.button + '") not found' + }); + + return false; + + } + + node.on("submit", false); + node.find(options.submit.settings.button).off('.vd').on(event, function(e) { + + e.preventDefault(); + + resetErrors(); + + _executeCallback(options.submit.callback.onValidate, [node]); + + if (!validateForm()) { + + displayErrors(); + _executeCallback(options.submit.callback.onError, [node, errors, formData]); + + } else { + + _executeCallback(options.submit.callback.onBeforeSubmit, [node]); + + if (typeof options.submit.callback.onSubmit === "function") { + if (_executeCallback(options.submit.callback.onSubmit, [node, formData]) === true) { + submitForm(); + } + } else { + submitForm(); + } + + _executeCallback(options.submit.callback.onAfterSubmit, [node]); + + } + options.debug && window.Debug.print(); + + return false; + + }); + + } + + function validateForm() { + + var isValid = isEmpty(errors); + + formData = {}; + + $.each( + node.find('input:not([type="submit"]), select, textarea').not(':disabled'), + function(index, input) { + + input = $(input); + + var value = _getInputValue(input[0]), + inputName = input.attr('name'); + + if (inputName) { + if (/\[]$/.test(inputName)) { + inputName = inputName.replace(/\[]$/, ''); + if (!(formData[inputName] instanceof Array)) { + formData[inputName] = []; + } + formData[inputName].push(value) + } else { + formData[inputName] = value; + } + } + + if (!!input.attr(_data.validation) || !!input.attr(_data.regex)) { + if (!validateInput(input[0], value)) { + isValid = false; + } + } + } + ); + + prepareFormData(); + + return isValid; + + } + + function prepareFormData() { + + var data = {}, + matches, + index; + + for (var i in formData) { + if (!formData.hasOwnProperty(i)) continue; + + index = 0; + matches = i.split(/\[(.+?)]/g); + + var tmpObject = {}, + tmpArray = []; + + for (var k = matches.length - 1; k >= 0; k--) { + if (matches[k] === "") { + matches.splice(k, 1); + continue; + } + + if (tmpArray.length < 1) { + tmpObject[matches[k]] = Number(formData[i]) || formData[i]; + } else { + tmpObject = {}; + tmpObject[matches[k]] = tmpArray[tmpArray.length - 1]; + } + tmpArray.push(tmpObject) + + } + + data = $.extend(true, data, tmpObject); + + } + + formData = data; + + } + + function validateInput(input, value) { + + var inputName = $(input).attr('name'), + value = value || _getInputValue(input); + + if (!inputName) { + options.debug && window.Debug.log({ + 'node': node, + 'function': 'validateInput()', + 'arguments': '$(input).attr("name")', + 'message': 'ERROR - Missing input [name]' + }); + + return false; + } + + var matches = inputName.replace(/]$/, '').split(/]\[|[[\]]/g), + inputShortName = window.Validation.labels[inputName] || + options.labels[inputName] || + $(input).attr(_data.label) || + matches[matches.length - 1], + + validationArray = $(input).attr(_data.validation), + validationMessage = $(input).attr(_data.validationMessage), + validationRegex = $(input).attr(_data.regex), + validationRegexReverse = !($(input).attr(_data.regexReverse) === undefined), + validationRegexMessage = $(input).attr(_data.regexMessage), + + validateOnce = false; + + if (validationArray) { + validationArray = _api._splitValidation(validationArray); + } + if (validationArray instanceof Array && validationArray.length > 0) { + if ($.trim(value) === '' && ~validationArray.indexOf('OPTIONAL')) { + return true; + } + + $.each(validationArray, function(i, rule) { + + if (validateOnce === true) { + return true; + } + + try { + + validateRule(value, rule); + + } catch (error) { + + if (validationMessage || !options.submit.settings.allErrors) { + validateOnce = true; + } + + error[0] = validationMessage || error[0]; + + registerError(inputName, error[0].replace('$', inputShortName).replace('%', error[1])); + + } + + }); + + } + if (validationRegex) { + + var rule = _buildRegexFromString(validationRegex); + if (!(rule instanceof RegExp)) { + return true; + } + + try { + + validateRule(value, rule, validationRegexReverse); + + } catch (error) { + + error[0] = validationRegexMessage || error[0]; + + registerError(inputName, error[0].replace('$', inputShortName)); + + } + + } + + return !errors[inputName] || errors[inputName] instanceof Array && errors[inputName].length === 0; + + } + + function validateRule(value, rule, reversed) { + if (rule instanceof RegExp) { + var isValid = rule.test(value); + + if (reversed) { + isValid = !isValid; + } + + if (!isValid) { + throw [options.messages['default'], '']; + } + return; + } + + if (options.rules[rule]) { + if (!options.rules[rule].test(value)) { + throw [options.messages[rule], '']; + } + return; + } + var comparison = rule.match(options.rules.COMPARISON); + + if (!comparison || comparison.length !== 4) { + options.debug && window.Debug.log({ + 'node': node, + 'function': 'validateRule()', + 'arguments': 'value: ' + value + ' rule: ' + rule, + 'message': 'WARNING - Invalid comparison' + }); + + return; + } + + var type = comparison[1], + operator = comparison[2], + compared = comparison[3], + comparedValue; + + switch (type) { + case "L": + if (isNaN(compared)) { + options.debug && window.Debug.log({ + 'node': node, + 'function': 'validateRule()', + 'arguments': 'compare: ' + compared + ' rule: ' + rule, + 'message': 'WARNING - Invalid rule, "L" compare must be numeric' + }); + + return false; + + } else { + + if (!value || eval(value.length + operator + parseFloat(compared)) === false) { + throw [options.messages[operator], compared]; + } + + } + + break; + case "V": + default: + if (isNaN(compared)) { + + comparedValue = node.find('[name="' + compared + '"]').val(); + if (!comparedValue) { + options.debug && window.Debug.log({ + 'node': node, + 'function': 'validateRule()', + 'arguments': 'compare: ' + compared + ' rule: ' + rule, + 'message': 'WARNING - Unable to find compared field [name="' + compared + '"]' + }); + + return false; + } + + if (!value || !eval('"' + encodeURIComponent(value) + '"' + operator + '"' + encodeURIComponent(comparedValue) + '"')) { + throw [options.messages[operator].replace(' characters', ''), compared]; + } + + } else { + if (!value || isNaN(value) || !eval(value + operator + parseFloat(compared))) { + throw [options.messages[operator].replace(' characters', ''), compared]; + } + + } + break; + + } + + } + + function registerError(inputName, error) { + + if (!errors[inputName]) { + errors[inputName] = []; + } + + error = error.capitalize(); + + var hasError = false; + for (var i = 0; i < errors[inputName].length; i++) { + if (errors[inputName][i] === error) { + hasError = true; + break; + } + } + + if (!hasError) { + errors[inputName].push(error); + } + + } + + function displayOneError(inputName) { + + var input, + inputId, + errorContainer, + label, + html = '
', + group, + groupInput; + + if (!errors.hasOwnProperty(inputName)) { + return false; + } + + input = node.find('[name="' + inputName + '"]'); + + label = null; + + if (!input[0]) { + options.debug && window.Debug.log({ + 'node': node, + 'function': 'displayOneError()', + 'arguments': '[name="' + inputName + '"]', + 'message': 'ERROR - Unable to find input by name "' + inputName + '"' + }); + + return false; + } + + group = input.attr(_data.group); + + if (group) { + + groupInput = node.find('[name="' + inputName + '"]'); + label = node.find('[id="' + group + '"]'); + + if (label[0]) { + label.addClass(options.submit.settings.errorClass); + errorContainer = label; + } + + } else { + + input.addClass(options.submit.settings.errorClass); + + if (options.submit.settings.inputContainer) { + input.parentsUntil(node, options.submit.settings.inputContainer).addClass(options.submit.settings.errorClass); + } + + inputId = input.attr('id'); + + if (inputId) { + label = node.find('label[for="' + inputId + '"]')[0]; + } + + if (!label) { + label = input.parentsUntil(node, 'label')[0]; + } + + if (label) { + label = $(label); + label.addClass(options.submit.settings.errorClass); + } + } + + if (options.submit.settings.display === 'inline') { + if (options.submit.settings.errorListContainer) { + errorContainer = input.parentsUntil(node, options.submit.settings.errorListContainer); + } else { + errorContainer = errorContainer || input.parent(); + } + } else if (options.submit.settings.display === 'block') { + errorContainer = node; + } + if (options.submit.settings.display === 'inline' && errorContainer.find('[' + _data.errorList + ']')[0]) { + return false; + } + + if (options.submit.settings.display === "inline" || + (options.submit.settings.display === "block" && !errorContainer.find('[' + _data.errorList + ']')[0]) + ) { + if (options.submit.settings.insertion === 'append') { + errorContainer.append(html); + } else if (options.submit.settings.insertion === 'prepend') { + errorContainer.prepend(html); + } + } + + for (var i = 0; i < errors[inputName].length; i++) { + if (options.submit.settings.errorTemplate) { + errorContainer.find('[' + _data.errorList + '] ul') + .append('
  • ' + options.submit.settings.errorTemplate.replace('{{validation-message}}', errors[inputName][i]) + '
  • '); + } else { + errorContainer.find('[' + _data.errorList + '] ul').append('
  • ' + errors[inputName][i] + '
  • '); + } + } + + if (options.submit.settings.clear || options.dynamic.settings.trigger) { + + if (group && groupInput) { + input = groupInput; + } + + var event = "coucou" + resetSuffix; + if (options.submit.settings.clear) { + event += " " + options.submit.settings.clear + resetSuffix; + if (~['radio', 'checkbox'].indexOf(input[0].type)) { + event += " change" + resetSuffix; + } + } + if (options.dynamic.settings.trigger) { + event += " " + options.dynamic.settings.trigger + resetSuffix; + if (options.dynamic.settings.trigger !== "focusout" && !~['radio', 'checkbox'].indexOf(input[0].type)) { + event += " change" + resetSuffix + " paste" + resetSuffix; + } + } + + input.unbind(event).on(event, function(a, b, c, d, e) { + + return function() { + if (e) { + if ($(c).hasClass(options.submit.settings.errorClass)) { + resetOneError(a, b, c, d, e); + } + } else if ($(b).hasClass(options.submit.settings.errorClass)) { + resetOneError(a, b, c, d); + } + }; + + }(inputName, input, label, errorContainer, group)); + } + + if (options.submit.settings.scrollToError && !window.Validation.hasScrolled) { + + window.Validation.hasScrolled = true; + + var offset = parseFloat(options.submit.settings.scrollToError.offset) || 0, + duration = parseFloat(options.submit.settings.scrollToError.duration) || 500, + handle = (options.submit.settings.display === 'block') ? errorContainer : input; + + $('html, body').animate({ + scrollTop: handle.offset().top + offset + }, duration); + + } + + } + + function displayErrors() { + + for (var inputName in errors) { + if (!errors.hasOwnProperty(inputName)) continue; + displayOneError(inputName); + } + + } + + function resetOneError(inputName, input, label, container, group) { + + delete errors[inputName]; + + if (container) { + + if (options.submit.settings.inputContainer) { + (group ? label : input).parentsUntil(node, options.submit.settings.inputContainer).removeClass(options.submit.settings.errorClass); + } + + label && label.removeClass(options.submit.settings.errorClass); + + input.removeClass(options.submit.settings.errorClass); + + if (options.submit.settings.display === 'inline') { + container.find('[' + _data.errorList + ']').remove(); + } + + } else { + + if (!input) { + input = node.find('[name="' + inputName + '"]'); + + if (!input[0]) { + options.debug && window.Debug.log({ + 'node': node, + 'function': 'resetOneError()', + 'arguments': '[name="' + inputName + '"]', + 'message': 'ERROR - Unable to find input by name "' + inputName + '"' + }); + + return false; + } + } + + input.trigger('coucou' + resetSuffix); + } + + } + + function resetErrors() { + + errors = []; + window.Validation.hasScrolled = false; + + node.find('[' + _data.errorList + ']').remove(); + node.find('.' + options.submit.settings.errorClass).removeClass(options.submit.settings.errorClass); + + } + + function submitForm() { + + node[0].submit() + + } + + function destroy() { + + resetErrors(); + node.find('[' + _data.validation + '],[' + _data.regex + ']').off(delegateSuffix + ' ' + resetSuffix); + + node.find(options.submit.settings.button).off(delegateSuffix).on('click' + delegateSuffix, function() { + $(this).closest('form')[0].submit(); + }); + + return true; + + } + + var _getInputValue = function(input) { + + var value; + switch ($(input).attr('type')) { + case 'checkbox': + value = ($(input).is(':checked')) ? 1 : ''; + break; + case 'radio': + value = node.find('input[name="' + $(input).attr('name') + '"]:checked').val() || ''; + break; + default: + value = $(input).val(); + break; + } + + return value; + + }; + + var _typeWatch = (function() { + var timer = 0; + return function(callback, ms) { + clearTimeout(timer); + timer = setTimeout(callback, ms); + }; + })(); + + var _executeCallback = function(callback, extraParams) { + + if (!callback) { + return false; + } + + var _callback; + + if (typeof callback === "function") { + + _callback = callback; + + } else if (typeof callback === "string" || callback instanceof Array) { + + _callback = window; + + if (typeof callback === "string") { + callback = [callback, []]; + } + + var _exploded = callback[0].split('.'), + _params = callback[1], + _isValid = true, + _splitIndex = 0; + + while (_splitIndex < _exploded.length) { + + if (typeof _callback !== 'undefined') { + _callback = _callback[_exploded[_splitIndex++]]; + } else { + _isValid = false; + break; + } + } + + if (!_isValid || typeof _callback !== "function") { + options.debug && window.Debug.log({ + 'node': node, + 'function': '_executeCallback()', + 'arguments': JSON.stringify(callback), + 'message': 'WARNING - Invalid callback function"' + }); + + return false; + } + + } + + return _callback.apply(this, $.merge(_params || [], (extraParams) ? extraParams : [])); + + }; + + this.__construct = function() { + + extendOptions(); + extendRules(); + extendMessages(); + + delegateDynamicValidation(); + delegateValidation(); + options.debug && window.Debug.print(); + + }(); + + return { + + registerError: registerError, + + displayOneError: displayOneError, + + displayErrors: displayErrors, + + resetOneError: resetOneError, + + resetErrors: resetErrors, + + destroy: destroy + + }; + + }; + + $.fn.validate = $.validate = function(options) { + + return _api.validate(this, options); + + }; + + $.fn.addValidation = function(validation) { + + return _api.addValidation(this, validation); + + }; + + $.fn.removeValidation = function(validation) { + + return _api.removeValidation(this, validation); + + }; + + $.fn.addError = function(error) { + + return _api.addError(this, error); + + }; + + $.fn.removeError = function(error) { + + return _api.removeError(this, error); + + }; + + $.fn.alterValidationRules = $.alterValidationRules = function(rules) { + + if (!(rules instanceof Array)) { + rules = [rules]; + } + + for (var i = 0; i < rules.length; i++) { + _api.alterValidationRules(rules[i]); + } + + }; + + var _api = { + + _formatValidation: function(validation) { + + validation = validation.toString().replace(/\s/g, ''); + + if (validation.charAt(0) === "[" && validation.charAt(validation.length - 1) === "]") { + validation = validation.replace(/^\[|]$/g, ''); + } + + return validation; + + }, + + _splitValidation: function(validation) { + + var validationArray = this._formatValidation(validation).split(','), + oneValidation; + + for (var i = 0; i < validationArray.length; i++) { + oneValidation = validationArray[i]; + if (/^[a-z]+$/i.test(oneValidation)) { + validationArray[i] = oneValidation.toUpperCase(); + } + } + + return validationArray; + }, + + _joinValidation: function(validation) { + + return '[' + validation.join(', ') + ']'; + + }, + + validate: function(node, options) { + + if (typeof node === "function") { + + if (!options.submit.settings.form) { + window.Debug.log({ + 'node': node, + 'function': '$.validate()', + 'arguments': '', + 'message': 'Undefined property "options.submit.settings.form - Validation dropped' + }); + + window.Debug.print(); + + return; + } + + node = $(options.submit.settings.form); + + if (!node[0] || node[0].nodeName.toLowerCase() !== "form") { + window.Debug.log({ + 'function': '$.validate()', + 'arguments': options.submit.settings.form, + 'message': 'Unable to find jQuery form element - Validation dropped' + }); + + window.Debug.print(); + + return; + } + + } else if (typeof node[0] === 'undefined') { + window.Debug.log({ + 'node': node, + 'function': '$.validate()', + 'arguments': '$("' + node.selector + '").validate()', + 'message': 'Unable to find jQuery form element - Validation dropped' + }); + + window.Debug.print(); + + return; + } + + if (options === "destroy") { + + if (!window.Validation.form[node.selector]) { + window.Debug.log({ + 'node': node, + 'function': '$.validate("destroy")', + 'arguments': '', + 'message': 'Unable to destroy "' + node.selector + '", perhaps it\'s already destroyed?' + }); + + window.Debug.print(); + + return; + } + + window.Validation.form[node.selector].destroy(); + + return; + + } + + return node.each(function() { + window.Validation.form[node.selector] = new Validation($(this), options); + }); + + }, + + addValidation: function(node, validation) { + + var self = this; + + validation = self._splitValidation(validation); + + if (!validation) { + return false; + } + + return node.each(function() { + + var $this = $(this), + validationData = $this.attr(_data.validation), + validationArray = (validationData && validationData.length) ? self._splitValidation(validationData) : [], + oneValidation; + + for (var i = 0; i < validation.length; i++) { + + oneValidation = self._formatValidation(validation[i]); + + if ($.inArray(oneValidation, validationArray) === -1) { + validationArray.push(oneValidation); + } + } + + if (validationArray.length) { + $this.attr(_data.validation, self._joinValidation(validationArray)); + } + + }); + + }, + + removeValidation: function(node, validation) { + + var self = this; + + validation = self._splitValidation(validation); + if (!validation) { + return false; + } + + return node.each(function() { + + var $this = $(this), + validationData = $this.attr(_data.validation), + validationArray = (validationData && validationData.length) ? self._splitValidation(validationData) : [], + oneValidation, + validationIndex; + + if (!validationArray.length) { + $this.removeAttr(_data.validation); + return true; + } + + for (var i = 0; i < validation.length; i++) { + oneValidation = self._formatValidation(validation[i]); + validationIndex = $.inArray(oneValidation, validationArray); + if (validationIndex !== -1) { + validationArray.splice(validationIndex, 1); + } + + } + + if (!validationArray.length) { + $this.removeAttr(_data.validation); + return true; + } + + $this.attr(_data.validation, self._joinValidation(validationArray)); + + }); + + }, + + addError: function(node, error) { + + if (!window.Validation.form[node.selector]) { + window.Debug.log({ + 'node': node, + 'function': '$.addError()', + 'arguments': 'window.Validation.form[' + node.selector + ']', + 'message': 'ERROR - Invalid node selector' + }); + + window.Debug.print(); + + return false; + } + + if (typeof error !== "object" || Object.prototype.toString.call(error) !== "[object Object]") { + window.Debug.log({ + 'node': node, + 'function': '$.addError()', + 'arguments': 'window.Validation.form[' + node.selector + ']', + 'message': 'ERROR - Invalid argument, must be type object' + }); + + window.Debug.print(); + + return false; + } + + var input, + onlyOnce = true; + for (var inputName in error) { + + if (!error.hasOwnProperty(inputName)) { + continue; + } + + if (!(error[inputName] instanceof Array)) { + error[inputName] = [error[inputName]]; + } + + input = $(node.selector).find('[name="' + inputName + '"]'); + if (!input[0]) { + window.Debug.log({ + 'node': node, + 'function': '$.addError()', + 'arguments': inputName, + 'message': 'ERROR - Unable to find ' + '$(' + node.selector + ').find("[name="' + inputName + '"]")' + }); + + window.Debug.print(); + + continue; + } + + if (onlyOnce) { + window.Validation.hasScrolled = false; + onlyOnce = false; + } + + window.Validation.form[node.selector].resetOneError(inputName, input); + + for (var i = 0; i < error[inputName].length; i++) { + + if (typeof error[inputName][i] !== "string") { + window.Debug.log({ + 'node': node, + 'function': '$.addError()', + 'arguments': error[inputName][i], + 'message': 'ERROR - Invalid error object property - Accepted format: {"inputName": "errorString"} or {"inputName": ["errorString", "errorString"]}' + }); + + window.Debug.print(); + + continue; + } + + window.Validation.form[node.selector].registerError(inputName, error[inputName][i]); + + } + + window.Validation.form[node.selector].displayOneError(inputName); + + } + + }, + + removeError: function(node, inputName) { + + if (!window.Validation.form[node.selector]) { + window.Debug.log({ + 'node': node, + 'function': '$.removeError()', + 'arguments': 'window.Validation.form[' + node.selector + ']', + 'message': 'ERROR - Invalid node selector' + }); + + window.Debug.print(); + + return false; + } + + if (!inputName) { + window.Validation.form[node.selector].resetErrors(); + return false; + } + + if (typeof inputName === "object" && Object.prototype.toString.call(inputName) !== "[object Array]") { + window.Debug.log({ + 'node': node, + 'function': '$.removeError()', + 'arguments': inputName, + 'message': 'ERROR - Invalid inputName, must be type String or Array' + }); + + window.Debug.print(); + + return false; + } + + if (!(inputName instanceof Array)) { + inputName = [inputName]; + } + + var input; + for (var i = 0; i < inputName.length; i++) { + + input = $(node.selector).find('[name="' + inputName[i] + '"]'); + if (!input[0]) { + window.Debug.log({ + 'node': node, + 'function': '$.removeError()', + 'arguments': inputName[i], + 'message': 'ERROR - Unable to find ' + '$(' + node.selector + ').find("[name="' + inputName[i] + '"]")' + }); + + window.Debug.print(); + + continue; + } + + window.Validation.form[node.selector].resetOneError(inputName[i], input); + + } + + }, + + alterValidationRules: function(ruleObj) { + + if (!ruleObj.rule || (!ruleObj.regex && !ruleObj.message)) { + window.Debug.log({ + 'function': '$.alterValidationRules()', + 'message': 'ERROR - Missing one or multiple parameter(s) {rule, regex, message}' + }); + window.Debug.print(); + return false; + } + + ruleObj.rule = ruleObj.rule.toUpperCase(); + + if (ruleObj.regex) { + + var regex = _buildRegexFromString(ruleObj.regex); + + if (!(regex instanceof RegExp)) { + window.Debug.log({ + 'function': '$.alterValidationRules(rule)', + 'arguments': regex.toString(), + 'message': 'ERROR - Invalid rule' + }); + window.Debug.print(); + return false; + } + + _rules[ruleObj.rule] = regex; + } + + if (ruleObj.message) { + _messages[ruleObj.rule] = ruleObj.message; + } + + return true; + } + + }; + + function _buildRegexFromString(regex) { + + if (!regex || (typeof regex !== "string" && !(regex instanceof RegExp))) { + _regexDebug(); + return false; + } + + if (typeof regex !== 'string') { + regex = regex.toString(); + } + + var separator = regex.charAt(0), + index = regex.length - 1, + pattern, + modifier, + rule; + + while (index > 0) { + if (/[gimsxeU]/.test(regex.charAt(index))) { + index--; + } else { + break; + } + } + + if (regex.charAt(index) !== separator) { + separator = null; + } + + if (separator && index !== regex.length - 1) { + modifier = regex.substr(index + 1, regex.length - 1); + } + + if (separator) { + pattern = regex.substr(1, index - 1); + } else { + pattern = regex; + } + + try { + rule = new RegExp(pattern, modifier); + } catch (error) { + _regexDebug(); + return false; + } + + return rule; + + function _regexDebug() { + window.Debug.log({ + 'function': '_buildRegexFromString()', + 'arguments': '{pattern: {' + (pattern || '') + '}, modifier: {' + (modifier || '') + '}', + 'message': 'WARNING - Invalid regex given: ' + regex + }); + window.Debug.print(); + } + + } + window.Debug = { + + table: {}, + log: function(debugObject) { + + if (!debugObject.message || typeof debugObject.message !== "string") { + return false; + } + + this.table[debugObject.message] = $.extend( + Object.preventExtensions({ + 'node': '', + 'function': '', + 'arguments': '' + }), debugObject + ); + + }, + print: function() { + + if (isEmpty(this.table)) { + return false; + } + + if (console.group !== undefined || console.table !== undefined) { + + console.groupCollapsed('--- jQuery Form Validation Debug ---'); + + if (console.table) { + console.table(this.table); + } else { + $.each(this.table, function(index, data) { + console.log(data['Name'] + ': ' + data['Execution Time'] + 'ms'); + }); + } + + console.groupEnd(); + + } else { + console.log('Debug is not available on your current browser, try the most recent version of Chrome or Firefox.'); + } + + this.table = {}; + + } + + }; + + String.prototype.capitalize = function() { + return this.charAt(0).toUpperCase() + this.slice(1); + }; + + function isEmpty(obj) { + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) + return false; + } + + return true; + } + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function(elt) { + var len = this.length >>> 0; + + var from = Number(arguments[1]) || 0; + from = (from < 0) ? Math.ceil(from) : Math.floor(from); + if (from < 0) + from += len; + + for (; from < len; from++) { + if (from in this && + this[from] === elt) + return from; + } + return -1; + }; + } + if (!JSON && !JSON.stringify) { + JSON.stringify = function(obj) { + var t = typeof(obj); + if (t !== "object" || obj === null) { + if (t === "string") { + obj = '"' + obj + '"'; + } + return String(obj); + } else { + var n, v, json = [], + arr = (obj && obj.constructor === Array); + for (n in obj) { + if (true) { + v = obj[n]; + t = typeof(v); + if (t === "string") { + v = '"' + v + '"'; + } else if (t === "object" && v !== null) { + v = JSON.stringify(v); + } + json.push((arr ? "" : '"' + n + '": ') + String(v)); + } + } + return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}"); + } + }; + } + +}(window, document, window.jQuery)); diff --git a/dist/jquery.validation.min.js b/dist/jquery.validation.min.js new file mode 100644 index 0000000..95e1d6b --- /dev/null +++ b/dist/jquery.validation.min.js @@ -0,0 +1,10 @@ +/*! + * jQuery Form Validation + * Copyright (C) 2015 RunningCoder.org + * Licensed under the MIT license + * + * @author Tom Bertrand + * @version 1.5.3 (2015-12-02) + * @link http://www.runningcoder.org/jqueryvalidation/ +*/ +!function(window,document,$,undefined){function _buildRegexFromString(a){function b(){}if(!a||"string"!=typeof a&&!(a instanceof RegExp))return b(),!1;"string"!=typeof a&&(a=a.toString());for(var c,d,e,f=a.charAt(0),g=a.length-1;g>0&&/[gimsxeU]/.test(a.charAt(g));)g--;a.charAt(g)!==f&&(f=null),f&&g!==a.length-1&&(d=a.substr(g+1,a.length-1)),c=f?a.substr(1,g-1):a;try{e=new RegExp(c,d)}catch(h){return b(),!1}return e}function isEmpty(a){for(var b in a)if(a.hasOwnProperty(b))return!1;return!0}window.Validation={form:[],labels:{},hasScrolled:!1},"function"!=typeof Object.preventExtensions&&(Object.preventExtensions=function(a){return a});var _rules={NOTEMPTY:/\S/,INTEGER:/^\d+$/,NUMERIC:/^\d+(?:[,\s]\d{3})*(?:\.\d+)?$/,MIXED:/^[\w\s-]+$/,NAME:/^['a-zãàáäâẽèéëêìíïîõòóöôùúüûñç\s-]+$/i,NOSPACE:/^(?!\s)\S*$/,TRIM:/^[^\s].*[^\s]$/,DATE:/^\d{4}-\d{2}-\d{2}(\s\d{2}:\d{2}(:\d{2})?)?$/,EMAIL:/^([^@]+?)@(([a-z0-9]-*)*[a-z0-9]+\.)+([a-z0-9]+)$/i,URL:/^(https?:\/\/)?((([a-z0-9]-*)*[a-z0-9]+\.?)*([a-z0-9]+))(\/[\w?=\.-]*)*$/,PHONE:/^(\()?\d{3}(\))?(-|\s)?\d{3}(-|\s)\d{4}$/,OPTIONAL:/\S/,COMPARISON:/^\s*([LV])\s*([<>]=?|==|!=)\s*([^<>=!]+?)\s*$/},_messages={"default":"$ contain error(s).",NOTEMPTY:"$ must not be empty.",INTEGER:"$ must be an integer.",NUMERIC:"$ must be numeric.",MIXED:"$ must be letters or numbers (no special characters).",NAME:"$ must not contain special characters.",NOSPACE:"$ must not contain spaces.",TRIM:"$ must not start or end with space character.",DATE:"$ is not a valid with format YYYY-MM-DD.",EMAIL:"$ is not valid.",URL:"$ is not valid.",PHONE:"$ is not a valid phone number.","<":"$ must be less than % characters.","<=":"$ must be less or equal to % characters.",">":"$ must be greater than % characters.",">=":"$ must be greater or equal to % characters.","==":"$ must be equal to %","!=":"$ must be different than %"},_data={validation:"data-validation",validationMessage:"data-validation-message",regex:"data-validation-regex",regexReverse:"data-validation-regex-reverse",regexMessage:"data-validation-regex-message",group:"data-validation-group",label:"data-validation-label",errorList:"data-error-list"},_options={submit:{settings:{form:null,display:"inline",insertion:"append",allErrors:!1,trigger:"click",button:"[type='submit']",errorClass:"error",errorListClass:"error-list",errorListContainer:null,errorTemplate:null,inputContainer:null,clear:"focusin",scrollToError:!1},callback:{onInit:null,onValidate:null,onError:null,onBeforeSubmit:null,onSubmit:null,onAfterSubmit:null}},dynamic:{settings:{trigger:null,delay:300},callback:{onSuccess:null,onError:null,onComplete:null}},rules:{},messages:{},labels:{},debug:!1},_supported={submit:{settings:{display:["inline","block"],insertion:["append","prepend"],allErrors:[!0,!1],clear:["focusin","keypress",!1],trigger:["click","dblclick","focusout","hover","mousedown","mouseenter","mouseleave","mousemove","mouseout","mouseover","mouseup","toggle"]}},dynamic:{settings:{trigger:["focusout","keydown","keypress","keyup"]}},debug:[!0,!1]},Validation=function(node,options){function extendRules(){options.rules=$.extend(!0,{},_rules,options.rules)}function extendMessages(){options.messages=$.extend(!0,{},_messages,options.messages)}function extendOptions(){options instanceof Object||(options={});var a=Object.preventExtensions($.extend(!0,{},_options));for(var b in options)if(options.hasOwnProperty(b)&&"debug"!==b)if(~["labels","messages","rules"].indexOf(b)&&options[b]instanceof Object)a[b]=options[b];else if(_options[b]&&options[b]instanceof Object)for(var c in options[b])if(options[b].hasOwnProperty(c)&&_options[b][c]&&options[b][c]instanceof Object){for(var d in options[b][c])options[b][c].hasOwnProperty(d)&&_supported[b]&&_supported[b][c]&&_supported[b][c][d]&&-1===$.inArray(options[b][c][d],_supported[b][c][d])&&delete options[b][c][d];a[b]&&a[b][c]&&(a[b][c]=$.extend(Object.preventExtensions(a[b][c]),options[b][c]))}a.dynamic.settings.trigger&&"keypress"===a.dynamic.settings.trigger&&"keypress"===a.submit.settings.clear&&(a.dynamic.settings.trigger="keydown"),options=a}function delegateDynamicValidation(){if(!options.dynamic.settings.trigger)return!1;if(!node.find("["+_data.validation+"],["+_data.regex+"]")[0])return!1;var a=options.dynamic.settings.trigger+delegateSuffix;"focusout"!==options.dynamic.settings.trigger&&(a+=" change"+delegateSuffix+" paste"+delegateSuffix),$.each(node.find("["+_data.validation+"],["+_data.regex+"]"),function(b,c){$(c).unbind(a).on(a,function(a){if($(this).is(":disabled"))return!1;var b=this,c=a.keyCode||null;_typeWatch(function(){validateInput(b)?_executeCallback(options.dynamic.callback.onSuccess,[node,b,c]):(displayOneError(b.name),_executeCallback(options.dynamic.callback.onError,[node,b,c,errors[b.name]])),_executeCallback(options.dynamic.callback.onComplete,[node,b,c])},options.dynamic.settings.delay)})})}function delegateValidation(){_executeCallback(options.submit.callback.onInit,[node]);var a=options.submit.settings.trigger+".vd";return node.find(options.submit.settings.button)[0]?(node.on("submit",!1),void node.find(options.submit.settings.button).off(".vd").on(a,function(a){return a.preventDefault(),resetErrors(),_executeCallback(options.submit.callback.onValidate,[node]),validateForm()?(_executeCallback(options.submit.callback.onBeforeSubmit,[node]),"function"==typeof options.submit.callback.onSubmit?_executeCallback(options.submit.callback.onSubmit,[node,formData])===!0&&submitForm():submitForm(),_executeCallback(options.submit.callback.onAfterSubmit,[node])):(displayErrors(),_executeCallback(options.submit.callback.onError,[node,errors,formData])),!1})):!1}function validateForm(){var a=isEmpty(errors);return formData={},$.each(node.find('input:not([type="submit"]), select, textarea').not(":disabled"),function(b,c){c=$(c);var d=_getInputValue(c[0]),e=c.attr("name");e&&(/\[]$/.test(e)?(e=e.replace(/\[]$/,""),formData[e]instanceof Array||(formData[e]=[]),formData[e].push(d)):formData[e]=d),(c.attr(_data.validation)||c.attr(_data.regex))&&(validateInput(c[0],d)||(a=!1))}),prepareFormData(),a}function prepareFormData(){var a,b,c={};for(var d in formData)if(formData.hasOwnProperty(d)){b=0,a=d.split(/\[(.+?)]/g);for(var e={},f=[],g=a.length-1;g>=0;g--)""!==a[g]?(f.length<1?e[a[g]]=Number(formData[d])||formData[d]:(e={},e[a[g]]=f[f.length-1]),f.push(e)):a.splice(g,1);c=$.extend(!0,c,e)}formData=c}function validateInput(a,b){var c=$(a).attr("name"),b=b||_getInputValue(a);if(!c)return!1;var d=c.replace(/]$/,"").split(/]\[|[[\]]/g),e=window.Validation.labels[c]||options.labels[c]||$(a).attr(_data.label)||d[d.length-1],f=$(a).attr(_data.validation),g=$(a).attr(_data.validationMessage),h=$(a).attr(_data.regex),i=!($(a).attr(_data.regexReverse)===undefined),j=$(a).attr(_data.regexMessage),k=!1;if(f&&(f=_api._splitValidation(f)),f instanceof Array&&f.length>0){if(""===$.trim(b)&&~f.indexOf("OPTIONAL"))return!0;$.each(f,function(a,d){if(k===!0)return!0;try{validateRule(b,d)}catch(f){(g||!options.submit.settings.allErrors)&&(k=!0),f[0]=g||f[0],registerError(c,f[0].replace("$",e).replace("%",f[1]))}})}if(h){var l=_buildRegexFromString(h);if(!(l instanceof RegExp))return!0;try{validateRule(b,l,i)}catch(m){m[0]=j||m[0],registerError(c,m[0].replace("$",e))}}return!errors[c]||errors[c]instanceof Array&&0===errors[c].length}function validateRule(value,rule,reversed){if(rule instanceof RegExp){var isValid=rule.test(value);if(reversed&&(isValid=!isValid),!isValid)throw[options.messages["default"],""]}else if(options.rules[rule]){if(!options.rules[rule].test(value))throw[options.messages[rule],""]}else{var comparison=rule.match(options.rules.COMPARISON);if(comparison&&4===comparison.length){var type=comparison[1],operator=comparison[2],compared=comparison[3],comparedValue;switch(type){case"L":if(isNaN(compared))return!1;if(!value||eval(value.length+operator+parseFloat(compared))===!1)throw[options.messages[operator],compared];break;case"V":default:if(isNaN(compared)){if(comparedValue=node.find('[name="'+compared+'"]').val(),!comparedValue)return!1;if(!value||!eval('"'+encodeURIComponent(value)+'"'+operator+'"'+encodeURIComponent(comparedValue)+'"'))throw[options.messages[operator].replace(" characters",""),compared]}else if(!value||isNaN(value)||!eval(value+operator+parseFloat(compared)))throw[options.messages[operator].replace(" characters",""),compared]}}}}function registerError(a,b){errors[a]||(errors[a]=[]),b=b.capitalize();for(var c=!1,d=0;d";if(!errors.hasOwnProperty(a))return!1;if(b=node.find('[name="'+a+'"]'),e=null,!b[0])return!1;if(f=b.attr(_data.group),f?(g=node.find('[name="'+a+'"]'),e=node.find('[id="'+f+'"]'),e[0]&&(e.addClass(options.submit.settings.errorClass),d=e)):(b.addClass(options.submit.settings.errorClass),options.submit.settings.inputContainer&&b.parentsUntil(node,options.submit.settings.inputContainer).addClass(options.submit.settings.errorClass),c=b.attr("id"),c&&(e=node.find('label[for="'+c+'"]')[0]),e||(e=b.parentsUntil(node,"label")[0]),e&&(e=$(e),e.addClass(options.submit.settings.errorClass))),"inline"===options.submit.settings.display?d=options.submit.settings.errorListContainer?b.parentsUntil(node,options.submit.settings.errorListContainer):d||b.parent():"block"===options.submit.settings.display&&(d=node),"inline"===options.submit.settings.display&&d.find("["+_data.errorList+"]")[0])return!1;("inline"===options.submit.settings.display||"block"===options.submit.settings.display&&!d.find("["+_data.errorList+"]")[0])&&("append"===options.submit.settings.insertion?d.append(h):"prepend"===options.submit.settings.insertion&&d.prepend(h));for(var i=0;i"+options.submit.settings.errorTemplate.replace("{{validation-message}}",errors[a][i])+"":"
  • "+errors[a][i]+"
  • ");if(options.submit.settings.clear||options.dynamic.settings.trigger){f&&g&&(b=g);var j="coucou"+resetSuffix;options.submit.settings.clear&&(j+=" "+options.submit.settings.clear+resetSuffix,~["radio","checkbox"].indexOf(b[0].type)&&(j+=" change"+resetSuffix)),options.dynamic.settings.trigger&&(j+=" "+options.dynamic.settings.trigger+resetSuffix,"focusout"===options.dynamic.settings.trigger||~["radio","checkbox"].indexOf(b[0].type)||(j+=" change"+resetSuffix+" paste"+resetSuffix)),b.unbind(j).on(j,function(a,b,c,d,e){return function(){e?$(c).hasClass(options.submit.settings.errorClass)&&resetOneError(a,b,c,d,e):$(b).hasClass(options.submit.settings.errorClass)&&resetOneError(a,b,c,d)}}(a,b,e,d,f))}if(options.submit.settings.scrollToError&&!window.Validation.hasScrolled){window.Validation.hasScrolled=!0;var k=parseFloat(options.submit.settings.scrollToError.offset)||0,l=parseFloat(options.submit.settings.scrollToError.duration)||500,m="block"===options.submit.settings.display?d:b;$("html, body").animate({scrollTop:m.offset().top+k},l)}}function displayErrors(){for(var a in errors)errors.hasOwnProperty(a)&&displayOneError(a)}function resetOneError(a,b,c,d,e){if(delete errors[a],d)options.submit.settings.inputContainer&&(e?c:b).parentsUntil(node,options.submit.settings.inputContainer).removeClass(options.submit.settings.errorClass),c&&c.removeClass(options.submit.settings.errorClass),b.removeClass(options.submit.settings.errorClass),"inline"===options.submit.settings.display&&d.find("["+_data.errorList+"]").remove();else{if(!b&&(b=node.find('[name="'+a+'"]'),!b[0]))return!1;b.trigger("coucou"+resetSuffix)}}function resetErrors(){errors=[],window.Validation.hasScrolled=!1,node.find("["+_data.errorList+"]").remove(),node.find("."+options.submit.settings.errorClass).removeClass(options.submit.settings.errorClass)}function submitForm(){node[0].submit()}function destroy(){return resetErrors(),node.find("["+_data.validation+"],["+_data.regex+"]").off(delegateSuffix+" "+resetSuffix),node.find(options.submit.settings.button).off(delegateSuffix).on("click"+delegateSuffix,function(){$(this).closest("form")[0].submit()}),!0}var errors=[],messages={},formData={},delegateSuffix=".vd",resetSuffix=".vr";window.Validation.hasScrolled=!1;var _getInputValue=function(a){var b;switch($(a).attr("type")){case"checkbox":b=$(a).is(":checked")?1:"";break;case"radio":b=node.find('input[name="'+$(a).attr("name")+'"]:checked').val()||"";break;default:b=$(a).val()}return b},_typeWatch=function(){var a=0;return function(b,c){clearTimeout(a),a=setTimeout(b,c)}}(),_executeCallback=function(a,b){if(!a)return!1;var c;if("function"==typeof a)c=a;else if("string"==typeof a||a instanceof Array){c=window,"string"==typeof a&&(a=[a,[]]);for(var d=a[0].split("."),e=a[1],f=!0,g=0;g>>0,c=Number(arguments[1])||0;for(c=0>c?Math.ceil(c):Math.floor(c),0>c&&(c+=b);b>c;c++)if(c in this&&this[c]===a)return c;return-1})}(window,document,window.jQuery); \ No newline at end of file diff --git a/example/demo.html b/example/demo.html new file mode 100644 index 0000000..3b9e166 --- /dev/null +++ b/example/demo.html @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + +

    Signup_v1 Demo

    + + + +
    + +
    + +
    + + + +
    + * +
    +
    +
    +
    + +
    + + + +
    + * +
    +
    +
    +
    + +
    + + + +
    + * +
    +
    +
    +
    + +
    + + + +
    + * +
    +
    +
    +
    + +
    + + + +
    + * +
    +
    +
    + + + + + +
    + + + + + \ No newline at end of file diff --git a/example/jquery.validation.css b/example/jquery.validation.css new file mode 100644 index 0000000..c2856a7 --- /dev/null +++ b/example/jquery.validation.css @@ -0,0 +1,385 @@ +/*------------------------------------*\ + CONTENTS +\*------------------------------------*/ +/* +NORMALIZE BUTTON & INPUT - https://github.com/necolas/normalize.css +LAYOUT +INPUT, BUTTON & LABEL +ERROR +*/ + +/*------------------------------------*\ + NORMALIZE BUTTON & INPUT +\*------------------------------------*/ + +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ + +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + */ + +button, +input, +optgroup, +select, +textarea { + color: inherit; /* 1 */ + font: inherit; /* 2 */ + margin: 0; /* 3 */ +} + +/** + * Address `overflow` set to `hidden` in IE 8/9/10/11. + */ + +button { + overflow: visible; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + * Correct `select` style inheritance in Firefox. + */ + +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} + +/** + * Re-set default cursor for disabled elements. + */ + +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * Remove inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +input { + line-height: normal; +} + + +/*------------------------------------*\ + LAYOUT +\*------------------------------------*/ + +.validation-form-container { + position: relative; + background-color: #fff; + font-family: "Helvetica Neue", "Helvetica", Arial; + color: rgba(0, 0, 0, 0.7); + font-size: 16px; + padding: 16px; + width: 100%; + max-width: 500px; + border-radius: 4px; + -webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1) inset; + box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1) inset; + line-height: 16px; +} + +.validation-form-container * { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.validation-form-container :last-child { + margin-bottom: 0em; +} + +.validation-form-container .field { + clear: both; + margin: 0em 0em 1em; +} + +.validation-form-container ul { + list-style: none; + margin: 0.2em 0; + padding: 0; +} + +.validation-form-container .ui.loader.active { + display: block; +} +.validation-form-container .ui.loader { + width: 32px; + height: 32px; + background: url(loader-medium.gif) no-repeat; + background-position: 48% 0px; + display: none; + position: absolute; + top: 50%; + left: 50%; + margin: 0px; + z-index: 1000; + -webkit-transform: translateX(-50%) translateY(-50%); + -ms-transform: translateX(-50%) translateY(-50%); + transform: translateX(-50%) translateY(-50%); +} + + +/*------------------------------------*\ + INPUT, BUTTON & LABEL +\*------------------------------------*/ + +.validation-form-container .ui.button { + cursor: pointer; + display: inline-block; + vertical-align: middle; + min-height: 1em; + outline: none; + border: none; + background-color: #FAFAFA; + color: #808080; + margin: 0em; + padding: 0.8em 1.5em; + font-size: 1rem; + text-transform: uppercase; + line-height: 1; + font-weight: bold; + font-style: normal; + text-align: center; + text-decoration: none; + background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0)), to(rgba(0, 0, 0, 0.05))); + background-image: -webkit-linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.05)); + background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.05)); + border-radius: 0.25em; + -webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.08) inset; + box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.08) inset; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-transition: opacity 0.25s ease, background-color 0.25s ease, color 0.25s ease, background 0.25s ease, -webkit-box-shadow 0.25s ease; + transition: opacity 0.25s ease, background-color 0.25s ease, color 0.25s ease, background 0.25s ease, box-shadow 0.25s ease; +} + +.validation-form-container .ui.blue.button { + background-color: #6ECFF5; + color: #FFFFFF; +} + +.validation-form-container .ui.blue.button:hover, +.validation-form-container .ui.blue.button.active { + background-color: #1AB8F3; + color: #FFFFFF; +} + +.validation-form-container .ui.blue.button:active { + background-color: #0AA5DF; + color: #FFFFFF; +} + +.validation-form-container .ui.mini.button { + font-size: 0.8rem; + padding: 0.6em 0.8em; +} + +.validation-form-container .ui.basic.button { + background-color: transparent !important; + background-image: none; + color: #808080 !important; + font-weight: normal; + text-transform: none; + -webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1) inset; + box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1) inset; +} + +.validation-form-container .ui.input { + width: 100%; + font-size: 1em; + display: inline-block; + position: relative; + color: rgba(0, 0, 0, 0.7); +} + +.validation-form-container .ui.labeled.input input { + padding-right: 2.5em !important; +} + +.validation-form-container textarea, +.validation-form-container input[type="text"], +.validation-form-container input[type="password"] { + width: 100%; + margin: 0em; + padding: 0.65em 1em; + font-size: 1em; + background-color: #FFFFFF; + border: 1px solid rgba(0, 0, 0, 0.15); + outline: none; + color: rgba(0, 0, 0, 0.7); + border-radius: 0.3125em; + -webkit-transition: background-color 0.3s ease-out, -webkit-box-shadow 0.2s ease, border-color 0.2s ease; + transition: background-color 0.3s ease-out, box-shadow 0.2s ease, border-color 0.2s ease; + -webkit-box-shadow: 0em 0em 0em 0em rgba(0, 0, 0, 0.3) inset; + box-shadow: 0em 0em 0em 0em rgba(0, 0, 0, 0.3) inset; + -webkit-appearance: none; + -webkit-tap-highlight-color: rgba(255, 255, 255, 0); +} + +.validation-form-container textarea:focus, +.validation-form-container input[type="text"]:focus, +.validation-form-container input[type="password"]:focus { + color: rgba(0, 0, 0, 0.85); + border-color: rgba(0, 0, 0, 0.2); + border-bottom-left-radius: 0; + border-top-left-radius: 0; + -webkit-appearance: none; + -webkit-box-shadow: 0.3em 0em 0em 0em rgba(0, 0, 0, 0.2) inset; + box-shadow: 0.3em 0em 0em 0em rgba(0, 0, 0, 0.2) inset; +} + +.validation-form-container textarea[readonly], +.validation-form-container textarea[disabled], +.validation-form-container input[readonly], +.validation-form-container input[disabled] { + cursor: not-allowed; + background-color: #f7f7f7; + color: #999; +} + +.validation-form-container .field > label { + margin: 0em 0em 0.3em; + display: block; + color: #555555; + font-size: 0.875em; + position: relative; +} + +.validation-form-container .ui.label { + display: inline-block; + vertical-align: middle; + margin: -0.25em 0.25em 0em; + background-color: #E8E8E8; + border-color: #E8E8E8; + padding: 0.5em 0.8em; + color: rgba(0, 0, 0, 0.65); + text-transform: uppercase; + font-weight: normal; + border-radius: 0.325em; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + -webkit-transition: background 0.1s linear; + transition: background 0.1s linear; +} + +.validation-form-container .ui.corner.label { + top: 1px; + right: 1px; + overflow: hidden; + font-size: 0.7em; + border-radius: 0 0.3125em; + background-color: transparent; + position: absolute; + z-index: 10; + margin: 0em; + width: 3em; + height: 3em; + padding: 0em; + text-align: center; + -webkit-transition: color 0.2s ease; + transition: color 0.2s ease; +} + +.validation-form-container .ui.corner.label:after { + position: absolute; + content: ""; + right: 0em; + top: 0em; + z-index: -1; + width: 0em; + height: 0em; + border-top: 0em solid transparent; + border-right: 3em solid transparent; + border-bottom: 3em solid transparent; + border-left: 0em solid transparent; + border-right-color: inherit; + -webkit-transition: border-color 0.2s ease; + transition: border-color 0.2s ease; +} + +.validation-form-container .ui.corner.label .icon { + font-size: 2em; + margin: 0.25em 0 0 0.5em; + width: auto; + display: inline-block; + height: 1em; + font-style: normal; + line-height: 1; + font-weight: normal; + text-decoration: inherit; + text-align: center; + speak: none; + -webkit-font-smoothing: antialiased; + -moz-font-smoothing: antialiased; + font-smoothing: antialiased; +} + + +/*------------------------------------*\ + ERROR +\*------------------------------------*/ + +div.error, +div.error-list, +label.error, +input.error, +select.error, +textarea.error { + color: #D95C5C !important; + border-color: #D95C5C !important; +} + + +.validation-form-container .error .corner.label { + border-color: #D95C5C; + color: #FFFFFF; +} \ No newline at end of file diff --git a/example/loader-medium.gif b/example/loader-medium.gif new file mode 100644 index 0000000..386eb5a Binary files /dev/null and b/example/loader-medium.gif differ diff --git a/html5-form-validation.jquery.json b/html5-form-validation.jquery.json index 927d811..a041b20 100644 --- a/html5-form-validation.jquery.json +++ b/html5-form-validation.jquery.json @@ -1,30 +1,30 @@ -{ - "name": "html5-form-validation", - "title": "jQuery Form Validation", - "description": "jQuery plugin that provides a client site form validation with builtin options and deep customization.", - "keywords": [ - "form", - "html5", - "validate", - "validation", - "input" - ], - "version": "1.1.0", - "author": { - "name": "Tom Bertrand", - "url": "http://www.runningcoder.org/jqueryvalidation/" - }, - "licenses": [ - { - "type": "MIT", - "url": "https://github.com/running-coder/jquery-form-validation/blob/master/LICENSE" - } - ], - "bugs": "https://github.com/running-coder/jquery-form-validation/issues", - "homepage": "http://www.runningcoder.org/jqueryvalidation/", - "docs": "http://www.runningcoder.org/jqueryvalidation/documentation/", - "demo": "http://www.runningcoder.org/jqueryvalidation/demo/", - "dependencies": { - "jquery": ">=1.7.2" - } +{ + "name": "html5-form-validation", + "title": "jQuery Form Validation", + "description": "jQuery plugin that provides a client site form validation with builtin options and deep customization.", + "keywords": [ + "form", + "html5", + "validate", + "validation", + "input" + ], + "version": "1.5.3", + "author": { + "name": "Tom Bertrand", + "url": "http://www.runningcoder.org/jqueryvalidation/" + }, + "licenses": [ + { + "type": "MIT", + "url": "https://github.com/running-coder/jquery-form-validation/blob/master/LICENSE" + } + ], + "bugs": "https://github.com/running-coder/jquery-form-validation/issues", + "homepage": "http://www.runningcoder.org/jqueryvalidation/", + "docs": "http://www.runningcoder.org/jqueryvalidation/documentation/", + "demo": "http://www.runningcoder.org/jqueryvalidation/demo/", + "dependencies": { + "jquery": ">=1.7.2" + } } \ No newline at end of file diff --git a/jquery.validation-1.1.0.js b/jquery.validation-1.1.0.js deleted file mode 100644 index 4fbe637..0000000 --- a/jquery.validation-1.1.0.js +++ /dev/null @@ -1,1333 +0,0 @@ -/** - * jQuery Form Validation - * - * @author Tom Bertrand - * @version 1.1.0 (2014-05-26) - * - * @copyright - * Copyright (C) 2014 Tom Bertrand. - * - * @link - * http://www.runningcoder.org/jqueryvalidation/ - * - * @license - * Licensed under the MIT license. - */ -(function (window, document, $, undefined) -{ - - window.Validation = {}; - - /** - * Fail-safe preventExtensions function for older browsers - */ - if (typeof Object.preventExtensions !== "function") { - Object.preventExtensions = function (obj) { return obj; } - } - - // Not using strict to avoid throwing a window error on bad config extend. - // console.debug is used instead to debug Validation - //"use strict"; - - // ================================================================================================================= - /** - * @private - * RegExp rules - */ - var _rules = { - // Validate not empty - NOTEMPTY: /./, - // Validate a numeric - NUMERIC: /^[0-9]+$/, - // Validate an alphanumeric string (no special chars) - MIXED: /^[\w\s-]+$/, - // Validate a spaceless string - NOSPACE: /^[^\s]+$/, - // Validate a spaceless string at start or end - TRIM: /^[^\s].*[^\s]$/, - // Validate a date YYYY-MM-DD - DATE: /^\d{4}-\d{2}-\d{2}(\s\d{2}:\d{2}(:\d{2})?)?$/, - // Validate an email - EMAIL: /^([^@]+?)@(([a-z0-9]-*)*[a-z0-9]+\.)+([a-z0-9]+)$/i, - // Validate an url - URL: /^(https?:\/\/)?((([a-z0-9]-*)*[a-z0-9]+\.?)*([a-z0-9]+))(\/[\w?=\.-]*)*$/, - // Validate a north american phone number - PHONE: /^(\()?\d{3}(\))?(-|\s)?\d{3}(-|\s)\d{4}$/, - // Validate value if it is not empty - OPTIONAL: /^.*$/, - // Validate values or length by comparison - COMPARISON: /^\s*([LV])\s*([<>]=?|==|!=)\s*([^<>=!]+?)\s*$/, - // Validate credit card number - LUHN: "_validateLuhn" - }; - - /** - * @private - * Error messages - */ - var _messages = Object.preventExtensions({ - 'default': '$ contain error(s).', - 'NOTEMPTY': '$ must not be empty.', - 'NUMERIC': '$ must be numeric.', - 'STRING': '$ must be a string.', - 'NOSPACE': '$ must not contain spaces.', - 'TRIM': '$ must not start or end with space character.', - 'MIXED': '$ must be letters or numbers (no special characters).', - 'DATE': '$ is not a valid with format YYYY-MM-DD.', - 'EMAIL': '$ is not valid.', - 'URL': '$ is not valid.', - 'PHONE': '$ is not a valid phone number.', - //'INARRAY': '$ is not a valid option.', - '<': '$ must be less than % characters.', - '<=': '$ must be less or equal to % characters.', - '>': '$ must be greater than % characters.', - '>=': '$ must be greater or equal to % characters.', - '==': '$ must be equal to %', - '!=': '$ must be different than %' - }), - _extendedMessages = false; - - /** - * @private - * HTML5 data attributes - */ - var _data = { - validation: 'data-validation', - validationMessage: 'data-validation-message', - regex: 'data-validation-regex', - regexMessage: 'data-validation-regex-message', - validationGroup: 'data-validation-group', - errorList: 'data-error-list' - }; - - /** - * @private - * Default options - * - * @link http://www.runningcoder.org/jqueryvalidation/documentation/ - */ - var _options = { - submit: { - settings: { - form: null, - display: "inline", - insertion: "append", - allErrors: false, - trigger: "click", - button: "input[type='submit']", - errorClass: "error", - errorListClass: "error-list", - inputContainer: null, - clear: "focusin", - scrollToError: false - }, - callback: { - onInit: null, - onValidate: null, - onError: null, - onBeforeSubmit: null, - onSubmit: null, - onAfterSubmit: null - } - }, - dynamic: { - settings: { - trigger: null, - delay: 300 - }, - callback: { - onSuccess: null, - onError: null, - onComplete: null - } - }, - messages: {} - }; - - /** - * @private - * Limit the supported options on matching keys - */ - var _supported = { - submit: { - settings: { - display: ["inline", "block"], - insertion: ["append", "prepend"], //"before", "insertBefore", "after", "insertAfter" - allErrors: [true, false], - clear: ["focusin", "keypress", false], - trigger: [ - "click", "dblclick", "focusout", - "hover", "mousedown", "mouseenter", - "mouseleave", "mousemove", "mouseout", - "mouseover", "mouseup", "toggle" - ], - scrollToError: [true, false] - } - }, - dynamic: { - settings: { - trigger: ["focusout", "keydown", "keypress", "keyup"] - } - } - }; - - // ================================================================================================================= - - /** - * @constructor - * Validation Class - * - * @param {object} node jQuery form object - * @param {object} options User defined options - */ - var Validation = function (node, options) { - - var errors = [], - hasScrolled = false; - - /** - * Extends user-defined "message" into the default Validation "_message". - * Notes: - * - preventExtensions prevents from modifying the Validation "_message" object structure - */ - function extendMessages () { - - if (!window.Validation.messages || _extendedMessages) { - return false; - } - - _messages = $.extend(_messages, window.Validation.messages); - - _extendedMessages = true; - - } - - /** - * Extends user-defined "options" into the default Validation "_options". - * Notes: - * - preventExtensions prevents from modifying the Validation "_options" object structure - * - filter through the "_supported" to delete unsupported "options" - */ - function extendOptions () { - - if (!(options instanceof Object)) { - options = {}; - } - - var tpmOptions = Object.preventExtensions($.extend(true, {}, _options)), - tmpMessages = Object.preventExtensions($.extend(true, {}, _messages)); - - tpmOptions.messages = $.extend(tmpMessages, options.messages || {}); - - for (var method in options) { - if (!options.hasOwnProperty(method) || !(options[method] instanceof Object)) { - continue; - } - - for (var type in options[method]) { - if (!options[method].hasOwnProperty(type) || !(options[method][type] instanceof Object)) { - continue; - } - - for (var option in options[method][type]) { - if (!options[method][type].hasOwnProperty(option)) { - continue; - } - - if (_supported[method] && - _supported[method][type] && - _supported[method][type][option] && - $.inArray(options[method][type][option], _supported[method][type][option]) === -1) { - - window.debug( - 'Validation.extendOptions - Delete unsupported property - ' + type + - ': {' + option.toString() + ': ' + options[method][type][option].toString() + '}' - ); - - delete options[method][type][option]; - } - - } - if (tpmOptions[method] && tpmOptions[method][type]) { - tpmOptions[method][type] = $.extend(Object.preventExtensions(tpmOptions[method][type]), options[method][type]); - } - } - } - - // @TODO Would there be a better fix to solve event conflict? - if (tpmOptions.dynamic.settings.trigger) { - if (tpmOptions.dynamic.settings.trigger === "keypress" && tpmOptions.submit.settings.clear === "keypress") { - tpmOptions.dynamic.settings.trigger = "keydown"; - } - } - - options = tpmOptions; - - } - - /** - * Delegates the dynamic validation on data-validation and data-validation-regex attributes based on trigger. - * - * @returns {boolean} false if the option is not set - */ - function delegateDynamicValidation() { - - if (!options.dynamic.settings.trigger) { - return false; - } - - $.each( - $(node).find('[' + _data.validation + '],[' + _data.regex + ']'), - function (index, input) { - - $(input).unbind(options.dynamic.settings.trigger).on(options.dynamic.settings.trigger, function (e) { - - if ($(this).is(':disabled')) { - return false; - } - - //e.preventDefault(); - - var input = this; - - _typeWatch(function () { - - if (!validateInput(input)) { - - displayOneError(input.name); - _executeCallback(options.dynamic.callback.onError, [node, input, errors[input.name]]); - - } else { - - _executeCallback(options.dynamic.callback.onSuccess, [node, input]); - - } - - _executeCallback(options.dynamic.callback.onComplete, [node, input]); - - }, options.dynamic.settings.delay); - - }); - } - ) - } - - var _typeWatch = (function(){ - var timer = 0; - return function(callback, ms){ - clearTimeout (timer); - timer = setTimeout(callback, ms); - } - })(); - - /** - * Delegates the submit validation on data-validation and data-validation-regex attributes based on trigger. - * Note: Disable the form submit function so the callbacks are not by-passed - */ - function delegateValidation () { - - _executeCallback(options.submit.callback.onInit, [node]); - - $(node).on("submit", false ); - $(node).find(options.submit.settings.button).unbind(options.submit.settings.trigger).on(options.submit.settings.trigger, function (e) { - - e.preventDefault(); - - resetErrors(); - - _executeCallback(options.submit.callback.onValidate, [node]); - - if (!validateForm()) { - - // OnError function receives the "errors" object as the last "extraParam" - _executeCallback(options.submit.callback.onError, [node, errors]); - - displayErrors(); - - } else { - - _executeCallback(options.submit.callback.onBeforeSubmit, [node]); - - (options.submit.callback.onSubmit) ? _executeCallback(options.submit.callback.onSubmit, [node]) : submitForm(); - - _executeCallback(options.submit.callback.onAfterSubmit, [node]); - - } - - return false; - - }); - - } - - /** - * For every "data-validation" & "data-pattern" attributes that are not disabled inside the jQuery "node" object - * the "validateInput" function will be called. - * - * @returns {boolean} true if no error(s) were found (valid form) - */ - function validateForm () { - - $.each( - $(node).find('[' + _data.validation + '],[' + _data.regex + ']'), - function (index, input) { - - if ($(this).is(':disabled')) { - return false; - } - - validateInput(input); - - } - ); - - return $.isEmptyObject(errors); - - } - - /** - * Prepare the information from the data attributes - * and call the "validateRule" function. - * - * @param {object} input Reference of the input element - * - * @returns {boolean} true if no error(s) were found (valid input) - */ - function validateInput (input) { - - var inputName = $(input).attr('name'); - - if (!inputName) { - window.debug('Validation.validateInput - Invalid {string} inputName on ' + input.toString()); - return false; - } - - var value = _getInputValue(input), - - matches = inputName.replace(/]$/, '').split(/]\[|[[\]]/g), - inputShortName = matches[matches.length - 1], - - validationArray = $(input).attr(_data.validation), - validationMessage = $(input).attr(_data.validationMessage), - validationRegex = $(input).attr(_data.regex), - validationRegexMessage = $(input).attr(_data.regexMessage), - - validateOnce = false; - - if (validationArray) { - validationArray = _api._splitValidation(validationArray); - } - - // Validates the "data-validation" - if (!$.isEmptyObject(validationArray)) { - - // "OPTIONAL" input will not be validated if it's empty - if (value === '' && $.inArray('OPTIONAL', validationArray) !== -1) { - return true; - } - - $.each(validationArray, function (i, rule) { - - if (validateOnce === true) { - return true; - } - - try { - - validateRule(value, rule); - - } catch (error) { - - if (validationMessage || !options.submit.settings.allErrors) { - validateOnce = true; - } - - error[0] = validationMessage || error[0]; - - registerError(inputName, error[0].replace('$', inputShortName).replace('%', error[1])); - - } - - }); - - } - - // Validates the "data-validation-regex" - if (validationRegex) { - - var pattern = validationRegex.split('/'); - - if (pattern.length > 1) { - - var tmpPattern = ""; - - // Do not loop through the last item knowing its a potential modifier - for (var k = 0; k < pattern.length - 1; k++) { - if (pattern[k] !== "") { - tmpPattern += pattern[k] + '/'; - } - } - // Remove last added "/" - tmpPattern = tmpPattern.slice(0, -1); - - // Test the last item for modifier(s) - if (/[gimsxeU]+/.test(pattern[pattern.length - 1])) { - var patternModifier = pattern[pattern.length - 1]; - } - - pattern = tmpPattern; - } else { - pattern = pattern[0]; - } - - // Validate the regex - try { - - var rule = new RegExp(pattern, patternModifier); - - } catch (error) { - - window.debug('Invalid data-validation-regex on ' + inputName); - return true; - - } - - try { - - validateRule(value, rule); - - } catch (error) { - - error[0] = validationRegexMessage || error[0]; - - registerError(inputName, error[0].replace('$', inputShortName)); - - } - - } - - return $.isEmptyObject(errors[inputName]); - - } - - /** - * Validate an input value against one rule. - * If a "value-rule" mismatch occurs, an error is thrown to the caller function. - * - * @param {string} value - * @param rule - * - * @returns {*} Error if a mismatch occurred. - */ - function validateRule (value, rule) { - - // Validate for custom "data-validation-regex" - if (rule instanceof RegExp) { - if (rule.test(value)) { - throw [options.messages['default'], '']; - } - return; - } - - // Validate for predefined "data-validation" _rules - if (_rules[rule]) { - /* - if (typeof _rules[rule] === "string") { - - try { - var isLuhn = eval(_rules[rule] + '(' + value.toString() + ')'); - } catch (error) { - } - - return; - } -*/ - - if (!_rules[rule].test(value)) { - throw [options.messages[rule], '']; - } - return; - } - - // Validate for comparison "data-validation" - var comparison = rule.match(_rules['COMPARISON']); - if (!comparison || comparison.length !== 4) { - window.debug('Validation.validateRule - Invalid validation rule: ' + rule); - return; - } - - var type = comparison[1], - operator = comparison[2], - compared = comparison[3], - comparedValue; - - switch (type) { - - // Compare input "Length" - case "L": - - if (isNaN(compared)) { - - comparedValue = $(node).find('[name="' + compared + '"]').val(); - if (!comparedValue) { - window.debug('$.Validation.validateRule - Unable to find value of input[name="' + compared + '"] inside rule ' + rule) - return false; - } - - if (!value || !eval('"' + encodeURIComponent(value) + '"' + operator + '"' + encodeURIComponent(comparedValue) + '"')) { - throw [options.messages[operator], compared]; - } - - } else { - - if (!value || eval(value.length + operator + parseFloat(compared)) == false) { - throw [options.messages[operator], compared]; - } - - } - break; - - // Compare input "Value" - case "V": - default: - - if (isNaN(compared)) { - - comparedValue = $(node).find('[name="' + compared + '"]').val(); - if (!comparedValue) { - window.debug('$.Validation.validateRule - Unable to find value of input[name="' + compared + '"] inside rule ' + rule) - return false; - } - - if (!value || !eval('"' + encodeURIComponent(value) + '"' + operator + '"' + encodeURIComponent(comparedValue) + '"')) { - throw [options.messages[operator].replace(' characters', ''), compared]; - } - - } else { - - if (!value || !eval(value + operator + parseFloat(compared))) { - throw [options.messages[operator].replace(' characters', ''), compared]; - } - - } - break; - - } - - } - - /** - * Register an error into the global "error" variable. - * - * @param {string} inputName Input where the error occurred - * @param {string} error Description of the error to be displayed - */ - function registerError (inputName, error) { - - if (!errors[inputName]) { - errors[inputName] = []; - } - - errors[inputName].push(error.capitalize()); - - } - - /** - * Display a single error based on "inputName" key inside the "errors" global array. - * The input, the label and the "inputContainer" will be given the "errorClass" and other - * settings will be considered. - * - * @param {string} inputName Key used for search into "errors" - * - * @returns {boolean} false if an unwanted behavior occurs - */ - function displayOneError (inputName) { - - var input, - inputId, - errorContainer, - label, - html = '
      ', - group, - groupInput; - - if (!errors.hasOwnProperty(inputName)) { - return false; - } - - input = $(node).find('[name="' + inputName + '"]'); - - label = null; - - if (!input[0]) { - window.debug('Validation.displayOneError unable to find ' + inputName); - return false; - } - - group = input.attr(_data.validationGroup); - - if (group) { - - groupInput = $(node).find('[name="' + inputName + '"]'); - label = $(node).find('[id="' + group + '"]'); - - if (label[0]) { - label.addClass(options.submit.settings.errorClass); - errorContainer = label; - } - - //$(node).find('[' + _data.validationGroup + '="' + group + '"]').addClass(options.submit.settings.errorClass) - - } else { - - input.addClass(options.submit.settings.errorClass); - - if (options.submit.settings.inputContainer) { - input.parentsUntil(node, options.submit.settings.inputContainer).addClass(options.submit.settings.errorClass) - } - - inputId = input.attr('id'); - - if (inputId) { - label = $(node).find('label[for="' + inputId + '"]')[0]; - } - - if (!label) { - label = input.parentsUntil(node, 'label')[0]; - } - - if (label) { - label = $(label); - label.addClass(options.submit.settings.errorClass); - } - } - - if (options.submit.settings.display === 'inline') { - errorContainer = errorContainer || input.parent(); - } else if (options.submit.settings.display === 'block') { - errorContainer = $(node); - } - - if (options.submit.settings.display === "inline" || - (options.submit.settings.display === "block" && !errorContainer.find('[' + _data.errorList + ']')[0]) - ) { - if (options.submit.settings.insertion === 'append') { - errorContainer.append(html); - } else if (options.submit.settings.insertion === 'prepend') { - errorContainer.prepend(html); - } - } - - for (var i = 0; i < errors[inputName].length; i++) { - errorContainer.find('ul').append('
    • ' + errors[inputName][i] + '
    • '); - } - - if (options.submit.settings.clear || options.dynamic.settings.trigger) { - - if (group && groupInput) { - input = groupInput; - } - - input.unbind(options.submit.settings.clear) - .on(options.submit.settings.clear + " " + options.dynamic.settings.trigger, function (a,b,c,d,e) { - - return function () { - - if (e) { - - if ($(c).hasClass(options.submit.settings.errorClass)) { - resetOneError(a,b,c,d,e); - } - - } else if ($(b).hasClass(options.submit.settings.errorClass)) { - resetOneError(a,b,c,d); - } - }; - - }(inputName, input, label, errorContainer, group)) - } - - if (options.submit.settings.scrollToError && !hasScrolled) { - - hasScrolled = true; - - if (typeof $.scrollTo !== 'function') { - window.debug('Missing jQuery.scrollTo, scrolling will not happen.'); - return false; - } - - $.scrollTo( - (options.submit.settings.display === 'block') ? - errorContainer : - input - , 500, { offset: -100 }); - - } - - } - - /** - * Display all of the errors - */ - function displayErrors () { - - for (var inputName in errors) { - displayOneError(inputName); - } - - } - - /** - * Remove an input error. - * - * @param {string} inputName Key reference to delete the error from "errors" global variable - * @param {object} input jQuery object of the input - * @param {object} label jQuery object of the input's label - * @param {object} container jQuery object of the "errorList" - * @param {string} [group] Name of the group if any (ex: used on input radio) - */ - function resetOneError(inputName, input, label, container, group) { - - try { - delete errors[inputName]; - hasScrolled = false; - } catch(error) { - window.debug('Validation.resetOneError unable to find and delete ' + inputName + ' inside {object} errors.'); - return false; - } - - if (options.submit.settings.inputContainer) { - (group ? label : input).parentsUntil(node, options.submit.settings.inputContainer).removeClass(options.submit.settings.errorClass) - } - - label && label.removeClass(options.submit.settings.errorClass); - - input.removeClass(options.submit.settings.errorClass); - - if (options.submit.settings.display === 'inline') { - container.find('[' + _data.errorList + ']').remove(); - } - - } - - /** - * Remove all of the input error(s) display. - */ - function resetErrors () { - - errors = [], - hasScrolled = false;; - - $(node).find('[' + _data.errorList + ']').remove(); - $(node).find('.' + options.submit.settings.errorClass).removeClass(options.submit.settings.errorClass); - - } - - /** - * Submits the form once it succeeded the validation process. - * Note: This function will be overridden if "options.submit.settings.onSubmit" is defined - */ - function submitForm () { - - node.submit(); - - } - - /** - * @private - * Helper to get the value of an regular, radio or chackbox input - * - * @param input - * - * @returns {string} value - */ - var _getInputValue = function (input) { - - var value; - - // Get the value or state of the input based on its type - switch ($(input).attr('type')) { - case 'checkbox': - value = ($(input).is(':checked')) ? 1 : ''; - break; - case 'radio': - value = $(node).find('input[name="' + $(input).attr('name') + '"]:checked').val() || ''; - break; - default: - value = $(input).val(); - break; - } - - return value; - - }; - - /** - * @private - * Executes an anonymous function or a string reached from the window scope. - * - * @example - * Note: These examples works with every callbacks (onInit, onError, onSubmit, onBeforeSubmit & onAfterSubmit) - * - * // An anonymous function inside the "onInit" option - * onInit: function() { console.log(':D'); }; - * - * * // myFunction() located on window.coucou scope - * onInit: 'window.coucou.myFunction' - * - * // myFunction(a,b) located on window.coucou scope passing 2 parameters - * onInit: ['window.coucou.myFunction', [':D', ':)']]; - * - * // Anonymous function to execute a local function - * onInit: function () { myFunction(':D'); } - * - * @param {string|array} callback The function to be called - * @param {array} [extraParams] In some cases the function can be called with Extra parameters (onError) - * - * @returns {boolean} - */ - var _executeCallback = function (callback, extraParams) { - - if (!callback) { - return false; - } - - var _callback; - - if (typeof callback === "function") { - - _callback = callback; - - } else if (typeof callback === "string" || callback instanceof Array) { - - _callback = window; - - if (typeof callback === "string") { - callback = [callback, []]; - } - - var _exploded = callback[0].split('.'), - _params = callback[1], - _isValid = true, - _splitIndex = 0; - - while (_splitIndex < _exploded.length) { - - if (typeof _callback !== 'undefined') { - _callback = _callback[_exploded[_splitIndex++]]; - } else { - _isValid = false; - break; - } - } - - if (!_isValid || typeof _callback !== "function") { - window.debug('Validation._executeFunction - Invalid function - ' + callback[0].toString()); - return false; - } - - } - - _callback.apply(this, $.merge(_params || [], (extraParams) ? extraParams : [])); - return true; - - }; - - var _validateLuhn = function (luhn) { - - var len = luhn.length, - mul = 0, - prodArr = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]], - sum = 0; - - while (len--) { - sum += prodArr[mul][parseInt(luhn.charAt(len), 10)]; - mul ^= 1; - } - - return sum % 10 === 0 && sum > 0; - - }; - - - - /** - * @private - * Constructs Validation - */ - this.__construct = function () { - - extendMessages(); - extendOptions(); - - delegateDynamicValidation(); - delegateValidation(); - - }(); - - return { - - /** - * @public - * Register error - * - * @param inputName - * @param error - */ - registerError: registerError, - - /** - * @public - * Display one error - * - * @param inputName - */ - displayOneError: displayOneError, - - /** - * @public - * Display all errors - */ - displayErrors: displayErrors - - }; - - }; - - // ================================================================================================================= - - /** - * @public - * jQuery public function to implement the Validation on the selected node(s). - * - * @param {object} options To configure the Validation class. - * - * @return {object} Modified DOM element - */ - $.fn.validate = $.validate = function (options) { - - return _api.validate(this, options); - - }; - - /** - * @public - * jQuery public function to add one or multiple "data-validation" argument. - * - * @param {string|array} validation Arguments to add in the node's data-validation - * - * @return {object} Modified DOM element - */ - $.fn.addValidation = function (validation) { - - return _api.addValidation(this, validation); - - }; - - /** - * @public - * jQuery public function to remove one or multiple "data-validation" argument. - * - * @param {string|array} validation Arguments to remove in the node's data-validation - * - * @return {object} Modified DOM element - */ - $.fn.removeValidation = function (validation) { - - return _api.removeValidation(this, validation); - - }; - - /** - * @public - * jQuery public function to add one or multiple errors. - * - * @param {object} error Object of errors where the keys are the input names - * @example - * $('form#myForm').addError({ - * 'username': 'Invalid username, please choose another one.' - * }); - * - * @return {object} Modified DOM element - */ - $.fn.addError = function (error) { - - return _api.addError(this, error); - - }; - - // ================================================================================================================= - - /** - * @private - * API to handles "addValidation" and "removeValidation" on attribute "data-validation". - * Note: Contains fail-safe operations to unify the validation parameter. - * - * @example - * $.addValidation('NOTEMPTY, L>=6') - * $.addValidation('[notempty, v>=6]') - * $.removeValidation(['OPTIONAL', 'V>=6']) - * - * @returns {object} Updated DOM object - */ - var _api = { - - /** - * @private - * This function unifies the data through the validation process. - * String, Uppercase and spaceless. - * - * @param {string|array} validation - * - * @returns {string} - */ - _formatValidation: function (validation) { - - validation = validation.toString().replace(/\s/g, ''); - - if (validation.charAt(0) === "[" && validation.charAt(validation.length - 1) === "]") { - validation = validation.replace(/^\[|\]$/g, ''); - } - - return validation; - - }, - - /** - * @private - * Splits the validation into an array, Uppercase the rules if they are not comparisons - * - * @param {string|array} validation - * - * @returns {array} Formatted validation keys - */ - _splitValidation: function (validation) { - - var validationArray = this._formatValidation(validation).split(','), - oneValidation; - - for (var i = 0; i < validationArray.length; i++) { - oneValidation = validationArray[i]; - if (/^[a-z]+$/i.test(oneValidation)) { - validationArray[i] = oneValidation.toUpperCase(); - } - } - - return validationArray; - }, - - /** - * @private - * Joins the validation array to create the "data-validation" value - * - * @param {array} validation - * - * @returns {string} - */ - _joinValidation: function (validation) { - - return '[' + validation.join(', ') + ']'; - - }, - - /** - * API method to attach the submit event type on the specified node. - * Note: Clears the previous event regardless to avoid double submits or unwanted behaviors. - * - * @param {object} node jQuery object(s) - * @param {object} options To configure the Validation class. - * - * @returns {*} - */ - validate: function (node, options) { - - if (typeof node === "function") { - - if (!options.submit.settings.form) { - window.debug('$.validate - Undefined property "options.submit.settings.form" - Validation dropped.'); - return; - } - - node = $(options.submit.settings.form); - - if (!node[0]) { - window.debug('$.validate - Unable to find jQuery form element "options.submit.settings.form" - ' + options.submit.settings.form + ' - Validation dropped.'); - return; - } - - } else if (typeof node[0] === 'undefined') { - - window.debug('$("' + node['selector'] + '").validate() - Unable to find jQuery element - Validation dropped.'); - return; - - } - - return node.each(function () { - - window.Validation[node.selector] = new Validation(this, options); - - }); - - }, - - /** - * API method to handle the addition of "data-validation" arguments. - * Note: ONLY the predefined validation arguments are allowed to be added - * inside the "data-validation" attribute (see configuration). - * - * @param {object} node jQuery objects - * @param {string|array} validation arguments to add in the node(s) "data-validation" - * - * @returns {*} - */ - addValidation: function (node, validation) { - - var self = this; - - validation = self._splitValidation(validation); - - if (!validation) { - return false; - } - - return node.each( function () { - - var $this = $(this), - validationData = $this.attr(_data.validation), - validationArray = (validationData && validationData.length) ? self._splitValidation(validationData) : [], - oneValidation; - - for (var i = 0; i < validation.length; i++) { - - oneValidation = self._formatValidation(validation[i]); - - if ($.inArray(oneValidation, validationArray) === -1) { - validationArray.push(oneValidation); - } - } - - if (validationArray.length) { - $this.attr(_data.validation, self._joinValidation(validationArray)); - } - - }); - - }, - - /** - * API method to handle the removal of "data-validation" arguments. - * - * @param {object} node jQuery objects - * @param {string|array} validation arguments to remove in the node(s) "data-validation" - * - * @returns {*} - */ - removeValidation: function (node, validation) { - - var self = this; - - validation = self._splitValidation(validation); - if (!validation) { - return false; - } - - return node.each( function () { - - var $this = $(this), - validationData = $this.attr(_data.validation), - validationArray = (validationData && validationData.length) ? self._splitValidation(validationData) : [], - oneValidation, - validationIndex; - - if (!validationArray.length) { - $this.removeAttr(_data.validation); - return true; - } - - for (var i = 0; i < validation.length; i++) { - oneValidation = self._formatValidation(validation[i]); - validationIndex = $.inArray(oneValidation, validationArray); - if (validationIndex !== -1) { - validationArray.splice(validationIndex, 1); - } - - } - - if (!validationArray.length) { - $this.removeAttr(_data.validation); - return true; - } - - $this.attr(_data.validation, self._joinValidation(validationArray)); - - }); - - }, - - /** - * API method to manually trigger a form error. - * Note: The same form jQuery selector MUST be used to recuperate the Validation configuration. - * - * @example - * $('#form-signup_v3').addError(errorMessage) - * - * @param {object} node jQuery object - * @param {object} error Object of errors to add on the node - * - * @returns {*} - */ - addError: function (node, error) { - - if (!window.Validation[node.selector]) { - window.debug('$.addError - Invalid node selector - Make sure you are using the same one you initialize the Validation with.'); - return false; - } - - if (typeof error !== "object" || $.isEmptyObject(error)) { - window.debug('$.addError - Invalid error object.'); - return false; - } - - for (var inputName in error) { - - if (!error.hasOwnProperty(inputName)) { - continue; - } - - if (typeof error[inputName] === "string") { - window.Validation[node.selector].registerError(inputName, error[inputName]) - } else if (error[inputName] instanceof Array) { - - for (var i = 0; i < error[inputName].length; i++) { - window.Validation[node.selector].registerError(inputName, error[inputName][i]) - } - - } else { - window.debug('$.addError - Invalid error object property - Accepted format: {"inputName": "errorString"} or {"inputName": ["errorString", "errorString"]}.'); - continue; - } - - window.Validation[node.selector].displayOneError(inputName); - - } - - } - - }; - - /** - * Creates a fail-safe debugging system inside the console - */ - window.debug = function () { - if (this.console && this.console.debug) { - this.console.debug('DEBUG: ' + Array.prototype.slice.call(arguments)); - } else { - window.log('DEBUG: ' + Array.prototype.slice.call(arguments)); - } - }; - - String.prototype.capitalize = function() { - return this.charAt(0).toUpperCase() + this.slice(1); - } - -}(window, document, window.jQuery)); \ No newline at end of file diff --git a/jquery.validation-1.1.0.min.js b/jquery.validation-1.1.0.min.js deleted file mode 100644 index eb9a890..0000000 --- a/jquery.validation-1.1.0.min.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * jQuery Form Validation - * - * @author Tom Bertrand - * @version 1.1.0 (2014-05-26) - * - * @copyright - * Copyright (C) 2014 Tom Bertrand. - * - * @link - * http://www.runningcoder.org/jqueryvalidation/ - * - * @license - * Licensed under the MIT license. - */ -(function(window,document,$,undefined){window.Validation={};if(typeof Object.preventExtensions!=="function"){Object.preventExtensions=function(e){return e}}var _rules={NOTEMPTY:/./,NUMERIC:/^[0-9]+$/,MIXED:/^[\w\s-]+$/,NOSPACE:/^[^\s]+$/,TRIM:/^[^\s].*[^\s]$/,DATE:/^\d{4}-\d{2}-\d{2}(\s\d{2}:\d{2}(:\d{2})?)?$/,EMAIL:/^([^@]+?)@(([a-z0-9]-*)*[a-z0-9]+\.)+([a-z0-9]+)$/i,URL:/^(https?:\/\/)?((([a-z0-9]-*)*[a-z0-9]+\.?)*([a-z0-9]+))(\/[\w?=\.-]*)*$/,PHONE:/^(\()?\d{3}(\))?(-|\s)?\d{3}(-|\s)\d{4}$/,OPTIONAL:/^.*$/,COMPARISON:/^\s*([LV])\s*([<>]=?|==|!=)\s*([^<>=!]+?)\s*$/,LUHN:"_validateLuhn"};var _messages=Object.preventExtensions({"default":"$ contain error(s).",NOTEMPTY:"$ must not be empty.",NUMERIC:"$ must be numeric.",STRING:"$ must be a string.",NOSPACE:"$ must not contain spaces.",TRIM:"$ must not start or end with space character.",MIXED:"$ must be letters or numbers (no special characters).",DATE:"$ is not a valid with format YYYY-MM-DD.",EMAIL:"$ is not valid.",URL:"$ is not valid.",PHONE:"$ is not a valid phone number.","<":"$ must be less than % characters.","<=":"$ must be less or equal to % characters.",">":"$ must be greater than % characters.",">=":"$ must be greater or equal to % characters.","==":"$ must be equal to %","!=":"$ must be different than %"}),_extendedMessages=false;var _data={validation:"data-validation",validationMessage:"data-validation-message",regex:"data-validation-regex",regexMessage:"data-validation-regex-message",validationGroup:"data-validation-group",errorList:"data-error-list"};var _options={submit:{settings:{form:null,display:"inline",insertion:"append",allErrors:false,trigger:"click",button:"input[type='submit']",errorClass:"error",errorListClass:"error-list",inputContainer:null,clear:"focusin",scrollToError:false},callback:{onInit:null,onValidate:null,onError:null,onBeforeSubmit:null,onSubmit:null,onAfterSubmit:null}},dynamic:{settings:{trigger:null,delay:300},callback:{onSuccess:null,onError:null,onComplete:null}},messages:{}};var _supported={submit:{settings:{display:["inline","block"],insertion:["append","prepend"],allErrors:[true,false],clear:["focusin","keypress",false],trigger:["click","dblclick","focusout","hover","mousedown","mouseenter","mouseleave","mousemove","mouseout","mouseover","mouseup","toggle"],scrollToError:[true,false]}},dynamic:{settings:{trigger:["focusout","keydown","keypress","keyup"]}}};var Validation=function(node,options){function extendMessages(){if(!window.Validation.messages||_extendedMessages){return false}_messages=$.extend(_messages,window.Validation.messages);_extendedMessages=true}function extendOptions(){if(!(options instanceof Object)){options={}}var e=Object.preventExtensions($.extend(true,{},_options)),t=Object.preventExtensions($.extend(true,{},_messages));e.messages=$.extend(t,options.messages||{});for(var n in options){if(!options.hasOwnProperty(n)||!(options[n]instanceof Object)){continue}for(var r in options[n]){if(!options[n].hasOwnProperty(r)||!(options[n][r]instanceof Object)){continue}for(var i in options[n][r]){if(!options[n][r].hasOwnProperty(i)){continue}if(_supported[n]&&_supported[n][r]&&_supported[n][r][i]&&$.inArray(options[n][r][i],_supported[n][r][i])===-1){window.debug("Validation.extendOptions - Delete unsupported property - "+r+": {"+i.toString()+": "+options[n][r][i].toString()+"}");delete options[n][r][i]}}if(e[n]&&e[n][r]){e[n][r]=$.extend(Object.preventExtensions(e[n][r]),options[n][r])}}}if(e.dynamic.settings.trigger){if(e.dynamic.settings.trigger==="keypress"&&e.submit.settings.clear==="keypress"){e.dynamic.settings.trigger="keydown"}}options=e}function delegateDynamicValidation(){if(!options.dynamic.settings.trigger){return false}$.each($(node).find("["+_data.validation+"],["+_data.regex+"]"),function(e,t){$(t).unbind(options.dynamic.settings.trigger).on(options.dynamic.settings.trigger,function(e){if($(this).is(":disabled")){return false}var t=this;_typeWatch(function(){if(!validateInput(t)){displayOneError(t.name);_executeCallback(options.dynamic.callback.onError,[node,t,errors[t.name]])}else{_executeCallback(options.dynamic.callback.onSuccess,[node,t])}_executeCallback(options.dynamic.callback.onComplete,[node,t])},options.dynamic.settings.delay)})})}function delegateValidation(){_executeCallback(options.submit.callback.onInit,[node]);$(node).on("submit",false);$(node).find(options.submit.settings.button).unbind(options.submit.settings.trigger).on(options.submit.settings.trigger,function(e){e.preventDefault();resetErrors();_executeCallback(options.submit.callback.onValidate,[node]);if(!validateForm()){_executeCallback(options.submit.callback.onError,[node,errors]);displayErrors()}else{_executeCallback(options.submit.callback.onBeforeSubmit,[node]);options.submit.callback.onSubmit?_executeCallback(options.submit.callback.onSubmit,[node]):submitForm();_executeCallback(options.submit.callback.onAfterSubmit,[node])}return false})}function validateForm(){$.each($(node).find("["+_data.validation+"],["+_data.regex+"]"),function(e,t){if($(this).is(":disabled")){return false}validateInput(t)});return $.isEmptyObject(errors)}function validateInput(e){var t=$(e).attr("name");if(!t){window.debug("Validation.validateInput - Invalid {string} inputName on "+e.toString());return false}var n=_getInputValue(e),r=t.replace(/]$/,"").split(/]\[|[[\]]/g),i=r[r.length-1],s=$(e).attr(_data.validation),o=$(e).attr(_data.validationMessage),u=$(e).attr(_data.regex),a=$(e).attr(_data.regexMessage),f=false;if(s){s=_api._splitValidation(s)}if(!$.isEmptyObject(s)){if(n===""&&$.inArray("OPTIONAL",s)!==-1){return true}$.each(s,function(e,r){if(f===true){return true}try{validateRule(n,r)}catch(s){if(o||!options.submit.settings.allErrors){f=true}s[0]=o||s[0];registerError(t,s[0].replace("$",i).replace("%",s[1]))}})}if(u){var l=u.split("/");if(l.length>1){var c="";for(var h=0;h
        ",o,u;if(!errors.hasOwnProperty(e)){return false}t=$(node).find('[name="'+e+'"]');i=null;if(!t[0]){window.debug("Validation.displayOneError unable to find "+e);return false}o=t.attr(_data.validationGroup);if(o){u=$(node).find('[name="'+e+'"]');i=$(node).find('[id="'+o+'"]');if(i[0]){i.addClass(options.submit.settings.errorClass);r=i}}else{t.addClass(options.submit.settings.errorClass);if(options.submit.settings.inputContainer){t.parentsUntil(node,options.submit.settings.inputContainer).addClass(options.submit.settings.errorClass)}n=t.attr("id");if(n){i=$(node).find('label[for="'+n+'"]')[0]}if(!i){i=t.parentsUntil(node,"label")[0]}if(i){i=$(i);i.addClass(options.submit.settings.errorClass)}}if(options.submit.settings.display==="inline"){r=r||t.parent()}else if(options.submit.settings.display==="block"){r=$(node)}if(options.submit.settings.display==="inline"||options.submit.settings.display==="block"&&!r.find("["+_data.errorList+"]")[0]){if(options.submit.settings.insertion==="append"){r.append(s)}else if(options.submit.settings.insertion==="prepend"){r.prepend(s)}}for(var a=0;a"+errors[e][a]+"")}if(options.submit.settings.clear||options.dynamic.settings.trigger){if(o&&u){t=u}t.unbind(options.submit.settings.clear).on(options.submit.settings.clear+" "+options.dynamic.settings.trigger,function(e,t,n,r,i){return function(){if(i){if($(n).hasClass(options.submit.settings.errorClass)){resetOneError(e,t,n,r,i)}}else if($(t).hasClass(options.submit.settings.errorClass)){resetOneError(e,t,n,r)}}}(e,t,i,r,o))}if(options.submit.settings.scrollToError&&!hasScrolled){hasScrolled=true;if(typeof $.scrollTo!=="function"){window.debug("Missing jQuery.scrollTo, scrolling will not happen.");return false}$.scrollTo(options.submit.settings.display==="block"?r:t,500,{offset:-100})}}function displayErrors(){for(var e in errors){displayOneError(e)}}function resetOneError(e,t,n,r,i){try{delete errors[e];hasScrolled=false}catch(s){window.debug("Validation.resetOneError unable to find and delete "+e+" inside {object} errors.");return false}if(options.submit.settings.inputContainer){(i?n:t).parentsUntil(node,options.submit.settings.inputContainer).removeClass(options.submit.settings.errorClass)}n&&n.removeClass(options.submit.settings.errorClass);t.removeClass(options.submit.settings.errorClass);if(options.submit.settings.display==="inline"){r.find("["+_data.errorList+"]").remove()}}function resetErrors(){errors=[],hasScrolled=false;$(node).find("["+_data.errorList+"]").remove();$(node).find("."+options.submit.settings.errorClass).removeClass(options.submit.settings.errorClass)}function submitForm(){node.submit()}var errors=[],hasScrolled=false;var _typeWatch=function(){var e=0;return function(t,n){clearTimeout(e);e=setTimeout(t,n)}}();var _getInputValue=function(e){var t;switch($(e).attr("type")){case"checkbox":t=$(e).is(":checked")?1:"";break;case"radio":t=$(node).find('input[name="'+$(e).attr("name")+'"]:checked').val()||"";break;default:t=$(e).val();break}return t};var _executeCallback=function(e,t){if(!e){return false}var n;if(typeof e==="function"){n=e}else if(typeof e==="string"||e instanceof Array){n=window;if(typeof e==="string"){e=[e,[]]}var r=e[0].split("."),i=e[1],s=true,o=0;while(o0};this.__construct=function(){extendMessages();extendOptions();delegateDynamicValidation();delegateValidation()}();return{registerError:registerError,displayOneError:displayOneError,displayErrors:displayErrors}};$.fn.validate=$.validate=function(e){return _api.validate(this,e)};$.fn.addValidation=function(e){return _api.addValidation(this,e)};$.fn.removeValidation=function(e){return _api.removeValidation(this,e)};$.fn.addError=function(e){return _api.addError(this,e)};var _api={_formatValidation:function(e){e=e.toString().replace(/\s/g,"");if(e.charAt(0)==="["&&e.charAt(e.length-1)==="]"){e=e.replace(/^\[|\]$/g,"")}return e},_splitValidation:function(e){var t=this._formatValidation(e).split(","),n;for(var r=0;r]=?|==|!=)\s*([^<>=!]+?)\s*$/ + }; + + /** + * @private + * Error messages + */ + var _messages = { + 'default': '$ contain error(s).', + 'NOTEMPTY': '$ must not be empty.', + 'INTEGER': '$ must be an integer.', + 'NUMERIC': '$ must be numeric.', + 'MIXED': '$ must be letters or numbers (no special characters).', + 'NAME': '$ must not contain special characters.', + 'NOSPACE': '$ must not contain spaces.', + 'TRIM': '$ must not start or end with space character.', + 'DATE': '$ is not a valid with format YYYY-MM-DD.', + 'EMAIL': '$ is not valid.', + 'URL': '$ is not valid.', + 'PHONE': '$ is not a valid phone number.', + '<': '$ must be less than % characters.', + '<=': '$ must be less or equal to % characters.', + '>': '$ must be greater than % characters.', + '>=': '$ must be greater or equal to % characters.', + '==': '$ must be equal to %', + '!=': '$ must be different than %' + }; + + /** + * @private + * HTML5 data attributes + */ + var _data = { + validation: 'data-validation', + validationMessage: 'data-validation-message', + regex: 'data-validation-regex', + regexReverse: 'data-validation-regex-reverse', + regexMessage: 'data-validation-regex-message', + group: 'data-validation-group', + label: 'data-validation-label', + errorList: 'data-error-list' + } + + /** + * @private + * Default options + * + * @link http://www.runningcoder.org/jqueryvalidation/documentation/ + */ + var _options = { + submit: { + settings: { + form: null, + display: "inline", + insertion: "append", + allErrors: false, + trigger: "click", + button: "[type='submit']", + errorClass: "error", + errorListClass: "error-list", + errorListContainer: null, + errorTemplate: null, + inputContainer: null, + clear: "focusin", + scrollToError: false + }, + callback: { + onInit: null, + onValidate: null, + onError: null, + onBeforeSubmit: null, + onSubmit: null, + onAfterSubmit: null + } + }, + dynamic: { + settings: { + trigger: null, + delay: 300 + }, + callback: { + onSuccess: null, + onError: null, + onComplete: null + } + }, + rules: {}, + messages: {}, + labels: {}, + debug: false + }; + + /** + * @private + * Limit the supported options on matching keys + */ + var _supported = { + submit: { + settings: { + display: ["inline", "block"], + insertion: ["append", "prepend"], //"before", "insertBefore", "after", "insertAfter" + allErrors: [true, false], + clear: ["focusin", "keypress", false], + trigger: [ + "click", "dblclick", "focusout", + "hover", "mousedown", "mouseenter", + "mouseleave", "mousemove", "mouseout", + "mouseover", "mouseup", "toggle" + ] + } + }, + dynamic: { + settings: { + trigger: ["focusout", "keydown", "keypress", "keyup"] + } + }, + debug: [true, false] + }; + + // ================================================================================================================= + + /** + * @constructor + * Validation Class + * + * @param {Object} node jQuery form object + * @param {Object} options User defined options + */ + var Validation = function (node, options) { + + var errors = [], + messages = {}, + formData = {}, + delegateSuffix = ".vd", // validation.delegate + resetSuffix = ".vr"; // validation.resetError + + window.Validation.hasScrolled = false; + + /** + * Extends user-defined "options.message" into the default Validation "_message". + */ + function extendRules() { + options.rules = $.extend( + true, + {}, + _rules, + options.rules + ); + } + + /** + * Extends user-defined "options.message" into the default Validation "_message". + */ + function extendMessages() { + options.messages = $.extend( + true, + {}, + _messages, + options.messages + ); + } + + /** + * Extends user-defined "options" into the default Validation "_options". + * Notes: + * - preventExtensions prevents from modifying the Validation "_options" object structure + * - filter through the "_supported" to delete unsupported "options" + */ + function extendOptions() { + + if (!(options instanceof Object)) { + options = {}; + } + + var tpmOptions = Object.preventExtensions($.extend(true, {}, _options)); + + for (var method in options) { + + if (!options.hasOwnProperty(method) || method === "debug") { + continue; + } + + if (~["labels", "messages", "rules"].indexOf(method) && options[method] instanceof Object) { + tpmOptions[method] = options[method]; + continue; + } + + if (!_options[method] || !(options[method] instanceof Object)) { + + // {debug} + options.debug && window.Debug.log({ + 'node': node, + 'function': 'extendOptions()', + 'arguments': '{' + method + ': ' + JSON.stringify(options[method]) + '}', + 'message': 'WARNING - ' + method + ' - invalid option' + }); + // {/debug} + + continue; + } + + for (var type in options[method]) { + if (!options[method].hasOwnProperty(type)) { + continue; + } + + if (!_options[method][type] || !(options[method][type] instanceof Object)) { + + // {debug} + options.debug && window.Debug.log({ + 'node': node, + 'function': 'extendOptions()', + 'arguments': '{' + type + ': ' + JSON.stringify(options[method][type]) + '}', + 'message': 'WARNING - ' + type + ' - invalid option' + }); + // {/debug} + + continue; + } + + for (var option in options[method][type]) { + if (!options[method][type].hasOwnProperty(option)) { + continue; + } + + if (_supported[method] && + _supported[method][type] && + _supported[method][type][option] && + $.inArray(options[method][type][option], _supported[method][type][option]) === -1) { + + // {debug} + options.debug && window.Debug.log({ + 'node': node, + 'function': 'extendOptions()', + 'arguments': '{' + option + ': ' + JSON.stringify(options[method][type][option]) + '}', + 'message': 'WARNING - ' + option.toString() + ': ' + JSON.stringify(options[method][type][option]) + ' - unsupported option' + }); + // {/debug} + + delete options[method][type][option]; + } + + } + if (tpmOptions[method] && tpmOptions[method][type]) { + tpmOptions[method][type] = $.extend(Object.preventExtensions(tpmOptions[method][type]), options[method][type]); + } + } + } + + // {debug} + if (options.debug && $.inArray(options.debug, _supported.debug !== -1)) { + tpmOptions.debug = options.debug; + } + // {/debug} + + // @TODO Would there be a better fix to solve event conflict? + if (tpmOptions.dynamic.settings.trigger) { + if (tpmOptions.dynamic.settings.trigger === "keypress" && tpmOptions.submit.settings.clear === "keypress") { + tpmOptions.dynamic.settings.trigger = "keydown"; + } + } + + options = tpmOptions; + + } + + /** + * Delegates the dynamic validation on data-validation and data-validation-regex attributes based on trigger. + * + * @returns {Boolean} false if the option is not set + */ + function delegateDynamicValidation() { + + if (!options.dynamic.settings.trigger) { + return false; + } + + // {debug} + options.debug && window.Debug.log({ + 'node': node, + 'function': 'delegateDynamicValidation()', + 'message': 'OK - Dynamic Validation activated on ' + node.length + ' form(s)' + }); + // {/debug} + + if (!node.find('[' + _data.validation + '],[' + _data.regex + ']')[0]) { + + // {debug} + options.debug && window.Debug.log({ + 'node': node, + 'function': 'delegateDynamicValidation()', + 'arguments': 'node.find([' + _data.validation + '],[' + _data.regex + '])', + 'message': 'ERROR - [' + _data.validation + '] not found' + }); + // {/debug} + + return false; + } + + var event = options.dynamic.settings.trigger + delegateSuffix; + if (options.dynamic.settings.trigger !== "focusout") { + event += " change" + delegateSuffix + " paste" + delegateSuffix; + } + + $.each( + node.find('[' + _data.validation + '],[' + _data.regex + ']'), + function (index, input) { + + $(input).unbind(event).on(event, function (e) { + + if ($(this).is(':disabled')) { + return false; + } + + //e.preventDefault(); + + var input = this, + keyCode = e.keyCode || null; + + _typeWatch(function () { + + if (!validateInput(input)) { + + displayOneError(input.name); + _executeCallback(options.dynamic.callback.onError, [node, input, keyCode, errors[input.name]]); + + } else { + + _executeCallback(options.dynamic.callback.onSuccess, [node, input, keyCode]); + + } + + _executeCallback(options.dynamic.callback.onComplete, [node, input, keyCode]); + + }, options.dynamic.settings.delay); + + }); + } + ); + } + + /** + * Delegates the submit validation on data-validation and data-validation-regex attributes based on trigger. + * Note: Disable the form submit function so the callbacks are not by-passed + */ + function delegateValidation() { + + _executeCallback(options.submit.callback.onInit, [node]); + + var event = options.submit.settings.trigger + '.vd'; + + // {debug} + options.debug && window.Debug.log({ + 'node': node, + 'function': 'delegateValidation()', + 'message': 'OK - Validation activated on ' + node.length + ' form(s)' + }); + // {/debug} + + if (!node.find(options.submit.settings.button)[0]) { + + // {debug} + options.debug && window.Debug.log({ + 'node': node, + 'function': 'delegateValidation()', + 'arguments': '{button: ' + options.submit.settings.button + '}', + 'message': 'ERROR - node.find("' + options.submit.settings.button + '") not found' + }); + // {/debug} + + return false; + + } + + node.on("submit", false); + node.find(options.submit.settings.button).off('.vd').on(event, function (e) { + + e.preventDefault(); + + resetErrors(); + + _executeCallback(options.submit.callback.onValidate, [node]); + + if (!validateForm()) { + + displayErrors(); + _executeCallback(options.submit.callback.onError, [node, errors, formData]); + + } else { + + _executeCallback(options.submit.callback.onBeforeSubmit, [node]); + + if (typeof options.submit.callback.onSubmit === "function") { + if (_executeCallback(options.submit.callback.onSubmit, [node, formData]) === true) { + submitForm(); + } + } else { + submitForm(); + } + + _executeCallback(options.submit.callback.onAfterSubmit, [node]); + + } + + // {debug} + options.debug && window.Debug.print(); + // {/debug} + + return false; + + }); + + } + + /** + * For every "data-validation" & "data-pattern" attributes that are not disabled inside the jQuery "node" object + * the "validateInput" function will be called. + * + * @returns {Boolean} true if no error(s) were found (valid form) + */ + function validateForm() { + + var isValid = isEmpty(errors); + + formData = {}; + + $.each( + node.find('input:not([type="submit"]), select, textarea').not(':disabled'), + function(index, input) { + + input = $(input); + + var value = _getInputValue(input[0]), + inputName = input.attr('name'); + + if (inputName) { + if (/\[]$/.test(inputName)) { + inputName = inputName.replace(/\[]$/, ''); + if (!(formData[inputName] instanceof Array)) { + formData[inputName] = []; + } + formData[inputName].push(value) + } else { + formData[inputName] = value; + } + } + + if (!!input.attr(_data.validation) || !!input.attr(_data.regex)) { + if (!validateInput(input[0], value)) { + isValid = false; + } + } + } + ); + + prepareFormData(); + + return isValid; + + } + + /** + * Loop through formData and build an object + * + * @returns {Object} data + */ + function prepareFormData() { + + var data = {}, + matches, + index; + + for (var i in formData) { + if (!formData.hasOwnProperty(i)) continue; + + index = 0; + matches = i.split(/\[(.+?)]/g); + + var tmpObject = {}, + tmpArray = []; + + for (var k = matches.length - 1; k >= 0; k--) { + if (matches[k] === "") { + matches.splice(k, 1); + continue; + } + + if (tmpArray.length < 1) { + tmpObject[matches[k]] = Number(formData[i]) || formData[i]; + } else { + tmpObject = {}; + tmpObject[matches[k]] = tmpArray[tmpArray.length - 1]; + } + tmpArray.push(tmpObject) + + } + + data = $.extend(true, data, tmpObject); + + } + + formData = data; + + } + + /** + * Prepare the information from the data attributes + * and call the "validateRule" function. + * + * @param {Object} input Reference of the input element + * + * @returns {Boolean} true if no error(s) were found (valid input) + */ + function validateInput(input, value) { + + var inputName = $(input).attr('name'), + value = value || _getInputValue(input); + + if (!inputName) { + + // {debug} + options.debug && window.Debug.log({ + 'node': node, + 'function': 'validateInput()', + 'arguments': '$(input).attr("name")', + 'message': 'ERROR - Missing input [name]' + }); + // {/debug} + + return false; + } + + var matches = inputName.replace(/]$/, '').split(/]\[|[[\]]/g), + inputShortName = window.Validation.labels[inputName] || + options.labels[inputName] || + $(input).attr(_data.label) || + matches[matches.length - 1], + + validationArray = $(input).attr(_data.validation), + validationMessage = $(input).attr(_data.validationMessage), + validationRegex = $(input).attr(_data.regex), + validationRegexReverse = !($(input).attr(_data.regexReverse) === undefined), + validationRegexMessage = $(input).attr(_data.regexMessage), + + validateOnce = false; + + if (validationArray) { + validationArray = _api._splitValidation(validationArray); + } + + // Validates the "data-validation" + if (validationArray instanceof Array && validationArray.length > 0) { + + // "OPTIONAL" input will not be validated if it's empty + if ($.trim(value) === '' && ~validationArray.indexOf('OPTIONAL')) { + return true; + } + + $.each(validationArray, function (i, rule) { + + if (validateOnce === true) { + return true; + } + + try { + + validateRule(value, rule); + + } catch (error) { + + if (validationMessage || !options.submit.settings.allErrors) { + validateOnce = true; + } + + error[0] = validationMessage || error[0]; + + registerError(inputName, error[0].replace('$', inputShortName).replace('%', error[1])); + + } + + }); + + } + + // Validates the "data-validation-regex" + if (validationRegex) { + + var rule = _buildRegexFromString(validationRegex); + + // Do not block validation if a regexp is bad, only skip it + if (!(rule instanceof RegExp)) { + return true; + } + + try { + + validateRule(value, rule, validationRegexReverse); + + } catch (error) { + + error[0] = validationRegexMessage || error[0]; + + registerError(inputName, error[0].replace('$', inputShortName)); + + } + + } + + return !errors[inputName] || errors[inputName] instanceof Array && errors[inputName].length === 0; + + } + + /** + * Validate an input value against one rule. + * If a "value-rule" mismatch occurs, an error is thrown to the caller function. + * + * @param {String} value + * @param {*} rule + * @param {Boolean} [reversed] + * + * @returns {*} Error if a mismatch occurred. + */ + function validateRule(value, rule, reversed) { + + // Validate for "data-validation-regex" and "data-validation-regex-reverse" + if (rule instanceof RegExp) { + var isValid = rule.test(value); + + if (reversed) { + isValid = !isValid; + } + + if (!isValid) { + throw [options.messages['default'], '']; + } + return; + } + + if (options.rules[rule]) { + if (!options.rules[rule].test(value)) { + throw [options.messages[rule], '']; + } + return; + } + + // Validate for comparison "data-validation" + var comparison = rule.match(options.rules.COMPARISON); + + if (!comparison || comparison.length !== 4) { + + // {debug} + options.debug && window.Debug.log({ + 'node': node, + 'function': 'validateRule()', + 'arguments': 'value: ' + value + ' rule: ' + rule, + 'message': 'WARNING - Invalid comparison' + }); + // {/debug} + + return; + } + + var type = comparison[1], + operator = comparison[2], + compared = comparison[3], + comparedValue; + + switch (type) { + + // Compare input "Length" + case "L": + + // Only numeric value for "L" are allowed + if (isNaN(compared)) { + + // {debug} + options.debug && window.Debug.log({ + 'node': node, + 'function': 'validateRule()', + 'arguments': 'compare: ' + compared + ' rule: ' + rule, + 'message': 'WARNING - Invalid rule, "L" compare must be numeric' + }); + // {/debug} + + return false; + + } else { + + if (!value || eval(value.length + operator + parseFloat(compared)) === false) { + throw [options.messages[operator], compared]; + } + + } + + break; + + // Compare input "Value" + case "V": + default: + + // Compare Field values + if (isNaN(compared)) { + + comparedValue = node.find('[name="' + compared + '"]').val(); + if (!comparedValue) { + + // {debug} + options.debug && window.Debug.log({ + 'node': node, + 'function': 'validateRule()', + 'arguments': 'compare: ' + compared + ' rule: ' + rule, + 'message': 'WARNING - Unable to find compared field [name="' + compared + '"]' + }); + // {/debug} + + return false; + } + + if (!value || !eval('"' + encodeURIComponent(value) + '"' + operator + '"' + encodeURIComponent(comparedValue) + '"')) { + throw [options.messages[operator].replace(' characters', ''), compared]; + } + + } else { + // Compare numeric value + if (!value || isNaN(value) || !eval(value + operator + parseFloat(compared))) { + throw [options.messages[operator].replace(' characters', ''), compared]; + } + + } + break; + + } + + } + + /** + * Register an error into the global "error" variable. + * + * @param {String} inputName Input where the error occurred + * @param {String} error Description of the error to be displayed + */ + function registerError(inputName, error) { + + if (!errors[inputName]) { + errors[inputName] = []; + } + + error = error.capitalize(); + + var hasError = false; + for (var i = 0; i < errors[inputName].length; i++) { + if (errors[inputName][i] === error) { + hasError = true; + break; + } + } + + if (!hasError) { + errors[inputName].push(error); + } + + } + + /** + * Display a single error based on "inputName" key inside the "errors" global array. + * The input, the label and the "inputContainer" will be given the "errorClass" and other + * settings will be considered. + * + * @param {String} inputName Key used for search into "errors" + * + * @returns {Boolean} false if an unwanted behavior occurs + */ + function displayOneError(inputName) { + + var input, + inputId, + errorContainer, + label, + html = '
          ', + group, + groupInput; + + if (!errors.hasOwnProperty(inputName)) { + return false; + } + + input = node.find('[name="' + inputName + '"]'); + + label = null; + + if (!input[0]) { + + // {debug} + options.debug && window.Debug.log({ + 'node': node, + 'function': 'displayOneError()', + 'arguments': '[name="' + inputName + '"]', + 'message': 'ERROR - Unable to find input by name "' + inputName + '"' + }); + // {/debug} + + return false; + } + + group = input.attr(_data.group); + + if (group) { + + groupInput = node.find('[name="' + inputName + '"]'); + label = node.find('[id="' + group + '"]'); + + if (label[0]) { + label.addClass(options.submit.settings.errorClass); + errorContainer = label; + } + + //node.find('[' + _data.group + '="' + group + '"]').addClass(options.submit.settings.errorClass) + + } else { + + input.addClass(options.submit.settings.errorClass); + + if (options.submit.settings.inputContainer) { + input.parentsUntil(node, options.submit.settings.inputContainer).addClass(options.submit.settings.errorClass); + } + + inputId = input.attr('id'); + + if (inputId) { + label = node.find('label[for="' + inputId + '"]')[0]; + } + + if (!label) { + label = input.parentsUntil(node, 'label')[0]; + } + + if (label) { + label = $(label); + label.addClass(options.submit.settings.errorClass); + } + } + + if (options.submit.settings.display === 'inline') { + if (options.submit.settings.errorListContainer) { + errorContainer = input.parentsUntil(node, options.submit.settings.errorListContainer); + } else { + errorContainer = errorContainer || input.parent(); + } + } else if (options.submit.settings.display === 'block') { + errorContainer = node; + } + + // Prevent double error list if the previous one has not been cleared. + if (options.submit.settings.display === 'inline' && errorContainer.find('[' + _data.errorList + ']')[0]) { + return false; + } + + if (options.submit.settings.display === "inline" || + (options.submit.settings.display === "block" && !errorContainer.find('[' + _data.errorList + ']')[0]) + ) { + if (options.submit.settings.insertion === 'append') { + errorContainer.append(html); + } else if (options.submit.settings.insertion === 'prepend') { + errorContainer.prepend(html); + } + } + + for (var i = 0; i < errors[inputName].length; i++) { + if (options.submit.settings.errorTemplate) { + errorContainer.find('[' + _data.errorList + '] ul') + .append('
        • ' + options.submit.settings.errorTemplate.replace('{{validation-message}}', errors[inputName][i]) + '
        • '); + } else { + errorContainer.find('[' + _data.errorList + '] ul').append('
        • ' + errors[inputName][i] + '
        • '); + } + } + + if (options.submit.settings.clear || options.dynamic.settings.trigger) { + + if (group && groupInput) { + input = groupInput; + } + + var event = "coucou" + resetSuffix; + if (options.submit.settings.clear) { + event += " " + options.submit.settings.clear + resetSuffix; + if (~['radio', 'checkbox'].indexOf(input[0].type)) { + event += " change" + resetSuffix; + } + } + if (options.dynamic.settings.trigger) { + event += " " + options.dynamic.settings.trigger + resetSuffix; + if (options.dynamic.settings.trigger !== "focusout" && !~['radio', 'checkbox'].indexOf(input[0].type)) { + event += " change" + resetSuffix + " paste" + resetSuffix; + } + } + + input.unbind(event).on(event, function (a, b, c, d, e) { + + return function () { + if (e) { + if ($(c).hasClass(options.submit.settings.errorClass)) { + resetOneError(a, b, c, d, e); + } + } else if ($(b).hasClass(options.submit.settings.errorClass)) { + resetOneError(a, b, c, d); + } + }; + + }(inputName, input, label, errorContainer, group)); + } + + if (options.submit.settings.scrollToError && !window.Validation.hasScrolled) { + + window.Validation.hasScrolled = true; + + var offset = parseFloat(options.submit.settings.scrollToError.offset) || 0, + duration = parseFloat(options.submit.settings.scrollToError.duration) || 500, + handle = (options.submit.settings.display === 'block') ? errorContainer : input; + + $('html, body').animate({ + scrollTop: handle.offset().top + offset + }, duration); + + } + + } + + /** + * Display all of the errors + */ + function displayErrors() { + + for (var inputName in errors) { + if (!errors.hasOwnProperty(inputName)) continue; + displayOneError(inputName); + } + + } + + /** + * Remove an input error. + * + * @param {String} inputName Key reference to delete the error from "errors" global variable + * @param {Object} input jQuery object of the input + * @param {Object} label jQuery object of the input's label + * @param {Object} container jQuery object of the "errorList" + * @param {String} [group] Name of the group if any (ex: used on input radio) + */ + function resetOneError(inputName, input, label, container, group) { + + delete errors[inputName]; + + if (container) { + + //window.Validation.hasScrolled = false; + + if (options.submit.settings.inputContainer) { + (group ? label : input).parentsUntil(node, options.submit.settings.inputContainer).removeClass(options.submit.settings.errorClass); + } + + label && label.removeClass(options.submit.settings.errorClass); + + input.removeClass(options.submit.settings.errorClass); + + if (options.submit.settings.display === 'inline') { + container.find('[' + _data.errorList + ']').remove(); + } + + } else { + + if (!input) { + input = node.find('[name="' + inputName + '"]'); + + if (!input[0]) { + + // {debug} + options.debug && window.Debug.log({ + 'node': node, + 'function': 'resetOneError()', + 'arguments': '[name="' + inputName + '"]', + 'message': 'ERROR - Unable to find input by name "' + inputName + '"' + }); + // {/debug} + + return false; + } + } + + input.trigger('coucou' + resetSuffix); + } + + } + + /** + * Remove all of the input error(s) display. + */ + function resetErrors() { + + errors = []; + window.Validation.hasScrolled = false; + + node.find('[' + _data.errorList + ']').remove(); + node.find('.' + options.submit.settings.errorClass).removeClass(options.submit.settings.errorClass); + + } + + /** + * Submits the form once it succeeded the validation process. + * Note: + * - This function will be overridden if "options.submit.settings.onSubmit" is defined + * - The node can't be submitted by jQuery since it has been disabled, use the form native submit function instead + */ + function submitForm() { + + node[0].submit() + + } + + /** + * Destroy the Validation instance + * + * @returns {Boolean} + */ + function destroy() { + + resetErrors(); + node.find('[' + _data.validation + '],[' + _data.regex + ']').off(delegateSuffix + ' ' + resetSuffix); + + node.find(options.submit.settings.button).off(delegateSuffix).on('click' + delegateSuffix, function () { + $(this).closest('form')[0].submit(); + }); + + //delete window.Validation.form[node.selector]; + + return true; + + } + + /** + * @private + * Helper to get the value of an regular, radio or chackbox input + * + * @param input + * + * @returns {String} value + */ + var _getInputValue = function (input) { + + var value; + + // Get the value or state of the input based on its type + switch ($(input).attr('type')) { + case 'checkbox': + value = ($(input).is(':checked')) ? 1 : ''; + break; + case 'radio': + value = node.find('input[name="' + $(input).attr('name') + '"]:checked').val() || ''; + break; + default: + value = $(input).val(); + break; + } + + return value; + + }; + + /** + * @private + * Execute function once the timer is reached. + * If the function is recalled before the timer ends, the first call will be canceled. + */ + var _typeWatch = (function () { + var timer = 0; + return function (callback, ms) { + clearTimeout(timer); + timer = setTimeout(callback, ms); + }; + })(); + + /** + * @private + * Executes an anonymous function or a string reached from the window scope. + * + * @example + * Note: These examples works with every callbacks (onInit, onError, onSubmit, onBeforeSubmit & onAfterSubmit) + * + * // An anonymous function inside the "onInit" option + * onInit: function() { console.log(':D'); }; + * + * * // myFunction() located on window.coucou scope + * onInit: 'window.coucou.myFunction' + * + * // myFunction(a,b) located on window.coucou scope passing 2 parameters + * onInit: ['window.coucou.myFunction', [':D', ':)']]; + * + * // Anonymous function to execute a local function + * onInit: function () { myFunction(':D'); } + * + * @param {String|Array} callback The function to be called + * @param {Array} [extraParams] In some cases the function can be called with Extra parameters (onError) + * + * @returns {Boolean} + */ + var _executeCallback = function (callback, extraParams) { + + if (!callback) { + return false; + } + + var _callback; + + if (typeof callback === "function") { + + _callback = callback; + + } else if (typeof callback === "string" || callback instanceof Array) { + + _callback = window; + + if (typeof callback === "string") { + callback = [callback, []]; + } + + var _exploded = callback[0].split('.'), + _params = callback[1], + _isValid = true, + _splitIndex = 0; + + while (_splitIndex < _exploded.length) { + + if (typeof _callback !== 'undefined') { + _callback = _callback[_exploded[_splitIndex++]]; + } else { + _isValid = false; + break; + } + } + + if (!_isValid || typeof _callback !== "function") { + + // {debug} + options.debug && window.Debug.log({ + 'node': node, + 'function': '_executeCallback()', + 'arguments': JSON.stringify(callback), + 'message': 'WARNING - Invalid callback function"' + }); + // {/debug} + + return false; + } + + } + + return _callback.apply(this, $.merge(_params || [], (extraParams) ? extraParams : [])); + + }; + + /** + * @private + * Constructs Validation + */ + this.__construct = function () { + + extendOptions(); + extendRules(); + extendMessages(); + + delegateDynamicValidation(); + delegateValidation(); + + // {debug} + options.debug && window.Debug.print(); + // {/debug} + + }(); + + return { + + /** + * @public + * Register error + * + * @param inputName + * @param error + */ + registerError: registerError, + + /** + * @public + * Display one error + * + * @param inputName + */ + displayOneError: displayOneError, + + /** + * @public + * Display all errors + */ + displayErrors: displayErrors, + + /** + * @public + * Remove one error + */ + resetOneError: resetOneError, + + /** + * @public + * Remove all errors + */ + resetErrors: resetErrors, + + /** + * @public + * Destroy the Validation instance + */ + destroy: destroy + + }; + + }; + + // ================================================================================================================= + + /** + * @public + * jQuery public function to implement the Validation on the selected node(s). + * + * @param {object} options To configure the Validation class. + * + * @return {object} Modified DOM element + */ + $.fn.validate = $.validate = function (options) { + + return _api.validate(this, options); + + }; + + /** + * @public + * jQuery public function to add one or multiple "data-validation" argument. + * + * @param {string|array} validation Arguments to add in the node's data-validation + * + * @return {object} Modified DOM element + */ + $.fn.addValidation = function (validation) { + + return _api.addValidation(this, validation); + + }; + + /** + * @public + * jQuery public function to remove one or multiple "data-validation" argument. + * + * @param {string|array} validation Arguments to remove in the node's data-validation + * + * @return {object} Modified DOM element + */ + $.fn.removeValidation = function (validation) { + + return _api.removeValidation(this, validation); + + }; + + /** + * @public + * jQuery public function to add one or multiple errors. + * + * @param {object} error Object of errors where the keys are the input names + * @example + * $('form#myForm').addError({ + * 'username': 'Invalid username, please choose another one.' + * }); + * + * @return {object} Modified DOM element + */ + $.fn.addError = function (error) { + + return _api.addError(this, error); + + }; + + /** + * @public + * jQuery public function to remove one or multiple errors. + * + * @param {array} error Array of errors where the keys are the input names + * @example + * $('form#myForm').removeError([ + * 'username' + * ]); + * + * @return {object} Modified DOM element + */ + $.fn.removeError = function (error) { + + return _api.removeError(this, error); + + }; + + /** + * @public + * jQuery public function to add a validation rule. + * + * @example + * $.alterValidationRules({ + * rule: 'FILENAME', + * regex: /^[^\\/:\*\?<>\|\"\']*$/, + * message: '$ has an invalid filename.' + * }) + * + * @param {Object|Array} name + */ + $.fn.alterValidationRules = $.alterValidationRules = function (rules) { + + if (!(rules instanceof Array)) { + rules = [rules]; + } + + for (var i = 0; i < rules.length; i++) { + _api.alterValidationRules(rules[i]); + } + + }; + + // ================================================================================================================= + + /** + * @private + * API to handles "addValidation" and "removeValidation" on attribute "data-validation". + * Note: Contains fail-safe operations to unify the validation parameter. + * + * @example + * $.addValidation('NOTEMPTY, L>=6') + * $.addValidation('[notempty, v>=6]') + * $.removeValidation(['OPTIONAL', 'V>=6']) + * + * @returns {object} Updated DOM object + */ + var _api = { + + /** + * @private + * This function unifies the data through the validation process. + * String, Uppercase and spaceless. + * + * @param {string|array} validation + * + * @returns {string} + */ + _formatValidation: function (validation) { + + validation = validation.toString().replace(/\s/g, ''); + + if (validation.charAt(0) === "[" && validation.charAt(validation.length - 1) === "]") { + validation = validation.replace(/^\[|]$/g, ''); + } + + return validation; + + }, + + /** + * @private + * Splits the validation into an array, Uppercase the rules if they are not comparisons + * + * @param {String|Array} validation + * + * @returns {Array} Formatted validation keys + */ + _splitValidation: function (validation) { + + var validationArray = this._formatValidation(validation).split(','), + oneValidation; + + for (var i = 0; i < validationArray.length; i++) { + oneValidation = validationArray[i]; + if (/^[a-z]+$/i.test(oneValidation)) { + validationArray[i] = oneValidation.toUpperCase(); + } + } + + return validationArray; + }, + + /** + * @private + * Joins the validation array to create the "data-validation" value + * + * @param {Array} validation + * + * @returns {String} + */ + _joinValidation: function (validation) { + + return '[' + validation.join(', ') + ']'; + + }, + + /** + * API method to attach the submit event type on the specified node. + * Note: Clears the previous event regardless to avoid double submits or unwanted behaviors. + * + * @param {Object} node jQuery object(s) + * @param {Object} options To configure the Validation class. + * + * @returns {*} + */ + validate: function (node, options) { + + if (typeof node === "function") { + + if (!options.submit.settings.form) { + + // {debug} + window.Debug.log({ + 'node': node, + 'function': '$.validate()', + 'arguments': '', + 'message': 'Undefined property "options.submit.settings.form - Validation dropped' + }); + + window.Debug.print(); + // {/debug} + + return; + } + + node = $(options.submit.settings.form); + + if (!node[0] || node[0].nodeName.toLowerCase() !== "form") { + + // {debug} + window.Debug.log({ + 'function': '$.validate()', + 'arguments': options.submit.settings.form, + 'message': 'Unable to find jQuery form element - Validation dropped' + }); + + window.Debug.print(); + // {/debug} + + return; + } + + } else if (typeof node[0] === 'undefined') { + + // {debug} + window.Debug.log({ + 'node': node, + 'function': '$.validate()', + 'arguments': '$("' + node.selector + '").validate()', + 'message': 'Unable to find jQuery form element - Validation dropped' + }); + + window.Debug.print(); + // {/debug} + + return; + } + + if (options === "destroy") { + + if (!window.Validation.form[node.selector]) { + + // {debug} + window.Debug.log({ + 'node': node, + 'function': '$.validate("destroy")', + 'arguments': '', + 'message': 'Unable to destroy "' + node.selector + '", perhaps it\'s already destroyed?' + }); + + window.Debug.print(); + // {/debug} + + return; + } + + window.Validation.form[node.selector].destroy(); + + return; + + } + + return node.each(function () { + window.Validation.form[node.selector] = new Validation($(this), options); + }); + + }, + + /** + * API method to handle the addition of "data-validation" arguments. + * Note: ONLY the predefined validation arguments are allowed to be added + * inside the "data-validation" attribute (see configuration). + * + * @param {Object} node jQuery objects + * @param {String|Array} validation arguments to add in the node(s) "data-validation" + * + * @returns {*} + */ + addValidation: function (node, validation) { + + var self = this; + + validation = self._splitValidation(validation); + + if (!validation) { + return false; + } + + return node.each(function () { + + var $this = $(this), + validationData = $this.attr(_data.validation), + validationArray = (validationData && validationData.length) ? self._splitValidation(validationData) : [], + oneValidation; + + for (var i = 0; i < validation.length; i++) { + + oneValidation = self._formatValidation(validation[i]); + + if ($.inArray(oneValidation, validationArray) === -1) { + validationArray.push(oneValidation); + } + } + + if (validationArray.length) { + $this.attr(_data.validation, self._joinValidation(validationArray)); + } + + }); + + }, + + /** + * API method to handle the removal of "data-validation" arguments. + * + * @param {Object} node jQuery objects + * @param {String|Array} validation arguments to remove in the node(s) "data-validation" + * + * @returns {*} + */ + removeValidation: function (node, validation) { + + var self = this; + + validation = self._splitValidation(validation); + if (!validation) { + return false; + } + + return node.each(function () { + + var $this = $(this), + validationData = $this.attr(_data.validation), + validationArray = (validationData && validationData.length) ? self._splitValidation(validationData) : [], + oneValidation, + validationIndex; + + if (!validationArray.length) { + $this.removeAttr(_data.validation); + return true; + } + + for (var i = 0; i < validation.length; i++) { + oneValidation = self._formatValidation(validation[i]); + validationIndex = $.inArray(oneValidation, validationArray); + if (validationIndex !== -1) { + validationArray.splice(validationIndex, 1); + } + + } + + if (!validationArray.length) { + $this.removeAttr(_data.validation); + return true; + } + + $this.attr(_data.validation, self._joinValidation(validationArray)); + + }); + + }, + + /** + * API method to manually trigger a form error. + * Note: The same form jQuery selector MUST be used to recuperate the Validation configuration. + * + * @example + * $('#form-signup_v3').addError({ + * 'inputName': 'my error message', + * 'inputName2': [ + * 'first error message', + * 'second error message' + * ] + * }) + * + * @param {Object} node jQuery object + * @param {Object} error Object of errors to add on the node + * + * @returns {*} + */ + addError: function (node, error) { + + if (!window.Validation.form[node.selector]) { + + // {debug} + window.Debug.log({ + 'node': node, + 'function': '$.addError()', + 'arguments': 'window.Validation.form[' + node.selector + ']', + 'message': 'ERROR - Invalid node selector' + }); + + window.Debug.print(); + // {/debug} + + return false; + } + + if (typeof error !== "object" || Object.prototype.toString.call(error) !== "[object Object]") { + + // {debug} + window.Debug.log({ + 'node': node, + 'function': '$.addError()', + 'arguments': 'window.Validation.form[' + node.selector + ']', + 'message': 'ERROR - Invalid argument, must be type object' + }); + + window.Debug.print(); + // {/debug} + + return false; + } + + var input, + onlyOnce = true; + for (var inputName in error) { + + if (!error.hasOwnProperty(inputName)) { + continue; + } + + if (!(error[inputName] instanceof Array)) { + error[inputName] = [error[inputName]]; + } + + input = $(node.selector).find('[name="' + inputName + '"]'); + if (!input[0]) { + + // {debug} + window.Debug.log({ + 'node': node, + 'function': '$.addError()', + 'arguments': inputName, + 'message': 'ERROR - Unable to find ' + '$(' + node.selector + ').find("[name="' + inputName + '"]")' + }); + + window.Debug.print(); + // {/debug} + + continue; + } + + if (onlyOnce) { + window.Validation.hasScrolled = false; + onlyOnce = false; + } + + window.Validation.form[node.selector].resetOneError(inputName, input); + + for (var i = 0; i < error[inputName].length; i++) { + + if (typeof error[inputName][i] !== "string") { + + // {debug} + window.Debug.log({ + 'node': node, + 'function': '$.addError()', + 'arguments': error[inputName][i], + 'message': 'ERROR - Invalid error object property - Accepted format: {"inputName": "errorString"} or {"inputName": ["errorString", "errorString"]}' + }); + + window.Debug.print(); + // {/debug} + + continue; + } + + window.Validation.form[node.selector].registerError(inputName, error[inputName][i]); + + } + + window.Validation.form[node.selector].displayOneError(inputName); + + } + + }, + + /** + * API method to manually remove a form error. + * Note: The same form jQuery selector MUST be used to recuperate the Validation configuration. + * + * @example + * $('#form-signin_v2').removeError([ + * 'signin_v2[username]', + * 'signin_v2[password]' + * ]) + * + * @param {Object} node jQuery object + * @param {Object} inputName Object of errors to remove on the node + * + * @returns {*} + */ + removeError: function (node, inputName) { + + if (!window.Validation.form[node.selector]) { + + // {debug} + window.Debug.log({ + 'node': node, + 'function': '$.removeError()', + 'arguments': 'window.Validation.form[' + node.selector + ']', + 'message': 'ERROR - Invalid node selector' + }); + + window.Debug.print(); + // {/debug} + + return false; + } + + if (!inputName) { + window.Validation.form[node.selector].resetErrors(); + return false; + } + + if (typeof inputName === "object" && Object.prototype.toString.call(inputName) !== "[object Array]") { + + // {debug} + window.Debug.log({ + 'node': node, + 'function': '$.removeError()', + 'arguments': inputName, + 'message': 'ERROR - Invalid inputName, must be type String or Array' + }); + + window.Debug.print(); + // {/debug} + + return false; + } + + if (!(inputName instanceof Array)) { + inputName = [inputName]; + } + + var input; + for (var i = 0; i < inputName.length; i++) { + + input = $(node.selector).find('[name="' + inputName[i] + '"]'); + if (!input[0]) { + + // {debug} + window.Debug.log({ + 'node': node, + 'function': '$.removeError()', + 'arguments': inputName[i], + 'message': 'ERROR - Unable to find ' + '$(' + node.selector + ').find("[name="' + inputName[i] + '"]")' + }); + + window.Debug.print(); + // {/debug} + + continue; + } + + window.Validation.form[node.selector].resetOneError(inputName[i], input); + + } + + }, + + /** + * API method to add a validation rule. + * + * @example + * $.alterValidationRules({ + * rule: 'FILENAME', + * regex: /^[^\\/:\*\?<>\|\"\']*$/, + * message: '$ has an invalid filename.' + * }) + * + * @param {Object} ruleObj + */ + alterValidationRules: function (ruleObj) { + + if (!ruleObj.rule || (!ruleObj.regex && !ruleObj.message)) { + // {debug} + window.Debug.log({ + 'function': '$.alterValidationRules()', + 'message': 'ERROR - Missing one or multiple parameter(s) {rule, regex, message}' + }); + window.Debug.print(); + // {/debug} + return false; + } + + ruleObj.rule = ruleObj.rule.toUpperCase(); + + if (ruleObj.regex) { + + var regex = _buildRegexFromString(ruleObj.regex); + + if (!(regex instanceof RegExp)) { + // {debug} + window.Debug.log({ + 'function': '$.alterValidationRules(rule)', + 'arguments': regex.toString(), + 'message': 'ERROR - Invalid rule' + }); + window.Debug.print(); + // {/debug} + return false; + } + + _rules[ruleObj.rule] = regex; + } + + if (ruleObj.message) { + _messages[ruleObj.rule] = ruleObj.message; + } + + return true; + } + + }; + + /** + * @private + * Converts string into a regex + * + * @param {String|Object} regex + * @returns {Object|Boolean} rule + */ + function _buildRegexFromString(regex) { + + if (!regex || (typeof regex !== "string" && !(regex instanceof RegExp))) { + _regexDebug(); + return false; + } + + if (typeof regex !== 'string') { + regex = regex.toString(); + } + + var separator = regex.charAt(0), + index = regex.length - 1, + pattern, + modifier, + rule; + + while (index > 0) { + if (/[gimsxeU]/.test(regex.charAt(index))) { + index--; + } else { + break; + } + } + + if (regex.charAt(index) !== separator) { + separator = null; + } + + if (separator && index !== regex.length - 1) { + modifier = regex.substr(index + 1, regex.length - 1); + } + + if (separator) { + pattern = regex.substr(1, index - 1); + } else { + pattern = regex; + } + + try { + rule = new RegExp(pattern, modifier); + } catch (error) { + _regexDebug(); + return false; + } + + return rule; + + function _regexDebug() { + // {debug} + window.Debug.log({ + 'function': '_buildRegexFromString()', + 'arguments': '{pattern: {' + (pattern || '') + '}, modifier: {' + (modifier || '') + '}', + 'message': 'WARNING - Invalid regex given: ' + regex + }); + window.Debug.print(); + // {/debug} + } + + } + + // {debug} + window.Debug = { + + table: {}, + log: function (debugObject) { + + if (!debugObject.message || typeof debugObject.message !== "string") { + return false; + } + + this.table[debugObject.message] = $.extend( + Object.preventExtensions( + { + 'node': '', + 'function': '', + 'arguments': '' + } + ), debugObject + ); + + }, + print: function () { + + if (isEmpty(this.table)) { + return false; + } + + if (console.group !== undefined || console.table !== undefined) { + + console.groupCollapsed('--- jQuery Form Validation Debug ---'); + + if (console.table) { + console.table(this.table); + } else { + $.each(this.table, function (index, data) { + console.log(data['Name'] + ': ' + data['Execution Time'] + 'ms'); + }); + } + + console.groupEnd(); + + } else { + console.log('Debug is not available on your current browser, try the most recent version of Chrome or Firefox.'); + } + + this.table = {}; + + } + + }; + // {/debug} + + String.prototype.capitalize = function () { + return this.charAt(0).toUpperCase() + this.slice(1); + }; + + function isEmpty (obj) { + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) + return false; + } + + return true; + } + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function (elt /*, from*/) { + var len = this.length >>> 0; + + var from = Number(arguments[1]) || 0; + from = (from < 0) + ? Math.ceil(from) + : Math.floor(from); + if (from < 0) + from += len; + + for (; from < len; from++) { + if (from in this && + this[from] === elt) + return from; + } + return -1; + }; + } + + // {debug} + if (!JSON && !JSON.stringify) { + JSON.stringify = function (obj) { + var t = typeof (obj); + if (t !== "object" || obj === null) { + // simple data type + if (t === "string") { + obj = '"' + obj + '"'; + } + return String(obj); + } + else { + var n, v, json = [], arr = (obj && obj.constructor === Array); + for (n in obj) { + if (true) { + v = obj[n]; + t = typeof(v); + if (t === "string") { + v = '"' + v + '"'; + } + else if (t === "object" && v !== null) { + v = JSON.stringify(v); + } + json.push((arr ? "" : '"' + n + '": ') + String(v)); + } + } + return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}"); + } + }; + } + // {/debug} + +}(window, document, window.jQuery)); \ No newline at end of file