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.

536 lines
16 KiB

  1. /* ******************************
  2. Form Validation Components
  3. Author: Jack Lukic
  4. Notes: First Commit April 08, 2012
  5. Refactored May 28, 2013
  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.form = function(fields, parameters) {
  11. var
  12. $allModules = $(this),
  13. $document = $(document),
  14. settings = $.extend(true, {}, $.fn.form.settings, parameters),
  15. eventNamespace = '.' + settings.namespace,
  16. moduleNamespace = 'module-' + settings.namespace,
  17. selector = $allModules.selector || '',
  18. time = new Date().getTime(),
  19. performance = [],
  20. query = arguments[0],
  21. methodInvoked = (typeof query == 'string'),
  22. queryArguments = [].slice.call(arguments, 1),
  23. invokedResponse
  24. ;
  25. $allModules
  26. .each(function() {
  27. var
  28. $module = $(this),
  29. $group = $(this).find(settings.selector.group),
  30. $field = $(this).find(settings.selector.field),
  31. $errorPrompt = $(this).find(settings.selector.prompt),
  32. formErrors = [],
  33. element = this,
  34. instance = $module.data('module-' + settings.namespace),
  35. namespace = settings.namespace,
  36. metadata = settings.metadata,
  37. className = settings.className,
  38. errors = settings.errors,
  39. module
  40. ;
  41. module = {
  42. initialize: function() {
  43. module.verbose('Initializing form validation');
  44. if(fields !== undefined || !$.isEmptyObject(fields) ) {
  45. // attach event handler
  46. if(settings.on == 'submit') {
  47. $module
  48. .on('submit.' + namespace, module.validate.form)
  49. ;
  50. }
  51. }
  52. else {
  53. module.error(errors.noFields, $module);
  54. }
  55. },
  56. destroy: function() {
  57. $module
  58. .off(namespace)
  59. ;
  60. },
  61. refresh: function() {
  62. $field = $module.find(settings.selector.field);
  63. },
  64. field: {
  65. find: function(identifier) {
  66. module.refresh();
  67. if( $field.filter('#' + identifier).size() > 0 ) {
  68. return $field.filter('#' + identifier);
  69. }
  70. else if( $field.filter('[name="' + identifier +'"]').size() > 0 ) {
  71. return $field.filter('[name="' + identifier +'"]');
  72. }
  73. else if( $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]').size() > 0 ) {
  74. return $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]');
  75. }
  76. return $('<input/>');
  77. },
  78. add: {
  79. error: function(field, errors) {
  80. var
  81. $field = module.field.find(field.identifier),
  82. $errorGroup = $field.closest($group),
  83. $errorPrompt = $group.find($errorPrompt),
  84. promptExists = ($errorPrompt.size() !== 0)
  85. ;
  86. $errorGroup
  87. .addClass(className.error)
  88. ;
  89. if(settings.inlineError) {
  90. // create message container on first invalid validation attempt
  91. if(!promptExists) {
  92. $errorPrompt = $('<div />')
  93. .addClass(className.prompt)
  94. .insertBefore($field)
  95. ;
  96. }
  97. // add prompt message
  98. $errorPrompt
  99. .html(errors[0])
  100. .fadeIn(settings.animateSpeed)
  101. ;
  102. }
  103. }
  104. },
  105. remove: {
  106. error: function(field) {
  107. var
  108. $field = module.field.find(field.identifier),
  109. $errorGroup = $field.closest($group),
  110. $errorPrompt = $group.find($errorPrompt)
  111. ;
  112. $errorGroup
  113. .removeClass(className.error)
  114. ;
  115. if(settings.inlineError) {
  116. $errorPrompt.hide();
  117. }
  118. }
  119. }
  120. },
  121. validate: {
  122. form: function(event) {
  123. var
  124. allValid = true
  125. ;
  126. // reset errors
  127. formErrors = [];
  128. $.each(fields, function(fieldName, field){
  129. // form is invalid after first bad field, but keep checking
  130. if( !( module.validate.field(field) ) ) {
  131. allValid = false;
  132. }
  133. });
  134. // Evaluate form callbacks
  135. return (allValid)
  136. ? $.proxy(settings.onSuccess, this)(event)
  137. : $.proxy(settings.onFailure, this)(formErrors)
  138. ;
  139. },
  140. // takes a validation object and returns whether field passes validation
  141. field: function(field) {
  142. var
  143. $field = module.field.find(field.identifier),
  144. fieldValid = true,
  145. fieldErrors = []
  146. ;
  147. if(field.rules !== undefined) {
  148. // iterate over all validation types for a certain field
  149. $.each(field.rules, function(index, rule) {
  150. if( !( module.validate.rule(field, rule) ) ) {
  151. module.debug('Field is invalid', field.identifier, rule.type);
  152. fieldErrors.push(rule.prompt);
  153. fieldValid = false;
  154. }
  155. });
  156. }
  157. if(fieldValid) {
  158. module.field.remove.error(field, fieldErrors);
  159. settings.onValid($field);
  160. }
  161. else {
  162. formErrors = formErrors.concat(fieldErrors);
  163. module.field.add.error(field, fieldErrors);
  164. $.proxy(settings.onInvalid, $field)(fieldErrors);
  165. return false;
  166. }
  167. return true;
  168. },
  169. // takes validation rule and returns whether field passes rule
  170. rule: function(field, validation) {
  171. var
  172. $field = module.field.find(field.identifier),
  173. type = validation.type,
  174. value = $field.val(),
  175. bracketRegExp = /\[(.*?)\]/i,
  176. bracket = bracketRegExp.exec(type),
  177. isValid = true,
  178. ancillary,
  179. functionType
  180. ;
  181. // if bracket notation is used, pass in extra parameters
  182. if(bracket !== undefined && bracket != null) {
  183. ancillary = bracket[1];
  184. functionType = type.replace(bracket[0], '');
  185. isValid = $.proxy(settings.rules[functionType], $module)(value, ancillary);
  186. }
  187. // normal notation
  188. else {
  189. isValid = (type == 'checked')
  190. ? $field.filter(':checked').size() > 0
  191. : settings.rules[type](value)
  192. ;
  193. }
  194. return isValid;
  195. }
  196. },
  197. setting: function(name, value) {
  198. if(value !== undefined) {
  199. if( $.isPlainObject(name) ) {
  200. $.extend(true, settings, name);
  201. }
  202. else {
  203. settings[name] = value;
  204. }
  205. }
  206. else {
  207. return settings[name];
  208. }
  209. },
  210. internal: function(name, value) {
  211. if(value !== undefined) {
  212. if( $.isPlainObject(name) ) {
  213. $.extend(true, module, name);
  214. }
  215. else {
  216. module[name] = value;
  217. }
  218. }
  219. else {
  220. return module[name];
  221. }
  222. },
  223. debug: function() {
  224. if(settings.debug) {
  225. module.performance.log(arguments[0]);
  226. module.debug = Function.prototype.bind.call(console.log, console, settings.moduleName + ':');
  227. }
  228. },
  229. verbose: function() {
  230. if(settings.verbose && settings.debug) {
  231. module.performance.log(arguments[0]);
  232. module.verbose = Function.prototype.bind.call(console.info, console, settings.moduleName + ':');
  233. }
  234. },
  235. error: function() {
  236. module.error = Function.prototype.bind.call(console.log, console, settings.moduleName + ':');
  237. },
  238. performance: {
  239. log: function(message) {
  240. var
  241. currentTime,
  242. executionTime,
  243. previousTime
  244. ;
  245. if(settings.performance) {
  246. currentTime = new Date().getTime();
  247. previousTime = time || currentTime,
  248. executionTime = currentTime - previousTime;
  249. time = currentTime;
  250. performance.push({
  251. 'Element' : element,
  252. 'Name' : message,
  253. 'Execution Time' : executionTime
  254. });
  255. clearTimeout(module.performance.timer);
  256. module.performance.timer = setTimeout(module.performance.display, 100);
  257. }
  258. },
  259. display: function() {
  260. var
  261. title = settings.moduleName,
  262. caption = settings.moduleName + ': ' + selector + '(' + $allModules.size() + ' elements)',
  263. totalExecutionTime = 0
  264. ;
  265. if(selector) {
  266. title += 'Performance (' + selector + ')';
  267. }
  268. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  269. console.groupCollapsed(title);
  270. if(console.table) {
  271. $.each(performance, function(index, data) {
  272. totalExecutionTime += data['Execution Time'];
  273. });
  274. console.table(performance);
  275. }
  276. else {
  277. $.each(performance, function(index, data) {
  278. totalExecutionTime += data['Execution Time'];
  279. });
  280. }
  281. console.log('Total Execution Time:', totalExecutionTime +'ms');
  282. console.groupEnd();
  283. performance = [];
  284. time = false;
  285. }
  286. }
  287. },
  288. invoke: function(query, passedArguments, context) {
  289. var
  290. maxDepth,
  291. found
  292. ;
  293. passedArguments = passedArguments || queryArguments;
  294. context = element || context;
  295. if(typeof query == 'string' && instance !== undefined) {
  296. query = query.split('.');
  297. maxDepth = query.length - 1;
  298. $.each(query, function(depth, value) {
  299. if( $.isPlainObject( instance[value] ) && (depth != maxDepth) ) {
  300. instance = instance[value];
  301. return true;
  302. }
  303. else if( instance[value] !== undefined ) {
  304. found = instance[value];
  305. return true;
  306. }
  307. module.error(errors.method);
  308. return false;
  309. });
  310. }
  311. if ( $.isFunction( found ) ) {
  312. module.verbose('Executing invoked function', found);
  313. return found.apply(context, passedArguments);
  314. }
  315. return found || false;
  316. }
  317. };
  318. if(methodInvoked) {
  319. if(instance === undefined) {
  320. module.initialize();
  321. }
  322. invokedResponse = module.invoke(query);
  323. }
  324. else {
  325. if(instance !== undefined) {
  326. module.destroy();
  327. }
  328. module.initialize();
  329. }
  330. })
  331. ;
  332. return (invokedResponse)
  333. ? invokedResponse
  334. : this
  335. ;
  336. };
  337. $.fn.form.settings = {
  338. // module info
  339. moduleName : 'Validate Form Module',
  340. debug : true,
  341. verbose : false,
  342. namespace : 'validate',
  343. animateSpeed : 150,
  344. inlineError : false,
  345. on: 'submit',
  346. onValid : function() {},
  347. onInvalid : function() {},
  348. onSuccess : function() { return true; },
  349. onFailure : function() { return false; },
  350. metadata : {
  351. validate: 'validate'
  352. },
  353. // errors
  354. errors: {
  355. method : 'The method you called is not defined.',
  356. noFields : 'No validation object specified.'
  357. },
  358. selector : {
  359. group : '.field',
  360. prompt : '.prompt',
  361. field : 'input, textarea, select'
  362. },
  363. className : {
  364. error : 'error',
  365. prompt : 'prompt'
  366. },
  367. defaults: {
  368. firstName: {
  369. identifier : 'first-name',
  370. rules: [
  371. {
  372. type : 'empty',
  373. prompt : 'Please enter your first name'
  374. }
  375. ]
  376. },
  377. lastName: {
  378. identifier : 'last-name',
  379. rules: [
  380. {
  381. type : 'empty',
  382. prompt : 'Please enter your last name'
  383. }
  384. ]
  385. },
  386. username: {
  387. identifier : 'username',
  388. rules: [
  389. {
  390. type : 'email',
  391. prompt : 'Please enter a username'
  392. }
  393. ]
  394. },
  395. email: {
  396. identifier : 'email',
  397. rules: [
  398. {
  399. type : 'empty',
  400. prompt : 'Please enter your email'
  401. },
  402. {
  403. type : 'email',
  404. prompt : 'Please enter a valid email'
  405. }
  406. ]
  407. },
  408. password: {
  409. identifier : 'password',
  410. rules: [
  411. {
  412. type : 'empty',
  413. prompt : 'Please enter a password'
  414. },
  415. {
  416. type : 'length[6]',
  417. prompt : 'Your password must be at least 6 characters'
  418. }
  419. ]
  420. },
  421. passwordConfirm: {
  422. identifier : 'password-confirm',
  423. rules: [
  424. {
  425. type : 'empty',
  426. prompt : 'Please confirm your password'
  427. },
  428. {
  429. identifier : 'password-confirm',
  430. type : 'match[password]',
  431. prompt : 'Please verify password matches'
  432. }
  433. ]
  434. },
  435. terms: {
  436. identifier : 'terms',
  437. rules: [
  438. {
  439. type : 'checked',
  440. prompt : 'You must agree to the terms and conditions'
  441. }
  442. ]
  443. }
  444. },
  445. rules: {
  446. empty: function(value) {
  447. return !(value === undefined || '' === value);
  448. },
  449. email: function(value){
  450. var
  451. 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])?")
  452. ;
  453. return emailRegExp.test(value);
  454. },
  455. length: function(value, requiredLength) {
  456. return (value !== undefined)
  457. ? (value.length >= requiredLength)
  458. : false
  459. ;
  460. },
  461. not: function(value, notValue) {
  462. return (value != notValue);
  463. },
  464. maxLength: function(value, maxLength) {
  465. return (value !== undefined)
  466. ? (value.length <= maxLength)
  467. : false
  468. ;
  469. },
  470. match: function(value, matchingField) {
  471. // use either id or name of field
  472. var
  473. $form = $(this),
  474. matchingValue
  475. ;
  476. if($form.find('#' + matchingField).size() > 0) {
  477. matchingValue = $form.find('#' + matchingField).val();
  478. }
  479. else if($form.find('[name=' + matchingField +']').size() > 0) {
  480. matchingValue = $form.find('[name=' + matchingField + ']').val();
  481. }
  482. else if( $form.find('[data-validate="'+ matchingField +'"]').size() > 0 ) {
  483. matchingValue = $form.find('[data-validate="'+ matchingField +'"]').val();
  484. }
  485. return (matchingValue !== undefined)
  486. ? ( value.toString() == matchingValue.toString() )
  487. : false
  488. ;
  489. },
  490. url: function(value) {
  491. var
  492. urlRegExp = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/
  493. ;
  494. return urlRegExp.test(value);
  495. }
  496. }
  497. };
  498. })( jQuery, window , document );