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.

1517 lines
49 KiB

  1. /*!
  2. * # Semantic UI 2.1.6 - Form Validation
  3. * http://github.com/semantic-org/semantic-ui/
  4. *
  5. *
  6. * Copyright 2015 Contributors
  7. * Released under the MIT license
  8. * http://opensource.org/licenses/MIT
  9. *
  10. */
  11. ;(function ( $, window, document, undefined ) {
  12. "use strict";
  13. $.fn.form = function(parameters) {
  14. var
  15. $allModules = $(this),
  16. moduleSelector = $allModules.selector || '',
  17. time = new Date().getTime(),
  18. performance = [],
  19. query = arguments[0],
  20. legacyParameters = arguments[1],
  21. methodInvoked = (typeof query == 'string'),
  22. queryArguments = [].slice.call(arguments, 1),
  23. returnedValue
  24. ;
  25. $allModules
  26. .each(function() {
  27. var
  28. $module = $(this),
  29. element = this,
  30. formErrors = [],
  31. keyHeldDown = false,
  32. // set at run-time
  33. $field,
  34. $group,
  35. $message,
  36. $prompt,
  37. $submit,
  38. $clear,
  39. $reset,
  40. settings,
  41. validation,
  42. metadata,
  43. selector,
  44. className,
  45. error,
  46. namespace,
  47. moduleNamespace,
  48. eventNamespace,
  49. instance,
  50. module
  51. ;
  52. module = {
  53. initialize: function() {
  54. // settings grabbed at run time
  55. module.get.settings();
  56. if(methodInvoked) {
  57. if(instance === undefined) {
  58. module.instantiate();
  59. }
  60. module.invoke(query);
  61. }
  62. else {
  63. module.verbose('Initializing form validation', $module, settings);
  64. module.bindEvents();
  65. module.set.defaults();
  66. module.instantiate();
  67. }
  68. },
  69. instantiate: function() {
  70. module.verbose('Storing instance of module', module);
  71. instance = module;
  72. $module
  73. .data(moduleNamespace, module)
  74. ;
  75. },
  76. destroy: function() {
  77. module.verbose('Destroying previous module', instance);
  78. module.removeEvents();
  79. $module
  80. .removeData(moduleNamespace)
  81. ;
  82. },
  83. refresh: function() {
  84. module.verbose('Refreshing selector cache');
  85. $field = $module.find(selector.field);
  86. $group = $module.find(selector.group);
  87. $message = $module.find(selector.message);
  88. $prompt = $module.find(selector.prompt);
  89. $submit = $module.find(selector.submit);
  90. $clear = $module.find(selector.clear);
  91. $reset = $module.find(selector.reset);
  92. },
  93. submit: function() {
  94. module.verbose('Submitting form', $module);
  95. $module
  96. .submit()
  97. ;
  98. },
  99. attachEvents: function(selector, action) {
  100. action = action || 'submit';
  101. $(selector)
  102. .on('click' + eventNamespace, function(event) {
  103. module[action]();
  104. event.preventDefault();
  105. })
  106. ;
  107. },
  108. bindEvents: function() {
  109. module.verbose('Attaching form events');
  110. $module
  111. .on('submit' + eventNamespace, module.validate.form)
  112. .on('blur' + eventNamespace, selector.field, module.event.field.blur)
  113. .on('click' + eventNamespace, selector.submit, module.submit)
  114. .on('click' + eventNamespace, selector.reset, module.reset)
  115. .on('click' + eventNamespace, selector.clear, module.clear)
  116. ;
  117. if(settings.keyboardShortcuts) {
  118. $module
  119. .on('keydown' + eventNamespace, selector.field, module.event.field.keydown)
  120. ;
  121. }
  122. $field
  123. .each(function() {
  124. var
  125. $input = $(this),
  126. type = $input.prop('type'),
  127. inputEvent = module.get.changeEvent(type, $input)
  128. ;
  129. $(this)
  130. .on(inputEvent + eventNamespace, module.event.field.change)
  131. ;
  132. })
  133. ;
  134. },
  135. clear: function() {
  136. $field
  137. .each(function () {
  138. var
  139. $field = $(this),
  140. $element = $field.parent(),
  141. $fieldGroup = $field.closest($group),
  142. $prompt = $fieldGroup.find(selector.prompt),
  143. defaultValue = $field.data(metadata.defaultValue) || '',
  144. isCheckbox = $element.is(selector.uiCheckbox),
  145. isDropdown = $element.is(selector.uiDropdown),
  146. isErrored = $fieldGroup.hasClass(className.error)
  147. ;
  148. if(isErrored) {
  149. module.verbose('Resetting error on field', $fieldGroup);
  150. $fieldGroup.removeClass(className.error);
  151. $prompt.remove();
  152. }
  153. if(isDropdown) {
  154. module.verbose('Resetting dropdown value', $element, defaultValue);
  155. $element.dropdown('clear');
  156. }
  157. else if(isCheckbox) {
  158. $field.prop('checked', false);
  159. }
  160. else {
  161. module.verbose('Resetting field value', $field, defaultValue);
  162. $field.val('');
  163. }
  164. })
  165. ;
  166. },
  167. reset: function() {
  168. $field
  169. .each(function () {
  170. var
  171. $field = $(this),
  172. $element = $field.parent(),
  173. $fieldGroup = $field.closest($group),
  174. $prompt = $fieldGroup.find(selector.prompt),
  175. defaultValue = $field.data(metadata.defaultValue),
  176. isCheckbox = $element.is(selector.uiCheckbox),
  177. isDropdown = $element.is(selector.uiDropdown),
  178. isErrored = $fieldGroup.hasClass(className.error)
  179. ;
  180. if(defaultValue === undefined) {
  181. return;
  182. }
  183. if(isErrored) {
  184. module.verbose('Resetting error on field', $fieldGroup);
  185. $fieldGroup.removeClass(className.error);
  186. $prompt.remove();
  187. }
  188. if(isDropdown) {
  189. module.verbose('Resetting dropdown value', $element, defaultValue);
  190. $element.dropdown('restore defaults');
  191. }
  192. else if(isCheckbox) {
  193. module.verbose('Resetting checkbox value', $element, defaultValue);
  194. $field.prop('checked', defaultValue);
  195. }
  196. else {
  197. module.verbose('Resetting field value', $field, defaultValue);
  198. $field.val(defaultValue);
  199. }
  200. })
  201. ;
  202. },
  203. is: {
  204. bracketedRule: function(rule) {
  205. return (rule.type && rule.type.match(settings.regExp.bracket));
  206. },
  207. valid: function() {
  208. var
  209. allValid = true
  210. ;
  211. module.verbose('Checking if form is valid');
  212. $.each(validation, function(fieldName, field) {
  213. if( !( module.validate.field(field, fieldName) ) ) {
  214. allValid = false;
  215. }
  216. });
  217. return allValid;
  218. }
  219. },
  220. removeEvents: function() {
  221. $module
  222. .off(eventNamespace)
  223. ;
  224. $field
  225. .off(eventNamespace)
  226. ;
  227. $submit
  228. .off(eventNamespace)
  229. ;
  230. $field
  231. .off(eventNamespace)
  232. ;
  233. },
  234. event: {
  235. field: {
  236. keydown: function(event) {
  237. var
  238. $field = $(this),
  239. key = event.which,
  240. keyCode = {
  241. enter : 13,
  242. escape : 27
  243. }
  244. ;
  245. if( key == keyCode.escape) {
  246. module.verbose('Escape key pressed blurring field');
  247. $field
  248. .blur()
  249. ;
  250. }
  251. if(!event.ctrlKey && key == keyCode.enter && $field.is(selector.input) && $field.not(selector.checkbox).length > 0 ) {
  252. if(!keyHeldDown) {
  253. $field
  254. .one('keyup' + eventNamespace, module.event.field.keyup)
  255. ;
  256. module.submit();
  257. module.debug('Enter pressed on input submitting form');
  258. }
  259. keyHeldDown = true;
  260. }
  261. },
  262. keyup: function() {
  263. keyHeldDown = false;
  264. },
  265. blur: function(event) {
  266. var
  267. $field = $(this),
  268. $fieldGroup = $field.closest($group),
  269. validationRules = module.get.validation($field)
  270. ;
  271. if( $fieldGroup.hasClass(className.error) ) {
  272. module.debug('Revalidating field', $field, validationRules);
  273. module.validate.form.call(module, event, true);
  274. }
  275. else if(settings.on == 'blur' || settings.on == 'change') {
  276. if(validationRules) {
  277. module.validate.field( validationRules );
  278. }
  279. }
  280. },
  281. change: function(event) {
  282. var
  283. $field = $(this),
  284. $fieldGroup = $field.closest($group)
  285. ;
  286. if(settings.on == 'change' || ( $fieldGroup.hasClass(className.error) && settings.revalidate) ) {
  287. clearTimeout(module.timer);
  288. module.timer = setTimeout(function() {
  289. module.debug('Revalidating field', $field, module.get.validation($field));
  290. module.validate.form.call(module, event, true);
  291. }, settings.delay);
  292. }
  293. }
  294. }
  295. },
  296. get: {
  297. ancillaryValue: function(rule) {
  298. if(!rule.type || !module.is.bracketedRule(rule)) {
  299. return false;
  300. }
  301. return rule.type.match(settings.regExp.bracket)[1] + '';
  302. },
  303. ruleName: function(rule) {
  304. if( module.is.bracketedRule(rule) ) {
  305. return rule.type.replace(rule.type.match(settings.regExp.bracket)[0], '');
  306. }
  307. return rule.type;
  308. },
  309. changeEvent: function(type, $input) {
  310. if(type == 'checkbox' || type == 'radio' || type == 'hidden' || $input.is('select')) {
  311. return 'change';
  312. }
  313. else {
  314. return module.get.inputEvent();
  315. }
  316. },
  317. inputEvent: function() {
  318. return (document.createElement('input').oninput !== undefined)
  319. ? 'input'
  320. : (document.createElement('input').onpropertychange !== undefined)
  321. ? 'propertychange'
  322. : 'keyup'
  323. ;
  324. },
  325. prompt: function(rule, field) {
  326. var
  327. ruleName = module.get.ruleName(rule),
  328. ancillary = module.get.ancillaryValue(rule),
  329. prompt = rule.prompt || settings.prompt[ruleName] || settings.text.unspecifiedRule,
  330. requiresValue = (prompt.search('{value}') !== -1),
  331. requiresName = (prompt.search('{name}') !== -1),
  332. $label,
  333. $field,
  334. name
  335. ;
  336. if(requiresName || requiresValue) {
  337. $field = module.get.field(field.identifier);
  338. }
  339. if(requiresValue) {
  340. prompt = prompt.replace('{value}', $field.val());
  341. }
  342. if(requiresName) {
  343. $label = $field.closest(selector.group).find('label').eq(0);
  344. name = ($label.size() == 1)
  345. ? $label.text()
  346. : $field.prop('placeholder') || settings.text.unspecifiedField
  347. ;
  348. prompt = prompt.replace('{name}', name);
  349. }
  350. prompt = prompt.replace('{identifier}', field.identifier);
  351. prompt = prompt.replace('{ruleValue}', ancillary);
  352. if(!rule.prompt) {
  353. module.verbose('Using default validation prompt for type', prompt, ruleName);
  354. }
  355. return prompt;
  356. },
  357. settings: function() {
  358. if($.isPlainObject(parameters)) {
  359. var
  360. keys = Object.keys(parameters),
  361. isLegacySettings = (keys.length > 0)
  362. ? (parameters[keys[0]].identifier !== undefined && parameters[keys[0]].rules !== undefined)
  363. : false,
  364. ruleKeys
  365. ;
  366. if(isLegacySettings) {
  367. // 1.x (ducktyped)
  368. settings = $.extend(true, {}, $.fn.form.settings, legacyParameters);
  369. validation = $.extend({}, $.fn.form.settings.defaults, parameters);
  370. module.error(settings.error.oldSyntax, element);
  371. module.verbose('Extending settings from legacy parameters', validation, settings);
  372. }
  373. else {
  374. // 2.x
  375. if(parameters.fields) {
  376. ruleKeys = Object.keys(parameters.fields);
  377. if( typeof parameters.fields[ruleKeys[0]] == 'string' || $.isArray(parameters.fields[ruleKeys[0]]) ) {
  378. $.each(parameters.fields, function(name, rules) {
  379. if(typeof rules == 'string') {
  380. rules = [rules];
  381. }
  382. parameters.fields[name] = {
  383. rules: []
  384. };
  385. $.each(rules, function(index, rule) {
  386. parameters.fields[name].rules.push({ type: rule });
  387. });
  388. });
  389. }
  390. }
  391. settings = $.extend(true, {}, $.fn.form.settings, parameters);
  392. validation = $.extend({}, $.fn.form.settings.defaults, settings.fields);
  393. module.verbose('Extending settings', validation, settings);
  394. }
  395. }
  396. else {
  397. settings = $.fn.form.settings;
  398. validation = $.fn.form.settings.defaults;
  399. module.verbose('Using default form validation', validation, settings);
  400. }
  401. // shorthand
  402. namespace = settings.namespace;
  403. metadata = settings.metadata;
  404. selector = settings.selector;
  405. className = settings.className;
  406. error = settings.error;
  407. moduleNamespace = 'module-' + namespace;
  408. eventNamespace = '.' + namespace;
  409. // grab instance
  410. instance = $module.data(moduleNamespace);
  411. // refresh selector cache
  412. module.refresh();
  413. },
  414. field: function(identifier) {
  415. module.verbose('Finding field with identifier', identifier);
  416. if( $field.filter('#' + identifier).length > 0 ) {
  417. return $field.filter('#' + identifier);
  418. }
  419. else if( $field.filter('[name="' + identifier +'"]').length > 0 ) {
  420. return $field.filter('[name="' + identifier +'"]');
  421. }
  422. else if( $field.filter('[name="' + identifier +'[]"]').length > 0 ) {
  423. return $field.filter('[name="' + identifier +'[]"]');
  424. }
  425. else if( $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]').length > 0 ) {
  426. return $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]');
  427. }
  428. return $('<input/>');
  429. },
  430. fields: function(fields) {
  431. var
  432. $fields = $()
  433. ;
  434. $.each(fields, function(index, name) {
  435. $fields = $fields.add( module.get.field(name) );
  436. });
  437. return $fields;
  438. },
  439. validation: function($field) {
  440. var
  441. fieldValidation,
  442. identifier
  443. ;
  444. if(!validation) {
  445. return false;
  446. }
  447. $.each(validation, function(fieldName, field) {
  448. identifier = field.identifier || fieldName;
  449. if( module.get.field(identifier)[0] == $field[0] ) {
  450. field.identifier = identifier;
  451. fieldValidation = field;
  452. }
  453. });
  454. return fieldValidation || false;
  455. },
  456. value: function (field) {
  457. var
  458. fields = [],
  459. results
  460. ;
  461. fields.push(field);
  462. results = module.get.values.call(element, fields);
  463. return results[field];
  464. },
  465. values: function (fields) {
  466. var
  467. $fields = $.isArray(fields)
  468. ? module.get.fields(fields)
  469. : $field,
  470. values = {}
  471. ;
  472. $fields.each(function(index, field) {
  473. var
  474. $field = $(field),
  475. type = $field.prop('type'),
  476. name = $field.prop('name'),
  477. value = $field.val(),
  478. isCheckbox = $field.is(selector.checkbox),
  479. isRadio = $field.is(selector.radio),
  480. isMultiple = (name.indexOf('[]') !== -1),
  481. isChecked = (isCheckbox)
  482. ? $field.is(':checked')
  483. : false
  484. ;
  485. if(name) {
  486. if(isMultiple) {
  487. name = name.replace('[]', '');
  488. if(!values[name]) {
  489. values[name] = [];
  490. }
  491. if(isCheckbox) {
  492. if(isChecked) {
  493. values[name].push(value || true);
  494. }
  495. else {
  496. values[name].push(false);
  497. }
  498. }
  499. else {
  500. values[name].push(value);
  501. }
  502. }
  503. else {
  504. if(isRadio) {
  505. if(isChecked) {
  506. values[name] = value;
  507. }
  508. }
  509. else if(isCheckbox) {
  510. if(isChecked) {
  511. values[name] = value || true;
  512. }
  513. else {
  514. values[name] = false;
  515. }
  516. }
  517. else {
  518. values[name] = value;
  519. }
  520. }
  521. }
  522. });
  523. return values;
  524. }
  525. },
  526. has: {
  527. field: function(identifier) {
  528. module.verbose('Checking for existence of a field with identifier', identifier);
  529. if(typeof identifier !== 'string') {
  530. module.error(error.identifier, identifier);
  531. }
  532. if( $field.filter('#' + identifier).length > 0 ) {
  533. return true;
  534. }
  535. else if( $field.filter('[name="' + identifier +'"]').length > 0 ) {
  536. return true;
  537. }
  538. else if( $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]').length > 0 ) {
  539. return true;
  540. }
  541. return false;
  542. }
  543. },
  544. add: {
  545. prompt: function(identifier, errors) {
  546. var
  547. $field = module.get.field(identifier),
  548. $fieldGroup = $field.closest($group),
  549. $prompt = $fieldGroup.children(selector.prompt),
  550. promptExists = ($prompt.length !== 0)
  551. ;
  552. errors = (typeof errors == 'string')
  553. ? [errors]
  554. : errors
  555. ;
  556. module.verbose('Adding field error state', identifier);
  557. $fieldGroup
  558. .addClass(className.error)
  559. ;
  560. if(settings.inline) {
  561. if(!promptExists) {
  562. $prompt = settings.templates.prompt(errors);
  563. $prompt
  564. .appendTo($fieldGroup)
  565. ;
  566. }
  567. $prompt
  568. .html(errors[0])
  569. ;
  570. if(!promptExists) {
  571. if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
  572. module.verbose('Displaying error with css transition', settings.transition);
  573. $prompt.transition(settings.transition + ' in', settings.duration);
  574. }
  575. else {
  576. module.verbose('Displaying error with fallback javascript animation');
  577. $prompt
  578. .fadeIn(settings.duration)
  579. ;
  580. }
  581. }
  582. else {
  583. module.verbose('Inline errors are disabled, no inline error added', identifier);
  584. }
  585. }
  586. },
  587. errors: function(errors) {
  588. module.debug('Adding form error messages', errors);
  589. module.set.error();
  590. $message
  591. .html( settings.templates.error(errors) )
  592. ;
  593. }
  594. },
  595. remove: {
  596. prompt: function(identifier) {
  597. var
  598. $field = module.get.field(identifier),
  599. $fieldGroup = $field.closest($group),
  600. $prompt = $fieldGroup.children(selector.prompt)
  601. ;
  602. $fieldGroup
  603. .removeClass(className.error)
  604. ;
  605. if(settings.inline && $prompt.is(':visible')) {
  606. module.verbose('Removing prompt for field', identifier);
  607. if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
  608. $prompt.transition(settings.transition + ' out', settings.duration, function() {
  609. $prompt.remove();
  610. });
  611. }
  612. else {
  613. $prompt
  614. .fadeOut(settings.duration, function(){
  615. $prompt.remove();
  616. })
  617. ;
  618. }
  619. }
  620. }
  621. },
  622. set: {
  623. success: function() {
  624. $module
  625. .removeClass(className.error)
  626. .addClass(className.success)
  627. ;
  628. },
  629. defaults: function () {
  630. $field
  631. .each(function () {
  632. var
  633. $field = $(this),
  634. isCheckbox = ($field.filter(selector.checkbox).length > 0),
  635. value = (isCheckbox)
  636. ? $field.is(':checked')
  637. : $field.val()
  638. ;
  639. $field.data(metadata.defaultValue, value);
  640. })
  641. ;
  642. },
  643. error: function() {
  644. $module
  645. .removeClass(className.success)
  646. .addClass(className.error)
  647. ;
  648. },
  649. value: function (field, value) {
  650. var
  651. fields = {}
  652. ;
  653. fields[field] = value;
  654. return module.set.values.call(element, fields);
  655. },
  656. values: function (fields) {
  657. if($.isEmptyObject(fields)) {
  658. return;
  659. }
  660. $.each(fields, function(key, value) {
  661. var
  662. $field = module.get.field(key),
  663. $element = $field.parent(),
  664. isMultiple = $.isArray(value),
  665. isCheckbox = $element.is(selector.uiCheckbox),
  666. isDropdown = $element.is(selector.uiDropdown),
  667. isRadio = ($field.is(selector.radio) && isCheckbox),
  668. fieldExists = ($field.length > 0),
  669. $multipleField
  670. ;
  671. if(fieldExists) {
  672. if(isMultiple && isCheckbox) {
  673. module.verbose('Selecting multiple', value, $field);
  674. $element.checkbox('uncheck');
  675. $.each(value, function(index, value) {
  676. $multipleField = $field.filter('[value="' + value + '"]');
  677. $element = $multipleField.parent();
  678. if($multipleField.length > 0) {
  679. $element.checkbox('check');
  680. }
  681. });
  682. }
  683. else if(isRadio) {
  684. module.verbose('Selecting radio value', value, $field);
  685. $field.filter('[value="' + value + '"]')
  686. .parent(selector.uiCheckbox)
  687. .checkbox('check')
  688. ;
  689. }
  690. else if(isCheckbox) {
  691. module.verbose('Setting checkbox value', value, $element);
  692. if(value === true) {
  693. $element.checkbox('check');
  694. }
  695. else {
  696. $element.checkbox('uncheck');
  697. }
  698. }
  699. else if(isDropdown) {
  700. module.verbose('Setting dropdown value', value, $element);
  701. $element.dropdown('set selected', value);
  702. }
  703. else {
  704. module.verbose('Setting field value', value, $field);
  705. $field.val(value);
  706. }
  707. }
  708. });
  709. }
  710. },
  711. validate: {
  712. form: function(event, ignoreCallbacks) {
  713. var
  714. values = module.get.values(),
  715. apiRequest
  716. ;
  717. // input keydown event will fire submit repeatedly by browser default
  718. if(keyHeldDown) {
  719. return false;
  720. }
  721. // reset errors
  722. formErrors = [];
  723. if( module.is.valid() ) {
  724. module.debug('Form has no validation errors, submitting');
  725. module.set.success();
  726. if(ignoreCallbacks !== true) {
  727. return settings.onSuccess.call(element, event, values);
  728. }
  729. }
  730. else {
  731. module.debug('Form has errors');
  732. module.set.error();
  733. if(!settings.inline) {
  734. module.add.errors(formErrors);
  735. }
  736. // prevent ajax submit
  737. if($module.data('moduleApi') !== undefined) {
  738. event.stopImmediatePropagation();
  739. }
  740. if(ignoreCallbacks !== true) {
  741. return settings.onFailure.call(element, formErrors, values);
  742. }
  743. }
  744. },
  745. // takes a validation object and returns whether field passes validation
  746. field: function(field, fieldName) {
  747. var
  748. identifier = field.identifier || fieldName,
  749. $field = module.get.field(identifier),
  750. fieldValid = true,
  751. fieldErrors = []
  752. ;
  753. if(!field.identifier) {
  754. module.debug('Using field name as identifier', identifier);
  755. field.identifier = identifier;
  756. }
  757. if($field.prop('disabled')) {
  758. module.debug('Field is disabled. Skipping', identifier);
  759. fieldValid = true;
  760. }
  761. else if(field.optional && $.trim($field.val()) === ''){
  762. module.debug('Field is optional and empty. Skipping', identifier);
  763. fieldValid = true;
  764. }
  765. else if(field.rules !== undefined) {
  766. $.each(field.rules, function(index, rule) {
  767. if( module.has.field(identifier) && !( module.validate.rule(field, rule) ) ) {
  768. module.debug('Field is invalid', identifier, rule.type);
  769. fieldErrors.push(module.get.prompt(rule, field));
  770. fieldValid = false;
  771. }
  772. });
  773. }
  774. if(fieldValid) {
  775. module.remove.prompt(identifier, fieldErrors);
  776. settings.onValid.call($field);
  777. }
  778. else {
  779. formErrors = formErrors.concat(fieldErrors);
  780. module.add.prompt(identifier, fieldErrors);
  781. settings.onInvalid.call($field, fieldErrors);
  782. return false;
  783. }
  784. return true;
  785. },
  786. // takes validation rule and returns whether field passes rule
  787. rule: function(field, rule) {
  788. var
  789. $field = module.get.field(field.identifier),
  790. type = rule.type,
  791. value = $field.val(),
  792. isValid = true,
  793. ancillary = module.get.ancillaryValue(rule),
  794. ruleName = module.get.ruleName(rule),
  795. ruleFunction = settings.rules[ruleName]
  796. ;
  797. if( !$.isFunction(ruleFunction) ) {
  798. module.error(error.noRule, ruleName);
  799. return;
  800. }
  801. // cast to string avoiding encoding special values
  802. value = (value === undefined || value === '' || value === null)
  803. ? ''
  804. : $.trim(value + '')
  805. ;
  806. return ruleFunction.call($field, value, ancillary);
  807. }
  808. },
  809. setting: function(name, value) {
  810. if( $.isPlainObject(name) ) {
  811. $.extend(true, settings, name);
  812. }
  813. else if(value !== undefined) {
  814. settings[name] = value;
  815. }
  816. else {
  817. return settings[name];
  818. }
  819. },
  820. internal: function(name, value) {
  821. if( $.isPlainObject(name) ) {
  822. $.extend(true, module, name);
  823. }
  824. else if(value !== undefined) {
  825. module[name] = value;
  826. }
  827. else {
  828. return module[name];
  829. }
  830. },
  831. debug: function() {
  832. if(settings.debug) {
  833. if(settings.performance) {
  834. module.performance.log(arguments);
  835. }
  836. else {
  837. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  838. module.debug.apply(console, arguments);
  839. }
  840. }
  841. },
  842. verbose: function() {
  843. if(settings.verbose && settings.debug) {
  844. if(settings.performance) {
  845. module.performance.log(arguments);
  846. }
  847. else {
  848. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  849. module.verbose.apply(console, arguments);
  850. }
  851. }
  852. },
  853. error: function() {
  854. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  855. module.error.apply(console, arguments);
  856. },
  857. performance: {
  858. log: function(message) {
  859. var
  860. currentTime,
  861. executionTime,
  862. previousTime
  863. ;
  864. if(settings.performance) {
  865. currentTime = new Date().getTime();
  866. previousTime = time || currentTime;
  867. executionTime = currentTime - previousTime;
  868. time = currentTime;
  869. performance.push({
  870. 'Name' : message[0],
  871. 'Arguments' : [].slice.call(message, 1) || '',
  872. 'Element' : element,
  873. 'Execution Time' : executionTime
  874. });
  875. }
  876. clearTimeout(module.performance.timer);
  877. module.performance.timer = setTimeout(module.performance.display, 500);
  878. },
  879. display: function() {
  880. var
  881. title = settings.name + ':',
  882. totalTime = 0
  883. ;
  884. time = false;
  885. clearTimeout(module.performance.timer);
  886. $.each(performance, function(index, data) {
  887. totalTime += data['Execution Time'];
  888. });
  889. title += ' ' + totalTime + 'ms';
  890. if(moduleSelector) {
  891. title += ' \'' + moduleSelector + '\'';
  892. }
  893. if($allModules.length > 1) {
  894. title += ' ' + '(' + $allModules.length + ')';
  895. }
  896. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  897. console.groupCollapsed(title);
  898. if(console.table) {
  899. console.table(performance);
  900. }
  901. else {
  902. $.each(performance, function(index, data) {
  903. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  904. });
  905. }
  906. console.groupEnd();
  907. }
  908. performance = [];
  909. }
  910. },
  911. invoke: function(query, passedArguments, context) {
  912. var
  913. object = instance,
  914. maxDepth,
  915. found,
  916. response
  917. ;
  918. passedArguments = passedArguments || queryArguments;
  919. context = element || context;
  920. if(typeof query == 'string' && object !== undefined) {
  921. query = query.split(/[\. ]/);
  922. maxDepth = query.length - 1;
  923. $.each(query, function(depth, value) {
  924. var camelCaseValue = (depth != maxDepth)
  925. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  926. : query
  927. ;
  928. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  929. object = object[camelCaseValue];
  930. }
  931. else if( object[camelCaseValue] !== undefined ) {
  932. found = object[camelCaseValue];
  933. return false;
  934. }
  935. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  936. object = object[value];
  937. }
  938. else if( object[value] !== undefined ) {
  939. found = object[value];
  940. return false;
  941. }
  942. else {
  943. return false;
  944. }
  945. });
  946. }
  947. if( $.isFunction( found ) ) {
  948. response = found.apply(context, passedArguments);
  949. }
  950. else if(found !== undefined) {
  951. response = found;
  952. }
  953. if($.isArray(returnedValue)) {
  954. returnedValue.push(response);
  955. }
  956. else if(returnedValue !== undefined) {
  957. returnedValue = [returnedValue, response];
  958. }
  959. else if(response !== undefined) {
  960. returnedValue = response;
  961. }
  962. return found;
  963. }
  964. };
  965. module.initialize();
  966. })
  967. ;
  968. return (returnedValue !== undefined)
  969. ? returnedValue
  970. : this
  971. ;
  972. };
  973. $.fn.form.settings = {
  974. name : 'Form',
  975. namespace : 'form',
  976. debug : false,
  977. verbose : false,
  978. performance : true,
  979. fields : false,
  980. keyboardShortcuts : true,
  981. on : 'submit',
  982. inline : false,
  983. delay : 200,
  984. revalidate : true,
  985. transition : 'scale',
  986. duration : 200,
  987. onValid : function() {},
  988. onInvalid : function() {},
  989. onSuccess : function() { return true; },
  990. onFailure : function() { return false; },
  991. metadata : {
  992. defaultValue : 'default',
  993. validate : 'validate'
  994. },
  995. regExp: {
  996. bracket : /\[(.*)\]/i,
  997. decimal : /^\d*(\.)\d+/,
  998. email : "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?",
  999. escape : /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,
  1000. flags : /^\/(.*)\/(.*)?/,
  1001. integer : /^\-?\d+$/,
  1002. number : /^\-?\d*(\.\d+)?$/,
  1003. url : /(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/i
  1004. },
  1005. text: {
  1006. unspecifiedRule : 'Please enter a valid value',
  1007. unspecifiedField : 'This field'
  1008. },
  1009. prompt: {
  1010. empty : '{name} must have a value',
  1011. checked : '{name} must be checked',
  1012. email : '{name} must be a valid e-mail',
  1013. url : '{name} must be a valid url',
  1014. regExp : '{name} is not formatted correctly',
  1015. integer : '{name} must be an integer',
  1016. decimal : '{name} must be a decimal number',
  1017. number : '{name} must be set to a number',
  1018. is : '{name} must be "{ruleValue}"',
  1019. isExactly : '{name} must be exactly "{ruleValue}"',
  1020. not : '{name} cannot be set to "{ruleValue}"',
  1021. notExactly : '{name} cannot be set to exactly "{ruleValue}"',
  1022. contain : '{name} cannot contain "{ruleValue}"',
  1023. containExactly : '{name} cannot contain exactly "{ruleValue}"',
  1024. doesntContain : '{name} must contain "{ruleValue}"',
  1025. doesntContainExactly : '{name} must contain exactly "{ruleValue}"',
  1026. minLength : '{name} must be at least {ruleValue} characters',
  1027. length : '{name} must be at least {ruleValue} characters',
  1028. exactLength : '{name} must be exactly {ruleValue} characters',
  1029. maxLength : '{name} cannot be longer than {ruleValue} characters',
  1030. match : '{name} must match {ruleValue} field',
  1031. different : '{name} must have a different value than {ruleValue} field',
  1032. creditCard : '{name} must be a valid credit card number',
  1033. minCount : '{name} must have at least {ruleValue} choices',
  1034. exactCount : '{name} must have exactly {ruleValue} choices',
  1035. maxCount : '{name} must have {ruleValue} or less choices'
  1036. },
  1037. selector : {
  1038. checkbox : 'input[type="checkbox"], input[type="radio"]',
  1039. clear : '.clear',
  1040. field : 'input, textarea, select',
  1041. group : '.field',
  1042. input : 'input',
  1043. message : '.error.message',
  1044. prompt : '.prompt.label',
  1045. radio : 'input[type="radio"]',
  1046. reset : '.reset:not([type="reset"])',
  1047. submit : '.submit:not([type="submit"])',
  1048. uiCheckbox : '.ui.checkbox',
  1049. uiDropdown : '.ui.dropdown'
  1050. },
  1051. className : {
  1052. error : 'error',
  1053. label : 'ui prompt label',
  1054. pressed : 'down',
  1055. success : 'success'
  1056. },
  1057. error: {
  1058. identifier : 'You must specify a string identifier for each field',
  1059. method : 'The method you called is not defined.',
  1060. noRule : 'There is no rule matching the one you specified',
  1061. oldSyntax : 'Starting in 2.0 forms now only take a single settings object. Validation settings converted to new syntax automatically.'
  1062. },
  1063. templates: {
  1064. // template that produces error message
  1065. error: function(errors) {
  1066. var
  1067. html = '<ul class="list">'
  1068. ;
  1069. $.each(errors, function(index, value) {
  1070. html += '<li>' + value + '</li>';
  1071. });
  1072. html += '</ul>';
  1073. return $(html);
  1074. },
  1075. // template that produces label
  1076. prompt: function(errors) {
  1077. return $('<div/>')
  1078. .addClass('ui basic red pointing prompt label')
  1079. .html(errors[0])
  1080. ;
  1081. }
  1082. },
  1083. rules: {
  1084. // is not empty or blank string
  1085. empty: function(value) {
  1086. return !(value === undefined || '' === value || $.isArray(value) && value.length === 0);
  1087. },
  1088. // checkbox checked
  1089. checked: function() {
  1090. return ($(this).filter(':checked').length > 0);
  1091. },
  1092. // is most likely an email
  1093. email: function(value){
  1094. var
  1095. emailRegExp = new RegExp($.fn.form.settings.regExp.email, 'i')
  1096. ;
  1097. return emailRegExp.test(value);
  1098. },
  1099. // value is most likely url
  1100. url: function(value) {
  1101. return $.fn.form.settings.regExp.url.test(value);
  1102. },
  1103. // matches specified regExp
  1104. regExp: function(value, regExp) {
  1105. var
  1106. regExpParts = regExp.match($.fn.form.settings.regExp.flags),
  1107. flags
  1108. ;
  1109. // regular expression specified as /baz/gi (flags)
  1110. if(regExpParts) {
  1111. regExp = (regExpParts.length >= 2)
  1112. ? regExpParts[1]
  1113. : regExp
  1114. ;
  1115. flags = (regExpParts.length >= 3)
  1116. ? regExpParts[2]
  1117. : ''
  1118. ;
  1119. }
  1120. return value.match( new RegExp(regExp, flags) );
  1121. },
  1122. // is valid integer or matches range
  1123. integer: function(value, range) {
  1124. var
  1125. intRegExp = $.fn.form.settings.regExp.integer,
  1126. min,
  1127. max,
  1128. parts
  1129. ;
  1130. if( !range || ['', '..'].indexOf(range) !== -1) {
  1131. // do nothing
  1132. }
  1133. else if(range.indexOf('..') == -1) {
  1134. if(intRegExp.test(range)) {
  1135. min = max = range - 0;
  1136. }
  1137. }
  1138. else {
  1139. parts = range.split('..', 2);
  1140. if(intRegExp.test(parts[0])) {
  1141. min = parts[0] - 0;
  1142. }
  1143. if(intRegExp.test(parts[1])) {
  1144. max = parts[1] - 0;
  1145. }
  1146. }
  1147. return (
  1148. intRegExp.test(value) &&
  1149. (min === undefined || value >= min) &&
  1150. (max === undefined || value <= max)
  1151. );
  1152. },
  1153. // is valid number (with decimal)
  1154. decimal: function(value) {
  1155. return $.fn.form.settings.regExp.decimal.test(value);
  1156. },
  1157. // is valid number
  1158. number: function(value) {
  1159. return $.fn.form.settings.regExp.number.test(value);
  1160. },
  1161. // is value (case insensitive)
  1162. is: function(value, text) {
  1163. text = (typeof text == 'string')
  1164. ? text.toLowerCase()
  1165. : text
  1166. ;
  1167. value = (typeof value == 'string')
  1168. ? value.toLowerCase()
  1169. : value
  1170. ;
  1171. return (value == text);
  1172. },
  1173. // is value
  1174. isExactly: function(value, text) {
  1175. return (value == text);
  1176. },
  1177. // value is not another value (case insensitive)
  1178. not: function(value, notValue) {
  1179. value = (typeof value == 'string')
  1180. ? value.toLowerCase()
  1181. : value
  1182. ;
  1183. notValue = (typeof notValue == 'string')
  1184. ? notValue.toLowerCase()
  1185. : notValue
  1186. ;
  1187. return (value != notValue);
  1188. },
  1189. // value is not another value (case sensitive)
  1190. notExactly: function(value, notValue) {
  1191. return (value != notValue);
  1192. },
  1193. // value contains text (insensitive)
  1194. contains: function(value, text) {
  1195. // escape regex characters
  1196. text = text.replace($.fn.form.settings.regExp.escape, "\\$&");
  1197. return (value.search( new RegExp(text, 'i') ) !== -1);
  1198. },
  1199. // value contains text (case sensitive)
  1200. containsExactly: function(value, text) {
  1201. // escape regex characters
  1202. text = text.replace($.fn.form.settings.regExp.escape, "\\$&");
  1203. return (value.search( new RegExp(text) ) !== -1);
  1204. },
  1205. // value contains text (insensitive)
  1206. doesntContain: function(value, text) {
  1207. // escape regex characters
  1208. text = text.replace($.fn.form.settings.regExp.escape, "\\$&");
  1209. return (value.search( new RegExp(text, 'i') ) === -1);
  1210. },
  1211. // value contains text (case sensitive)
  1212. doesntContainExactly: function(value, text) {
  1213. // escape regex characters
  1214. text = text.replace($.fn.form.settings.regExp.escape, "\\$&");
  1215. return (value.search( new RegExp(text) ) === -1);
  1216. },
  1217. // is at least string length
  1218. minLength: function(value, requiredLength) {
  1219. return (value !== undefined)
  1220. ? (value.length >= requiredLength)
  1221. : false
  1222. ;
  1223. },
  1224. // see rls notes for 2.0.6 (this is a duplicate of minLength)
  1225. length: function(value, requiredLength) {
  1226. return (value !== undefined)
  1227. ? (value.length >= requiredLength)
  1228. : false
  1229. ;
  1230. },
  1231. // is exactly length
  1232. exactLength: function(value, requiredLength) {
  1233. return (value !== undefined)
  1234. ? (value.length == requiredLength)
  1235. : false
  1236. ;
  1237. },
  1238. // is less than length
  1239. maxLength: function(value, maxLength) {
  1240. return (value !== undefined)
  1241. ? (value.length <= maxLength)
  1242. : false
  1243. ;
  1244. },
  1245. // matches another field
  1246. match: function(value, identifier) {
  1247. var
  1248. $form = $(this),
  1249. matchingValue
  1250. ;
  1251. if( $('[data-validate="'+ identifier +'"]').length > 0 ) {
  1252. matchingValue = $('[data-validate="'+ identifier +'"]').val();
  1253. }
  1254. else if($('#' + identifier).length > 0) {
  1255. matchingValue = $('#' + identifier).val();
  1256. }
  1257. else if($('[name="' + identifier +'"]').length > 0) {
  1258. matchingValue = $('[name="' + identifier + '"]').val();
  1259. }
  1260. else if( $('[name="' + identifier +'[]"]').length > 0 ) {
  1261. matchingValue = $('[name="' + identifier +'[]"]');
  1262. }
  1263. return (matchingValue !== undefined)
  1264. ? ( value.toString() == matchingValue.toString() )
  1265. : false
  1266. ;
  1267. },
  1268. // different than another field
  1269. different: function(value, identifier) {
  1270. // use either id or name of field
  1271. var
  1272. $form = $(this),
  1273. matchingValue
  1274. ;
  1275. if( $('[data-validate="'+ identifier +'"]').length > 0 ) {
  1276. matchingValue = $('[data-validate="'+ identifier +'"]').val();
  1277. }
  1278. else if($('#' + identifier).length > 0) {
  1279. matchingValue = $('#' + identifier).val();
  1280. }
  1281. else if($('[name="' + identifier +'"]').length > 0) {
  1282. matchingValue = $('[name="' + identifier + '"]').val();
  1283. }
  1284. else if( $('[name="' + identifier +'[]"]').length > 0 ) {
  1285. matchingValue = $('[name="' + identifier +'[]"]');
  1286. }
  1287. return (matchingValue !== undefined)
  1288. ? ( value.toString() !== matchingValue.toString() )
  1289. : false
  1290. ;
  1291. },
  1292. creditCard: function(cardNumber, cardTypes) {
  1293. var
  1294. cards = {
  1295. visa: {
  1296. pattern : /^4/,
  1297. length : [16]
  1298. },
  1299. amex: {
  1300. pattern : /^3[47]/,
  1301. length : [15]
  1302. },
  1303. mastercard: {
  1304. pattern : /^5[1-5]/,
  1305. length : [16]
  1306. },
  1307. discover: {
  1308. pattern : /^(6011|622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[0-1][0-9]|92[0-5]|64[4-9])|65)/,
  1309. length : [16]
  1310. },
  1311. unionPay: {
  1312. pattern : /^(62|88)/,
  1313. length : [16, 17, 18, 19]
  1314. },
  1315. jcb: {
  1316. pattern : /^35(2[89]|[3-8][0-9])/,
  1317. length : [16]
  1318. },
  1319. maestro: {
  1320. pattern : /^(5018|5020|5038|6304|6759|676[1-3])/,
  1321. length : [12, 13, 14, 15, 16, 17, 18, 19]
  1322. },
  1323. dinersClub: {
  1324. pattern : /^(30[0-5]|^36)/,
  1325. length : [14]
  1326. },
  1327. laser: {
  1328. pattern : /^(6304|670[69]|6771)/,
  1329. length : [16, 17, 18, 19]
  1330. },
  1331. visaElectron: {
  1332. pattern : /^(4026|417500|4508|4844|491(3|7))/,
  1333. length : [16]
  1334. }
  1335. },
  1336. valid = {},
  1337. validCard = false,
  1338. requiredTypes = (typeof cardTypes == 'string')
  1339. ? cardTypes.split(',')
  1340. : false,
  1341. unionPay,
  1342. validation
  1343. ;
  1344. if(typeof cardNumber !== 'string' || cardNumber.length === 0) {
  1345. return;
  1346. }
  1347. // verify card types
  1348. if(requiredTypes) {
  1349. $.each(requiredTypes, function(index, type){
  1350. // verify each card type
  1351. validation = cards[type];
  1352. if(validation) {
  1353. valid = {
  1354. length : ($.inArray(cardNumber.length, validation.length) !== -1),
  1355. pattern : (cardNumber.search(validation.pattern) !== -1)
  1356. };
  1357. if(valid.length && valid.pattern) {
  1358. validCard = true;
  1359. }
  1360. }
  1361. });
  1362. if(!validCard) {
  1363. return false;
  1364. }
  1365. }
  1366. // skip luhn for UnionPay
  1367. unionPay = {
  1368. number : ($.inArray(cardNumber.length, cards.unionPay.length) !== -1),
  1369. pattern : (cardNumber.search(cards.unionPay.pattern) !== -1)
  1370. };
  1371. if(unionPay.number && unionPay.pattern) {
  1372. return true;
  1373. }
  1374. // verify luhn, adapted from <https://gist.github.com/2134376>
  1375. var
  1376. length = cardNumber.length,
  1377. multiple = 0,
  1378. producedValue = [
  1379. [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
  1380. [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]
  1381. ],
  1382. sum = 0
  1383. ;
  1384. while (length--) {
  1385. sum += producedValue[multiple][parseInt(cardNumber.charAt(length), 10)];
  1386. multiple ^= 1;
  1387. }
  1388. return (sum % 10 === 0 && sum > 0);
  1389. },
  1390. minCount: function(value, minCount) {
  1391. if(minCount == 0) {
  1392. return true;
  1393. }
  1394. if(minCount == 1) {
  1395. return (value !== '');
  1396. }
  1397. return (value.split(',').length >= minCount);
  1398. },
  1399. exactCount: function(value, exactCount) {
  1400. if(exactCount == 0) {
  1401. return (value === '');
  1402. }
  1403. if(exactCount == 1) {
  1404. return (value !== '' && value.search(',') === -1);
  1405. }
  1406. return (value.split(',').length == exactCount);
  1407. },
  1408. maxCount: function(value, maxCount) {
  1409. if(maxCount == 0) {
  1410. return false;
  1411. }
  1412. if(maxCount == 1) {
  1413. return (value.search(',') === -1);
  1414. }
  1415. return (value.split(',').length <= maxCount);
  1416. }
  1417. }
  1418. };
  1419. })( jQuery, window, document );