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.

823 lines
25 KiB

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