docs: 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 = '<div class="' + options.submit.settings.errorListClass + '" ' + _data.errorList + '><ul></ul></div>',
                  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('<li>' + options.submit.settings.errorTemplate.replace('{{validation-message}}', errors[inputName][i]) + '</li>');
                  } else {
                      errorContainer.find('[' + _data.errorList + '] ul').append('<li>' + errors[inputName][i] + '</li>');
                  }
              }
  
              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
  
          };
  
      }