You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

408 lines
13 KiB

  1. /* ******************************
  2. Form Validation Components
  3. Author: Jack Lukic
  4. Notes: First Commit April 08, 2012
  5. Refactored Feb 22, 2012
  6. Allows you to validate forms based on a form validation object
  7. Form validation objects are bound by either data-validate="" metadata, or form id or name tags
  8. ****************************** */
  9. ;(function ( $, window, document, undefined ) {
  10. $.fn.validateForm = function(fields, parameters) {
  11. var
  12. $allModules = $(this),
  13. settings = $.extend(true, {}, $.fn.validateForm.settings, parameters),
  14. // make arguments available
  15. query = arguments[0],
  16. passedArguments = [].slice.call(arguments, 1),
  17. invokedResponse
  18. ;
  19. $allModules
  20. .each(function() {
  21. var
  22. $module = $(this),
  23. $group = $(this).find(settings.selector.group),
  24. $field = $(this).find(settings.selector.field),
  25. $errorPrompt = $(this).find(settings.selector.prompt),
  26. formErrors = [],
  27. selector = $module.selector || '',
  28. element = this,
  29. instance = $module.data('module-' + settings.namespace),
  30. methodInvoked = (typeof query == 'string'),
  31. namespace = settings.namespace,
  32. metadata = settings.metadata,
  33. className = settings.className,
  34. errors = settings.errors,
  35. module
  36. ;
  37. module = {
  38. initialize: function() {
  39. module.verbose('Initializing form validation');
  40. if(fields !== undefined || !$.isEmptyObject(fields) ) {
  41. // add default text if set
  42. if($.fn.defaultText !== undefined) {
  43. $.each(fields, function(fieldName, field) {
  44. module.field.add.defaultText(field);
  45. });
  46. }
  47. // attach event handler
  48. $module
  49. .on('submit.' + namespace, module.validate.form)
  50. ;
  51. }
  52. else {
  53. module.error(errors.noFields, $module);
  54. }
  55. },
  56. destroy: function() {
  57. $module
  58. .off(namespace)
  59. ;
  60. },
  61. field: {
  62. find: function(identifier) {
  63. var
  64. $field = $module.find(settings.selector.field)
  65. ;
  66. if( $field.filter('#' + identifier).size() > 0 ) {
  67. return $field.filter('#' + identifier);
  68. }
  69. else if( $field.filter('[name="' + identifier +'"]').size() > 0 ) {
  70. return $field.filter('[name="' + identifier +'"]');
  71. }
  72. else if( $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]').size() > 0 ) {
  73. return $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]');
  74. }
  75. return $('<input/>');
  76. },
  77. add: {
  78. defaultText: function(field) {
  79. var
  80. $field = module.field.find(field.identifier)
  81. ;
  82. if(field.defaultText !== undefined) {
  83. $field.defaultText(field.defaultText);
  84. }
  85. },
  86. error: function(field, errors) {
  87. var
  88. $field = module.field.find(field.identifier),
  89. $errorGroup = $field.closest($group),
  90. $errorPrompt = $group.find($errorPrompt),
  91. promptExists = ($errorPrompt.size() !== 0)
  92. ;
  93. $errorGroup
  94. .addClass(className.error)
  95. ;
  96. if(settings.inlineError) {
  97. // create message container on first invalid validation attempt
  98. if(!promptExists) {
  99. $errorPrompt = $('<div />')
  100. .addClass(className.prompt)
  101. .insertBefore($field)
  102. ;
  103. }
  104. // add prompt message
  105. $errorPrompt
  106. .html(errors[0])
  107. .fadeIn(settings.animateSpeed)
  108. ;
  109. }
  110. }
  111. },
  112. remove: {
  113. error: function(field) {
  114. var
  115. $field = module.field.find(field.identifier),
  116. $errorGroup = $field.closest($group),
  117. $errorPrompt = $group.find($errorPrompt)
  118. ;
  119. $errorGroup
  120. .removeClass(className.error)
  121. ;
  122. if(settings.inlineError) {
  123. $errorPrompt.hide();
  124. }
  125. }
  126. }
  127. },
  128. validate: {
  129. form: function(event) {
  130. var
  131. allValid = true
  132. ;
  133. // reset errors
  134. formErrors = [];
  135. $.each(fields, function(fieldName, field){
  136. // form is invalid after first bad field, but keep checking
  137. if( !( module.validate.field(field) ) ) {
  138. allValid = false;
  139. }
  140. });
  141. // Evaluate form callbacks
  142. return (allValid)
  143. ? $.proxy(settings.onSuccess, this)(event)
  144. : $.proxy(settings.onFailure, this)(formErrors)
  145. ;
  146. },
  147. // takes a validation object and returns whether field passes validation
  148. field: function(field) {
  149. var
  150. $field = module.field.find(field.identifier),
  151. fieldValid = true,
  152. fieldErrors = []
  153. ;
  154. if(field.rules !== undefined) {
  155. // iterate over all validation types for a certain field
  156. $.each(field.rules, function(index, rule) {
  157. if( !( module.validate.rule(field, rule) ) ) {
  158. module.debug('Field is invalid', field.identifier, rule.type);
  159. fieldErrors.push(rule.prompt);
  160. fieldValid = false;
  161. }
  162. });
  163. }
  164. if(fieldValid) {
  165. module.field.remove.error(field, fieldErrors);
  166. settings.onValid($field);
  167. }
  168. else {
  169. formErrors = formErrors.concat(fieldErrors);
  170. module.field.add.error(field, fieldErrors);
  171. $.proxy(settings.onInvalid, $field)(fieldErrors);
  172. return false;
  173. }
  174. return true;
  175. },
  176. // takes validation rule and returns whether field passes rule
  177. rule: function(field, validation) {
  178. var
  179. $field = module.field.find(field.identifier),
  180. type = validation.type,
  181. defaultText = (field.defaultText !== undefined)
  182. ? field.defaultText
  183. : false,
  184. value = ($field.val() == defaultText)
  185. ? ''
  186. : $field.val(),
  187. bracketRegExp = /\[(.*?)\]/i,
  188. bracket = bracketRegExp.exec(type),
  189. isValid = true,
  190. ancillary,
  191. functionType
  192. ;
  193. // if bracket notation is used, pass in extra parameters
  194. if(bracket !== undefined && bracket != null) {
  195. ancillary = bracket[1];
  196. functionType = type.replace(bracket[0], '');
  197. isValid = $.proxy(settings.rules[functionType], $module)(value, ancillary);
  198. }
  199. // normal notation
  200. else {
  201. isValid = (type == 'checked')
  202. ? $field.filter(':checked').size() > 0
  203. : settings.rules[type](value)
  204. ;
  205. }
  206. return isValid;
  207. }
  208. },
  209. /* standard module */
  210. setting: function(name, value) {
  211. if(value === undefined) {
  212. return settings[name];
  213. }
  214. settings[name] = value;
  215. },
  216. verbose: function() {
  217. if(settings.verbose) {
  218. module.debug.apply(this, arguments);
  219. }
  220. },
  221. debug: function() {
  222. var
  223. output = [],
  224. message = settings.moduleName + ': ' + arguments[0],
  225. variables = [].slice.call( arguments, 1 ),
  226. log = console.info || console.log || function(){}
  227. ;
  228. log = Function.prototype.bind.call(log, console);
  229. if(settings.debug) {
  230. output.push(message);
  231. log.apply(console, output.concat(variables) );
  232. }
  233. },
  234. error: function() {
  235. var
  236. output = [],
  237. errorMessage = settings.moduleName + ': ' + arguments[0],
  238. variables = [].slice.call( arguments, 1 ),
  239. log = console.warn || console.log || function(){}
  240. ;
  241. log = Function.prototype.bind.call(log, console);
  242. if(settings.debug) {
  243. output.push(errorMessage);
  244. output.concat(variables);
  245. log.apply(console, output.concat(variables) );
  246. }
  247. },
  248. invoke: function(query, context, passedArguments) {
  249. var
  250. maxDepth,
  251. found
  252. ;
  253. passedArguments = passedArguments || [].slice.call( arguments, 2 );
  254. if(typeof query == 'string' && instance !== undefined) {
  255. query = query.split('.');
  256. maxDepth = query.length - 1;
  257. $.each(query, function(depth, value) {
  258. if( $.isPlainObject( instance[value] ) && (depth != maxDepth) ) {
  259. instance = instance[value];
  260. return true;
  261. }
  262. else if( instance[value] !== undefined ) {
  263. found = instance[value];
  264. return true;
  265. }
  266. module.error(errors.method);
  267. return false;
  268. });
  269. }
  270. if ( $.isFunction( found ) ) {
  271. return found.apply(context, passedArguments);
  272. }
  273. // return retrieved variable or chain
  274. return found;
  275. }
  276. };
  277. // check for invoking internal method
  278. if(methodInvoked) {
  279. invokedResponse = module.invoke(query, this, passedArguments);
  280. }
  281. // otherwise initialize
  282. else {
  283. module.initialize();
  284. }
  285. })
  286. ;
  287. // chain or return queried method
  288. return (invokedResponse !== undefined)
  289. ? invokedResponse
  290. : this
  291. ;
  292. };
  293. $.fn.validateForm.settings = {
  294. // module info
  295. moduleName : 'Validate Form Module',
  296. debug : true,
  297. verbose : false,
  298. namespace : 'validate',
  299. animateSpeed : 150,
  300. inlineError : false,
  301. onValid : function() {},
  302. onInvalid : function() {},
  303. onSuccess : function() { return true; },
  304. onFailure : function() { return false; },
  305. metadata : {
  306. validate: 'validate'
  307. },
  308. // errors
  309. errors: {
  310. method : 'The method you called is not defined.',
  311. noFields : 'No validation object specified.'
  312. },
  313. selector : {
  314. group : '.field',
  315. prompt : '.prompt',
  316. field : 'input, textarea, select'
  317. },
  318. className : {
  319. error : 'error',
  320. prompt : 'prompt'
  321. },
  322. rules: {
  323. empty: function(value) {
  324. return !(value === undefined || '' === value);
  325. },
  326. email: function(value){
  327. var
  328. emailRegExp = new RegExp("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")
  329. ;
  330. return emailRegExp.test(value);
  331. },
  332. length: function(value, requiredLength) {
  333. return (value !== undefined)
  334. ? (value.length >= requiredLength)
  335. : false
  336. ;
  337. },
  338. not: function(value, notValue) {
  339. return (value != notValue);
  340. },
  341. maxLength: function(value, maxLength) {
  342. return (value !== undefined)
  343. ? (value.length <= maxLength)
  344. : false
  345. ;
  346. },
  347. match: function(value, matchingField) {
  348. // use either id or name of field
  349. var
  350. $form = $(this),
  351. matchingValue
  352. ;
  353. if($form.find('#' + matchingField).size() > 0) {
  354. matchingValue = $form.find('#' + matchingField).val();
  355. }
  356. else if($form.find('[name=' + matchingField +']').size() > 0) {
  357. matchingValue = $form.find('[name=' + matchingField + ']').val();
  358. }
  359. else if( $form.find('[data-validate="'+ matchingField +'"]').size() > 0 ) {
  360. matchingValue = $form.find('[data-validate="'+ matchingField +'"]').val();
  361. }
  362. return (matchingValue !== undefined)
  363. ? ( value.toString() == matchingValue.toString() )
  364. : false
  365. ;
  366. },
  367. url: function(value) {
  368. var
  369. urlRegExp = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/
  370. ;
  371. return urlRegExp.test(value);
  372. }
  373. }
  374. };
  375. })( jQuery, window , document );