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.

1421 lines
44 KiB

10 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
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
10 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
10 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
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
10 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
10 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
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
10 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
10 years ago
10 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
10 years ago
10 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
10 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
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
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
9 years ago
9 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
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
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
10 years ago
10 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
10 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
10 years ago
9 years ago
9 years ago
10 years ago
9 years ago
10 years ago
  1. /*!
  2. * # Semantic UI 2.1.3 - 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. values = module.get.values(),
  648. apiRequest
  649. ;
  650. // input keydown event will fire submit repeatedly by browser default
  651. if(keyHeldDown) {
  652. return false;
  653. }
  654. // reset errors
  655. formErrors = [];
  656. if( module.is.valid() ) {
  657. module.debug('Form has no validation errors, submitting');
  658. module.set.success();
  659. return settings.onSuccess.call(element, event, values);
  660. }
  661. else {
  662. module.debug('Form has errors');
  663. module.set.error();
  664. if(!settings.inline) {
  665. module.add.errors(formErrors);
  666. }
  667. // prevent ajax submit
  668. if($module.data('moduleApi') !== undefined) {
  669. event.stopImmediatePropagation();
  670. }
  671. return settings.onFailure.call(element, formErrors, values);
  672. }
  673. },
  674. // takes a validation object and returns whether field passes validation
  675. field: function(field) {
  676. var
  677. $field = module.get.field(field.identifier),
  678. fieldValid = true,
  679. fieldErrors = []
  680. ;
  681. if($field.prop('disabled')) {
  682. module.debug('Field is disabled. Skipping', field.identifier);
  683. fieldValid = true;
  684. }
  685. else if(field.optional && $.trim($field.val()) === ''){
  686. module.debug('Field is optional and empty. Skipping', field.identifier);
  687. fieldValid = true;
  688. }
  689. else if(field.rules !== undefined) {
  690. $.each(field.rules, function(index, rule) {
  691. if( module.has.field(field.identifier) && !( module.validate.rule(field, rule) ) ) {
  692. module.debug('Field is invalid', field.identifier, rule.type);
  693. fieldErrors.push(rule.prompt);
  694. fieldValid = false;
  695. }
  696. });
  697. }
  698. if(fieldValid) {
  699. module.remove.prompt(field, fieldErrors);
  700. settings.onValid.call($field);
  701. }
  702. else {
  703. formErrors = formErrors.concat(fieldErrors);
  704. module.add.prompt(field.identifier, fieldErrors);
  705. settings.onInvalid.call($field, fieldErrors);
  706. return false;
  707. }
  708. return true;
  709. },
  710. // takes validation rule and returns whether field passes rule
  711. rule: function(field, validation) {
  712. var
  713. $field = module.get.field(field.identifier),
  714. type = validation.type,
  715. value = $field.val(),
  716. bracket = type.match(settings.regExp.bracket),
  717. isValid = true,
  718. rule,
  719. ancillary,
  720. functionType
  721. ;
  722. // cast to string avoiding encoding special values
  723. value = (value === undefined || value === '' || value === null)
  724. ? ''
  725. : $.trim(value + '')
  726. ;
  727. // if bracket notation is used, pass in extra parameters
  728. if(bracket) {
  729. ancillary = '' + bracket[1];
  730. functionType = type.replace(bracket[0], '');
  731. rule = settings.rules[functionType];
  732. if( !$.isFunction(rule) ) {
  733. module.error(error.noRule, functionType);
  734. return;
  735. }
  736. isValid = rule.call($field, value, ancillary);
  737. }
  738. else {
  739. rule = settings.rules[type];
  740. if( !$.isFunction(rule) ) {
  741. module.error(error.noRule, type);
  742. return;
  743. }
  744. isValid = rule.call($field, value);
  745. }
  746. return isValid;
  747. }
  748. },
  749. setting: function(name, value) {
  750. if( $.isPlainObject(name) ) {
  751. $.extend(true, settings, name);
  752. }
  753. else if(value !== undefined) {
  754. settings[name] = value;
  755. }
  756. else {
  757. return settings[name];
  758. }
  759. },
  760. internal: function(name, value) {
  761. if( $.isPlainObject(name) ) {
  762. $.extend(true, module, name);
  763. }
  764. else if(value !== undefined) {
  765. module[name] = value;
  766. }
  767. else {
  768. return module[name];
  769. }
  770. },
  771. debug: function() {
  772. if(settings.debug) {
  773. if(settings.performance) {
  774. module.performance.log(arguments);
  775. }
  776. else {
  777. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  778. module.debug.apply(console, arguments);
  779. }
  780. }
  781. },
  782. verbose: function() {
  783. if(settings.verbose && settings.debug) {
  784. if(settings.performance) {
  785. module.performance.log(arguments);
  786. }
  787. else {
  788. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  789. module.verbose.apply(console, arguments);
  790. }
  791. }
  792. },
  793. error: function() {
  794. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  795. module.error.apply(console, arguments);
  796. },
  797. performance: {
  798. log: function(message) {
  799. var
  800. currentTime,
  801. executionTime,
  802. previousTime
  803. ;
  804. if(settings.performance) {
  805. currentTime = new Date().getTime();
  806. previousTime = time || currentTime;
  807. executionTime = currentTime - previousTime;
  808. time = currentTime;
  809. performance.push({
  810. 'Name' : message[0],
  811. 'Arguments' : [].slice.call(message, 1) || '',
  812. 'Element' : element,
  813. 'Execution Time' : executionTime
  814. });
  815. }
  816. clearTimeout(module.performance.timer);
  817. module.performance.timer = setTimeout(module.performance.display, 500);
  818. },
  819. display: function() {
  820. var
  821. title = settings.name + ':',
  822. totalTime = 0
  823. ;
  824. time = false;
  825. clearTimeout(module.performance.timer);
  826. $.each(performance, function(index, data) {
  827. totalTime += data['Execution Time'];
  828. });
  829. title += ' ' + totalTime + 'ms';
  830. if(moduleSelector) {
  831. title += ' \'' + moduleSelector + '\'';
  832. }
  833. if($allModules.length > 1) {
  834. title += ' ' + '(' + $allModules.length + ')';
  835. }
  836. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  837. console.groupCollapsed(title);
  838. if(console.table) {
  839. console.table(performance);
  840. }
  841. else {
  842. $.each(performance, function(index, data) {
  843. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  844. });
  845. }
  846. console.groupEnd();
  847. }
  848. performance = [];
  849. }
  850. },
  851. invoke: function(query, passedArguments, context) {
  852. var
  853. object = instance,
  854. maxDepth,
  855. found,
  856. response
  857. ;
  858. passedArguments = passedArguments || queryArguments;
  859. context = element || context;
  860. if(typeof query == 'string' && object !== undefined) {
  861. query = query.split(/[\. ]/);
  862. maxDepth = query.length - 1;
  863. $.each(query, function(depth, value) {
  864. var camelCaseValue = (depth != maxDepth)
  865. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  866. : query
  867. ;
  868. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  869. object = object[camelCaseValue];
  870. }
  871. else if( object[camelCaseValue] !== undefined ) {
  872. found = object[camelCaseValue];
  873. return false;
  874. }
  875. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  876. object = object[value];
  877. }
  878. else if( object[value] !== undefined ) {
  879. found = object[value];
  880. return false;
  881. }
  882. else {
  883. return false;
  884. }
  885. });
  886. }
  887. if( $.isFunction( found ) ) {
  888. response = found.apply(context, passedArguments);
  889. }
  890. else if(found !== undefined) {
  891. response = found;
  892. }
  893. if($.isArray(returnedValue)) {
  894. returnedValue.push(response);
  895. }
  896. else if(returnedValue !== undefined) {
  897. returnedValue = [returnedValue, response];
  898. }
  899. else if(response !== undefined) {
  900. returnedValue = response;
  901. }
  902. return found;
  903. }
  904. };
  905. module.initialize();
  906. })
  907. ;
  908. return (returnedValue !== undefined)
  909. ? returnedValue
  910. : this
  911. ;
  912. };
  913. $.fn.form.settings = {
  914. name : 'Form',
  915. namespace : 'form',
  916. debug : false,
  917. verbose : false,
  918. performance : true,
  919. fields : false,
  920. keyboardShortcuts : true,
  921. on : 'submit',
  922. inline : false,
  923. delay : 200,
  924. revalidate : true,
  925. transition : 'scale',
  926. duration : 200,
  927. onValid : function() {},
  928. onInvalid : function() {},
  929. onSuccess : function() { return true; },
  930. onFailure : function() { return false; },
  931. metadata : {
  932. defaultValue : 'default',
  933. validate : 'validate'
  934. },
  935. regExp: {
  936. bracket : /\[(.*)\]/i,
  937. decimal : /^\-?\d*(\.\d+)?$/,
  938. 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])?",
  939. escape : /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,
  940. flags : /^\/(.*)\/(.*)?/,
  941. integer : /^\-?\d+$/,
  942. number : /^\-?\d*(\.\d+)?$/,
  943. url : /(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/i
  944. },
  945. selector : {
  946. checkbox : 'input[type="checkbox"], input[type="radio"]',
  947. clear : '.clear',
  948. field : 'input, textarea, select',
  949. group : '.field',
  950. input : 'input',
  951. message : '.error.message',
  952. prompt : '.prompt.label',
  953. radio : 'input[type="radio"]',
  954. reset : '.reset:not([type="reset"])',
  955. submit : '.submit:not([type="submit"])',
  956. uiCheckbox : '.ui.checkbox',
  957. uiDropdown : '.ui.dropdown'
  958. },
  959. className : {
  960. error : 'error',
  961. label : 'ui prompt label',
  962. pressed : 'down',
  963. success : 'success'
  964. },
  965. error: {
  966. identifier : 'You must specify a string identifier for each field',
  967. method : 'The method you called is not defined.',
  968. noRule : 'There is no rule matching the one you specified',
  969. oldSyntax : 'Starting in 2.0 forms now only take a single settings object. Validation settings converted to new syntax automatically.'
  970. },
  971. templates: {
  972. // template that produces error message
  973. error: function(errors) {
  974. var
  975. html = '<ul class="list">'
  976. ;
  977. $.each(errors, function(index, value) {
  978. html += '<li>' + value + '</li>';
  979. });
  980. html += '</ul>';
  981. return $(html);
  982. },
  983. // template that produces label
  984. prompt: function(errors) {
  985. return $('<div/>')
  986. .addClass('ui basic red pointing prompt label')
  987. .html(errors[0])
  988. ;
  989. }
  990. },
  991. rules: {
  992. // is not empty or blank string
  993. empty: function(value) {
  994. return !(value === undefined || '' === value || $.isArray(value) && value.length === 0);
  995. },
  996. // checkbox checked
  997. checked: function() {
  998. return ($(this).filter(':checked').length > 0);
  999. },
  1000. // is most likely an email
  1001. email: function(value){
  1002. var
  1003. emailRegExp = new RegExp($.fn.form.settings.regExp.email, 'i')
  1004. ;
  1005. return emailRegExp.test(value);
  1006. },
  1007. // value is most likely url
  1008. url: function(value) {
  1009. return $.fn.form.settings.regExp.url.test(value);
  1010. },
  1011. // matches specified regExp
  1012. regExp: function(value, regExp) {
  1013. var
  1014. regExpParts = regExp.match($.fn.form.settings.regExp.flags),
  1015. flags
  1016. ;
  1017. // regular expression specified as /baz/gi (flags)
  1018. if(regExpParts) {
  1019. regExp = (regExpParts.length >= 2)
  1020. ? regExpParts[1]
  1021. : regExp
  1022. ;
  1023. flags = (regExpParts.length >= 3)
  1024. ? regExpParts[2]
  1025. : ''
  1026. ;
  1027. }
  1028. return value.match( new RegExp(regExp, flags) );
  1029. },
  1030. // is valid integer or matches range
  1031. integer: function(value, range) {
  1032. var
  1033. intRegExp = $.fn.form.settings.regExp.integer,
  1034. min,
  1035. max,
  1036. parts
  1037. ;
  1038. if(range === undefined || range === '' || range === '..') {
  1039. // do nothing
  1040. }
  1041. else if(range.indexOf('..') == -1) {
  1042. if(intRegExp.test(range)) {
  1043. min = max = range - 0;
  1044. }
  1045. }
  1046. else {
  1047. parts = range.split('..', 2);
  1048. if(intRegExp.test(parts[0])) {
  1049. min = parts[0] - 0;
  1050. }
  1051. if(intRegExp.test(parts[1])) {
  1052. max = parts[1] - 0;
  1053. }
  1054. }
  1055. return (
  1056. intRegExp.test(value) &&
  1057. (min === undefined || value >= min) &&
  1058. (max === undefined || value <= max)
  1059. );
  1060. },
  1061. // is valid number (with decimal)
  1062. decimal: function(value) {
  1063. return $.fn.form.settings.regExp.decimal.test(value);
  1064. },
  1065. // is valid number
  1066. number: function(value) {
  1067. return $.fn.form.settings.regExp.number.test(value);
  1068. },
  1069. // is value (case insensitive)
  1070. is: function(value, text) {
  1071. text = (typeof text == 'string')
  1072. ? text.toLowerCase()
  1073. : text
  1074. ;
  1075. value = (typeof value == 'string')
  1076. ? value.toLowerCase()
  1077. : value
  1078. ;
  1079. return (value == text);
  1080. },
  1081. // is value
  1082. isExactly: function(value, text) {
  1083. return (value == text);
  1084. },
  1085. // value is not another value (case insensitive)
  1086. not: function(value, notValue) {
  1087. value = (typeof value == 'string')
  1088. ? value.toLowerCase()
  1089. : value
  1090. ;
  1091. notValue = (typeof notValue == 'string')
  1092. ? notValue.toLowerCase()
  1093. : notValue
  1094. ;
  1095. return (value != notValue);
  1096. },
  1097. // value is not another value (case sensitive)
  1098. notExactly: function(value, notValue) {
  1099. return (value != notValue);
  1100. },
  1101. // value contains text (insensitive)
  1102. contains: function(value, text) {
  1103. // escape regex characters
  1104. text = text.replace($.fn.form.settings.regExp.escape, "\\$&");
  1105. return (value.search( new RegExp(text, 'i') ) !== -1);
  1106. },
  1107. // value contains text (case sensitive)
  1108. containsExactly: function(value, text) {
  1109. // escape regex characters
  1110. text = text.replace($.fn.form.settings.regExp.escape, "\\$&");
  1111. return (value.search( new RegExp(text) ) !== -1);
  1112. },
  1113. // value contains text (insensitive)
  1114. doesntContain: function(value, text) {
  1115. // escape regex characters
  1116. text = text.replace($.fn.form.settings.regExp.escape, "\\$&");
  1117. return (value.search( new RegExp(text, 'i') ) === -1);
  1118. },
  1119. // value contains text (case sensitive)
  1120. doesntContainExactly: function(value, text) {
  1121. // escape regex characters
  1122. text = text.replace($.fn.form.settings.regExp.escape, "\\$&");
  1123. return (value.search( new RegExp(text) ) === -1);
  1124. },
  1125. // is at least string length
  1126. minLength: function(value, requiredLength) {
  1127. return (value !== undefined)
  1128. ? (value.length >= requiredLength)
  1129. : false
  1130. ;
  1131. },
  1132. // see rls notes for 2.0.6 (this is a duplicate of minLength)
  1133. length: function(value, requiredLength) {
  1134. return (value !== undefined)
  1135. ? (value.length >= requiredLength)
  1136. : false
  1137. ;
  1138. },
  1139. // is exactly length
  1140. exactLength: function(value, requiredLength) {
  1141. return (value !== undefined)
  1142. ? (value.length == requiredLength)
  1143. : false
  1144. ;
  1145. },
  1146. // is less than length
  1147. maxLength: function(value, maxLength) {
  1148. return (value !== undefined)
  1149. ? (value.length <= maxLength)
  1150. : false
  1151. ;
  1152. },
  1153. // matches another field
  1154. match: function(value, identifier) {
  1155. var
  1156. $form = $(this),
  1157. matchingValue
  1158. ;
  1159. if( $('[data-validate="'+ identifier +'"]').length > 0 ) {
  1160. matchingValue = $('[data-validate="'+ identifier +'"]').val();
  1161. }
  1162. else if($('#' + identifier).length > 0) {
  1163. matchingValue = $('#' + identifier).val();
  1164. }
  1165. else if($('[name="' + identifier +'"]').length > 0) {
  1166. matchingValue = $('[name="' + identifier + '"]').val();
  1167. }
  1168. else if( $('[name="' + identifier +'[]"]').length > 0 ) {
  1169. matchingValue = $('[name="' + identifier +'[]"]');
  1170. }
  1171. return (matchingValue !== undefined)
  1172. ? ( value.toString() == matchingValue.toString() )
  1173. : false
  1174. ;
  1175. },
  1176. creditCard: function(cardNumber, cardTypes) {
  1177. var
  1178. cards = {
  1179. visa: {
  1180. pattern : /^4/,
  1181. length : [16]
  1182. },
  1183. amex: {
  1184. pattern : /^3[47]/,
  1185. length : [15]
  1186. },
  1187. mastercard: {
  1188. pattern : /^5[1-5]/,
  1189. length : [16]
  1190. },
  1191. discover: {
  1192. 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)/,
  1193. length : [16]
  1194. },
  1195. unionPay: {
  1196. pattern : /^(62|88)/,
  1197. length : [16, 17, 18, 19]
  1198. },
  1199. jcb: {
  1200. pattern : /^35(2[89]|[3-8][0-9])/,
  1201. length : [16]
  1202. },
  1203. maestro: {
  1204. pattern : /^(5018|5020|5038|6304|6759|676[1-3])/,
  1205. length : [12, 13, 14, 15, 16, 17, 18, 19]
  1206. },
  1207. dinersClub: {
  1208. pattern : /^(30[0-5]|^36)/,
  1209. length : [14]
  1210. },
  1211. laser: {
  1212. pattern : /^(6304|670[69]|6771)/,
  1213. length : [16, 17, 18, 19]
  1214. },
  1215. visaElectron: {
  1216. pattern : /^(4026|417500|4508|4844|491(3|7))/,
  1217. length : [16]
  1218. }
  1219. },
  1220. valid = {},
  1221. validCard = false,
  1222. requiredTypes = (typeof cardTypes == 'string')
  1223. ? cardTypes.split(',')
  1224. : false,
  1225. unionPay,
  1226. validation
  1227. ;
  1228. if(typeof cardNumber !== 'string' || cardNumber.length === 0) {
  1229. return;
  1230. }
  1231. // verify card types
  1232. if(requiredTypes) {
  1233. $.each(requiredTypes, function(index, type){
  1234. // verify each card type
  1235. validation = cards[type];
  1236. if(validation) {
  1237. valid = {
  1238. length : ($.inArray(cardNumber.length, validation.length) !== -1),
  1239. pattern : (cardNumber.search(validation.pattern) !== -1)
  1240. };
  1241. if(valid.length && valid.pattern) {
  1242. validCard = true;
  1243. }
  1244. }
  1245. });
  1246. if(!validCard) {
  1247. return false;
  1248. }
  1249. }
  1250. // skip luhn for UnionPay
  1251. unionPay = {
  1252. number : ($.inArray(cardNumber.length, cards.unionPay.length) !== -1),
  1253. pattern : (cardNumber.search(cards.unionPay.pattern) !== -1)
  1254. };
  1255. if(unionPay.number && unionPay.pattern) {
  1256. return true;
  1257. }
  1258. // verify luhn, adapted from <https://gist.github.com/2134376>
  1259. var
  1260. length = cardNumber.length,
  1261. multiple = 0,
  1262. producedValue = [
  1263. [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
  1264. [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]
  1265. ],
  1266. sum = 0
  1267. ;
  1268. while (length--) {
  1269. sum += producedValue[multiple][parseInt(cardNumber.charAt(length), 10)];
  1270. multiple ^= 1;
  1271. }
  1272. return (sum % 10 === 0 && sum > 0);
  1273. },
  1274. // different than another field
  1275. different: function(value, identifier) {
  1276. // use either id or name of field
  1277. var
  1278. $form = $(this),
  1279. matchingValue
  1280. ;
  1281. if( $('[data-validate="'+ identifier +'"]').length > 0 ) {
  1282. matchingValue = $('[data-validate="'+ identifier +'"]').val();
  1283. }
  1284. else if($('#' + identifier).length > 0) {
  1285. matchingValue = $('#' + identifier).val();
  1286. }
  1287. else if($('[name="' + identifier +'"]').length > 0) {
  1288. matchingValue = $('[name="' + identifier + '"]').val();
  1289. }
  1290. else if( $('[name="' + identifier +'[]"]').length > 0 ) {
  1291. matchingValue = $('[name="' + identifier +'[]"]');
  1292. }
  1293. return (matchingValue !== undefined)
  1294. ? ( value.toString() !== matchingValue.toString() )
  1295. : false
  1296. ;
  1297. },
  1298. exactCount: function(value, exactCount) {
  1299. if(exactCount == 0) {
  1300. return (value === '');
  1301. }
  1302. if(exactCount == 1) {
  1303. return (value !== '' && value.search(',') === -1);
  1304. }
  1305. return (value.split(',').length == exactCount);
  1306. },
  1307. minCount: function(value, minCount) {
  1308. if(minCount == 0) {
  1309. return true;
  1310. }
  1311. if(minCount == 1) {
  1312. return (value !== '');
  1313. }
  1314. return (value.split(',').length >= minCount);
  1315. },
  1316. maxCount: function(value, maxCount) {
  1317. if(maxCount == 0) {
  1318. return false;
  1319. }
  1320. if(maxCount == 1) {
  1321. return (value.search(',') === -1);
  1322. }
  1323. return (value.split(',').length <= maxCount);
  1324. }
  1325. }
  1326. };
  1327. })( jQuery, window , document );