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.

836 lines
25 KiB

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