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.

697 lines
22 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
  1. /*
  2. * # Semantic - Form Validation
  3. * http://github.com/jlukic/semantic-ui/
  4. *
  5. *
  6. * Copyright 2013 Contributors
  7. * Released under the MIT license
  8. * http://opensource.org/licenses/MIT
  9. *
  10. */
  11. ;(function ( $, window, document, undefined ) {
  12. $.fn.form = function(fields, parameters) {
  13. var
  14. $allModules = $(this),
  15. settings = $.extend(true, {}, $.fn.form.settings, parameters),
  16. validation = $.extend({}, $.fn.form.settings.defaults, fields),
  17. namespace = settings.namespace,
  18. metadata = settings.metadata,
  19. selector = settings.selector,
  20. className = settings.className,
  21. error = settings.error,
  22. eventNamespace = '.' + namespace,
  23. moduleNamespace = 'module-' + namespace,
  24. moduleSelector = $allModules.selector || '',
  25. time = new Date().getTime(),
  26. performance = [],
  27. query = arguments[0],
  28. methodInvoked = (typeof query == 'string'),
  29. queryArguments = [].slice.call(arguments, 1),
  30. returnedValue
  31. ;
  32. $allModules
  33. .each(function() {
  34. var
  35. $module = $(this),
  36. $field = $(this).find(selector.field),
  37. $group = $(this).find(selector.group),
  38. $message = $(this).find(selector.message),
  39. $prompt = $(this).find(selector.prompt),
  40. $submit = $(this).find(selector.submit),
  41. formErrors = [],
  42. element = this,
  43. instance = $module.data(moduleNamespace),
  44. module
  45. ;
  46. module = {
  47. initialize: function() {
  48. module.verbose('Initializing form validation', $module, validation, settings);
  49. if(settings.keyboardShortcuts) {
  50. $field
  51. .on('keydown' + eventNamespace, module.event.field.keydown)
  52. ;
  53. }
  54. $module
  55. .on('submit' + eventNamespace, module.validate.form)
  56. ;
  57. $field
  58. .on('blur' + eventNamespace, module.event.field.blur)
  59. ;
  60. $submit
  61. .on('click' + eventNamespace, module.submit)
  62. ;
  63. $field
  64. .on(module.get.changeEvent() + eventNamespace, module.event.field.change)
  65. ;
  66. module.instantiate();
  67. },
  68. instantiate: function() {
  69. module.verbose('Storing instance of module', module);
  70. instance = module;
  71. $module
  72. .data(moduleNamespace, module)
  73. ;
  74. },
  75. destroy: function() {
  76. module.verbose('Destroying previous module', instance);
  77. $module
  78. .off(eventNamespace)
  79. .removeData(moduleNamespace)
  80. ;
  81. },
  82. refresh: function() {
  83. module.verbose('Refreshing selector cache');
  84. $field = $module.find(selector.field);
  85. },
  86. submit: function() {
  87. module.verbose('Submitting form', $module);
  88. $module
  89. .submit()
  90. ;
  91. },
  92. event: {
  93. field: {
  94. keydown: function(event) {
  95. var
  96. $field = $(this),
  97. key = event.which,
  98. keyCode = {
  99. enter : 13,
  100. escape : 27
  101. }
  102. ;
  103. if( key == keyCode.escape) {
  104. module.verbose('Escape key pressed blurring field');
  105. $field
  106. .blur()
  107. ;
  108. }
  109. if(!event.ctrlKey && key == keyCode.enter && $field.is(selector.input) ) {
  110. module.debug('Enter key pressed, submitting form');
  111. $submit
  112. .addClass(className.down)
  113. ;
  114. $field
  115. .one('keyup' + eventNamespace, module.event.field.keyup)
  116. ;
  117. event.preventDefault();
  118. return false;
  119. }
  120. },
  121. keyup: function() {
  122. module.verbose('Doing keyboard shortcut form submit');
  123. $submit.removeClass(className.down);
  124. module.submit();
  125. },
  126. blur: function() {
  127. var
  128. $field = $(this),
  129. $fieldGroup = $field.closest($group)
  130. ;
  131. if( $fieldGroup.hasClass(className.error) ) {
  132. module.debug('Revalidating field', $field, module.get.validation($field));
  133. module.validate.field( module.get.validation($field) );
  134. }
  135. else if(settings.on == 'blur' || settings.on == 'change') {
  136. module.validate.field( module.get.validation($field) );
  137. }
  138. },
  139. change: function() {
  140. var
  141. $field = $(this),
  142. $fieldGroup = $field.closest($group)
  143. ;
  144. if( $fieldGroup.hasClass(className.error) ) {
  145. module.debug('Revalidating field', $field, module.get.validation($field));
  146. module.validate.field( module.get.validation($field) );
  147. }
  148. else if(settings.on == 'change') {
  149. module.validate.field( module.get.validation($field) );
  150. }
  151. }
  152. }
  153. },
  154. get: {
  155. changeEvent: function() {
  156. return (document.createElement('input').oninput !== undefined)
  157. ? 'input'
  158. : (document.createElement('input').onpropertychange !== undefined)
  159. ? 'propertychange'
  160. : 'keyup'
  161. ;
  162. },
  163. field: function(identifier) {
  164. module.verbose('Finding field with identifier', identifier);
  165. if( $field.filter('#' + identifier).size() > 0 ) {
  166. return $field.filter('#' + identifier);
  167. }
  168. else if( $field.filter('[name="' + identifier +'"]').size() > 0 ) {
  169. return $field.filter('[name="' + identifier +'"]');
  170. }
  171. else if( $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]').size() > 0 ) {
  172. return $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]');
  173. }
  174. return $('<input/>');
  175. },
  176. validation: function($field) {
  177. var
  178. rules
  179. ;
  180. $.each(validation, function(fieldName, field) {
  181. if( module.get.field(field.identifier).get(0) == $field.get(0) ) {
  182. rules = field;
  183. }
  184. });
  185. return rules || false;
  186. }
  187. },
  188. has: {
  189. field: function(identifier) {
  190. module.verbose('Checking for existence of a field with identifier', identifier);
  191. if( $field.filter('#' + identifier).size() > 0 ) {
  192. return true;
  193. }
  194. else if( $field.filter('[name="' + identifier +'"]').size() > 0 ) {
  195. return true;
  196. }
  197. else if( $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]').size() > 0 ) {
  198. return true;
  199. }
  200. return false;
  201. }
  202. },
  203. add: {
  204. prompt: function(field, errors) {
  205. var
  206. $field = module.get.field(field.identifier),
  207. $fieldGroup = $field.closest($group),
  208. $prompt = $fieldGroup.find(selector.prompt),
  209. promptExists = ($prompt.size() !== 0)
  210. ;
  211. module.verbose('Adding inline error', field);
  212. $fieldGroup
  213. .addClass(className.error)
  214. ;
  215. if(settings.inline) {
  216. if(!promptExists) {
  217. $prompt = settings.templates.prompt(errors);
  218. $prompt
  219. .appendTo($fieldGroup)
  220. ;
  221. }
  222. $prompt
  223. .html(errors[0])
  224. ;
  225. if(!promptExists) {
  226. if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
  227. module.verbose('Displaying error with css transition', settings.transition);
  228. $prompt.transition(settings.transition + ' in', settings.duration);
  229. }
  230. else {
  231. module.verbose('Displaying error with fallback javascript animation');
  232. $prompt
  233. .fadeIn(settings.duration)
  234. ;
  235. }
  236. }
  237. }
  238. },
  239. errors: function(errors) {
  240. module.debug('Adding form error messages', errors);
  241. $message
  242. .html( settings.templates.error(errors) )
  243. ;
  244. }
  245. },
  246. remove: {
  247. prompt: function(field) {
  248. var
  249. $field = module.get.field(field.identifier),
  250. $fieldGroup = $field.closest($group),
  251. $prompt = $fieldGroup.find(selector.prompt)
  252. ;
  253. $fieldGroup
  254. .removeClass(className.error)
  255. ;
  256. if(settings.inline && $prompt.is(':visible')) {
  257. module.verbose('Removing prompt for field', field);
  258. if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
  259. $prompt.transition(settings.transition + ' out', settings.duration, function() {
  260. $prompt.remove();
  261. });
  262. }
  263. else {
  264. $prompt
  265. .fadeOut(settings.duration, function(){
  266. $prompt.remove();
  267. })
  268. ;
  269. }
  270. }
  271. }
  272. },
  273. validate: {
  274. form: function(event) {
  275. var
  276. allValid = true
  277. ;
  278. // reset errors
  279. formErrors = [];
  280. $.each(validation, function(fieldName, field) {
  281. if( !( module.validate.field(field) ) ) {
  282. allValid = false;
  283. }
  284. });
  285. if(allValid) {
  286. module.debug('Form has no validation errors, submitting');
  287. $module
  288. .removeClass(className.error)
  289. .addClass(className.success)
  290. ;
  291. $.proxy(settings.onSuccess, this)(event);
  292. }
  293. else {
  294. module.debug('Form has errors');
  295. $module.addClass(className.error);
  296. if(!settings.inline) {
  297. module.add.errors(formErrors);
  298. }
  299. return $.proxy(settings.onFailure, this)(formErrors);
  300. }
  301. },
  302. // takes a validation object and returns whether field passes validation
  303. field: function(field) {
  304. var
  305. $field = module.get.field(field.identifier),
  306. fieldValid = true,
  307. fieldErrors = []
  308. ;
  309. if(field.rules !== undefined) {
  310. $.each(field.rules, function(index, rule) {
  311. if( module.has.field(field.identifier) && !( module.validate.rule(field, rule) ) ) {
  312. module.debug('Field is invalid', field.identifier, rule.type);
  313. fieldErrors.push(rule.prompt);
  314. fieldValid = false;
  315. }
  316. });
  317. }
  318. if(fieldValid) {
  319. module.remove.prompt(field, fieldErrors);
  320. $.proxy(settings.onValid, $field)();
  321. }
  322. else {
  323. formErrors = formErrors.concat(fieldErrors);
  324. module.add.prompt(field, fieldErrors);
  325. $.proxy(settings.onInvalid, $field)(fieldErrors);
  326. return false;
  327. }
  328. return true;
  329. },
  330. // takes validation rule and returns whether field passes rule
  331. rule: function(field, validation) {
  332. var
  333. $field = module.get.field(field.identifier),
  334. type = validation.type,
  335. value = $field.val() + '',
  336. bracketRegExp = /\[(.*?)\]/i,
  337. bracket = bracketRegExp.exec(type),
  338. isValid = true,
  339. ancillary,
  340. functionType
  341. ;
  342. // if bracket notation is used, pass in extra parameters
  343. if(bracket !== undefined && bracket !== null) {
  344. ancillary = '' + bracket[1];
  345. functionType = type.replace(bracket[0], '');
  346. isValid = $.proxy(settings.rules[functionType], $module)(value, ancillary);
  347. }
  348. // normal notation
  349. else {
  350. isValid = $.proxy(settings.rules[type], $field)(value);
  351. }
  352. return isValid;
  353. }
  354. },
  355. setting: function(name, value) {
  356. if( $.isPlainObject(name) ) {
  357. $.extend(true, settings, name);
  358. }
  359. else if(value !== undefined) {
  360. settings[name] = value;
  361. }
  362. else {
  363. return settings[name];
  364. }
  365. },
  366. internal: function(name, value) {
  367. if( $.isPlainObject(name) ) {
  368. $.extend(true, module, name);
  369. }
  370. else if(value !== undefined) {
  371. module[name] = value;
  372. }
  373. else {
  374. return module[name];
  375. }
  376. },
  377. debug: function() {
  378. if(settings.debug) {
  379. if(settings.performance) {
  380. module.performance.log(arguments);
  381. }
  382. else {
  383. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  384. module.debug.apply(console, arguments);
  385. }
  386. }
  387. },
  388. verbose: function() {
  389. if(settings.verbose && settings.debug) {
  390. if(settings.performance) {
  391. module.performance.log(arguments);
  392. }
  393. else {
  394. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  395. module.verbose.apply(console, arguments);
  396. }
  397. }
  398. },
  399. error: function() {
  400. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  401. module.error.apply(console, arguments);
  402. },
  403. performance: {
  404. log: function(message) {
  405. var
  406. currentTime,
  407. executionTime,
  408. previousTime
  409. ;
  410. if(settings.performance) {
  411. currentTime = new Date().getTime();
  412. previousTime = time || currentTime;
  413. executionTime = currentTime - previousTime;
  414. time = currentTime;
  415. performance.push({
  416. 'Element' : element,
  417. 'Name' : message[0],
  418. 'Arguments' : [].slice.call(message, 1) || '',
  419. 'Execution Time' : executionTime
  420. });
  421. }
  422. clearTimeout(module.performance.timer);
  423. module.performance.timer = setTimeout(module.performance.display, 100);
  424. },
  425. display: function() {
  426. var
  427. title = settings.name + ':',
  428. totalTime = 0
  429. ;
  430. time = false;
  431. clearTimeout(module.performance.timer);
  432. $.each(performance, function(index, data) {
  433. totalTime += data['Execution Time'];
  434. });
  435. title += ' ' + totalTime + 'ms';
  436. if(moduleSelector) {
  437. title += ' \'' + moduleSelector + '\'';
  438. }
  439. if($allModules.size() > 1) {
  440. title += ' ' + '(' + $allModules.size() + ')';
  441. }
  442. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  443. console.groupCollapsed(title);
  444. if(console.table) {
  445. console.table(performance);
  446. }
  447. else {
  448. $.each(performance, function(index, data) {
  449. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  450. });
  451. }
  452. console.groupEnd();
  453. }
  454. performance = [];
  455. }
  456. },
  457. invoke: function(query, passedArguments, context) {
  458. var
  459. maxDepth,
  460. found,
  461. response
  462. ;
  463. passedArguments = passedArguments || queryArguments;
  464. context = element || context;
  465. if(typeof query == 'string' && instance !== undefined) {
  466. query = query.split(/[\. ]/);
  467. maxDepth = query.length - 1;
  468. $.each(query, function(depth, value) {
  469. var camelCaseValue = (depth != maxDepth)
  470. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  471. : query
  472. ;
  473. if( $.isPlainObject( instance[camelCaseValue] ) && (depth != maxDepth) ) {
  474. instance = instance[camelCaseValue];
  475. }
  476. else if( instance[camelCaseValue] !== undefined ) {
  477. found = instance[camelCaseValue];
  478. return false;
  479. }
  480. else if( $.isPlainObject( instance[value] ) && (depth != maxDepth) ) {
  481. instance = instance[value];
  482. }
  483. else if( instance[value] !== undefined ) {
  484. found = instance[value];
  485. return false;
  486. }
  487. else {
  488. module.error(error.method, query);
  489. return false;
  490. }
  491. });
  492. }
  493. if ( $.isFunction( found ) ) {
  494. response = found.apply(context, passedArguments);
  495. }
  496. else if(found !== undefined) {
  497. response = found;
  498. }
  499. if($.isArray(returnedValue)) {
  500. returnedValue.push(response);
  501. }
  502. else if(returnedValue !== undefined) {
  503. returnedValue = [returnedValue, response];
  504. }
  505. else if(response !== undefined) {
  506. returnedValue = response;
  507. }
  508. return found;
  509. }
  510. };
  511. if(methodInvoked) {
  512. if(instance === undefined) {
  513. module.initialize();
  514. }
  515. module.invoke(query);
  516. }
  517. else {
  518. if(instance !== undefined) {
  519. module.destroy();
  520. }
  521. module.initialize();
  522. }
  523. })
  524. ;
  525. return (returnedValue !== undefined)
  526. ? returnedValue
  527. : this
  528. ;
  529. };
  530. $.fn.form.settings = {
  531. name : 'Form',
  532. namespace : 'form',
  533. debug : true,
  534. verbose : true,
  535. performance : true,
  536. keyboardShortcuts : true,
  537. on : 'submit',
  538. inline : false,
  539. transition : 'scale',
  540. duration : 150,
  541. onValid : function() {},
  542. onInvalid : function() {},
  543. onSuccess : function() { return true; },
  544. onFailure : function() { return false; },
  545. metadata : {
  546. validate: 'validate'
  547. },
  548. selector : {
  549. message : '.error.message',
  550. field : 'input, textarea, select',
  551. group : '.field',
  552. input : 'input',
  553. prompt : '.prompt',
  554. submit : '.submit'
  555. },
  556. className : {
  557. error : 'error',
  558. success : 'success',
  559. down : 'down',
  560. label : 'ui label prompt'
  561. },
  562. // errors
  563. error: {
  564. method : 'The method you called is not defined.'
  565. },
  566. templates: {
  567. error: function(errors) {
  568. var
  569. html = '<ul class="list">'
  570. ;
  571. $.each(errors, function(index, value) {
  572. html += '<li>' + value + '</li>';
  573. });
  574. html += '</ul>';
  575. return $(html);
  576. },
  577. prompt: function(errors) {
  578. return $('<div/>')
  579. .addClass('ui red pointing prompt label')
  580. .html(errors[0])
  581. ;
  582. }
  583. },
  584. rules: {
  585. checked: function() {
  586. return ($(this).filter(':checked').size() > 0);
  587. },
  588. empty: function(value) {
  589. return !(value === undefined || '' === value);
  590. },
  591. email: function(value){
  592. var
  593. 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])?")
  594. ;
  595. return emailRegExp.test(value);
  596. },
  597. length: function(value, requiredLength) {
  598. return (value !== undefined)
  599. ? (value.length >= requiredLength)
  600. : false
  601. ;
  602. },
  603. not: function(value, notValue) {
  604. return (value != notValue);
  605. },
  606. contains: function(value, text) {
  607. text = text.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
  608. return (value.search(text) !== -1);
  609. },
  610. is: function(value, text) {
  611. return (value == text);
  612. },
  613. maxLength: function(value, maxLength) {
  614. return (value !== undefined)
  615. ? (value.length <= maxLength)
  616. : false
  617. ;
  618. },
  619. match: function(value, fieldIdentifier) {
  620. // use either id or name of field
  621. var
  622. $form = $(this),
  623. matchingValue
  624. ;
  625. if($form.find('#' + fieldIdentifier).size() > 0) {
  626. matchingValue = $form.find('#' + fieldIdentifier).val();
  627. }
  628. else if($form.find('[name=' + fieldIdentifier +']').size() > 0) {
  629. matchingValue = $form.find('[name=' + fieldIdentifier + ']').val();
  630. }
  631. else if( $form.find('[data-validate="'+ fieldIdentifier +'"]').size() > 0 ) {
  632. matchingValue = $form.find('[data-validate="'+ fieldIdentifier +'"]').val();
  633. }
  634. return (matchingValue !== undefined)
  635. ? ( value.toString() == matchingValue.toString() )
  636. : false
  637. ;
  638. },
  639. url: function(value) {
  640. var
  641. urlRegExp = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/
  642. ;
  643. return urlRegExp.test(value);
  644. }
  645. }
  646. };
  647. })( jQuery, window , document );