PageRenderTime 53ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/files/parsleyjs/2.2.0-rc1/parsley.js

https://gitlab.com/Mirros/jsdelivr
JavaScript | 1189 lines | 970 code | 8 blank | 211 comment | 191 complexity | 1d49d748f2d64123313d1741137ad694 MD5 | raw file
  1. /*!
  2. * Parsleyjs
  3. * Guillaume Potier - <guillaume@wisembly.com>
  4. * Version 2.2.0-rc1 - built Sun Aug 16 2015 14:04:07
  5. * MIT Licensed
  6. *
  7. */
  8. !(function (factory) {
  9. if (typeof define === 'function' && define.amd) {
  10. // AMD. Register as an anonymous module depending on jQuery.
  11. define(['jquery'], factory);
  12. } else if (typeof exports === 'object') {
  13. // Node/CommonJS
  14. module.exports = factory(require('jquery'));
  15. } else {
  16. // Register plugin with global jQuery object.
  17. factory(jQuery);
  18. }
  19. }(function ($) {
  20. // small hack for requirejs if jquery is loaded through map and not path
  21. // see http://requirejs.org/docs/jquery.html
  22. if ('undefined' === typeof $ && 'undefined' !== typeof window.jQuery)
  23. $ = window.jQuery;
  24. var globalID = 1,
  25. pastWarnings = {};
  26. var ParsleyUtils = {
  27. // Parsley DOM-API
  28. // returns object from dom attributes and values
  29. attr: function ($element, namespace, obj) {
  30. var
  31. attribute, attributes,
  32. regex = new RegExp('^' + namespace, 'i');
  33. if ('undefined' === typeof obj)
  34. obj = {};
  35. else {
  36. // Clear all own properties. This won't affect prototype's values
  37. for (var i in obj) {
  38. if (obj.hasOwnProperty(i))
  39. delete obj[i];
  40. }
  41. }
  42. if ('undefined' === typeof $element || 'undefined' === typeof $element[0])
  43. return obj;
  44. attributes = $element[0].attributes;
  45. for (var i = attributes.length; i--; ) {
  46. attribute = attributes[i];
  47. if (attribute && attribute.specified && regex.test(attribute.name)) {
  48. obj[this.camelize(attribute.name.slice(namespace.length))] = this.deserializeValue(attribute.value);
  49. }
  50. }
  51. return obj;
  52. },
  53. checkAttr: function ($element, namespace, checkAttr) {
  54. return $element.is('[' + namespace + checkAttr + ']');
  55. },
  56. setAttr: function ($element, namespace, attr, value) {
  57. $element[0].setAttribute(this.dasherize(namespace + attr), String(value));
  58. },
  59. generateID: function () {
  60. return '' + globalID++;
  61. },
  62. /** Third party functions **/
  63. // Zepto deserialize function
  64. deserializeValue: function (value) {
  65. var num;
  66. try {
  67. return value ?
  68. value == "true" ||
  69. (value == "false" ? false :
  70. value == "null" ? null :
  71. !isNaN(num = Number(value)) ? num :
  72. /^[\[\{]/.test(value) ? $.parseJSON(value) :
  73. value)
  74. : value;
  75. } catch (e) { return value; }
  76. },
  77. // Zepto camelize function
  78. camelize: function (str) {
  79. return str.replace(/-+(.)?/g, function (match, chr) {
  80. return chr ? chr.toUpperCase() : '';
  81. });
  82. },
  83. // Zepto dasherize function
  84. dasherize: function (str) {
  85. return str.replace(/::/g, '/')
  86. .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
  87. .replace(/([a-z\d])([A-Z])/g, '$1_$2')
  88. .replace(/_/g, '-')
  89. .toLowerCase();
  90. },
  91. warn: function() {
  92. if (window.console && 'function' === typeof window.console.warn)
  93. window.console.warn.apply(window.console, arguments);
  94. },
  95. warnOnce: function(msg) {
  96. if (!pastWarnings[msg]) {
  97. pastWarnings[msg] = true;
  98. this.warn.apply(this, arguments);
  99. }
  100. },
  101. _resetWarnings: function() {
  102. pastWarnings = {};
  103. },
  104. trimString: function(string) {
  105. return string.replace(/^\s+|\s+$/g, '');
  106. },
  107. // Object.create polyfill, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Polyfill
  108. objectCreate: Object.create || (function () {
  109. var Object = function () {};
  110. return function (prototype) {
  111. if (arguments.length > 1) {
  112. throw Error('Second argument not supported');
  113. }
  114. if (typeof prototype != 'object') {
  115. throw TypeError('Argument must be an object');
  116. }
  117. Object.prototype = prototype;
  118. var result = new Object();
  119. Object.prototype = null;
  120. return result;
  121. };
  122. })()
  123. };
  124. // All these options could be overriden and specified directly in DOM using
  125. // `data-parsley-` default DOM-API
  126. // eg: `inputs` can be set in DOM using `data-parsley-inputs="input, textarea"`
  127. // eg: `data-parsley-stop-on-first-failing-constraint="false"`
  128. var ParsleyDefaults = {
  129. // ### General
  130. // Default data-namespace for DOM API
  131. namespace: 'data-parsley-',
  132. // Supported inputs by default
  133. inputs: 'input, textarea, select',
  134. // Excluded inputs by default
  135. excluded: 'input[type=button], input[type=submit], input[type=reset], input[type=hidden]',
  136. // Stop validating field on highest priority failing constraint
  137. priorityEnabled: true,
  138. // ### Field only
  139. // identifier used to group together inputs (e.g. radio buttons...)
  140. multiple: null,
  141. // identifier (or array of identifiers) used to validate only a select group of inputs
  142. group: null,
  143. // ### UI
  144. // Enable\Disable error messages
  145. uiEnabled: true,
  146. // Key events threshold before validation
  147. validationThreshold: 3,
  148. // Focused field on form validation error. 'first'|'last'|'none'
  149. focus: 'first',
  150. // `$.Event()` that will trigger validation. eg: `keyup`, `change`...
  151. trigger: false,
  152. // Class that would be added on every failing validation Parsley field
  153. errorClass: 'parsley-error',
  154. // Same for success validation
  155. successClass: 'parsley-success',
  156. // Return the `$element` that will receive these above success or error classes
  157. // Could also be (and given directly from DOM) a valid selector like `'#div'`
  158. classHandler: function (ParsleyField) {},
  159. // Return the `$element` where errors will be appended
  160. // Could also be (and given directly from DOM) a valid selector like `'#div'`
  161. errorsContainer: function (ParsleyField) {},
  162. // ul elem that would receive errors' list
  163. errorsWrapper: '<ul class="parsley-errors-list"></ul>',
  164. // li elem that would receive error message
  165. errorTemplate: '<li></li>'
  166. };
  167. var ParsleyAbstract = function () {};
  168. ParsleyAbstract.prototype = {
  169. asyncSupport: true, // Deprecated
  170. actualizeOptions: function () {
  171. ParsleyUtils.attr(this.$element, this.options.namespace, this.domOptions);
  172. if (this.parent && this.parent.actualizeOptions)
  173. this.parent.actualizeOptions();
  174. return this;
  175. },
  176. _resetOptions: function (initOptions) {
  177. this.domOptions = ParsleyUtils.objectCreate(this.parent.options);
  178. this.options = ParsleyUtils.objectCreate(this.domOptions);
  179. // Shallow copy of ownProperties of initOptions:
  180. for (var i in initOptions) {
  181. if (initOptions.hasOwnProperty(i))
  182. this.options[i] = initOptions[i];
  183. }
  184. this.actualizeOptions();
  185. },
  186. _listeners: null,
  187. // Register a callback for the given event name.
  188. // Callback is called with context as the first argument and the `this`.
  189. // The context is the current parsley instance, or window.Parsley if global.
  190. // A return value of `false` will interrupt the calls
  191. on: function (name, fn) {
  192. this._listeners = this._listeners || {};
  193. var queue = this._listeners[name] = this._listeners[name] || [];
  194. queue.push(fn);
  195. return this;
  196. },
  197. // Deprecated. Use `on` instead.
  198. subscribe: function(name, fn) {
  199. $.listenTo(this, name.toLowerCase(), fn);
  200. },
  201. // Unregister a callback (or all if none is given) for the given event name
  202. off: function (name, fn) {
  203. var queue = this._listeners && this._listeners[name];
  204. if (queue) {
  205. if (!fn) {
  206. delete this._listeners[name];
  207. } else {
  208. for(var i = queue.length; i--; )
  209. if (queue[i] === fn)
  210. queue.splice(i, 1);
  211. }
  212. }
  213. return this;
  214. },
  215. // Deprecated. Use `off`
  216. unsubscribe: function(name, fn) {
  217. $.unsubscribeTo(this, name.toLowerCase());
  218. },
  219. // Trigger an event of the given name.
  220. // A return value of `false` interrupts the callback chain.
  221. // Returns false if execution was interrupted.
  222. trigger: function (name, target) {
  223. target = target || this;
  224. var queue = this._listeners && this._listeners[name];
  225. var result, parentResult;
  226. if (queue) {
  227. for(var i = queue.length; i--; ) {
  228. result = queue[i].call(target, target);
  229. if (result === false) return result;
  230. }
  231. }
  232. if (this.parent) {
  233. return this.parent.trigger(name, target);
  234. }
  235. return true;
  236. },
  237. // Reset UI
  238. reset: function () {
  239. // Field case: just emit a reset event for UI
  240. if ('ParsleyForm' !== this.__class__)
  241. return this._trigger('reset');
  242. // Form case: emit a reset event for each field
  243. for (var i = 0; i < this.fields.length; i++)
  244. this.fields[i]._trigger('reset');
  245. this._trigger('reset');
  246. },
  247. // Destroy Parsley instance (+ UI)
  248. destroy: function () {
  249. // Field case: emit destroy event to clean UI and then destroy stored instance
  250. if ('ParsleyForm' !== this.__class__) {
  251. this.$element.removeData('Parsley');
  252. this.$element.removeData('ParsleyFieldMultiple');
  253. this._trigger('destroy');
  254. return;
  255. }
  256. // Form case: destroy all its fields and then destroy stored instance
  257. for (var i = 0; i < this.fields.length; i++)
  258. this.fields[i].destroy();
  259. this.$element.removeData('Parsley');
  260. this._trigger('destroy');
  261. },
  262. asyncIsValid: function() {
  263. ParsleyUtils.warnOnce("asyncIsValid is deprecated; please use whenIsValid instead");
  264. return this.whenValid.apply(this, arguments);
  265. },
  266. _findRelatedMultiple: function() {
  267. return this.parent.$element.find('[' + this.options.namespace + 'multiple="' + this.options.multiple +'"]');
  268. }
  269. };
  270. var requirementConverters = {
  271. string: function(string) {
  272. return string;
  273. },
  274. integer: function(string) {
  275. if (isNaN(string))
  276. throw 'Requirement is not an integer: "' + string + '"';
  277. return parseInt(string, 10);
  278. },
  279. number: function(string) {
  280. if (isNaN(string))
  281. throw 'Requirement is not a number: "' + string + '"';
  282. return parseFloat(string);
  283. },
  284. reference: function(string) { // Unused for now
  285. var result = $(string);
  286. if (result.length === 0)
  287. throw 'No such reference: "' + string + '"';
  288. return result;
  289. },
  290. boolean: function(string) {
  291. return string !== 'false';
  292. },
  293. object: function(string) {
  294. return ParsleyUtils.deserializeValue(string);
  295. },
  296. regexp: function(regexp) {
  297. var flags = '';
  298. // Test if RegExp is literal, if not, nothing to be done, otherwise, we need to isolate flags and pattern
  299. if (!!(/^\/.*\/(?:[gimy]*)$/.test(regexp))) {
  300. // Replace the regexp literal string with the first match group: ([gimy]*)
  301. // If no flag is present, this will be a blank string
  302. flags = regexp.replace(/.*\/([gimy]*)$/, '$1');
  303. // Again, replace the regexp literal string with the first match group:
  304. // everything excluding the opening and closing slashes and the flags
  305. regexp = regexp.replace(new RegExp('^/(.*?)/' + flags + '$'), '$1');
  306. }
  307. return new RegExp(regexp, flags);
  308. }
  309. };
  310. var convertArrayRequirement = function(string, length) {
  311. var m = string.match(/^\s*\[(.*)\]\s*$/)
  312. if (!m)
  313. throw 'Requirement is not an array: "' + string + '"';
  314. var values = m[1].split(',').map(ParsleyUtils.trimString);
  315. if (values.length !== length)
  316. throw 'Requirement has ' + values.length + ' values when ' + length + ' are needed';
  317. return values;
  318. };
  319. var convertRequirement = function(requirementType, string) {
  320. var converter = requirementConverters[requirementType || 'string'];
  321. if (!converter)
  322. throw 'Unknown requirement specification: "' + requirementType + '"';
  323. return converter(string);
  324. };
  325. var convertExtraOptionRequirement = function(requirementSpec, string, extraOptionReader) {
  326. var main = null, extra = {};
  327. for(var key in requirementSpec) {
  328. if (key) {
  329. var value = extraOptionReader(key);
  330. if('string' === typeof value)
  331. value = convertRequirement(requirementSpec[key], value);
  332. extra[key] = value;
  333. } else {
  334. main = convertRequirement(requirementSpec[key], string)
  335. }
  336. }
  337. return [main, extra];
  338. };
  339. // A Validator needs to implement the methods `validate` and `parseRequirements`
  340. var ParsleyValidator = function(spec) {
  341. $.extend(true, this, spec);
  342. };
  343. ParsleyValidator.prototype = {
  344. // Returns `true` iff the given `value` is valid according the given requirements.
  345. validate: function(value, requirementFirstArg) {
  346. if(this.fn) { // Legacy style validator
  347. if(arguments.length > 3) // If more args then value, requirement, instance...
  348. requirementFirstArg = [].slice.call(arguments, 1, -1); // Skip first arg (value) and last (instance), combining the rest
  349. return this.fn.call(this, value, requirementFirstArg);
  350. }
  351. if ($.isArray(value)) {
  352. if (!this.validateMultiple)
  353. throw 'Validator `' + this.name + '` does not handle multiple values';
  354. return this.validateMultiple.apply(this, arguments);
  355. } else {
  356. if (this.validateNumber) {
  357. if (isNaN(value))
  358. return false;
  359. value = parseFloat(value);
  360. return this.validateNumber.apply(this, arguments);
  361. }
  362. if (this.validateString) {
  363. return this.validateString.apply(this, arguments);
  364. }
  365. throw 'Validator `' + this.name + '` only handles multiple values';
  366. }
  367. },
  368. // Parses `requirements` into an array of arguments,
  369. // according to `this.requirementType`
  370. parseRequirements: function(requirements, extraOptionReader) {
  371. if ('string' !== typeof requirements) {
  372. // Assume requirement already parsed
  373. // but make sure we return an array
  374. return $.isArray(requirements) ? requirements : [requirements];
  375. }
  376. var type = this.requirementType;
  377. if ($.isArray(type)) {
  378. var values = convertArrayRequirement(requirements, type.length);
  379. for (var i = 0; i < values.length; i++)
  380. values[i] = convertRequirement(type[i], values[i]);
  381. return values;
  382. } else if ($.isPlainObject(type)) {
  383. return convertExtraOptionRequirement(type, requirements, extraOptionReader)
  384. } else {
  385. return [convertRequirement(type, requirements)];
  386. }
  387. },
  388. // Defaults:
  389. requirementType: 'string',
  390. priority: 2
  391. };
  392. var ParsleyValidatorRegistry = function (validators, catalog) {
  393. this.__class__ = 'ParsleyValidatorRegistry';
  394. // Default Parsley locale is en
  395. this.locale = 'en';
  396. this.init(validators || {}, catalog || {});
  397. };
  398. var typeRegexes = {
  399. email: /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i,
  400. number: /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/,
  401. integer: /^-?\d+$/,
  402. digits: /^\d+$/,
  403. alphanum: /^\w+$/i,
  404. url: new RegExp(
  405. "^" +
  406. // protocol identifier
  407. "(?:(?:https?|ftp)://)?" + // ** mod: make scheme optional
  408. // user:pass authentication
  409. "(?:\\S+(?::\\S*)?@)?" +
  410. "(?:" +
  411. // IP address exclusion
  412. // private & local networks
  413. // "(?!(?:10|127)(?:\\.\\d{1,3}){3})" + // ** mod: allow local networks
  414. // "(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})" + // ** mod: allow local networks
  415. // "(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})" + // ** mod: allow local networks
  416. // IP address dotted notation octets
  417. // excludes loopback network 0.0.0.0
  418. // excludes reserved space >= 224.0.0.0
  419. // excludes network & broacast addresses
  420. // (first & last IP address of each class)
  421. "(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" +
  422. "(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" +
  423. "(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))" +
  424. "|" +
  425. // host name
  426. "(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)" +
  427. // domain name
  428. "(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*" +
  429. // TLD identifier
  430. "(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))" +
  431. ")" +
  432. // port number
  433. "(?::\\d{2,5})?" +
  434. // resource path
  435. "(?:/\\S*)?" +
  436. "$", 'i'
  437. )
  438. };
  439. typeRegexes.range = typeRegexes.number;
  440. ParsleyValidatorRegistry.prototype = {
  441. init: function (validators, catalog) {
  442. this.catalog = catalog;
  443. // Copy prototype's validators:
  444. this.validators = $.extend({}, this.validators);
  445. for (var name in validators)
  446. this.addValidator(name, validators[name].fn, validators[name].priority);
  447. window.Parsley.trigger('parsley:validator:init');
  448. },
  449. // Set new messages locale if we have dictionary loaded in ParsleyConfig.i18n
  450. setLocale: function (locale) {
  451. if ('undefined' === typeof this.catalog[locale])
  452. throw new Error(locale + ' is not available in the catalog');
  453. this.locale = locale;
  454. return this;
  455. },
  456. // Add a new messages catalog for a given locale. Set locale for this catalog if set === `true`
  457. addCatalog: function (locale, messages, set) {
  458. if ('object' === typeof messages)
  459. this.catalog[locale] = messages;
  460. if (true === set)
  461. return this.setLocale(locale);
  462. return this;
  463. },
  464. // Add a specific message for a given constraint in a given locale
  465. addMessage: function (locale, name, message) {
  466. if ('undefined' === typeof this.catalog[locale])
  467. this.catalog[locale] = {};
  468. this.catalog[locale][name.toLowerCase()] = message;
  469. return this;
  470. },
  471. // Add a new validator
  472. //
  473. // addValidator('custom', {
  474. // requirementType: ['integer', 'integer'],
  475. // validateString: function(value, from, to) {},
  476. // priority: 22,
  477. // messages: {
  478. // en: "Hey, that's no good",
  479. // fr: "Aye aye, pas bon du tout",
  480. // }
  481. // }
  482. //
  483. // Old API was addValidator(name, function, priority)
  484. //
  485. addValidator: function (name, arg1, arg2) {
  486. if (this.validators[name])
  487. ParsleyUtils.warn('Validator "' + name + '" is already defined.');
  488. else if (ParsleyDefaults.hasOwnProperty(name)) {
  489. ParsleyUtils.warn('"' + name + '" is a restricted keyword and is not a valid validator name.');
  490. return;
  491. };
  492. return this._setValidator.apply(this, arguments);
  493. },
  494. updateValidator: function (name, arg1, arg2) {
  495. if (!this.validators[name]) {
  496. ParsleyUtils.warn('Validator "' + name + '" is not already defined.');
  497. return this.addValidator.apply(this, arguments);
  498. }
  499. return this._setValidator(this, arguments);
  500. },
  501. removeValidator: function (name) {
  502. if (!this.validators[name])
  503. ParsleyUtils.warn('Validator "' + name + '" is not defined.');
  504. delete this.validators[name];
  505. return this;
  506. },
  507. _setValidator: function (name, validator, priority) {
  508. if ('object' !== typeof validator) {
  509. // Old style validator, with `fn` and `priority`
  510. validator = {
  511. fn: validator,
  512. priority: priority
  513. };
  514. };
  515. if (!validator.validate) {
  516. validator = new ParsleyValidator(validator);
  517. };
  518. this.validators[name] = validator;
  519. for (var locale in validator.messages || {})
  520. this.addMessage(locale, name, validator.messages[locale]);
  521. return this;
  522. },
  523. getErrorMessage: function (constraint) {
  524. var message;
  525. // Type constraints are a bit different, we have to match their requirements too to find right error message
  526. if ('type' === constraint.name) {
  527. var typeMessages = this.catalog[this.locale][constraint.name] || {};
  528. message = typeMessages[constraint.requirements];
  529. } else
  530. message = this.formatMessage(this.catalog[this.locale][constraint.name], constraint.requirements);
  531. return message || this.catalog[this.locale].defaultMessage || this.catalog.en.defaultMessage;
  532. },
  533. // Kind of light `sprintf()` implementation
  534. formatMessage: function (string, parameters) {
  535. if ('object' === typeof parameters) {
  536. for (var i in parameters)
  537. string = this.formatMessage(string, parameters[i]);
  538. return string;
  539. }
  540. return 'string' === typeof string ? string.replace(new RegExp('%s', 'i'), parameters) : '';
  541. },
  542. // Here is the Parsley default validators list.
  543. // A validator is an object with the following key values:
  544. // - priority: an integer
  545. // - requirement: 'string' (default), 'integer', 'number', 'regexp' or an Array of these
  546. // - validateString, validateMultiple, validateNumber: functions returning `true`, `false` or a promise
  547. // Alternatively, a validator can be a function that returns such an object
  548. //
  549. validators: {
  550. notblank: {
  551. validateString: function(value) {
  552. return /\S/.test(value);
  553. },
  554. priority: 2
  555. },
  556. required: {
  557. validateMultiple: function(values) {
  558. return values.length > 0;
  559. },
  560. validateString: function(value) {
  561. return /\S/.test(value);
  562. },
  563. priority: 512
  564. },
  565. type: {
  566. validateString: function(value, type) {
  567. var regex = typeRegexes[type];
  568. if (!regex)
  569. throw new Error('validator type `' + type + '` is not supported');
  570. return regex.test(value);
  571. },
  572. priority: 256
  573. },
  574. pattern: {
  575. validateString: function(value, regexp) {
  576. return regexp.test(value);
  577. },
  578. requirementType: 'regexp',
  579. priority: 64
  580. },
  581. minlength: {
  582. validateString: function (value, requirement) {
  583. return value.length >= requirement;
  584. },
  585. requirementType: 'integer',
  586. priority: 30
  587. },
  588. maxlength: {
  589. validateString: function (value, requirement) {
  590. return value.length <= requirement;
  591. },
  592. requirementType: 'integer',
  593. priority: 30
  594. },
  595. length: {
  596. validateString: function (value, min, max) {
  597. return value.length >= min && value.length <= max;
  598. },
  599. requirementType: ['integer', 'integer'],
  600. priority: 30
  601. },
  602. mincheck: {
  603. validateMultiple: function (values, requirement) {
  604. return values.length >= requirement;
  605. },
  606. requirementType: 'integer',
  607. priority: 30
  608. },
  609. maxcheck: {
  610. validateMultiple: function (values, requirement) {
  611. return values.length <= requirement;
  612. },
  613. requirementType: 'integer',
  614. priority: 30
  615. },
  616. check: {
  617. validateMultiple: function (values, min, max) {
  618. return values.length >= min && values.length <= max;
  619. },
  620. requirementType: ['integer', 'integer'],
  621. priority: 30
  622. },
  623. min: {
  624. validateNumber: function (value, requirement) {
  625. return value >= requirement;
  626. },
  627. requirementType: 'number',
  628. priority: 30
  629. },
  630. max: {
  631. validateNumber: function (value, requirement) {
  632. return value <= requirement;
  633. },
  634. requirementType: 'number',
  635. priority: 30
  636. },
  637. range: {
  638. validateNumber: function (value, min, max) {
  639. return value >= min && value <= max;
  640. },
  641. requirementType: ['number', 'number'],
  642. priority: 30
  643. },
  644. equalto: {
  645. validateString: function (value, refOrValue) {
  646. var $reference = $(refOrValue);
  647. if ($reference.length)
  648. return value === $reference.val();
  649. else
  650. return value === refOrValue;
  651. },
  652. priority: 256
  653. }
  654. }
  655. };
  656. var ParsleyUI = function (options) {
  657. this.__class__ = 'ParsleyUI';
  658. };
  659. ParsleyUI.prototype = {
  660. listen: function () {
  661. var that = this;
  662. window.Parsley
  663. .on('form:init', function () { that.setupForm (this); } )
  664. .on('field:init', function () { that.setupField(this); } )
  665. .on('field:validated', function () { that.reflow (this); } )
  666. .on('form:validated', function () { that.focus (this); } )
  667. .on('field:reset', function () { that.reset (this); } )
  668. .on('form:destroy', function () { that.destroy (this); } )
  669. .on('field:destroy', function () { that.destroy (this); } );
  670. return this;
  671. },
  672. reflow: function (fieldInstance) {
  673. // If this field has not an active UI (case for multiples) don't bother doing something
  674. if ('undefined' === typeof fieldInstance._ui || false === fieldInstance._ui.active)
  675. return;
  676. // Diff between two validation results
  677. var diff = this._diff(fieldInstance.validationResult, fieldInstance._ui.lastValidationResult);
  678. // Then store current validation result for next reflow
  679. fieldInstance._ui.lastValidationResult = fieldInstance.validationResult;
  680. // Field have been validated at least once if here. Useful for binded key events...
  681. fieldInstance._ui.validatedOnce = true;
  682. // Handle valid / invalid / none field class
  683. this.manageStatusClass(fieldInstance);
  684. // Add, remove, updated errors messages
  685. this.manageErrorsMessages(fieldInstance, diff);
  686. // Triggers impl
  687. this.actualizeTriggers(fieldInstance);
  688. // If field is not valid for the first time, bind keyup trigger to ease UX and quickly inform user
  689. if ((diff.kept.length || diff.added.length) && true !== fieldInstance._ui.failedOnce)
  690. this.manageFailingFieldTrigger(fieldInstance);
  691. },
  692. // Returns an array of field's error message(s)
  693. getErrorsMessages: function (fieldInstance) {
  694. // No error message, field is valid
  695. if (true === fieldInstance.validationResult)
  696. return [];
  697. var messages = [];
  698. for (var i = 0; i < fieldInstance.validationResult.length; i++)
  699. messages.push(this._getErrorMessage(fieldInstance, fieldInstance.validationResult[i].assert));
  700. return messages;
  701. },
  702. manageStatusClass: function (fieldInstance) {
  703. if (fieldInstance.hasConstraints() && fieldInstance.needsValidation() && true === fieldInstance.validationResult)
  704. this._successClass(fieldInstance);
  705. else if (fieldInstance.validationResult.length > 0)
  706. this._errorClass(fieldInstance);
  707. else
  708. this._resetClass(fieldInstance);
  709. },
  710. manageErrorsMessages: function (fieldInstance, diff) {
  711. if ('undefined' !== typeof fieldInstance.options.errorsMessagesDisabled)
  712. return;
  713. // Case where we have errorMessage option that configure an unique field error message, regardless failing validators
  714. if ('undefined' !== typeof fieldInstance.options.errorMessage) {
  715. if ((diff.added.length || diff.kept.length)) {
  716. this._insertErrorWrapper(fieldInstance);
  717. if (0 === fieldInstance._ui.$errorsWrapper.find('.parsley-custom-error-message').length)
  718. fieldInstance._ui.$errorsWrapper
  719. .append(
  720. $(fieldInstance.options.errorTemplate)
  721. .addClass('parsley-custom-error-message')
  722. );
  723. return fieldInstance._ui.$errorsWrapper
  724. .addClass('filled')
  725. .find('.parsley-custom-error-message')
  726. .html(fieldInstance.options.errorMessage);
  727. }
  728. return fieldInstance._ui.$errorsWrapper
  729. .removeClass('filled')
  730. .find('.parsley-custom-error-message')
  731. .remove();
  732. }
  733. // Show, hide, update failing constraints messages
  734. for (var i = 0; i < diff.removed.length; i++)
  735. this.removeError(fieldInstance, diff.removed[i].assert.name, true);
  736. for (i = 0; i < diff.added.length; i++)
  737. this.addError(fieldInstance, diff.added[i].assert.name, undefined, diff.added[i].assert, true);
  738. for (i = 0; i < diff.kept.length; i++)
  739. this.updateError(fieldInstance, diff.kept[i].assert.name, undefined, diff.kept[i].assert, true);
  740. },
  741. // TODO: strange API here, intuitive for manual usage with addError(pslyInstance, 'foo', 'bar')
  742. // but a little bit complex for above internal usage, with forced undefined parameter...
  743. addError: function (fieldInstance, name, message, assert, doNotUpdateClass) {
  744. this._insertErrorWrapper(fieldInstance);
  745. fieldInstance._ui.$errorsWrapper
  746. .addClass('filled')
  747. .append(
  748. $(fieldInstance.options.errorTemplate)
  749. .addClass('parsley-' + name)
  750. .html(message || this._getErrorMessage(fieldInstance, assert))
  751. );
  752. if (true !== doNotUpdateClass)
  753. this._errorClass(fieldInstance);
  754. },
  755. // Same as above
  756. updateError: function (fieldInstance, name, message, assert, doNotUpdateClass) {
  757. fieldInstance._ui.$errorsWrapper
  758. .addClass('filled')
  759. .find('.parsley-' + name)
  760. .html(message || this._getErrorMessage(fieldInstance, assert));
  761. if (true !== doNotUpdateClass)
  762. this._errorClass(fieldInstance);
  763. },
  764. // Same as above twice
  765. removeError: function (fieldInstance, name, doNotUpdateClass) {
  766. fieldInstance._ui.$errorsWrapper
  767. .removeClass('filled')
  768. .find('.parsley-' + name)
  769. .remove();
  770. // edge case possible here: remove a standard Parsley error that is still failing in fieldInstance.validationResult
  771. // but highly improbable cuz' manually removing a well Parsley handled error makes no sense.
  772. if (true !== doNotUpdateClass)
  773. this.manageStatusClass(fieldInstance);
  774. },
  775. focus: function (formInstance) {
  776. formInstance._focusedField = null;
  777. if (true === formInstance.validationResult || 'none' === formInstance.options.focus)
  778. return null;
  779. for (var i = 0; i < formInstance.fields.length; i++) {
  780. var field = formInstance.fields[i];
  781. if (true !== field.validationResult && field.validationResult.length > 0 && 'undefined' === typeof field.options.noFocus) {
  782. formInstance._focusedField = field.$element;
  783. if ('first' === formInstance.options.focus)
  784. break;
  785. }
  786. }
  787. if (null === formInstance._focusedField)
  788. return null;
  789. return formInstance._focusedField.focus();
  790. },
  791. _getErrorMessage: function (fieldInstance, constraint) {
  792. var customConstraintErrorMessage = constraint.name + 'Message';
  793. if ('undefined' !== typeof fieldInstance.options[customConstraintErrorMessage])
  794. return window.Parsley.formatMessage(fieldInstance.options[customConstraintErrorMessage], constraint.requirements);
  795. return window.Parsley.getErrorMessage(constraint);
  796. },
  797. _diff: function (newResult, oldResult, deep) {
  798. var
  799. added = [],
  800. kept = [];
  801. for (var i = 0; i < newResult.length; i++) {
  802. var found = false;
  803. for (var j = 0; j < oldResult.length; j++)
  804. if (newResult[i].assert.name === oldResult[j].assert.name) {
  805. found = true;
  806. break;
  807. }
  808. if (found)
  809. kept.push(newResult[i]);
  810. else
  811. added.push(newResult[i]);
  812. }
  813. return {
  814. kept: kept,
  815. added: added,
  816. removed: !deep ? this._diff(oldResult, newResult, true).added : []
  817. };
  818. },
  819. setupForm: function (formInstance) {
  820. formInstance.$element.on('submit.Parsley', false, $.proxy(formInstance.onSubmitValidate, formInstance));
  821. // UI could be disabled
  822. if (false === formInstance.options.uiEnabled)
  823. return;
  824. formInstance.$element.attr('novalidate', '');
  825. },
  826. setupField: function (fieldInstance) {
  827. var _ui = { active: false };
  828. // UI could be disabled
  829. if (false === fieldInstance.options.uiEnabled)
  830. return;
  831. _ui.active = true;
  832. // Give field its Parsley id in DOM
  833. fieldInstance.$element.attr(fieldInstance.options.namespace + 'id', fieldInstance.__id__);
  834. /** Generate important UI elements and store them in fieldInstance **/
  835. // $errorClassHandler is the $element that woul have parsley-error and parsley-success classes
  836. _ui.$errorClassHandler = this._manageClassHandler(fieldInstance);
  837. // $errorsWrapper is a div that would contain the various field errors, it will be appended into $errorsContainer
  838. _ui.errorsWrapperId = 'parsley-id-' + (fieldInstance.options.multiple ? 'multiple-' + fieldInstance.options.multiple : fieldInstance.__id__);
  839. _ui.$errorsWrapper = $(fieldInstance.options.errorsWrapper).attr('id', _ui.errorsWrapperId);
  840. // ValidationResult UI storage to detect what have changed bwt two validations, and update DOM accordingly
  841. _ui.lastValidationResult = [];
  842. _ui.validatedOnce = false;
  843. _ui.validationInformationVisible = false;
  844. // Store it in fieldInstance for later
  845. fieldInstance._ui = _ui;
  846. // Bind triggers first time
  847. this.actualizeTriggers(fieldInstance);
  848. },
  849. // Determine which element will have `parsley-error` and `parsley-success` classes
  850. _manageClassHandler: function (fieldInstance) {
  851. // An element selector could be passed through DOM with `data-parsley-class-handler=#foo`
  852. if ('string' === typeof fieldInstance.options.classHandler && $(fieldInstance.options.classHandler).length)
  853. return $(fieldInstance.options.classHandler);
  854. // Class handled could also be determined by function given in Parsley options
  855. var $handler = fieldInstance.options.classHandler(fieldInstance);
  856. // If this function returned a valid existing DOM element, go for it
  857. if ('undefined' !== typeof $handler && $handler.length)
  858. return $handler;
  859. // Otherwise, if simple element (input, texatrea, select...) it will perfectly host the classes
  860. if (!fieldInstance.options.multiple || fieldInstance.$element.is('select'))
  861. return fieldInstance.$element;
  862. // But if multiple element (radio, checkbox), that would be their parent
  863. return fieldInstance.$element.parent();
  864. },
  865. _insertErrorWrapper: function (fieldInstance) {
  866. var $errorsContainer;
  867. // Nothing to do if already inserted
  868. if (0 !== fieldInstance._ui.$errorsWrapper.parent().length)
  869. return fieldInstance._ui.$errorsWrapper.parent();
  870. if ('string' === typeof fieldInstance.options.errorsContainer) {
  871. if ($(fieldInstance.options.errorsContainer).length)
  872. return $(fieldInstance.options.errorsContainer).append(fieldInstance._ui.$errorsWrapper);
  873. else
  874. ParsleyUtils.warn('The errors container `' + fieldInstance.options.errorsContainer + '` does not exist in DOM');
  875. }
  876. else if ('function' === typeof fieldInstance.options.errorsContainer)
  877. $errorsContainer = fieldInstance.options.errorsContainer(fieldInstance);
  878. if ('undefined' !== typeof $errorsContainer && $errorsContainer.length)
  879. return $errorsContainer.append(fieldInstance._ui.$errorsWrapper);
  880. var $from = fieldInstance.$element;
  881. if (fieldInstance.options.multiple)
  882. $from = $from.parent();
  883. return $from.after(fieldInstance._ui.$errorsWrapper);
  884. },
  885. actualizeTriggers: function (fieldInstance) {
  886. var $toBind = fieldInstance.$element;
  887. if (fieldInstance.options.multiple)
  888. $toBind = $('[' + fieldInstance.options.namespace + 'multiple="' + fieldInstance.options.multiple + '"]');
  889. // Remove Parsley events already binded on this field
  890. $toBind.off('.Parsley');
  891. // If no trigger is set, all good
  892. if (false === fieldInstance.options.trigger)
  893. return;
  894. var triggers = fieldInstance.options.trigger.replace(/^\s+/g , '').replace(/\s+$/g , '');
  895. if ('' === triggers)
  896. return;
  897. // Bind fieldInstance.eventValidate if exists (for parsley.ajax for example), ParsleyUI.eventValidate otherwise
  898. $toBind.on(
  899. triggers.split(' ').join('.Parsley ') + '.Parsley',
  900. $.proxy('function' === typeof fieldInstance.eventValidate ? fieldInstance.eventValidate : this.eventValidate, fieldInstance));
  901. },
  902. // Called through $.proxy with fieldInstance. `this` context is ParsleyField
  903. eventValidate: function (event) {
  904. // For keyup, keypress, keydown... events that could be a little bit obstrusive
  905. // do not validate if val length < min threshold on first validation. Once field have been validated once and info
  906. // about success or failure have been displayed, always validate with this trigger to reflect every yalidation change.
  907. if (new RegExp('key').test(event.type))
  908. if (!this._ui.validationInformationVisible && this.getValue().length <= this.options.validationThreshold)
  909. return;
  910. this._ui.validatedOnce = true;
  911. this.validate();
  912. },
  913. manageFailingFieldTrigger: function (fieldInstance) {
  914. fieldInstance._ui.failedOnce = true;
  915. // Radio and checkboxes fields must bind every field multiple
  916. if (fieldInstance.options.multiple)
  917. $('[' + fieldInstance.options.namespace + 'multiple="' + fieldInstance.options.multiple + '"]').each(function () {
  918. if (!new RegExp('change', 'i').test($(this).parsley().options.trigger || ''))
  919. return $(this).on('change.ParsleyFailedOnce', false, $.proxy(fieldInstance.validate, fieldInstance));
  920. });
  921. // Select case
  922. if (fieldInstance.$element.is('select'))
  923. if (!new RegExp('change', 'i').test(fieldInstance.options.trigger || ''))
  924. return fieldInstance.$element.on('change.ParsleyFailedOnce', false, $.proxy(fieldInstance.validate, fieldInstance));
  925. // All other inputs fields
  926. if (!new RegExp('keyup', 'i').test(fieldInstance.options.trigger || ''))
  927. return fieldInstance.$element.on('keyup.ParsleyFailedOnce', false, $.proxy(fieldInstance.validate, fieldInstance));
  928. },
  929. reset: function (parsleyInstance) {
  930. // Reset all event listeners
  931. this.actualizeTriggers(parsleyInstance);
  932. parsleyInstance.$element.off('.ParsleyFailedOnce');
  933. // Nothing to do if UI never initialized for this field
  934. if ('undefined' === typeof parsleyInstance._ui)
  935. return;
  936. if ('ParsleyForm' === parsleyInstance.__class__)
  937. return;
  938. // Reset all errors' li
  939. parsleyInstance._ui.$errorsWrapper
  940. .removeClass('filled')
  941. .children()
  942. .remove();
  943. // Reset validation class
  944. this._resetClass(parsleyInstance);
  945. // Reset validation flags and last validation result
  946. parsleyInstance._ui.validatedOnce = false;
  947. parsleyInstance._ui.lastValidationResult = [];
  948. parsleyInstance._ui.validationInformationVisible = false;
  949. parsleyInstance._ui.failedOnce = false;
  950. },
  951. destroy: function (parsleyInstance) {
  952. this.reset(parsleyInstance);
  953. if ('ParsleyForm' === parsleyInstance.__class__)
  954. return;
  955. if ('undefined' !== typeof parsleyInstance._ui)
  956. parsleyInstance._ui.$errorsWrapper.remove();
  957. delete parsleyInstance._ui;
  958. },
  959. _successClass: function (fieldInstance) {
  960. fieldInstance._ui.validationInformationVisible = true;
  961. fieldInstance._ui.$errorClassHandler.removeClass(fieldInstance.options.errorClass).addClass(fieldInstance.options.successClass);
  962. },
  963. _errorClass: function (fieldInstance) {
  964. fieldInstance._ui.validationInformationVisible = true;
  965. fieldInstance._ui.$errorClassHandler.removeClass(fieldInstance.options.successClass).addClass(fieldInstance.options.errorClass);
  966. },
  967. _resetClass: function (fieldInstance) {
  968. fieldInstance._ui.$errorClassHandler.removeClass(fieldInstance.options.successClass).removeClass(fieldInstance.options.errorClass);
  969. }
  970. };
  971. var ParsleyForm = function (element, domOptions, options) {
  972. this.__class__ = 'ParsleyForm';
  973. this.__id__ = ParsleyUtils.generateID();
  974. this.$element = $(element);
  975. this.domOptions = domOptions;
  976. this.options = options;
  977. this.parent = window.Parsley;
  978. this.fields = [];
  979. this.validationResult = null;
  980. };
  981. var statusMapping = { pending: null, resolved: true, rejected: false };
  982. ParsleyForm.prototype = {
  983. onSubmitValidate: function (event) {
  984. var that = this;
  985. // This is a Parsley generated submit event, do not validate, do not prevent, simply exit and keep normal behavior
  986. if (true === event.parsley)
  987. return;
  988. // Because some validations might be asynchroneous,
  989. // we cancel this submit and will fake it after validation.
  990. event.stopImmediatePropagation();
  991. event.preventDefault();
  992. this.whenValidate(undefined, undefined, event)
  993. .done(function() { that._submit(); })
  994. .always(function() { that._submitSource = null; });
  995. return this;
  996. },
  997. // internal
  998. // _submit submits the form, this time without going through the validations.
  999. // Care must be taken to "fake" the actual submit button being clicked.
  1000. _submit: function() {
  1001. if (false === this._trigger('submit'))
  1002. return;
  1003. this.$element.find('.parsley_synthetic_submit_button').remove();
  1004. if (this._submitSource) {
  1005. $('<input class=".parsley_synthetic_submit_button" type="hidden">')
  1006. .attr('name', this._submitSource.name)
  1007. .attr('value', this._submitSource.value)
  1008. .appendTo(this.$element);
  1009. }
  1010. this.$element.trigger($.extend($.Event('submit'), { parsley: true }));
  1011. },
  1012. // Performs validation on fields while triggering events.
  1013. // @returns `true` if al validations succeeds, `false`
  1014. // if a failure is immediately detected, or `null`
  1015. // if dependant on a promise.
  1016. // Prefer `whenValidate`.
  1017. validate: function (group, force, event) {
  1018. return statusMapping[ this.whenValidate(group, force, event).state() ];
  1019. },
  1020. whenValidate: function (group, force, event) {
  1021. var that = this;
  1022. this.submitEvent = event;
  1023. this.validationResult = true;
  1024. // fire validate event to eventually modify things before very validation
  1025. this._trigger('validate');
  1026. // Refresh form DOM options and form's fields that could have changed
  1027. this._refreshFields();
  1028. var promises = this._withoutReactualizingFormOptions(function(){
  1029. return $.map(this.fields, function(field) {
  1030. // do not validate a field if not the same as given validation group
  1031. if (!group || that._isFieldInGroup(field, group))
  1032. return field.whenValidate(force);
  1033. });
  1034. });
  1035. return $.when.apply($, promises)
  1036. .done( function() { that._trigger('success'); })
  1037. .fail( function() { that.validationResult = false; that._trigger('error'); })
  1038. .always(function() { that._trigger('validated'); });
  1039. },
  1040. // Iterate over refreshed fields, and stop on first failure.
  1041. // Returns `true` if all fields are valid, `false` if a failure is detected
  1042. // or `null` if the result depends on an unresolved promise.
  1043. // Prefer using `whenValid` instead.
  1044. isValid: function (group, force) {
  1045. return statusMapping[ this.whenValid(group, force).state() ];
  1046. },
  1047. // Iterate over refreshed fields and validate them.
  1048. // Returns a promise.
  1049. // A validation that immediately fails will interrupt the validations.
  1050. whenValid: function (group, force) {
  1051. var that = this;
  1052. this._refreshFields();
  1053. var promises = this._withoutReactualizingFormOptions(function(){
  1054. return $.map(this.fields, function(field) {
  1055. // do not validate a field if not the same as given validation group
  1056. if (!group || that._isFieldInGroup(field, group))
  1057. return field.whenValid(force);
  1058. });
  1059. });
  1060. return $.when.apply($, promises);
  1061. },
  1062. _isFieldInGroup: function (field, group) {
  1063. if($.isArray(field.options.group))
  1064. return -1 !== $.inArray(group, field.options.group);
  1065. return field.options.group === group;
  1066. },
  1067. _refreshFields: function () {
  1068. return this.actualizeOptions()._bindFields();
  1069. },
  1070. _bindFields: function () {
  1071. var self = this,
  1072. oldFields = this.fields;
  1073. this.fields = [];
  1074. this.fieldsMappedById = {};
  1075. this._withoutReactualizingFormOptions(function(){
  1076. this.$element
  1077. .find(this.options.inputs)
  1078. .not(this.options.excluded)
  1079. .each(function () {
  1080. var fieldInstance = new Parsley.Factory(this, {}, self);
  1081. // Only add valid and not excluded `ParsleyField` and `ParsleyFieldMultiple` children
  1082. if (('ParsleyField' === fieldInstance.__class__ || 'ParsleyFieldMultiple' === fieldInstance.__class__) && (true !== fieldInstance.options.excluded))
  1083. if ('undefined' === typeof self.fieldsMappedById[fieldInstance.__class__ + '-' + fieldInstance.__id__]) {
  1084. self.fieldsMappedById[fieldInstance.__class__ + '-' + fieldInstance.__id__] = fieldInstance;
  1085. self.fields.push(fieldInstance);
  1086. }
  1087. });
  1088. $(oldFields).not(self.fields).each(function () {
  1089. this._trigger('reset');
  1090. });
  1091. });
  1092. return this;
  1093. },
  1094. // Internal only.
  1095. // Looping on a form's fields to do validation or similar
  1096. // will trigger reactualizing options on all of them, which
  1097. // in turn will reactualize the form's options.
  1098. // To avoid calling actualizeOptions so many times on the form
  1099. // for nothing, _withoutReactualizingFormOptions temporarily disables
  1100. // the method actualizeOptions on this form while `fn` is called.
  1101. _withoutReactualizingFormOptions: function (fn) {
  1102. var oldActualizeOptions = this.actualizeOptions;
  1103. this.actualizeOptions = function() { return this };
  1104. var result = fn.call(this); // Keep the current `this`.
  1105. this.actualizeOptions = oldActualizeOptions;
  1106. return result;
  1107. },
  1108. // Internal only.
  1109. // Shortcut to trigger an event
  1110. // Returns true iff event is not interrupted and default not prevented.
  1111. _trigger: function (eventName) {
  1112. eventName = 'form:' + eventName;
  1113. return this.trigger.apply(this, arguments);
  1114. }
  1115. };
  1116. var ConstraintFactory = function (parsleyField, name, requirements, priority, isDomConstraint) {
  1117. if (!new RegExp('ParsleyField').test(parsleyField.__class__))
  1118. throw new Error('ParsleyField or ParsleyFieldMultiple instance expected');
  1119. var validatorSpec = window.Parsley._validatorRegistry.validators[name];
  1120. var validator = new ParsleyValidator(validatorSpec);
  1121. $.extend(this, {
  1122. validator: validator,
  1123. name: name,
  1124. requirements: requirements,
  1125. priority: priority || parsleyField.options[name + 'Priority'] || validator.priority,
  1126. isDomConstraint: true === isDomConstraint
  1127. });
  1128. this._parseRequirements(parsleyField.options);
  1129. };
  1130. var capitalize = function(str) {
  1131. var cap = str[0].toUpperCase();
  1132. return cap + str.slice(1);
  1133. };
  1134. ConstraintFactory.prototype = {
  1135. validate: function(value, instance) {
  1136. var args = this.requirementList.slice(0); // Make copy
  1137. args.unshift(value);
  1138. args.push(instance);
  1139. return this.validator.validate.apply(this.validator, args);
  1140. },
  1141. _parseRequirements: function(options) {
  1142. var that = this;
  1143. this.requirementList = this.validator.parseRequirements(this.requirements, function(key) {
  1144. return options[that.name + capitalize(key)];
  1145. });
  1146. }
  1147. };
  1148. var ParsleyField = function (field, domOptions, options, parsleyFormInstance) {
  1149. this.__class__ = 'ParsleyField';
  1150. this.__id__ = ParsleyUtils.generateID();
  1151. this.$element = $(field);
  1152. // Set parent if we have one
  1153. if ('undefined' !== typeof parsleyFormInstance) {
  1154. this.parent = parsleyFormInstance;
  1155. }
  1156. this.options = options;
  1157. this.domOptions = domOptions;
  1158. // Initialize some properties
  1159. this.constraints = [];
  1160. this.constraintsByName = {};
  1161. this.validationResult = [];
  1162. // Bind constraints
  1163. this._bindConstraints();
  1164. };
  1165. var statusMapping = { pending: null, resolved: true, rejected: false };
  1166. ParsleyField.prototype = {
  1167. // # Public API
  1168. // Validate field and trigger some events for mainly `ParsleyUI`
  1169. // @returns `true`, an array of the validators that failed, or
  1170. // `null` if validation is not finished. Prefer using whenValidate
  1171. validate: function (force) {
  1172. var promise = this.whenValidate(force);
  1173. switch (promise.state()) {
  1174. case 'pending': return null;
  1175. case 'resolved': return true;
  1176. case 'rejected': return this.validationResult;
  1177. };
  1178. },
  1179. // Validate field and trigger some events for mainly `ParsleyUI`
  1180. // @returns a promise that succeeds only when all validations do.
  1181. whenVal