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.

1420 lines
44 KiB

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