PageRenderTime 58ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/vendors/jqBootstrapValidation/src/jqBootstrapValidation.js

https://github.com/aduyng/travel-time-watcher-chrome-extension
JavaScript | 1200 lines | 968 code | 123 blank | 109 comment | 137 complexity | 711b836149dd2e9f551921ba065dfc95 MD5 | raw file
Possible License(s): Apache-2.0, MIT, BSD-3-Clause, GPL-2.0
  1. (function( $ ){
  2. var createdElements = [];
  3. var defaults = {
  4. options: {
  5. prependExistingHelpBlock: false,
  6. sniffHtml: true, // sniff for 'required', 'maxlength', etc
  7. preventSubmit: true, // stop the form submit event from firing if validation fails
  8. submitError: false, // function called if there is an error when trying to submit
  9. submitSuccess: false, // function called just before a successful submit event is sent to the server
  10. semanticallyStrict: false, // set to true to tidy up generated HTML output
  11. bindEvents: [],
  12. autoAdd: {
  13. helpBlocks: true
  14. },
  15. filter: function () {
  16. // return $(this).is(":visible"); // only validate elements you can see
  17. return true; // validate everything
  18. }
  19. },
  20. methods: {
  21. init : function( options ) {
  22. // Get a clean copy of the defaults for extending
  23. var settings = $.extend(true, {}, defaults);
  24. // Set up the options based on the input
  25. settings.options = $.extend(true, settings.options, options);
  26. var $siblingElements = this;
  27. var uniqueForms = $.unique(
  28. $siblingElements.map( function () {
  29. return $(this).parents("form")[0];
  30. }).toArray()
  31. );
  32. $(uniqueForms).bind("submit.validationSubmit", function (e) {
  33. var $form = $(this);
  34. var warningsFound = 0;
  35. // Get all inputs
  36. var $allInputs = $form.find("input,textarea,select").not("[type=submit],[type=image]").filter(settings.options.filter);
  37. var $allControlGroups = $form.find(".control-group");
  38. // Only trigger validation on the ones that actually _have_ validation
  39. var $inputsWithValidators = $allInputs.filter(function () {
  40. return $(this).triggerHandler("getValidatorCount.validation") > 0;
  41. });
  42. $inputsWithValidators.trigger("submit.validation");
  43. // But all of them are out-of-focus now, because we're submitting.
  44. $allInputs.trigger("validationLostFocus.validation");
  45. // Okay, now check each controlgroup for errors (or warnings)
  46. $allControlGroups.each(function (i, el) {
  47. var $controlGroup = $(el);
  48. if ($controlGroup.hasClass("warning") || $controlGroup.hasClass("error")) {
  49. $controlGroup.removeClass("warning").addClass("error");
  50. warningsFound++;
  51. }
  52. });
  53. if (warningsFound) {
  54. // If we found any warnings, maybe we should prevent the submit
  55. // event, and trigger 'submitError' (if they're set up)
  56. if (settings.options.preventSubmit) {
  57. e.preventDefault();
  58. e.stopImmediatePropagation();
  59. }
  60. $form.addClass("error");
  61. if ($.isFunction(settings.options.submitError)) {
  62. settings.options.submitError($form, e, $inputsWithValidators.jqBootstrapValidation("collectErrors", true));
  63. }
  64. } else {
  65. // Woo! No errors! We can pass the submit event to submitSuccess
  66. // (if it has been set up)
  67. $form.removeClass("error");
  68. if ($.isFunction(settings.options.submitSuccess)) {
  69. settings.options.submitSuccess($form, e);
  70. }
  71. }
  72. });
  73. return this.each(function(){
  74. // Get references to everything we're interested in
  75. var $this = $(this),
  76. $controlGroup = $this.parents(".control-group").first(),
  77. $helpBlock = $controlGroup.find(".help-block").first(),
  78. $form = $this.parents("form").first(),
  79. validatorNames = [];
  80. // create message container if not exists
  81. if (!$helpBlock.length && settings.options.autoAdd && settings.options.autoAdd.helpBlocks) {
  82. $helpBlock = $('<div class="help-block" />');
  83. $controlGroup.find('.controls').append($helpBlock);
  84. createdElements.push($helpBlock[0]);
  85. }
  86. // =============================================================
  87. // SNIFF HTML FOR VALIDATORS
  88. // =============================================================
  89. // *snort sniff snuffle*
  90. if (settings.options.sniffHtml) {
  91. var message;
  92. // ---------------------------------------------------------
  93. // PATTERN
  94. // ---------------------------------------------------------
  95. if ($this.data("validationPatternPattern")) {
  96. $this.attr("pattern", $this.data("validationPatternPattern"));
  97. }
  98. if ($this.attr("pattern") !== undefined) {
  99. message = "Not in the expected format<!-- data-validation-pattern-message to override -->";
  100. if ($this.data("validationPatternMessage")) {
  101. message = $this.data("validationPatternMessage");
  102. }
  103. $this.data("validationPatternMessage", message);
  104. $this.data("validationPatternRegex", $this.attr("pattern"));
  105. }
  106. // ---------------------------------------------------------
  107. // MAX
  108. // ---------------------------------------------------------
  109. if ($this.attr("max") !== undefined || $this.attr("aria-valuemax") !== undefined) {
  110. var max = ($this.attr("max") !== undefined ? $this.attr("max") : $this.attr("aria-valuemax"));
  111. message = "Too high: Maximum of '" + max + "'<!-- data-validation-max-message to override -->";
  112. if ($this.data("validationMaxMessage")) {
  113. message = $this.data("validationMaxMessage");
  114. }
  115. $this.data("validationMaxMessage", message);
  116. $this.data("validationMaxMax", max);
  117. }
  118. // ---------------------------------------------------------
  119. // MIN
  120. // ---------------------------------------------------------
  121. if ($this.attr("min") !== undefined || $this.attr("aria-valuemin") !== undefined) {
  122. var min = ($this.attr("min") !== undefined ? $this.attr("min") : $this.attr("aria-valuemin"));
  123. message = "Too low: Minimum of '" + min + "'<!-- data-validation-min-message to override -->";
  124. if ($this.data("validationMinMessage")) {
  125. message = $this.data("validationMinMessage");
  126. }
  127. $this.data("validationMinMessage", message);
  128. $this.data("validationMinMin", min);
  129. }
  130. // ---------------------------------------------------------
  131. // MAXLENGTH
  132. // ---------------------------------------------------------
  133. if ($this.attr("maxlength") !== undefined) {
  134. message = "Too long: Maximum of '" + $this.attr("maxlength") + "' characters<!-- data-validation-maxlength-message to override -->";
  135. if ($this.data("validationMaxlengthMessage")) {
  136. message = $this.data("validationMaxlengthMessage");
  137. }
  138. $this.data("validationMaxlengthMessage", message);
  139. $this.data("validationMaxlengthMaxlength", $this.attr("maxlength"));
  140. }
  141. // ---------------------------------------------------------
  142. // MINLENGTH
  143. // ---------------------------------------------------------
  144. if ($this.attr("minlength") !== undefined) {
  145. message = "Too short: Minimum of '" + $this.attr("minlength") + "' characters<!-- data-validation-minlength-message to override -->";
  146. if ($this.data("validationMinlengthMessage")) {
  147. message = $this.data("validationMinlengthMessage");
  148. }
  149. $this.data("validationMinlengthMessage", message);
  150. $this.data("validationMinlengthMinlength", $this.attr("minlength"));
  151. }
  152. // ---------------------------------------------------------
  153. // REQUIRED
  154. // ---------------------------------------------------------
  155. if ($this.attr("required") !== undefined || $this.attr("aria-required") !== undefined) {
  156. message = settings.builtInValidators.required.message;
  157. if ($this.data("validationRequiredMessage")) {
  158. message = $this.data("validationRequiredMessage");
  159. }
  160. $this.data("validationRequiredMessage", message);
  161. }
  162. // ---------------------------------------------------------
  163. // NUMBER
  164. // ---------------------------------------------------------
  165. if ($this.attr("type") !== undefined && $this.attr("type").toLowerCase() === "number") {
  166. message = settings.validatorTypes.number.message; // TODO: fix this
  167. if ($this.data("validationNumberMessage")) {
  168. message = $this.data("validationNumberMessage");
  169. }
  170. $this.data("validationNumberMessage", message);
  171. var step = settings.validatorTypes.number.step; // TODO: and this
  172. if ($this.data("validationNumberStep")) {
  173. step = $this.data("validationNumberStep");
  174. }
  175. $this.data("validationNumberStep", step);
  176. var decimal = settings.validatorTypes.number.decimal;
  177. if ($this.data("validationNumberDecimal")) {
  178. decimal = $this.data("validationNumberDecimal");
  179. }
  180. $this.data("validationNumberDecimal", decimal);
  181. }
  182. // ---------------------------------------------------------
  183. // EMAIL
  184. // ---------------------------------------------------------
  185. if ($this.attr("type") !== undefined && $this.attr("type").toLowerCase() === "email") {
  186. message = "Not a valid email address<!-- data-validation-email-message to override -->";
  187. if ($this.data("validationEmailMessage")) {
  188. message = $this.data("validationEmailMessage");
  189. }
  190. $this.data("validationEmailMessage", message);
  191. }
  192. // ---------------------------------------------------------
  193. // MINCHECKED
  194. // ---------------------------------------------------------
  195. if ($this.attr("minchecked") !== undefined) {
  196. message = "Not enough options checked; Minimum of '" + $this.attr("minchecked") + "' required<!-- data-validation-minchecked-message to override -->";
  197. if ($this.data("validationMincheckedMessage")) {
  198. message = $this.data("validationMincheckedMessage");
  199. }
  200. $this.data("validationMincheckedMessage", message);
  201. $this.data("validationMincheckedMinchecked", $this.attr("minchecked"));
  202. }
  203. // ---------------------------------------------------------
  204. // MAXCHECKED
  205. // ---------------------------------------------------------
  206. if ($this.attr("maxchecked") !== undefined) {
  207. message = "Too many options checked; Maximum of '" + $this.attr("maxchecked") + "' required<!-- data-validation-maxchecked-message to override -->";
  208. if ($this.data("validationMaxcheckedMessage")) {
  209. message = $this.data("validationMaxcheckedMessage");
  210. }
  211. $this.data("validationMaxcheckedMessage", message);
  212. $this.data("validationMaxcheckedMaxchecked", $this.attr("maxchecked"));
  213. }
  214. }
  215. // =============================================================
  216. // COLLECT VALIDATOR NAMES
  217. // =============================================================
  218. // Get named validators
  219. if ($this.data("validation") !== undefined) {
  220. validatorNames = $this.data("validation").split(",");
  221. }
  222. // Get extra ones defined on the element's data attributes
  223. $.each($this.data(), function (i, el) {
  224. var parts = i.replace(/([A-Z])/g, ",$1").split(",");
  225. if (parts[0] === "validation" && parts[1]) {
  226. validatorNames.push(parts[1]);
  227. }
  228. });
  229. // =============================================================
  230. // NORMALISE VALIDATOR NAMES
  231. // =============================================================
  232. var validatorNamesToInspect = validatorNames;
  233. var newValidatorNamesToInspect = [];
  234. var uppercaseEachValidatorName = function (i, el) {
  235. validatorNames[i] = formatValidatorName(el);
  236. };
  237. var inspectValidators = function(i, el) {
  238. if ($this.data("validation" + el + "Shortcut") !== undefined) {
  239. // Are these custom validators?
  240. // Pull them out!
  241. $.each($this.data("validation" + el + "Shortcut").split(","), function(i2, el2) {
  242. newValidatorNamesToInspect.push(el2);
  243. });
  244. } else if (settings.builtInValidators[el.toLowerCase()]) {
  245. // Is this a recognised built-in?
  246. // Pull it out!
  247. var validator = settings.builtInValidators[el.toLowerCase()];
  248. if (validator.type.toLowerCase() === "shortcut") {
  249. $.each(validator.shortcut.split(","), function (i, el) {
  250. el = formatValidatorName(el);
  251. newValidatorNamesToInspect.push(el);
  252. validatorNames.push(el);
  253. });
  254. }
  255. }
  256. };
  257. do // repeatedly expand 'shortcut' validators into their real validators
  258. {
  259. // Uppercase only the first letter of each name
  260. $.each(validatorNames, uppercaseEachValidatorName);
  261. // Remove duplicate validator names
  262. validatorNames = $.unique(validatorNames);
  263. // Pull out the new validator names from each shortcut
  264. newValidatorNamesToInspect = [];
  265. $.each(validatorNamesToInspect, inspectValidators);
  266. validatorNamesToInspect = newValidatorNamesToInspect;
  267. } while (validatorNamesToInspect.length > 0);
  268. // =============================================================
  269. // SET UP VALIDATOR ARRAYS
  270. // =============================================================
  271. /* We're gonna generate something like
  272. *
  273. * {
  274. * "regex": [
  275. * { -- a validator object here --},
  276. * { -- a validator object here --}
  277. * ],
  278. * "required": [
  279. * { -- a validator object here --},
  280. * { -- a validator object here --}
  281. * ]
  282. * }
  283. *
  284. * with a few more entries.
  285. *
  286. * Because we only add a few validators to each field, most of the
  287. * keys will be empty arrays with no validator objects in them, and
  288. * thats fine.
  289. */
  290. var validators = {};
  291. $.each(validatorNames, function (i, el) {
  292. // Set up the 'override' message
  293. var message = $this.data("validation" + el + "Message");
  294. var hasOverrideMessage = !!message;
  295. var foundValidator = false;
  296. if (!message) {
  297. message = "'" + el + "' validation failed <!-- Add attribute 'data-validation-" + el.toLowerCase() + "-message' to input to change this message -->";
  298. }
  299. $.each(
  300. settings.validatorTypes,
  301. function (validatorType, validatorTemplate) {
  302. if (validators[validatorType] === undefined) {
  303. validators[validatorType] = [];
  304. }
  305. if (!foundValidator && $this.data("validation" + el + formatValidatorName(validatorTemplate.name)) !== undefined) {
  306. var initted = validatorTemplate.init($this, el);
  307. if (hasOverrideMessage) {
  308. initted.message = message;
  309. }
  310. validators[validatorType].push(
  311. $.extend(
  312. true,
  313. {
  314. name: formatValidatorName(validatorTemplate.name),
  315. message: message
  316. },
  317. initted
  318. )
  319. );
  320. foundValidator = true;
  321. }
  322. }
  323. );
  324. if (!foundValidator && settings.builtInValidators[el.toLowerCase()]) {
  325. var validator = $.extend(true, {}, settings.builtInValidators[el.toLowerCase()]);
  326. if (hasOverrideMessage) {
  327. validator.message = message;
  328. }
  329. var validatorType = validator.type.toLowerCase();
  330. if (validatorType === "shortcut") {
  331. foundValidator = true;
  332. } else {
  333. $.each(
  334. settings.validatorTypes,
  335. function (validatorTemplateType, validatorTemplate) {
  336. if (validators[validatorTemplateType] === undefined) {
  337. validators[validatorTemplateType] = [];
  338. }
  339. if (!foundValidator && validatorType === validatorTemplateType.toLowerCase()) {
  340. $this.data(
  341. "validation" + el + formatValidatorName(validatorTemplate.name),
  342. validator[validatorTemplate.name.toLowerCase()]
  343. );
  344. validators[validatorType].push(
  345. $.extend(
  346. validator,
  347. validatorTemplate.init($this, el)
  348. )
  349. );
  350. foundValidator = true;
  351. }
  352. }
  353. );
  354. }
  355. }
  356. if (! foundValidator) {
  357. $.error("Cannot find validation info for '" + el + "'");
  358. }
  359. });
  360. // =============================================================
  361. // STORE FALLBACK VALUES
  362. // =============================================================
  363. $helpBlock.data(
  364. "original-contents",
  365. (
  366. $helpBlock.data("original-contents") ?
  367. $helpBlock.data("original-contents") :
  368. $helpBlock.html()
  369. )
  370. );
  371. $helpBlock.data(
  372. "original-role",
  373. (
  374. $helpBlock.data("original-role") ?
  375. $helpBlock.data("original-role") :
  376. $helpBlock.attr("role")
  377. )
  378. );
  379. $controlGroup.data(
  380. "original-classes",
  381. (
  382. $controlGroup.data("original-clases") ?
  383. $controlGroup.data("original-classes") :
  384. $controlGroup.attr("class")
  385. )
  386. );
  387. $this.data(
  388. "original-aria-invalid",
  389. (
  390. $this.data("original-aria-invalid") ?
  391. $this.data("original-aria-invalid") :
  392. $this.attr("aria-invalid")
  393. )
  394. );
  395. // =============================================================
  396. // VALIDATION
  397. // =============================================================
  398. $this.bind(
  399. "validation.validation",
  400. function (event, params) {
  401. var value = getValue($this);
  402. // Get a list of the errors to apply
  403. var errorsFound = [];
  404. $.each(validators, function (validatorType, validatorTypeArray) {
  405. if (
  406. value || // has a truthy value
  407. value.length || // not an empty string
  408. ( // am including empty values
  409. (
  410. params &&
  411. params.includeEmpty
  412. ) ||
  413. !!settings.validatorTypes[validatorType].includeEmpty
  414. ) ||
  415. ( // validator is blocking submit
  416. !!settings.validatorTypes[validatorType].blockSubmit &&
  417. params &&
  418. !!params.submitting
  419. )
  420. )
  421. {
  422. $.each(
  423. validatorTypeArray,
  424. function (i, validator) {
  425. if (settings.validatorTypes[validatorType].validate($this, value, validator)) {
  426. errorsFound.push(validator.message);
  427. }
  428. }
  429. );
  430. }
  431. });
  432. return errorsFound;
  433. }
  434. );
  435. $this.bind(
  436. "getValidators.validation",
  437. function () {
  438. return validators;
  439. }
  440. );
  441. var numValidators = 0;
  442. $.each(validators, function (i, el) {
  443. numValidators += el.length;
  444. });
  445. $this.bind("getValidatorCount.validation", function () {
  446. return numValidators;
  447. });
  448. // =============================================================
  449. // WATCH FOR CHANGES
  450. // =============================================================
  451. $this.bind(
  452. "submit.validation",
  453. function () {
  454. return $this.triggerHandler("change.validation", {submitting: true});
  455. }
  456. );
  457. $this.bind(
  458. (
  459. settings.options.bindEvents.length > 0 ?
  460. settings.options.bindEvents :
  461. [
  462. "keyup",
  463. "focus",
  464. "blur",
  465. "click",
  466. "keydown",
  467. "keypress",
  468. "change"
  469. ]
  470. ).concat(["revalidate"]).join(".validation ") + ".validation",
  471. function (e, params) {
  472. var value = getValue($this);
  473. var errorsFound = [];
  474. if (params && !!params.submitting) {
  475. $controlGroup.data("jqbvIsSubmitting", true);
  476. } else if (e.type !== "revalidate") {
  477. $controlGroup.data("jqbvIsSubmitting", false);
  478. }
  479. var formIsSubmitting = !!$controlGroup.data("jqbvIsSubmitting");
  480. $controlGroup.find("input,textarea,select").each(function (i, el) {
  481. var oldCount = errorsFound.length;
  482. $.each($(el).triggerHandler("validation.validation", params), function (j, message) {
  483. errorsFound.push(message);
  484. });
  485. if (errorsFound.length > oldCount) {
  486. $(el).attr("aria-invalid", "true");
  487. } else {
  488. var original = $this.data("original-aria-invalid");
  489. $(el).attr("aria-invalid", (original !== undefined ? original : false));
  490. }
  491. });
  492. $form.find("input,select,textarea").not($this).not("[name=\"" + $this.attr("name") + "\"]").trigger("validationLostFocus.validation");
  493. errorsFound = $.unique(errorsFound.sort());
  494. // Were there any errors?
  495. if (errorsFound.length) {
  496. // Better flag it up as a warning.
  497. $controlGroup.removeClass("success error warning").addClass(formIsSubmitting ? "error" : "warning");
  498. // How many errors did we find?
  499. if (settings.options.semanticallyStrict && errorsFound.length === 1) {
  500. // Only one? Being strict? Just output it.
  501. $helpBlock.html(errorsFound[0] +
  502. ( settings.options.prependExistingHelpBlock ? $helpBlock.data("original-contents") : "" ));
  503. } else {
  504. // Multiple? Being sloppy? Glue them together into an UL.
  505. $helpBlock.html("<ul role=\"alert\"><li>" + errorsFound.join("</li><li>") + "</li></ul>" +
  506. ( settings.options.prependExistingHelpBlock ? $helpBlock.data("original-contents") : "" ));
  507. }
  508. } else {
  509. $controlGroup.removeClass("warning error success");
  510. if (value.length > 0) {
  511. $controlGroup.addClass("success");
  512. }
  513. $helpBlock.html($helpBlock.data("original-contents"));
  514. }
  515. if (e.type === "blur") {
  516. $controlGroup.removeClass("success");
  517. }
  518. }
  519. );
  520. $this.bind("validationLostFocus.validation", function () {
  521. $controlGroup.removeClass("success");
  522. });
  523. });
  524. },
  525. destroy : function( ) {
  526. return this.each(
  527. function() {
  528. var
  529. $this = $(this),
  530. $controlGroup = $this.parents(".control-group").first(),
  531. $helpBlock = $controlGroup.find(".help-block").first(),
  532. $form = $this.parents("form").first();
  533. // remove our events
  534. $this.unbind('.validation'); // events are namespaced.
  535. $form.unbind(".validationSubmit");
  536. // reset help text
  537. $helpBlock.html($helpBlock.data("original-contents"));
  538. // reset classes
  539. $controlGroup.attr("class", $controlGroup.data("original-classes"));
  540. // reset aria
  541. $this.attr("aria-invalid", $this.data("original-aria-invalid"));
  542. // reset role
  543. $helpBlock.attr("role", $this.data("original-role"));
  544. // remove all elements we created
  545. if ($.inArray($helpBlock[0], createdElements) > -1) {
  546. $helpBlock.remove();
  547. }
  548. }
  549. );
  550. },
  551. collectErrors : function(includeEmpty) {
  552. var errorMessages = {};
  553. this.each(function (i, el) {
  554. var $el = $(el);
  555. var name = $el.attr("name");
  556. var errors = $el.triggerHandler("validation.validation", {includeEmpty: true});
  557. errorMessages[name] = $.extend(true, errors, errorMessages[name]);
  558. });
  559. $.each(errorMessages, function (i, el) {
  560. if (el.length === 0) {
  561. delete errorMessages[i];
  562. }
  563. });
  564. return errorMessages;
  565. },
  566. hasErrors: function() {
  567. var errorMessages = [];
  568. this.find('input,select,textarea').add(this).each(function (i, el) {
  569. errorMessages = errorMessages.concat(
  570. $(el).triggerHandler("getValidators.validation") ? $(el).triggerHandler("validation.validation", {submitting: true}) : []
  571. );
  572. });
  573. return (errorMessages.length > 0);
  574. },
  575. override : function (newDefaults) {
  576. defaults = $.extend(true, defaults, newDefaults);
  577. }
  578. },
  579. validatorTypes: {
  580. callback: {
  581. name: "callback",
  582. init: function($this, name) {
  583. var result = {
  584. validatorName: name,
  585. callback: $this.data("validation" + name + "Callback"),
  586. lastValue: $this.val(),
  587. lastValid: true,
  588. lastFinished: true
  589. };
  590. var message = "Not valid";
  591. if ($this.data("validation" + name + "Message")) {
  592. message = $this.data("validation" + name + "Message");
  593. }
  594. result.message = message;
  595. return result;
  596. },
  597. validate: function($this, value, validator) {
  598. if (validator.lastValue === value && validator.lastFinished) {
  599. return !validator.lastValid;
  600. }
  601. if (validator.lastFinished === true)
  602. {
  603. validator.lastValue = value;
  604. validator.lastValid = true;
  605. validator.lastFinished = false;
  606. var rrjqbvValidator = validator;
  607. var rrjqbvThis = $this;
  608. executeFunctionByName(
  609. validator.callback,
  610. window,
  611. $this,
  612. value,
  613. function(data) {
  614. if (rrjqbvValidator.lastValue === data.value) {
  615. rrjqbvValidator.lastValid = data.valid;
  616. if (data.message) {
  617. rrjqbvValidator.message = data.message;
  618. }
  619. rrjqbvValidator.lastFinished = true;
  620. rrjqbvThis.data(
  621. "validation" + rrjqbvValidator.validatorName + "Message",
  622. rrjqbvValidator.message
  623. );
  624. // Timeout is set to avoid problems with the events being considered 'already fired'
  625. setTimeout(function() {
  626. if (!$this.is(":focus") && $this.parents("form").first().data("jqbvIsSubmitting")) {
  627. rrjqbvThis.trigger("blur.validation");
  628. } else {
  629. rrjqbvThis.trigger("revalidate.validation");
  630. }
  631. }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst
  632. }
  633. }
  634. );
  635. }
  636. return false;
  637. }
  638. },
  639. ajax: {
  640. name: "ajax",
  641. init: function ($this, name) {
  642. return {
  643. validatorName: name,
  644. url: $this.data("validation" + name + "Ajax"),
  645. lastValue: $this.val(),
  646. lastValid: true,
  647. lastFinished: true
  648. };
  649. },
  650. validate: function ($this, value, validator) {
  651. if (""+validator.lastValue === ""+value && validator.lastFinished === true) {
  652. return validator.lastValid === false;
  653. }
  654. if (validator.lastFinished === true)
  655. {
  656. validator.lastValue = value;
  657. validator.lastValid = true;
  658. validator.lastFinished = false;
  659. $.ajax({
  660. url: validator.url,
  661. data: "value=" + encodeURIComponent(value) + "&field=" + $this.attr("name"),
  662. dataType: "json",
  663. success: function (data) {
  664. if (""+validator.lastValue === ""+data.value) {
  665. validator.lastValid = !!(data.valid);
  666. if (data.message) {
  667. validator.message = data.message;
  668. }
  669. validator.lastFinished = true;
  670. $this.data("validation" + validator.validatorName + "Message", validator.message);
  671. // Timeout is set to avoid problems with the events being considered 'already fired'
  672. setTimeout(function () {
  673. $this.trigger("revalidate.validation");
  674. }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst
  675. }
  676. },
  677. failure: function () {
  678. validator.lastValid = true;
  679. validator.message = "ajax call failed";
  680. validator.lastFinished = true;
  681. $this.data("validation" + validator.validatorName + "Message", validator.message);
  682. // Timeout is set to avoid problems with the events being considered 'already fired'
  683. setTimeout(function () {
  684. $this.trigger("revalidate.validation");
  685. }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst
  686. }
  687. });
  688. }
  689. return false;
  690. }
  691. },
  692. regex: {
  693. name: "regex",
  694. init: function ($this, name) {
  695. var result = {};
  696. var regexString = $this.data("validation" + name + "Regex");
  697. result.regex = regexFromString(regexString);
  698. if (regexString === undefined) {
  699. $.error("Can't find regex for '" + name + "' validator on '" + $this.attr("name") + "'");
  700. }
  701. var message = "Not in the expected format";
  702. if ($this.data("validation" + name + "Message")) {
  703. message = $this.data("validation" + name + "Message");
  704. }
  705. result.message = message;
  706. result.originalName = name;
  707. return result;
  708. },
  709. validate: function ($this, value, validator) {
  710. return (!validator.regex.test(value) && ! validator.negative) ||
  711. (validator.regex.test(value) && validator.negative);
  712. }
  713. },
  714. email: {
  715. name: "email",
  716. init: function ($this, name) {
  717. var result = {};
  718. result.regex = regexFromString('[a-zA-Z0-9.!#$%&\u2019*+/=?^_`{|}~-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}');
  719. var message = "Not a valid email address";
  720. if ($this.data("validation" + name + "Message")) {
  721. message = $this.data("validation" + name + "Message");
  722. }
  723. result.message = message;
  724. result.originalName = name;
  725. return result;
  726. },
  727. validate: function ($this, value, validator) {
  728. return (!validator.regex.test(value) && ! validator.negative) ||
  729. (validator.regex.test(value) && validator.negative);
  730. }
  731. },
  732. required: {
  733. name: "required",
  734. init: function ($this, name) {
  735. var message = "This is required";
  736. if ($this.data("validation" + name + "Message")) {
  737. message = $this.data("validation" + name + "Message");
  738. }
  739. return {message: message, includeEmpty: true};
  740. },
  741. validate: function ($this, value, validator) {
  742. return !!(
  743. (value.length === 0 && !validator.negative) ||
  744. (value.length > 0 && validator.negative)
  745. );
  746. },
  747. blockSubmit: true
  748. },
  749. match: {
  750. name: "match",
  751. init: function ($this, name) {
  752. var elementName = $this.data("validation" + name + "Match");
  753. var $form = $this.parents("form").first();
  754. var $element = $form.find("[name=\"" + elementName + "\"]").first();
  755. $element.bind("validation.validation", function () {
  756. $this.trigger("revalidate.validation", {submitting: true});
  757. });
  758. var result = {};
  759. result.element = $element;
  760. if ($element.length === 0) {
  761. $.error("Can't find field '" + elementName + "' to match '" + $this.attr("name") + "' against in '" + name + "' validator");
  762. }
  763. var message = "Must match";
  764. var $label = null;
  765. if (($label = $form.find("label[for=\"" + elementName + "\"]")).length) {
  766. message += " '" + $label.text() + "'";
  767. } else if (($label = $element.parents(".control-group").first().find("label")).length) {
  768. message += " '" + $label.first().text() + "'";
  769. }
  770. if ($this.data("validation" + name + "Message")) {
  771. message = $this.data("validation" + name + "Message");
  772. }
  773. result.message = message;
  774. return result;
  775. },
  776. validate: function ($this, value, validator) {
  777. return (value !== validator.element.val() && ! validator.negative) ||
  778. (value === validator.element.val() && validator.negative);
  779. },
  780. blockSubmit: true,
  781. includeEmpty: true
  782. },
  783. max: {
  784. name: "max",
  785. init: function ($this, name) {
  786. var result = {};
  787. result.max = $this.data("validation" + name + "Max");
  788. result.message = "Too high: Maximum of '" + result.max + "'";
  789. if ($this.data("validation" + name + "Message")) {
  790. result.message = $this.data("validation" + name + "Message");
  791. }
  792. return result;
  793. },
  794. validate: function ($this, value, validator) {
  795. return (parseFloat(value, 10) > parseFloat(validator.max, 10) && ! validator.negative) ||
  796. (parseFloat(value, 10) <= parseFloat(validator.max, 10) && validator.negative);
  797. }
  798. },
  799. min: {
  800. name: "min",
  801. init: function ($this, name) {
  802. var result = {};
  803. result.min = $this.data("validation" + name + "Min");
  804. result.message = "Too low: Minimum of '" + result.min + "'";
  805. if ($this.data("validation" + name + "Message")) {
  806. result.message = $this.data("validation" + name + "Message");
  807. }
  808. return result;
  809. },
  810. validate: function ($this, value, validator) {
  811. return (parseFloat(value) < parseFloat(validator.min) && ! validator.negative) ||
  812. (parseFloat(value) >= parseFloat(validator.min) && validator.negative);
  813. }
  814. },
  815. maxlength: {
  816. name: "maxlength",
  817. init: function ($this, name) {
  818. var result = {};
  819. result.maxlength = $this.data("validation" + name + "Maxlength");
  820. result.message = "Too long: Maximum of '" + result.maxlength + "' characters";
  821. if ($this.data("validation" + name + "Message")) {
  822. result.message = $this.data("validation" + name + "Message");
  823. }
  824. return result;
  825. },
  826. validate: function ($this, value, validator) {
  827. return ((value.length > validator.maxlength) && ! validator.negative) ||
  828. ((value.length <= validator.maxlength) && validator.negative);
  829. }
  830. },
  831. minlength: {
  832. name: "minlength",
  833. init: function ($this, name) {
  834. var result = {};
  835. result.minlength = $this.data("validation" + name + "Minlength");
  836. result.message = "Too short: Minimum of '" + result.minlength + "' characters";
  837. if ($this.data("validation" + name + "Message")) {
  838. result.message = $this.data("validation" + name + "Message");
  839. }
  840. return result;
  841. },
  842. validate: function ($this, value, validator) {
  843. return ((value.length < validator.minlength) && ! validator.negative) ||
  844. ((value.length >= validator.minlength) && validator.negative);
  845. }
  846. },
  847. maxchecked: {
  848. name: "maxchecked",
  849. init: function ($this, name) {
  850. var result = {};
  851. var elements = $this.parents("form").first().find("[name=\"" + $this.attr("name") + "\"]");
  852. elements.bind("change.validation click.validation", function () {
  853. $this.trigger("revalidate.validation", {includeEmpty: true});
  854. });
  855. result.elements = elements;
  856. result.maxchecked = $this.data("validation" + name + "Maxchecked");
  857. var message = "Too many: Max '" + result.maxchecked + "' checked";
  858. if ($this.data("validation" + name + "Message")) {
  859. message = $this.data("validation" + name + "Message");
  860. }
  861. result.message = message;
  862. return result;
  863. },
  864. validate: function ($this, value, validator) {
  865. return (validator.elements.filter(":checked").length > validator.maxchecked && ! validator.negative) ||
  866. (validator.elements.filter(":checked").length <= validator.maxchecked && validator.negative);
  867. },
  868. blockSubmit: true
  869. },
  870. minchecked: {
  871. name: "minchecked",
  872. init: function ($this, name) {
  873. var result = {};
  874. var elements = $this.parents("form").first().find("[name=\"" + $this.attr("name") + "\"]");
  875. elements.bind("change.validation click.validation", function () {
  876. $this.trigger("revalidate.validation", {includeEmpty: true});
  877. });
  878. result.elements = elements;
  879. result.minchecked = $this.data("validation" + name + "Minchecked");
  880. var message = "Too few: Min '" + result.minchecked + "' checked";
  881. if ($this.data("validation" + name + "Message")) {
  882. message = $this.data("validation" + name + "Message");
  883. }
  884. result.message = message;
  885. return result;
  886. },
  887. validate: function ($this, value, validator) {
  888. return (validator.elements.filter(":checked").length < validator.minchecked && ! validator.negative) ||
  889. (validator.elements.filter(":checked").length >= validator.minchecked && validator.negative);
  890. },
  891. blockSubmit: true,
  892. includeEmpty: true
  893. },
  894. number: {
  895. name: "number",
  896. init: function ($this, name) {
  897. var result = {};
  898. result.step = 1;
  899. if ($this.attr("step")) {
  900. result.step = $this.attr("step");
  901. }
  902. if ($this.data("validation" + name + "Step")) {
  903. result.step = $this.data("validation" + name + "Step");
  904. }
  905. result.decimal = ".";
  906. if ($this.data("validation" + name + "Decimal")) {
  907. result.decimal = $this.data("validation" + name + "Decimal");
  908. }
  909. result.thousands = "";
  910. if ($this.data("validation" + name + "Thousands")) {
  911. result.thousands = $this.data("validation" + name + "Thousands");
  912. }
  913. result.regex = regexFromString("([+-]?\\d+(\\" + result.decimal + "\\d+)?)?");
  914. result.message = "Must be a number";
  915. var dataMessage = $this.data("validation" + name + "Message");
  916. if (dataMessage) {
  917. result.message = dataMessage;
  918. }
  919. return result;
  920. },
  921. validate: function ($this, value, validator) {
  922. var globalValue = value.replace(validator.decimal, ".").replace(validator.thousands, "");
  923. var multipliedValue = parseFloat(globalValue);
  924. var multipliedStep = parseFloat(validator.step);
  925. while (multipliedStep % 1 !== 0) {
  926. /* thanks to @jkey #57 */
  927. multipliedStep = parseFloat(multipliedStep.toPrecision(12)) * 10;
  928. multipliedValue = parseFloat(multipliedValue.toPrecision(12)) * 10;
  929. }
  930. var regexResult = validator.regex.test(value);
  931. var stepResult = parseFloat(multipliedValue) % parseFloat(multipliedStep) === 0;
  932. var typeResult = !isNaN(parseFloat(globalValue)) && isFinite(globalValue);
  933. var result = !(regexResult && stepResult && typeResult);
  934. return result;
  935. },
  936. message: "Must be a number"
  937. }
  938. },
  939. builtInValidators: {
  940. email: {
  941. name: "Email",
  942. type: "email"
  943. },
  944. passwordagain: {
  945. name: "Passwordagain",
  946. type: "match",
  947. match: "password",
  948. message: "Does not match the given password<!-- data-validator-paswordagain-message to override -->"
  949. },
  950. positive: {
  951. name: "Positive",
  952. type: "shortcut",
  953. shortcut: "number,positivenumber"
  954. },
  955. negative: {
  956. name: "Negative",
  957. type: "shortcut",
  958. shortcut: "number,negativenumber"
  959. },
  960. integer: {
  961. name: "Integer",
  962. type: "regex",
  963. regex: "[+-]?\\d+",
  964. message: "No decimal places allowed<!-- data-validator-integer-message to override -->"
  965. },
  966. positivenumber: {
  967. name: "Positivenumber",
  968. type: "min",
  969. min: 0,
  970. message: "Must be a positive number<!-- data-validator-positivenumber-message to override -->"
  971. },
  972. negativenumber: {
  973. name: "Negativenumber",
  974. type: "max",
  975. max: 0,
  976. message: "Must be a negative number<!-- data-validator-negativenumber-message to override -->"
  977. },
  978. required: {
  979. name: "Required",
  980. type: "required",
  981. message: "This is required<!-- data-validator-required-message to override -->"
  982. },
  983. checkone: {
  984. name: "Checkone",
  985. type: "minchecked",
  986. minchecked: 1,
  987. message: "Check at least one option<!-- data-validation-checkone-message to override -->"
  988. },
  989. number: {
  990. name: "Number",
  991. type: "number",
  992. decimal: ".",
  993. step: "1"
  994. },
  995. pattern: {
  996. name: "Pattern",
  997. type: "regex",
  998. message: "Not in expected format"
  999. }
  1000. }
  1001. };
  1002. var formatValidatorName = function (name) {
  1003. return name
  1004. .toLowerCase()
  1005. .replace(
  1006. /(^|\s)([a-z])/g ,
  1007. function(m,p1,p2) {
  1008. return p1+p2.toUpperCase();
  1009. }
  1010. )
  1011. ;
  1012. };
  1013. var getValue = function ($this) {
  1014. // Extract the value we're talking about
  1015. var value = null;
  1016. var type = $this.attr("type");
  1017. if (type === "checkbox") {
  1018. value = ($this.is(":checked") ? value : "");
  1019. var checkboxParent = $this.parents("form").first() || $this.parents(".control-group").first();
  1020. if (checkboxParent) {
  1021. value = checkboxParent.find("input[name='" + $this.attr("name") + "']:checked").map(function (i, el) { return $(el).val(); }).toArray().join(",");
  1022. }
  1023. }
  1024. else if (type === "radio") {
  1025. value = ($('input[name="' + $this.attr("name") + '"]:checked').length > 0 ? $this.val() : "");
  1026. var radioParent = $this.parents("form").first() || $this.parents(".control-group").first();
  1027. if (radioParent) {
  1028. value = radioParent.find("input[name='" + $this.attr("name") + "']:checked").map(function (i, el) { return $(el).val(); }).toArray().join(",");
  1029. }
  1030. }
  1031. else {
  1032. value = $this.val();
  1033. }
  1034. return value;
  1035. };
  1036. function regexFromString(inputstring) {
  1037. return new RegExp("^" + inputstring + "$");
  1038. }
  1039. /**
  1040. * Thanks to Jason Bunting / Alex Nazarov via StackOverflow.com
  1041. *
  1042. * http://stackoverflow.com/a/4351575
  1043. **/
  1044. function executeFunctionByName(functionName, context /*, args */) {
  1045. var args = Array.prototype.slice.call(arguments, 2);
  1046. var namespaces = functionName.split(".");
  1047. var func = namespaces.pop();
  1048. for (var i = 0; i < namespaces.length; i++) {
  1049. context = context[namespaces[i]];
  1050. }
  1051. return context[func].apply(context, args);
  1052. }
  1053. $.fn.jqBootstrapValidation = function( method ) {
  1054. if ( defaults.methods[method] ) {
  1055. return defaults.methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
  1056. } else if ( typeof method === 'object' || ! method ) {
  1057. return defaults.methods.init.apply( this, arguments );
  1058. } else {
  1059. $.error( 'Method ' + method + ' does not exist on jQuery.jqBootstrapValidation' );
  1060. return null;
  1061. }
  1062. };
  1063. $.jqBootstrapValidation = function (options) {
  1064. $(":input").not("[type=image],[type=submit]").jqBootstrapValidation.apply(this,arguments);
  1065. };
  1066. })( jQuery );