jqBootstrapValidation.js 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938
  1. /* jqBootstrapValidation
  2. * A plugin for automating validation on Twitter Bootstrap formatted forms.
  3. *
  4. * v1.3.6
  5. *
  6. * License: MIT <http://opensource.org/licenses/mit-license.php> - see LICENSE file
  7. *
  8. * http://ReactiveRaven.github.com/jqBootstrapValidation/
  9. */
  10. (function($) {
  11. var createdElements = [];
  12. var defaults = {
  13. options: {
  14. prependExistingHelpBlock: false,
  15. sniffHtml: true, // sniff for 'required', 'maxlength', etc
  16. preventSubmit: true, // stop the form submit event from firing if validation fails
  17. submitError: false, // function called if there is an error when trying to submit
  18. submitSuccess: false, // function called just before a successful submit event is sent to the server
  19. semanticallyStrict: false, // set to true to tidy up generated HTML output
  20. autoAdd: {
  21. helpBlocks: true
  22. },
  23. filter: function() {
  24. // return $(this).is(":visible"); // only validate elements you can see
  25. return true; // validate everything
  26. }
  27. },
  28. methods: {
  29. init: function(options) {
  30. var settings = $.extend(true, {}, defaults);
  31. settings.options = $.extend(true, settings.options, options);
  32. var $siblingElements = this;
  33. var uniqueForms = $.unique(
  34. $siblingElements.map(function() {
  35. return $(this).parents("form")[0];
  36. }).toArray()
  37. );
  38. $(uniqueForms).bind("submit", function(e) {
  39. var $form = $(this);
  40. var warningsFound = 0;
  41. var $inputs = $form.find("input,textarea,select").not("[type=submit],[type=image]").filter(settings.options.filter);
  42. $inputs.trigger("submit.validation").trigger("validationLostFocus.validation");
  43. $inputs.each(function(i, el) {
  44. var $this = $(el),
  45. $controlGroup = $this.parents(".form-group").first();
  46. if (
  47. $controlGroup.hasClass("warning")
  48. ) {
  49. $controlGroup.removeClass("warning").addClass("error");
  50. warningsFound++;
  51. }
  52. });
  53. $inputs.trigger("validationLostFocus.validation");
  54. if (warningsFound) {
  55. if (settings.options.preventSubmit) {
  56. e.preventDefault();
  57. }
  58. $form.addClass("error");
  59. if ($.isFunction(settings.options.submitError)) {
  60. settings.options.submitError($form, e, $inputs.jqBootstrapValidation("collectErrors", true));
  61. }
  62. } else {
  63. $form.removeClass("error");
  64. if ($.isFunction(settings.options.submitSuccess)) {
  65. settings.options.submitSuccess($form, e);
  66. }
  67. }
  68. });
  69. return this.each(function() {
  70. // Get references to everything we're interested in
  71. var $this = $(this),
  72. $controlGroup = $this.parents(".form-group").first(),
  73. $helpBlock = $controlGroup.find(".help-block").first(),
  74. $form = $this.parents("form").first(),
  75. validatorNames = [];
  76. // create message container if not exists
  77. if (!$helpBlock.length && settings.options.autoAdd && settings.options.autoAdd.helpBlocks) {
  78. $helpBlock = $('<div class="help-block" />');
  79. $controlGroup.find('.controls').append($helpBlock);
  80. createdElements.push($helpBlock[0]);
  81. }
  82. // =============================================================
  83. // SNIFF HTML FOR VALIDATORS
  84. // =============================================================
  85. // *snort sniff snuffle*
  86. if (settings.options.sniffHtml) {
  87. var message = "";
  88. // ---------------------------------------------------------
  89. // PATTERN
  90. // ---------------------------------------------------------
  91. if ($this.attr("pattern") !== undefined) {
  92. message = "Not in the expected format<!-- data-validation-pattern-message to override -->";
  93. if ($this.data("validationPatternMessage")) {
  94. message = $this.data("validationPatternMessage");
  95. }
  96. $this.data("validationPatternMessage", message);
  97. $this.data("validationPatternRegex", $this.attr("pattern"));
  98. }
  99. // ---------------------------------------------------------
  100. // MAX
  101. // ---------------------------------------------------------
  102. if ($this.attr("max") !== undefined || $this.attr("aria-valuemax") !== undefined) {
  103. var max = ($this.attr("max") !== undefined ? $this.attr("max") : $this.attr("aria-valuemax"));
  104. message = "Too high: Maximum of '" + max + "'<!-- data-validation-max-message to override -->";
  105. if ($this.data("validationMaxMessage")) {
  106. message = $this.data("validationMaxMessage");
  107. }
  108. $this.data("validationMaxMessage", message);
  109. $this.data("validationMaxMax", max);
  110. }
  111. // ---------------------------------------------------------
  112. // MIN
  113. // ---------------------------------------------------------
  114. if ($this.attr("min") !== undefined || $this.attr("aria-valuemin") !== undefined) {
  115. var min = ($this.attr("min") !== undefined ? $this.attr("min") : $this.attr("aria-valuemin"));
  116. message = "Too low: Minimum of '" + min + "'<!-- data-validation-min-message to override -->";
  117. if ($this.data("validationMinMessage")) {
  118. message = $this.data("validationMinMessage");
  119. }
  120. $this.data("validationMinMessage", message);
  121. $this.data("validationMinMin", min);
  122. }
  123. // ---------------------------------------------------------
  124. // MAXLENGTH
  125. // ---------------------------------------------------------
  126. if ($this.attr("maxlength") !== undefined) {
  127. message = "Too long: Maximum of '" + $this.attr("maxlength") + "' characters<!-- data-validation-maxlength-message to override -->";
  128. if ($this.data("validationMaxlengthMessage")) {
  129. message = $this.data("validationMaxlengthMessage");
  130. }
  131. $this.data("validationMaxlengthMessage", message);
  132. $this.data("validationMaxlengthMaxlength", $this.attr("maxlength"));
  133. }
  134. // ---------------------------------------------------------
  135. // MINLENGTH
  136. // ---------------------------------------------------------
  137. if ($this.attr("minlength") !== undefined) {
  138. message = "Too short: Minimum of '" + $this.attr("minlength") + "' characters<!-- data-validation-minlength-message to override -->";
  139. if ($this.data("validationMinlengthMessage")) {
  140. message = $this.data("validationMinlengthMessage");
  141. }
  142. $this.data("validationMinlengthMessage", message);
  143. $this.data("validationMinlengthMinlength", $this.attr("minlength"));
  144. }
  145. // ---------------------------------------------------------
  146. // REQUIRED
  147. // ---------------------------------------------------------
  148. if ($this.attr("required") !== undefined || $this.attr("aria-required") !== undefined) {
  149. message = settings.builtInValidators.required.message;
  150. if ($this.data("validationRequiredMessage")) {
  151. message = $this.data("validationRequiredMessage");
  152. }
  153. $this.data("validationRequiredMessage", message);
  154. }
  155. // ---------------------------------------------------------
  156. // NUMBER
  157. // ---------------------------------------------------------
  158. if ($this.attr("type") !== undefined && $this.attr("type").toLowerCase() === "number") {
  159. message = settings.builtInValidators.number.message;
  160. if ($this.data("validationNumberMessage")) {
  161. message = $this.data("validationNumberMessage");
  162. }
  163. $this.data("validationNumberMessage", message);
  164. }
  165. // ---------------------------------------------------------
  166. // EMAIL
  167. // ---------------------------------------------------------
  168. if ($this.attr("type") !== undefined && $this.attr("type").toLowerCase() === "email") {
  169. message = "Not a valid email address<!-- data-validator-validemail-message to override -->";
  170. if ($this.data("validationValidemailMessage")) {
  171. message = $this.data("validationValidemailMessage");
  172. } else if ($this.data("validationEmailMessage")) {
  173. message = $this.data("validationEmailMessage");
  174. }
  175. $this.data("validationValidemailMessage", message);
  176. }
  177. // ---------------------------------------------------------
  178. // MINCHECKED
  179. // ---------------------------------------------------------
  180. if ($this.attr("minchecked") !== undefined) {
  181. message = "Not enough options checked; Minimum of '" + $this.attr("minchecked") + "' required<!-- data-validation-minchecked-message to override -->";
  182. if ($this.data("validationMincheckedMessage")) {
  183. message = $this.data("validationMincheckedMessage");
  184. }
  185. $this.data("validationMincheckedMessage", message);
  186. $this.data("validationMincheckedMinchecked", $this.attr("minchecked"));
  187. }
  188. // ---------------------------------------------------------
  189. // MAXCHECKED
  190. // ---------------------------------------------------------
  191. if ($this.attr("maxchecked") !== undefined) {
  192. message = "Too many options checked; Maximum of '" + $this.attr("maxchecked") + "' required<!-- data-validation-maxchecked-message to override -->";
  193. if ($this.data("validationMaxcheckedMessage")) {
  194. message = $this.data("validationMaxcheckedMessage");
  195. }
  196. $this.data("validationMaxcheckedMessage", message);
  197. $this.data("validationMaxcheckedMaxchecked", $this.attr("maxchecked"));
  198. }
  199. }
  200. // =============================================================
  201. // COLLECT VALIDATOR NAMES
  202. // =============================================================
  203. // Get named validators
  204. if ($this.data("validation") !== undefined) {
  205. validatorNames = $this.data("validation").split(",");
  206. }
  207. // Get extra ones defined on the element's data attributes
  208. $.each($this.data(), function(i, el) {
  209. var parts = i.replace(/([A-Z])/g, ",$1").split(",");
  210. if (parts[0] === "validation" && parts[1]) {
  211. validatorNames.push(parts[1]);
  212. }
  213. });
  214. // =============================================================
  215. // NORMALISE VALIDATOR NAMES
  216. // =============================================================
  217. var validatorNamesToInspect = validatorNames;
  218. var newValidatorNamesToInspect = [];
  219. do // repeatedly expand 'shortcut' validators into their real validators
  220. {
  221. // Uppercase only the first letter of each name
  222. $.each(validatorNames, function(i, el) {
  223. validatorNames[i] = formatValidatorName(el);
  224. });
  225. // Remove duplicate validator names
  226. validatorNames = $.unique(validatorNames);
  227. // Pull out the new validator names from each shortcut
  228. newValidatorNamesToInspect = [];
  229. $.each(validatorNamesToInspect, function(i, el) {
  230. if ($this.data("validation" + el + "Shortcut") !== undefined) {
  231. // Are these custom validators?
  232. // Pull them out!
  233. $.each($this.data("validation" + el + "Shortcut").split(","), function(i2, el2) {
  234. newValidatorNamesToInspect.push(el2);
  235. });
  236. } else if (settings.builtInValidators[el.toLowerCase()]) {
  237. // Is this a recognised built-in?
  238. // Pull it out!
  239. var validator = settings.builtInValidators[el.toLowerCase()];
  240. if (validator.type.toLowerCase() === "shortcut") {
  241. $.each(validator.shortcut.split(","), function(i, el) {
  242. el = formatValidatorName(el);
  243. newValidatorNamesToInspect.push(el);
  244. validatorNames.push(el);
  245. });
  246. }
  247. }
  248. });
  249. validatorNamesToInspect = newValidatorNamesToInspect;
  250. } while (validatorNamesToInspect.length > 0)
  251. // =============================================================
  252. // SET UP VALIDATOR ARRAYS
  253. // =============================================================
  254. var validators = {};
  255. $.each(validatorNames, function(i, el) {
  256. // Set up the 'override' message
  257. var message = $this.data("validation" + el + "Message");
  258. var hasOverrideMessage = (message !== undefined);
  259. var foundValidator = false;
  260. message =
  261. (
  262. message ?
  263. message :
  264. "'" + el + "' validation failed <!-- Add attribute 'data-validation-" + el.toLowerCase() + "-message' to input to change this message -->"
  265. );
  266. $.each(
  267. settings.validatorTypes,
  268. function(validatorType, validatorTemplate) {
  269. if (validators[validatorType] === undefined) {
  270. validators[validatorType] = [];
  271. }
  272. if (!foundValidator && $this.data("validation" + el + formatValidatorName(validatorTemplate.name)) !== undefined) {
  273. validators[validatorType].push(
  274. $.extend(
  275. true, {
  276. name: formatValidatorName(validatorTemplate.name),
  277. message: message
  278. },
  279. validatorTemplate.init($this, el)
  280. )
  281. );
  282. foundValidator = true;
  283. }
  284. }
  285. );
  286. if (!foundValidator && settings.builtInValidators[el.toLowerCase()]) {
  287. var validator = $.extend(true, {}, settings.builtInValidators[el.toLowerCase()]);
  288. if (hasOverrideMessage) {
  289. validator.message = message;
  290. }
  291. var validatorType = validator.type.toLowerCase();
  292. if (validatorType === "shortcut") {
  293. foundValidator = true;
  294. } else {
  295. $.each(
  296. settings.validatorTypes,
  297. function(validatorTemplateType, validatorTemplate) {
  298. if (validators[validatorTemplateType] === undefined) {
  299. validators[validatorTemplateType] = [];
  300. }
  301. if (!foundValidator && validatorType === validatorTemplateType.toLowerCase()) {
  302. $this.data("validation" + el + formatValidatorName(validatorTemplate.name), validator[validatorTemplate.name.toLowerCase()]);
  303. validators[validatorType].push(
  304. $.extend(
  305. validator,
  306. validatorTemplate.init($this, el)
  307. )
  308. );
  309. foundValidator = true;
  310. }
  311. }
  312. );
  313. }
  314. }
  315. if (!foundValidator) {
  316. $.error("Cannot find validation info for '" + el + "'");
  317. }
  318. });
  319. // =============================================================
  320. // STORE FALLBACK VALUES
  321. // =============================================================
  322. $helpBlock.data(
  323. "original-contents",
  324. (
  325. $helpBlock.data("original-contents") ?
  326. $helpBlock.data("original-contents") :
  327. $helpBlock.html()
  328. )
  329. );
  330. $helpBlock.data(
  331. "original-role",
  332. (
  333. $helpBlock.data("original-role") ?
  334. $helpBlock.data("original-role") :
  335. $helpBlock.attr("role")
  336. )
  337. );
  338. $controlGroup.data(
  339. "original-classes",
  340. (
  341. $controlGroup.data("original-clases") ?
  342. $controlGroup.data("original-classes") :
  343. $controlGroup.attr("class")
  344. )
  345. );
  346. $this.data(
  347. "original-aria-invalid",
  348. (
  349. $this.data("original-aria-invalid") ?
  350. $this.data("original-aria-invalid") :
  351. $this.attr("aria-invalid")
  352. )
  353. );
  354. // =============================================================
  355. // VALIDATION
  356. // =============================================================
  357. $this.bind(
  358. "validation.validation",
  359. function(event, params) {
  360. var value = getValue($this);
  361. // Get a list of the errors to apply
  362. var errorsFound = [];
  363. $.each(validators, function(validatorType, validatorTypeArray) {
  364. if (value || value.length || (params && params.includeEmpty) || (!!settings.validatorTypes[validatorType].blockSubmit && params && !!params.submitting)) {
  365. $.each(validatorTypeArray, function(i, validator) {
  366. if (settings.validatorTypes[validatorType].validate($this, value, validator)) {
  367. errorsFound.push(validator.message);
  368. }
  369. });
  370. }
  371. });
  372. return errorsFound;
  373. }
  374. );
  375. $this.bind(
  376. "getValidators.validation",
  377. function() {
  378. return validators;
  379. }
  380. );
  381. // =============================================================
  382. // WATCH FOR CHANGES
  383. // =============================================================
  384. $this.bind(
  385. "submit.validation",
  386. function() {
  387. return $this.triggerHandler("change.validation", {
  388. submitting: true
  389. });
  390. }
  391. );
  392. $this.bind(
  393. [
  394. "keyup",
  395. "focus",
  396. "blur",
  397. "click",
  398. "keydown",
  399. "keypress",
  400. "change"
  401. ].join(".validation ") + ".validation",
  402. function(e, params) {
  403. var value = getValue($this);
  404. var errorsFound = [];
  405. $controlGroup.find("input,textarea,select").each(function(i, el) {
  406. var oldCount = errorsFound.length;
  407. $.each($(el).triggerHandler("validation.validation", params), function(j, message) {
  408. errorsFound.push(message);
  409. });
  410. if (errorsFound.length > oldCount) {
  411. $(el).attr("aria-invalid", "true");
  412. } else {
  413. var original = $this.data("original-aria-invalid");
  414. $(el).attr("aria-invalid", (original !== undefined ? original : false));
  415. }
  416. });
  417. $form.find("input,select,textarea").not($this).not("[name=\"" + $this.attr("name") + "\"]").trigger("validationLostFocus.validation");
  418. errorsFound = $.unique(errorsFound.sort());
  419. // Were there any errors?
  420. if (errorsFound.length) {
  421. // Better flag it up as a warning.
  422. $controlGroup.removeClass("success error").addClass("warning");
  423. // How many errors did we find?
  424. if (settings.options.semanticallyStrict && errorsFound.length === 1) {
  425. // Only one? Being strict? Just output it.
  426. $helpBlock.html(errorsFound[0] +
  427. (settings.options.prependExistingHelpBlock ? $helpBlock.data("original-contents") : ""));
  428. } else {
  429. // Multiple? Being sloppy? Glue them together into an UL.
  430. $helpBlock.html("<ul role=\"alert\"><li>" + errorsFound.join("</li><li>") + "</li></ul>" +
  431. (settings.options.prependExistingHelpBlock ? $helpBlock.data("original-contents") : ""));
  432. }
  433. } else {
  434. $controlGroup.removeClass("warning error success");
  435. if (value.length > 0) {
  436. $controlGroup.addClass("success");
  437. }
  438. $helpBlock.html($helpBlock.data("original-contents"));
  439. }
  440. if (e.type === "blur") {
  441. $controlGroup.removeClass("success");
  442. }
  443. }
  444. );
  445. $this.bind("validationLostFocus.validation", function() {
  446. $controlGroup.removeClass("success");
  447. });
  448. });
  449. },
  450. destroy: function() {
  451. return this.each(
  452. function() {
  453. var
  454. $this = $(this),
  455. $controlGroup = $this.parents(".form-group").first(),
  456. $helpBlock = $controlGroup.find(".help-block").first();
  457. // remove our events
  458. $this.unbind('.validation'); // events are namespaced.
  459. // reset help text
  460. $helpBlock.html($helpBlock.data("original-contents"));
  461. // reset classes
  462. $controlGroup.attr("class", $controlGroup.data("original-classes"));
  463. // reset aria
  464. $this.attr("aria-invalid", $this.data("original-aria-invalid"));
  465. // reset role
  466. $helpBlock.attr("role", $this.data("original-role"));
  467. // remove all elements we created
  468. if (createdElements.indexOf($helpBlock[0]) > -1) {
  469. $helpBlock.remove();
  470. }
  471. }
  472. );
  473. },
  474. collectErrors: function(includeEmpty) {
  475. var errorMessages = {};
  476. this.each(function(i, el) {
  477. var $el = $(el);
  478. var name = $el.attr("name");
  479. var errors = $el.triggerHandler("validation.validation", {
  480. includeEmpty: true
  481. });
  482. errorMessages[name] = $.extend(true, errors, errorMessages[name]);
  483. });
  484. $.each(errorMessages, function(i, el) {
  485. if (el.length === 0) {
  486. delete errorMessages[i];
  487. }
  488. });
  489. return errorMessages;
  490. },
  491. hasErrors: function() {
  492. var errorMessages = [];
  493. this.each(function(i, el) {
  494. errorMessages = errorMessages.concat(
  495. $(el).triggerHandler("getValidators.validation") ? $(el).triggerHandler("validation.validation", {
  496. submitting: true
  497. }) : []
  498. );
  499. });
  500. return (errorMessages.length > 0);
  501. },
  502. override: function(newDefaults) {
  503. defaults = $.extend(true, defaults, newDefaults);
  504. }
  505. },
  506. validatorTypes: {
  507. callback: {
  508. name: "callback",
  509. init: function($this, name) {
  510. return {
  511. validatorName: name,
  512. callback: $this.data("validation" + name + "Callback"),
  513. lastValue: $this.val(),
  514. lastValid: true,
  515. lastFinished: true
  516. };
  517. },
  518. validate: function($this, value, validator) {
  519. if (validator.lastValue === value && validator.lastFinished) {
  520. return !validator.lastValid;
  521. }
  522. if (validator.lastFinished === true) {
  523. validator.lastValue = value;
  524. validator.lastValid = true;
  525. validator.lastFinished = false;
  526. var rrjqbvValidator = validator;
  527. var rrjqbvThis = $this;
  528. executeFunctionByName(
  529. validator.callback,
  530. window,
  531. $this,
  532. value,
  533. function(data) {
  534. if (rrjqbvValidator.lastValue === data.value) {
  535. rrjqbvValidator.lastValid = data.valid;
  536. if (data.message) {
  537. rrjqbvValidator.message = data.message;
  538. }
  539. rrjqbvValidator.lastFinished = true;
  540. rrjqbvThis.data("validation" + rrjqbvValidator.validatorName + "Message", rrjqbvValidator.message);
  541. // Timeout is set to avoid problems with the events being considered 'already fired'
  542. setTimeout(function() {
  543. rrjqbvThis.trigger("change.validation");
  544. }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst
  545. }
  546. }
  547. );
  548. }
  549. return false;
  550. }
  551. },
  552. ajax: {
  553. name: "ajax",
  554. init: function($this, name) {
  555. return {
  556. validatorName: name,
  557. url: $this.data("validation" + name + "Ajax"),
  558. lastValue: $this.val(),
  559. lastValid: true,
  560. lastFinished: true
  561. };
  562. },
  563. validate: function($this, value, validator) {
  564. if ("" + validator.lastValue === "" + value && validator.lastFinished === true) {
  565. return validator.lastValid === false;
  566. }
  567. if (validator.lastFinished === true) {
  568. validator.lastValue = value;
  569. validator.lastValid = true;
  570. validator.lastFinished = false;
  571. $.ajax({
  572. url: validator.url,
  573. data: "value=" + value + "&field=" + $this.attr("name"),
  574. dataType: "json",
  575. success: function(data) {
  576. if ("" + validator.lastValue === "" + data.value) {
  577. validator.lastValid = !!(data.valid);
  578. if (data.message) {
  579. validator.message = data.message;
  580. }
  581. validator.lastFinished = true;
  582. $this.data("validation" + validator.validatorName + "Message", validator.message);
  583. // Timeout is set to avoid problems with the events being considered 'already fired'
  584. setTimeout(function() {
  585. $this.trigger("change.validation");
  586. }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst
  587. }
  588. },
  589. failure: function() {
  590. validator.lastValid = true;
  591. validator.message = "ajax call failed";
  592. validator.lastFinished = true;
  593. $this.data("validation" + validator.validatorName + "Message", validator.message);
  594. // Timeout is set to avoid problems with the events being considered 'already fired'
  595. setTimeout(function() {
  596. $this.trigger("change.validation");
  597. }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst
  598. }
  599. });
  600. }
  601. return false;
  602. }
  603. },
  604. regex: {
  605. name: "regex",
  606. init: function($this, name) {
  607. return {
  608. regex: regexFromString($this.data("validation" + name + "Regex"))
  609. };
  610. },
  611. validate: function($this, value, validator) {
  612. return (!validator.regex.test(value) && !validator.negative) ||
  613. (validator.regex.test(value) && validator.negative);
  614. }
  615. },
  616. required: {
  617. name: "required",
  618. init: function($this, name) {
  619. return {};
  620. },
  621. validate: function($this, value, validator) {
  622. return !!(value.length === 0 && !validator.negative) ||
  623. !!(value.length > 0 && validator.negative);
  624. },
  625. blockSubmit: true
  626. },
  627. match: {
  628. name: "match",
  629. init: function($this, name) {
  630. var element = $this.parents("form").first().find("[name=\"" + $this.data("validation" + name + "Match") + "\"]").first();
  631. element.bind("validation.validation", function() {
  632. $this.trigger("change.validation", {
  633. submitting: true
  634. });
  635. });
  636. return {
  637. "element": element
  638. };
  639. },
  640. validate: function($this, value, validator) {
  641. return (value !== validator.element.val() && !validator.negative) ||
  642. (value === validator.element.val() && validator.negative);
  643. },
  644. blockSubmit: true
  645. },
  646. max: {
  647. name: "max",
  648. init: function($this, name) {
  649. return {
  650. max: $this.data("validation" + name + "Max")
  651. };
  652. },
  653. validate: function($this, value, validator) {
  654. return (parseFloat(value, 10) > parseFloat(validator.max, 10) && !validator.negative) ||
  655. (parseFloat(value, 10) <= parseFloat(validator.max, 10) && validator.negative);
  656. }
  657. },
  658. min: {
  659. name: "min",
  660. init: function($this, name) {
  661. return {
  662. min: $this.data("validation" + name + "Min")
  663. };
  664. },
  665. validate: function($this, value, validator) {
  666. return (parseFloat(value) < parseFloat(validator.min) && !validator.negative) ||
  667. (parseFloat(value) >= parseFloat(validator.min) && validator.negative);
  668. }
  669. },
  670. maxlength: {
  671. name: "maxlength",
  672. init: function($this, name) {
  673. return {
  674. maxlength: $this.data("validation" + name + "Maxlength")
  675. };
  676. },
  677. validate: function($this, value, validator) {
  678. return ((value.length > validator.maxlength) && !validator.negative) ||
  679. ((value.length <= validator.maxlength) && validator.negative);
  680. }
  681. },
  682. minlength: {
  683. name: "minlength",
  684. init: function($this, name) {
  685. return {
  686. minlength: $this.data("validation" + name + "Minlength")
  687. };
  688. },
  689. validate: function($this, value, validator) {
  690. return ((value.length < validator.minlength) && !validator.negative) ||
  691. ((value.length >= validator.minlength) && validator.negative);
  692. }
  693. },
  694. maxchecked: {
  695. name: "maxchecked",
  696. init: function($this, name) {
  697. var elements = $this.parents("form").first().find("[name=\"" + $this.attr("name") + "\"]");
  698. elements.bind("click.validation", function() {
  699. $this.trigger("change.validation", {
  700. includeEmpty: true
  701. });
  702. });
  703. return {
  704. maxchecked: $this.data("validation" + name + "Maxchecked"),
  705. elements: elements
  706. };
  707. },
  708. validate: function($this, value, validator) {
  709. return (validator.elements.filter(":checked").length > validator.maxchecked && !validator.negative) ||
  710. (validator.elements.filter(":checked").length <= validator.maxchecked && validator.negative);
  711. },
  712. blockSubmit: true
  713. },
  714. minchecked: {
  715. name: "minchecked",
  716. init: function($this, name) {
  717. var elements = $this.parents("form").first().find("[name=\"" + $this.attr("name") + "\"]");
  718. elements.bind("click.validation", function() {
  719. $this.trigger("change.validation", {
  720. includeEmpty: true
  721. });
  722. });
  723. return {
  724. minchecked: $this.data("validation" + name + "Minchecked"),
  725. elements: elements
  726. };
  727. },
  728. validate: function($this, value, validator) {
  729. return (validator.elements.filter(":checked").length < validator.minchecked && !validator.negative) ||
  730. (validator.elements.filter(":checked").length >= validator.minchecked && validator.negative);
  731. },
  732. blockSubmit: true
  733. }
  734. },
  735. builtInValidators: {
  736. email: {
  737. name: "Email",
  738. type: "shortcut",
  739. shortcut: "validemail"
  740. },
  741. validemail: {
  742. name: "Validemail",
  743. type: "regex",
  744. regex: "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\\.[A-Za-z]{2,4}",
  745. message: "Not a valid email address<!-- data-validator-validemail-message to override -->"
  746. },
  747. passwordagain: {
  748. name: "Passwordagain",
  749. type: "match",
  750. match: "password",
  751. message: "Does not match the given password<!-- data-validator-paswordagain-message to override -->"
  752. },
  753. positive: {
  754. name: "Positive",
  755. type: "shortcut",
  756. shortcut: "number,positivenumber"
  757. },
  758. negative: {
  759. name: "Negative",
  760. type: "shortcut",
  761. shortcut: "number,negativenumber"
  762. },
  763. number: {
  764. name: "Number",
  765. type: "regex",
  766. regex: "([+-]?\\\d+(\\\.\\\d*)?([eE][+-]?[0-9]+)?)?",
  767. message: "Must be a number<!-- data-validator-number-message to override -->"
  768. },
  769. integer: {
  770. name: "Integer",
  771. type: "regex",
  772. regex: "[+-]?\\\d+",
  773. message: "No decimal places allowed<!-- data-validator-integer-message to override -->"
  774. },
  775. positivenumber: {
  776. name: "Positivenumber",
  777. type: "min",
  778. min: 0,
  779. message: "Must be a positive number<!-- data-validator-positivenumber-message to override -->"
  780. },
  781. negativenumber: {
  782. name: "Negativenumber",
  783. type: "max",
  784. max: 0,
  785. message: "Must be a negative number<!-- data-validator-negativenumber-message to override -->"
  786. },
  787. required: {
  788. name: "Required",
  789. type: "required",
  790. message: "This is required<!-- data-validator-required-message to override -->"
  791. },
  792. checkone: {
  793. name: "Checkone",
  794. type: "minchecked",
  795. minchecked: 1,
  796. message: "Check at least one option<!-- data-validation-checkone-message to override -->"
  797. }
  798. }
  799. };
  800. var formatValidatorName = function(name) {
  801. return name
  802. .toLowerCase()
  803. .replace(
  804. /(^|\s)([a-z])/g,
  805. function(m, p1, p2) {
  806. return p1 + p2.toUpperCase();
  807. }
  808. );
  809. };
  810. var getValue = function($this) {
  811. // Extract the value we're talking about
  812. var value = $this.val();
  813. var type = $this.attr("type");
  814. if (type === "checkbox") {
  815. value = ($this.is(":checked") ? value : "");
  816. }
  817. if (type === "radio") {
  818. value = ($('input[name="' + $this.attr("name") + '"]:checked').length > 0 ? value : "");
  819. }
  820. return value;
  821. };
  822. function regexFromString(inputstring) {
  823. return new RegExp("^" + inputstring + "$");
  824. }
  825. /**
  826. * Thanks to Jason Bunting via StackOverflow.com
  827. *
  828. * http://stackoverflow.com/questions/359788/how-to-execute-a-javascript-function-when-i-have-its-name-as-a-string#answer-359910
  829. * Short link: http://tinyurl.com/executeFunctionByName
  830. **/
  831. function executeFunctionByName(functionName, context /*, args*/ ) {
  832. var args = Array.prototype.slice.call(arguments).splice(2);
  833. var namespaces = functionName.split(".");
  834. var func = namespaces.pop();
  835. for (var i = 0; i < namespaces.length; i++) {
  836. context = context[namespaces[i]];
  837. }
  838. return context[func].apply(this, args);
  839. }
  840. $.fn.jqBootstrapValidation = function(method) {
  841. if (defaults.methods[method]) {
  842. return defaults.methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
  843. } else if (typeof method === 'object' || !method) {
  844. return defaults.methods.init.apply(this, arguments);
  845. } else {
  846. $.error('Method ' + method + ' does not exist on jQuery.jqBootstrapValidation');
  847. return null;
  848. }
  849. };
  850. $.jqBootstrapValidation = function(options) {
  851. $(":input").not("[type=image],[type=submit]").jqBootstrapValidation.apply(this, arguments);
  852. };
  853. })(jQuery);