parsley-rails 1.2.4.0 → 2.0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -6
  3. data/lib/parsley-rails/version.rb +1 -1
  4. data/update.sh +6 -11
  5. data/vendor/assets/javascripts/parsley.i18n.bg.js +31 -41
  6. data/vendor/assets/javascripts/parsley.i18n.da.js +30 -40
  7. data/vendor/assets/javascripts/parsley.i18n.de.js +27 -35
  8. data/vendor/assets/javascripts/parsley.i18n.en.extra.js +6 -0
  9. data/vendor/assets/javascripts/parsley.i18n.en.js +30 -45
  10. data/vendor/assets/javascripts/parsley.i18n.es.js +29 -41
  11. data/vendor/assets/javascripts/parsley.i18n.fr.extra.js +6 -0
  12. data/vendor/assets/javascripts/parsley.i18n.fr.js +30 -41
  13. data/vendor/assets/javascripts/parsley.i18n.he.extra.js +6 -0
  14. data/vendor/assets/javascripts/parsley.i18n.he.js +31 -42
  15. data/vendor/assets/javascripts/parsley.i18n.id.js +30 -37
  16. data/vendor/assets/javascripts/parsley.i18n.it.extra.js +6 -0
  17. data/vendor/assets/javascripts/parsley.i18n.it.js +30 -37
  18. data/vendor/assets/javascripts/parsley.i18n.ja.js +30 -40
  19. data/vendor/assets/javascripts/parsley.i18n.nl.js +37 -41
  20. data/vendor/assets/javascripts/parsley.i18n.pl.js +33 -40
  21. data/vendor/assets/javascripts/parsley.i18n.pt-br.js +33 -0
  22. data/vendor/assets/javascripts/parsley.i18n.ru.js +38 -44
  23. data/vendor/assets/javascripts/parsley.i18n.sv.extra.js +6 -0
  24. data/vendor/assets/javascripts/parsley.i18n.sv.js +30 -42
  25. data/vendor/assets/javascripts/parsley.i18n.zh_cn.extra.js +6 -0
  26. data/vendor/assets/javascripts/parsley.i18n.zh_cn.js +30 -43
  27. data/vendor/assets/javascripts/parsley.js +2009 -1507
  28. data/vendor/assets/javascripts/parsley.remote.js +2351 -0
  29. metadata +16 -32
  30. data/vendor/assets/javascripts/parsley.extend.js +0 -167
  31. data/vendor/assets/javascripts/parsley.i18n.ar.js +0 -44
  32. data/vendor/assets/javascripts/parsley.i18n.ca.js +0 -40
  33. data/vendor/assets/javascripts/parsley.i18n.cs.js +0 -45
  34. data/vendor/assets/javascripts/parsley.i18n.cy.js +0 -38
  35. data/vendor/assets/javascripts/parsley.i18n.et.js +0 -46
  36. data/vendor/assets/javascripts/parsley.i18n.fa.js +0 -40
  37. data/vendor/assets/javascripts/parsley.i18n.fi.js +0 -40
  38. data/vendor/assets/javascripts/parsley.i18n.hr.js +0 -48
  39. data/vendor/assets/javascripts/parsley.i18n.hu.js +0 -44
  40. data/vendor/assets/javascripts/parsley.i18n.is.js +0 -32
  41. data/vendor/assets/javascripts/parsley.i18n.kr.js +0 -48
  42. data/vendor/assets/javascripts/parsley.i18n.lt.js +0 -40
  43. data/vendor/assets/javascripts/parsley.i18n.mn.js +0 -43
  44. data/vendor/assets/javascripts/parsley.i18n.no.js +0 -44
  45. data/vendor/assets/javascripts/parsley.i18n.pt_br.js +0 -42
  46. data/vendor/assets/javascripts/parsley.i18n.ro.js +0 -44
  47. data/vendor/assets/javascripts/parsley.i18n.th.js +0 -44
  48. data/vendor/assets/javascripts/parsley.i18n.tr.js +0 -48
  49. data/vendor/assets/javascripts/parsley.i18n.ua.js +0 -35
  50. data/vendor/assets/javascripts/parsley.i18n.vn.js +0 -35
  51. data/vendor/assets/javascripts/parsley.i18n.zh_tw.js +0 -39
  52. data/vendor/assets/javascripts/parsley.l10n.es.js +0 -183
@@ -1,40 +1,33 @@
1
- window.ParsleyConfig = window.ParsleyConfig || {};
2
-
3
- (function ($) {
4
- window.ParsleyConfig = $.extend( true, {}, window.ParsleyConfig, {
5
- messages: {
6
- // parsley ////////PL/by/Tymek.Cz////////////////
7
- defaultMessage: "Wartość nieprawidłowa"
8
- , type: {
9
- email: "Niepoprawny adres e-mail"
10
- , url: "Niepoprawny adres URL"
11
- , urlstrict: "Niepoprawny format adresu adres URL"
12
- , number: "Wpisz poprawną liczbę"
13
- , digits: "Dozwolone tylko cyfry"
14
- , dateIso: "Niepoprawny format (użyj RRRR-MM-DD)"
15
- , alphanum: "Dozwolone tylko znaki alfanumeryczne"
16
- }
17
- , notnull: "Wartość nie może być równa zero"
18
- , notblank: "Pole nie może pozostać puste"
19
- , required: "Pole wymagane"
20
- , regexp: "Niepoprawna wartość"
21
- , min: "Wpisz wartość większą od %s"
22
- , max: "Wpisz wartość mniejszą od %s"
23
- , range: "Wpisz wartość pomiędzy %s i %s"
24
- , minlength: "Wpisz %s lub więcej znaków"
25
- , maxlength: "Wpisz %s lub mniej znaków"
26
- , rangelength: "Wpisz od %s do %s znaków"
27
- , mincheck: "Wybierz %s lub więcej opcji"
28
- , maxcheck: "Wybierz %s lub mniej opcji"
29
- , rangecheck: "Wybierz od %s do %s opcji"
30
- , equalto: "Wartość nie jest identyczna"
31
-
32
- // parsley.extend /PL/by/Tymek.Cz////////////////
33
- , minwords: "Wpisz więcej niż %s wyrazów"
34
- , maxwords: "Wpisz co najwyżej %s wyrazów"
35
- , rangewords: "Wpisz od %s do %s wyrazów"
36
- , greaterthan: "Podaj wartość większą od %s"
37
- , lessthan: "Podaj wartość mniejszą od %s"
38
- }
39
- });
40
- }(window.jQuery || window.Zepto));
1
+ // ParsleyConfig definition if not already set
2
+ window.ParsleyConfig = window.ParsleyConfig || {};
3
+ window.ParsleyConfig.i18n = window.ParsleyConfig.i18n || {};
4
+
5
+ // Define then the messages
6
+ window.ParsleyConfig.i18n.pl = $.extend(window.ParsleyConfig.i18n.pl || {}, {
7
+ defaultMessage: "Wartość wygląda na nieprawidłową",
8
+ type: {
9
+ email: "Wpisz poprawny adres e-mail.",
10
+ url: "Wpisz poprawny adres URL.",
11
+ number: "Wpisz poprawną liczbę.",
12
+ integer: "Dozwolone jedynie liczby człkowite.",
13
+ digits: "Dozwolone jedynie cyfry.",
14
+ alphanum: "Dozwolone jedynie znaki alfanumeryczne."
15
+ },
16
+ notblank: "Pole nie może zostać puste",
17
+ required: "Pole jest wymagane.",
18
+ pattern: "Wartość wygląda na nieprawidłową.",
19
+ min: "Wartość powinna być większa od %s.",
20
+ max: "Wartość powinna być mniejsza od %s.",
21
+ range: "Wartość powinna być większa od %s i mniejsza od %s.",
22
+ minlength: "Ilość znaków powinna wynosić %s lub więcej.",
23
+ maxlength: "Ilość znaków powinna wynosić %s lub mniej.",
24
+ length: "Ilość znaków powinna wynosić od %s do %s.",
25
+ mincheck: "Musisz wybrać minimum %s opcji.",
26
+ maxcheck: "Możesz wybrać maksymalnie %s opcji.",
27
+ check: "Minimalnie możesz wybrać od %s do %s opcji",
28
+ equalto: "Wartości nie identyczne"
29
+ });
30
+
31
+ // If file is loaded after Parsley main file, auto-load locale
32
+ if ('undefined' !== typeof window.ParsleyValidator)
33
+ window.ParsleyValidator.addCatalog('pl', window.ParsleyConfig.i18n.pl, true);
@@ -0,0 +1,33 @@
1
+ // ParsleyConfig definition if not already set
2
+ window.ParsleyConfig = window.ParsleyConfig || {};
3
+ window.ParsleyConfig.i18n = window.ParsleyConfig.i18n || {};
4
+
5
+ // Define then the messages
6
+ window.ParsleyConfig.i18n['pt-br'] = $.extend(window.ParsleyConfig.i18n['pt-br'] || {}, {
7
+ defaultMessage: "This value seems to be invalid.",
8
+ type: {
9
+ email: "Esse campo deve ser um email válido.",
10
+ url: "Esse campo deve ser uma url válida.",
11
+ number: "Esse campo deve ser um número válido.",
12
+ integer: "Esse campo deve ser um inteiro válido.",
13
+ digits: "Esse campo deve conter apenas dígitos.",
14
+ alphanum: "Esse campo deve ser alfa numérico."
15
+ },
16
+ notblank: "Esse campo não pode ficar vazio.",
17
+ required: "Esse campo é obrigatório.",
18
+ pattern: "Esse campo parece estar inválido.",
19
+ min: "Esse campo deve ser maior ou igual a %s.",
20
+ max: "Esse campo deve ser menor ou igual a %s.",
21
+ range: "Esse campo deve estar entre %s e %s.",
22
+ minlength: "Esse campo é pequeno demais. Ele deveria ter %s characteres ou mais.",
23
+ maxlength: "Esse campo grande demais. Ele deveri ter %s characteres ou menos.",
24
+ length: "O tamanho desse campo é inválido. Ele deveria ter entre %s e %s characteres.",
25
+ mincheck: "Você deve escolher pelo menos %s opções.",
26
+ maxcheck: "Você deve escolher %s opções ou mais",
27
+ check: "Você deve escolher entre %s e %s opções.",
28
+ equalto: "Este valor deveria ser igual."
29
+ });
30
+
31
+ // If file is loaded after Parsley main file, auto-load locale
32
+ if ('undefined' !== typeof window.ParsleyValidator)
33
+ window.ParsleyValidator.addCatalog('pt-br', window.ParsleyConfig.i18n['pt-br'], true);
@@ -1,44 +1,38 @@
1
- window.ParsleyConfig = window.ParsleyConfig || {};
2
-
3
- (function ($) {
4
- window.ParsleyConfig = $.extend( true, {}, window.ParsleyConfig, {
5
- messages: {
6
- // parsley //////////////////////////////////////
7
- defaultMessage: "Поле заполнено некорректно."
8
- , type: {
9
- email: "Поле должно быть адресом электронной почты."
10
- , url: "Поле должно быть ссылкой на сайт."
11
- , urlstrict: "Поле должно быть ссылкой на сайт."
12
- , number: "Поле должно быть числом."
13
- , digits: "Поле должно содержать только цифры."
14
- , dateIso: "Поле должно быть датой в формате (ГГГГ-ММ-ДД)."
15
- , alphanum: "Поле должно содержать только цифры и буквы."
16
- , phone: "Поле должно содержать корректный номер телефона."
17
- }
18
- , notnull: "Поле должно быть не нулевым."
19
- , notblank: "Поле не должно быть пустым."
20
- , required: "Поле обязательно для заполнения."
21
- , regexp: "Поле заполнено некорректно."
22
- , min: "Значение поля должно быть больше %s."
23
- , max: "Значение поля должно быть меньше %s."
24
- , range: "Значение поля должно быть между %s и %s."
25
- , minlength: "В поле должно быть минимум %s символов(а)."
26
- , maxlength: "В поле должно быть не больше %s символов(а)."
27
- , rangelength: "В поле должно быть от %s до %s символов(а)."
28
- , mincheck: "Необходимо выбрать не менее %s пунктов(а)."
29
- , maxcheck: "Необходимо выбрать не более %s пунктов(а)."
30
- , rangecheck: "Необходимо выбрать от %s до %s пунктов."
31
- , equalto: "Значения полей должны быть одинаковыми."
32
-
33
- // parsley.extend ///////////////////////////////
34
- , minwords: "В поле должно быть не менее %s слов."
35
- , maxwords: "В поле должно быть не более %s слов."
36
- , rangewords: "Количество слов в поле должно быть в диапазоне от %s до %s."
37
- , greaterthan: "Значение в поле должно быть более %s."
38
- , lessthan: "Значение в поле должно быть менее %s."
39
- , beforedate: "Дата должна быть до %s."
40
- , afterdate: "Дата должна быть после %s."
41
- , americandate: "В поле должна быть корректная дата в формате MM/DD/YYYY."
42
- }
43
- });
44
- }(window.jQuery || window.Zepto));
1
+ //Parsley localization for Russian language
2
+ //Evgeni Makarov
3
+ //github.com/emakarov
4
+
5
+
6
+ // ParsleyConfig definition if not already set
7
+ window.ParsleyConfig = window.ParsleyConfig || {};
8
+ window.ParsleyConfig.i18n = window.ParsleyConfig.i18n || {};
9
+
10
+ // Define then the messages
11
+ window.ParsleyConfig.i18n.ru = $.extend(window.ParsleyConfig.i18n.ru || {}, {
12
+ defaultMessage: "Некорректное значение.",
13
+ type: {
14
+ email: "Введите адрес электронной почты.",
15
+ url: "Введите URL адрес.",
16
+ number: "Введите число.",
17
+ integer: "Введите целое число.",
18
+ digits: "Введите только цифры.",
19
+ alphanum: "Введите буквенно-цифровое значение."
20
+ },
21
+ notblank: "Это поле должно быть заполнено.",
22
+ required: "Обязательное поле.",
23
+ pattern: "Это значение некорректно.",
24
+ min: "Это значение должно быть не менее чем %s.",
25
+ max: "Это значение должно быть не более чем %s.",
26
+ range: "Это значение должно быть в интервале от %s до %s.",
27
+ minlength: "Введите не менее %s символов.",
28
+ maxlength: "Введите не более %s символов.",
29
+ length: "Длина строки должна быть от %s до %s символов.",
30
+ mincheck: "Выберите не менее %s значений.",
31
+ maxcheck: "Выберите не более %s значений.",
32
+ check: "Выберите от %s до %s значений.",
33
+ equalto: "Это значение должно совпадать."
34
+ });
35
+
36
+ // If file is loaded after Parsley main file, auto-load locale
37
+ if ('undefined' !== typeof window.ParsleyValidator)
38
+ window.ParsleyValidator.addCatalog('ru', window.ParsleyConfig.i18n.ru, true);
@@ -0,0 +1,6 @@
1
+ window.ParsleyConfig = window.ParsleyConfig || {};
2
+ window.ParsleyConfig.i18n = window.ParsleyConfig.i18n || {};
3
+
4
+ window.ParsleyConfig.i18n.sv = $.extend(window.ParsleyConfig.i18n.sv || {}, {
5
+ dateiso: "Ange ett giltigt datum (ÅÅÅÅ-MM-DD)."
6
+ });
@@ -1,45 +1,33 @@
1
- /**
2
- * Swedish i18n.
3
- */
1
+ // ParsleyConfig definition if not already set
4
2
  window.ParsleyConfig = window.ParsleyConfig || {};
3
+ window.ParsleyConfig.i18n = window.ParsleyConfig.i18n || {};
5
4
 
6
- (function ($) {
7
- window.ParsleyConfig = $.extend( true, {}, window.ParsleyConfig, {
8
- messages: {
9
- // parsley //////////////////////////////////////
10
- defaultMessage: "Ogiltigt värde."
11
- , type: {
12
- email: "Värdet måste vara en giltig e-postadress."
13
- , url: "Värdet måste vara en giltig URL."
14
- , urlstrict: "Värdet måste vara en giltig URL."
15
- , number: "Värdet måste vara ett giltigt nummer."
16
- , digits: "Värdet får enbart innehålla siffror."
17
- , dateIso: "Värdet måste vara ett giltigt datum (YYYY-MM-DD)."
18
- , alphanum: "Värdet får bara innehålla bokstäver och siffror."
19
- }
20
- , notnull: "Värdet får inte vara null."
21
- , notblank: "Fältet får inte vara tomt."
22
- , required: "Måste fyllas i."
23
- , regexp: "Värdet verkar inte vara giltigt."
24
- , min: "Värdet måste vara större än %s."
25
- , max: "Värdet måste vara mindre än %s."
26
- , range: "Värdet måste vara mellan %s och %s."
27
- , minlength: "Värdet är för kort. Det måste innehålla minst %s tecken."
28
- , maxlength: "Värdet är för långt. Det får maximalt innehålla %s tecken."
29
- , rangelength: "Värdets längd är felaktig. Det måste innehålla mellan %s och %s tecken."
30
- , mincheck: "Minst %s värden måste väljas."
31
- , maxcheck: "Maximalt %s värden får väljas."
32
- , rangecheck: "Du måste göra minst %s och maximalt %s val."
33
- , equalto: "Värdet måste vara lika."
5
+ // Define then the messages
6
+ window.ParsleyConfig.i18n.sv = $.extend(window.ParsleyConfig.i18n.sv || {}, {
7
+ defaultMessage: "Ogiltigt värde.",
8
+ type: {
9
+ email: "Ange en giltig e-postadress.",
10
+ url: "Ange en giltig URL.",
11
+ number: "Ange ett giltigt nummer.",
12
+ integer: "Ange ett heltal.",
13
+ digits: "Ange endast siffror.",
14
+ alphanum: "Ange endast bokstäver och siffror."
15
+ },
16
+ notblank: "Värdet får inte vara tomt.",
17
+ required: "Måste fyllas i.",
18
+ pattern: "Värdet är ej giltigt.",
19
+ min: "Värdet måste vara större än eller lika med %s.",
20
+ max: "Värdet måste vara mindre än eller lika med %s.",
21
+ range: "Värdet måste vara mellan %s och %s.",
22
+ minlength: "Värdet måste vara minst %s tecken.",
23
+ maxlength: "Värdet får maximalt innehålla %s tecken.",
24
+ length: "Värdet måste vara mellan %s och %s tecken.",
25
+ mincheck: "Minst %s val måste göras.",
26
+ maxcheck: "Maximalt %s val får göras.",
27
+ check: "Mellan %s och %s val måste göras.",
28
+ equalto: "Värdena måste vara lika."
29
+ });
34
30
 
35
- // parsley.extend ///////////////////////////////
36
- , minwords: "Fältet måste innehålla minst %s ord."
37
- , maxwords: "Fältet får maximalt innehålla %s ord."
38
- , rangewords: "Fältet ska innehålla mellan %s och %s ord."
39
- , greaterthan: "Värdet måste vara större än %s."
40
- , lessthan: "Värdet måste vara mindre än %s."
41
- , beforedate: "Datumet måste vara före %s."
42
- , afterdate: "Datumet måste vara efter %s."
43
- }
44
- });
45
- }(window.jQuery || window.Zepto));
31
+ // If file is loaded after Parsley main file, auto-load locale
32
+ if ('undefined' !== typeof window.ParsleyValidator)
33
+ window.ParsleyValidator.addCatalog('sv', window.ParsleyConfig.i18n.sv, true);
@@ -0,0 +1,6 @@
1
+ window.ParsleyConfig = window.ParsleyConfig || {};
2
+ window.ParsleyConfig.i18n = window.ParsleyConfig.i18n || {};
3
+
4
+ window.ParsleyConfig.i18n.zh_cn = $.extend(window.ParsleyConfig.i18n.zh_cn || {}, {
5
+ dateiso: "请输入正确格式的日期 (YYYY-MM-DD)."
6
+ });
@@ -1,46 +1,33 @@
1
- /**
2
- * /!\ This file is just an example template to create/update your own language file /!\
3
- */
4
-
1
+ // ParsleyConfig definition if not already set
5
2
  window.ParsleyConfig = window.ParsleyConfig || {};
3
+ window.ParsleyConfig.i18n = window.ParsleyConfig.i18n || {};
6
4
 
7
- (function ($) {
8
- window.ParsleyConfig = $.extend( true, {}, window.ParsleyConfig, {
9
- messages: {
10
- // parsley //////////////////////////////////////
11
- defaultMessage: "不正确的值"
12
- , type: {
13
- email: "字段值应该是一个正确的电子邮件地址"
14
- , url: "字段值应该是一个正确的URL地址"
15
- , urlstrict: "字段值应该是一个正确的URL地址"
16
- , number: "字段值应该是一个合法的数字"
17
- , digits: "字段值应该是一个单独的数字"
18
- , dateIso: "字段值应该是一个正确的日期描述(YYYY-MM-DD)."
19
- , alphanum: "字段值应该是只包含字母和数字"
20
- }
21
- , notnull: "字段值不可为null"
22
- , notblank: "字段值不可为空"
23
- , required: "字段值是必填的"
24
- , regexp: "字段值不合法"
25
- , min: "字段值应该大于 %s"
26
- , max: "字段值应该小于 %s."
27
- , range: "字段值应该大于 %s 并小于 %s."
28
- , minlength: "字段值太短了,长度应该大于等于 %s 个字符"
29
- , maxlength: "字段值太长了,长度应该小于等于 %s 个字符"
30
- , rangelength: "字段值长度错了,长度应该在 %s 和 %s 个字符之间"
31
- , mincheck: "你至少要选择 %s 个选项"
32
- , maxcheck: "你最多只能选择 %s 个选项"
33
- , rangecheck: "你只能选择 %s 到 %s 个选项"
34
- , equalto: "字段值应该和给定的值一样"
5
+ // Define then the messages
6
+ window.ParsleyConfig.i18n.zh_cn = $.extend(window.ParsleyConfig.i18n.zh_cn || {}, {
7
+ defaultMessage: "不正确的值",
8
+ type: {
9
+ email: "请输入一个有效的电子邮箱地址",
10
+ url: "请输入一个有效的链接",
11
+ number: "请输入正确的数字",
12
+ integer: "请输入正确的整数",
13
+ digits: "请输入正确的号码",
14
+ alphanum: "请输入字母或数字"
15
+ },
16
+ notblank: "请输入值",
17
+ required: "必填项",
18
+ pattern: "格式不正确",
19
+ min: "输入值请大于或等于 %s",
20
+ max: "输入值请小于或等于 %s",
21
+ range: "输入值应该在 %s 到 %s 之间",
22
+ minlength: "请输入至少 %s 个字符",
23
+ maxlength: "请输入至多 %s 个字符",
24
+ length: "字符长度应该在 %s 到 %s 之间",
25
+ mincheck: "请至少选择 %s 个选项",
26
+ maxcheck: "请选择不超过 %s 个选项",
27
+ check: "请选择 %s 到 %s 个选项",
28
+ equalto: "输入值不同"
29
+ });
35
30
 
36
- // parsley.extend ///////////////////////////////
37
- , minwords: "字段值应该至少有 %s 个词"
38
- , maxwords: "字段值最多只能有 %s 个词"
39
- , rangewords: "字段值应该有 %s 到 %s 个词"
40
- , greaterthan: "字段值应该大于 %s"
41
- , lessthan: "字段值应该小于 %s"
42
- , beforedate: "字段值所表示的日期应该早于 %s."
43
- , afterdate: "字段值所表示的日期应该晚于 %s."
44
- }
45
- });
46
- }(window.jQuery || window.Zepto));
31
+ // If file is loaded after Parsley main file, auto-load locale
32
+ if ('undefined' !== typeof window.ParsleyValidator)
33
+ window.ParsleyValidator.addCatalog('zh_cn', window.ParsleyConfig.i18n.zh_cn, true);
@@ -1,1595 +1,2097 @@
1
- /*
2
- * Parsley.js allows you to verify your form inputs frontend side, without writing a line of javascript. Or so..
3
- *
4
- * Author: Guillaume Potier - @guillaumepotier
1
+ /*!
2
+ * Parsleyjs
3
+ * Guillaume Potier - <guillaume@wisembly.com>
4
+ * Version 2.0.0 - built Sat Apr 19 2014 17:29:18
5
+ * MIT Licensed
6
+ *
5
7
  */
6
-
7
- !function ($) {
8
-
9
- 'use strict';
10
-
11
- /**
12
- * Validator class stores all constraints functions and associated messages.
13
- * Provides public interface to add, remove or modify them
14
- *
15
- * @class Validator
16
- * @constructor
17
- */
18
- var Validator = function ( options ) {
19
- /**
20
- * Error messages
21
- *
22
- * @property messages
23
- * @type {Object}
24
- */
25
- this.messages = {
26
- defaultMessage: "This value seems to be invalid."
27
- , type: {
28
- email: "This value should be a valid email."
29
- , url: "This value should be a valid url."
30
- , urlstrict: "This value should be a valid url."
31
- , number: "This value should be a valid number."
32
- , digits: "This value should be digits."
33
- , dateIso: "This value should be a valid date (YYYY-MM-DD)."
34
- , alphanum: "This value should be alphanumeric."
35
- , phone: "This value should be a valid phone number."
8
+ !(function($) {
9
+ var ParsleyUtils = {
10
+ // Parsley DOM-API
11
+ // returns object from dom attributes and values
12
+ // if attr is given, returns bool if attr present in DOM or not
13
+ attr: function ($element, namespace, checkAttr) {
14
+ var
15
+ attribute,
16
+ obj = {},
17
+ regex = new RegExp('^' + namespace, 'i');
18
+ if ('undefined' === typeof $element || 'undefined' === typeof $element[0])
19
+ return {};
20
+ for (var i in $element[0].attributes) {
21
+ attribute = $element[0].attributes[i];
22
+ if ('undefined' !== typeof attribute && null !== attribute && attribute.specified && regex.test(attribute.name)) {
23
+ if ('undefined' !== typeof checkAttr && new RegExp(checkAttr + '$', 'i').test(attribute.name))
24
+ return true;
25
+ obj[this.camelize(attribute.name.replace(namespace, ''))] = this.deserializeValue(attribute.value);
36
26
  }
37
- , notnull: "This value should not be null."
38
- , notblank: "This value should not be blank."
39
- , required: "This value is required."
40
- , regexp: "This value seems to be invalid."
41
- , min: "This value should be greater than or equal to %s."
42
- , max: "This value should be lower than or equal to %s."
43
- , range: "This value should be between %s and %s."
44
- , minlength: "This value is too short. It should have %s characters or more."
45
- , maxlength: "This value is too long. It should have %s characters or less."
46
- , rangelength: "This value length is invalid. It should be between %s and %s characters long."
47
- , mincheck: "You must select at least %s choices."
48
- , maxcheck: "You must select %s choices or less."
49
- , rangecheck: "You must select between %s and %s choices."
50
- , equalto: "This value should be the same."
27
+ }
28
+ return 'undefined' === typeof checkAttr ? obj : false;
51
29
  },
52
-
53
- this.init( options );
30
+ setAttr: function ($element, namespace, attr, value) {
31
+ $element[0].setAttribute(this.dasherize(namespace + attr), String(value));
32
+ },
33
+ // Recursive object / array getter
34
+ get: function (obj, path) {
35
+ var
36
+ i = 0,
37
+ paths = (path || '').split('.');
38
+ while (this.isObject(obj) || this.isArray(obj)) {
39
+ obj = obj[paths[i++]];
40
+ if (i === paths.length)
41
+ return obj;
42
+ }
43
+ return undefined;
44
+ },
45
+ hash: function (length) {
46
+ return String(Math.random()).substring(2, length ? length + 2 : 9);
47
+ },
48
+ /** Third party functions **/
49
+ // Underscore isArray
50
+ isArray: function (mixed) {
51
+ return Object.prototype.toString.call(mixed) === '[object Array]';
52
+ },
53
+ // Underscore isObject
54
+ isObject: function (mixed) {
55
+ return mixed === Object(mixed);
56
+ },
57
+ // Zepto deserialize function
58
+ deserializeValue: function (value) {
59
+ var num;
60
+ try {
61
+ return value ?
62
+ value == "true" ||
63
+ (value == "false" ? false :
64
+ value == "null" ? null :
65
+ !isNaN(num = Number(value)) ? num :
66
+ /^[\[\{]/.test(value) ? $.parseJSON(value) :
67
+ value)
68
+ : value;
69
+ } catch (e) { return value; }
70
+ },
71
+ // Zepto camelize function
72
+ camelize: function (str) {
73
+ return str.replace(/-+(.)?/g, function(match, chr) {
74
+ return chr ? chr.toUpperCase() : '';
75
+ });
76
+ },
77
+ // Zepto dasherize function
78
+ dasherize: function (str) {
79
+ return str.replace(/::/g, '/')
80
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
81
+ .replace(/([a-z\d])([A-Z])/g, '$1_$2')
82
+ .replace(/_/g, '-')
83
+ .toLowerCase();
84
+ }
85
+ };
86
+ // All these options could be overriden and specified directly in DOM using
87
+ // `data-parsley-` default DOM-API
88
+ // eg: `inputs` can be set in DOM using `data-parsley-inputs="input, textarea"`
89
+ // eg: `data-parsley-stop-on-first-failing-constraint="false"`
90
+ var ParsleyDefaults = {
91
+ // ### General
92
+ // Default data-namespace for DOM API
93
+ namespace: 'data-parsley-',
94
+ // Supported inputs by default
95
+ inputs: 'input, textarea, select',
96
+ // Excluded inputs by default
97
+ excluded: 'input[type=button], input[type=submit], input[type=reset], input[type=hidden]',
98
+ // Stop validating field on highest priority failing constraint
99
+ priorityEnabled: true,
100
+ // ### UI
101
+ // Enable\Disable error messages
102
+ uiEnabled: true,
103
+ // Key events threshold before validation
104
+ validationThreshold: 3,
105
+ // Focused field on form validation error. 'fist'|'last'|'none'
106
+ focus: 'first',
107
+ // `$.Event()` that will trigger validation. eg: `keyup`, `change`..
108
+ trigger: false,
109
+ // Class that would be added on every failing validation Parsley field
110
+ errorClass: 'parsley-error',
111
+ // Same for success validation
112
+ successClass: 'parsley-success',
113
+ // Return the `$element` that will receive these above success or error classes
114
+ // Could also be (and given directly from DOM) a valid selector like `'#div'`
115
+ classHandler: function (ParsleyField) {},
116
+ // Return the `$element` where errors will be appended
117
+ // Could also be (and given directly from DOM) a valid selector like `'#div'`
118
+ errorsContainer: function (ParsleyField) {},
119
+ // ul elem that would receive errors' list
120
+ errorsWrapper: '<ul class="parsley-errors-list"></ul>',
121
+ // li elem that would receive error message
122
+ errorTemplate: '<li></li>'
54
123
  };
55
124
 
56
- Validator.prototype = {
57
-
58
- constructor: Validator
59
-
60
- /**
61
- * Validator list. Built-in validators functions
62
- *
63
- * @property validators
64
- * @type {Object}
65
- */
66
- , validators: {
67
- notnull: function () {
68
- return {
69
- validate: function ( val ) {
70
- return val.length > 0;
71
- }
72
- , priority: 2
73
- }
74
- }
75
- , notblank: function () {
76
- return {
77
- validate: function ( val ) {
78
- return 'string' === typeof val && '' !== val.replace( /^\s+/g, '' ).replace( /\s+$/g, '' );
79
- }
80
- , priority: 2
81
- }
82
- }
83
- , required: function () {
84
- var that = this;
85
- return {
86
- validate: function ( val ) {
87
- // for checkboxes and select multiples. Check there is at least one required value
88
- if ( 'object' === typeof val ) {
89
- for ( var i in val ) {
90
- if ( that.required().validate( val[ i ] ) ) {
91
- return true;
92
- }
93
- }
94
-
95
- return false;
96
- }
97
-
98
- return that.notnull().validate( val ) && that.notblank().validate( val );
99
- }
100
- , priority: 512
101
- }
102
- }
103
- , type: function () {
104
- return {
105
- validate: function ( val, type ) {
106
- var regExp;
107
-
108
- switch ( type ) {
109
- case 'number':
110
- regExp = /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/;
111
- break;
112
- case 'digits':
113
- regExp = /^\d+$/;
114
- break;
115
- case 'alphanum':
116
- regExp = /^\w+$/;
117
- break;
118
- case 'email':
119
- regExp = /^((([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]))){2,6}$/i;
120
- break;
121
- case 'url':
122
- val = new RegExp( '(https?|s?ftp|git)', 'i' ).test( val ) ? val : 'http://' + val;
123
- /* falls through */
124
- case 'urlstrict':
125
- regExp = /^(https?|s?ftp|git):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([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])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i;
126
- break;
127
- case 'dateIso':
128
- regExp = /^(\d{4})\D?(0[1-9]|1[0-2])\D?([12]\d|0[1-9]|3[01])$/;
129
- break;
130
- case 'phone':
131
- regExp = /^((\+\d{1,3}(-| )?\(?\d\)?(-| )?\d{1,5})|(\(?\d{2,6}\)?))(-| )?(\d{3,4})(-| )?(\d{4})(( x| ext)\d{1,5}){0,1}$/;
132
- break;
133
- default:
134
- return false;
135
- }
136
-
137
- // test regExp if not null
138
- return '' !== val ? regExp.test( val ) : false;
139
- }
140
- , priority: 256
141
- }
142
- }
143
- , regexp: function () {
144
- return {
145
- validate: function ( val, regExp, self ) {
146
- return new RegExp( regExp, self.options.regexpFlag || '' ).test( val );
147
- }
148
- , priority: 64
149
- }
150
- }
151
- , minlength: function () {
152
- return {
153
- validate: function ( val, min ) {
154
- return val.length >= min;
155
- }
156
- , priority: 32
157
- }
158
- }
159
- , maxlength: function () {
160
- return {
161
- validate: function ( val, max ) {
162
- return val.length <= max;
163
- }
164
- , priority: 32
165
- }
166
- }
167
- , rangelength: function () {
168
- var that = this;
169
- return {
170
- validate: function ( val, arrayRange ) {
171
- return that.minlength().validate( val, arrayRange[ 0 ] ) && that.maxlength().validate( val, arrayRange[ 1 ] );
172
- }
173
- , priority: 32
174
- }
175
- }
176
- , min: function () {
177
- return {
178
- validate: function ( val, min ) {
179
- return Number( val ) >= min;
180
- }
181
- , priority: 32
182
- }
183
- }
184
- , max: function () {
185
- return {
186
- validate: function ( val, max ) {
187
- return Number( val ) <= max;
188
- }
189
- , priority: 32
190
- }
191
- }
192
- , range: function () {
193
- var that = this;
194
- return {
195
- validate: function ( val, arrayRange ) {
196
- return that.min().validate( val, arrayRange[ 0 ] ) && that.max().validate( val, arrayRange[ 1 ] );
197
- }
198
- , priority: 32
199
- }
200
- }
201
- , equalto: function () {
202
- return {
203
- validate: function ( val, elem, self ) {
204
- self.options.validateIfUnchanged = true;
205
- return val === $( elem ).val();
206
- }
207
- , priority: 64
208
- }
209
- }
210
- , remote: function () {
211
- return {
212
- validate: function ( val, url, self ) {
213
- var result = null
214
- , data = {}
215
- , dataType = {};
216
-
217
- data[ self.$element.attr( 'name' ) ] = val;
218
-
219
- if ( 'undefined' !== typeof self.options.remoteDatatype )
220
- dataType = { dataType: self.options.remoteDatatype };
221
-
222
- var manage = function ( isConstraintValid, message ) {
223
- // remove error message if we got a server message, different from previous message
224
- if ( 'undefined' !== typeof message && 'undefined' !== typeof self.Validator.messages.remote && message !== self.Validator.messages.remote ) {
225
- $( self.UI.ulError + ' .remote' ).remove();
226
- }
227
-
228
- if (false === isConstraintValid) {
229
- self.options.listeners.onFieldError( self.element, self.constraints, self );
230
- } else if (true === isConstraintValid && false === self.options.listeners.onFieldSuccess( self.element, self.constraints, self )) {
231
- // if onFieldSuccess returns (bool) false, consider that field is invalid
232
- isConstraintValid = false;
233
- }
234
-
235
- self.updtConstraint( { name: 'remote', valid: isConstraintValid }, message );
236
- self.manageValidationResult();
237
- };
238
-
239
- // transform string response into object
240
- var handleResponse = function ( response ) {
241
- if ( 'object' === typeof response ) {
242
- return response;
243
- }
244
-
245
- try {
246
- response = $.parseJSON( response );
247
- } catch ( err ) {}
248
-
249
- return response;
250
- }
251
-
252
- var manageErrorMessage = function ( response ) {
253
- return 'object' === typeof response && null !== response ? ( 'undefined' !== typeof response.error ? response.error : ( 'undefined' !== typeof response.message ? response.message : null ) ) : null;
254
- }
255
-
256
- $.ajax( $.extend( {}, {
257
- url: url
258
- , data: data
259
- , type: self.options.remoteMethod || 'GET'
260
- , success: function ( response ) {
261
- response = handleResponse( response );
262
- manage( 1 === response || true === response || ( 'object' === typeof response && null !== response && 'undefined' !== typeof response.success ), manageErrorMessage( response )
263
- );
264
- }
265
- , error: function ( response ) {
266
- response = handleResponse( response );
267
- manage( false, manageErrorMessage( response ) );
268
- }
269
- }, dataType ) );
270
-
271
- return result;
272
- }
273
- , priority: 64
274
- }
275
- }
276
-
277
- /**
278
- * Aliases for checkboxes constraints
279
- */
280
- , mincheck: function () {
281
- var that = this;
282
- return {
283
- validate: function ( obj, val ) { return that.minlength().validate( obj, val ) }
284
- , priority: 32
285
- }
286
- }
287
- , maxcheck: function () {
288
- var that = this;
289
- return {
290
- validate: function ( obj, val ) { return that.maxlength().validate( obj, val ) }
291
- , priority: 32
292
- }
293
- }
294
- , rangecheck: function () {
295
- var that = this;
296
- return {
297
- validate: function ( obj, arrayRange ) { return that.rangelength().validate( obj, arrayRange ) }
298
- , priority: 32
299
- }
125
+ var ParsleyAbstract = function() {};
126
+ ParsleyAbstract.prototype = {
127
+ asyncSupport: false,
128
+ actualizeOptions: function () {
129
+ this.options = this.OptionsFactory.get(this);
130
+ return this;
131
+ },
132
+ // ParsleyValidator validate proxy function . Could be replaced by third party scripts
133
+ validateThroughValidator: function (value, constraints, priority) {
134
+ return window.ParsleyValidator.validate.apply(window.ParsleyValidator, [value, constraints, priority]);
135
+ },
136
+ // Subscribe an event and a handler for a specific field or a specific form
137
+ // If on a ParsleyForm instance, it will be attached to form instance and also
138
+ // To every field instance for this form
139
+ subscribe: function (name, fn) {
140
+ $.listenTo(this, name.toLowerCase(), fn);
141
+ return this;
142
+ },
143
+ // Same as subscribe above. Unsubscribe an event for field, or form + its fields
144
+ unsubscribe: function (name) {
145
+ $.unsubscribeTo(this, name.toLowerCase());
146
+ return this;
147
+ },
148
+ // Reset UI
149
+ reset: function () {
150
+ // Field case: just emit a reset event for UI
151
+ if ('ParsleyForm' !== this.__class__)
152
+ return $.emit('parsley:field:reset', this);
153
+ // Form case: emit a reset event for each field
154
+ for (var i = 0; i < this.fields.length; i++)
155
+ $.emit('parsley:field:reset', this.fields[i]);
156
+ $.emit('parsley:form:reset', this);
157
+ },
158
+ // Destroy Parsley instance (+ UI)
159
+ destroy: function () {
160
+ // Field case: emit destroy event to clean UI and then destroy stored instance
161
+ if ('ParsleyForm' !== this.__class__) {
162
+ this.$element.removeData('Parsley');
163
+ this.$element.removeData('ParsleyFieldMultiple');
164
+ $.emit('parsley:field:destroy', this);
165
+ return;
300
166
  }
167
+ // Form case: destroy all its fields and then destroy stored instance
168
+ for (var i = 0; i < this.fields.length; i++)
169
+ this.fields[i].destroy();
170
+ this.$element.removeData('Parsley');
171
+ $.emit('parsley:form:destroy', this);
301
172
  }
302
-
173
+ };
174
+ /*!
175
+ * validator.js
176
+ * Guillaume Potier - <guillaume@wisembly.com>
177
+ * Version 0.5.8 - built Sun Mar 16 2014 17:18:21
178
+ * MIT Licensed
179
+ *
180
+ */
181
+ ( function ( exports ) {
182
+ /**
183
+ * Validator
184
+ */
185
+ var Validator = function ( options ) {
186
+ this.__class__ = 'Validator';
187
+ this.__version__ = '0.5.8';
188
+ this.options = options || {};
189
+ this.bindingKey = this.options.bindingKey || '_validatorjsConstraint';
190
+ return this;
191
+ };
192
+ Validator.prototype = {
193
+ constructor: Validator,
303
194
  /*
304
- * Register custom validators and messages
195
+ * Validate string: validate( string, Assert, string ) || validate( string, [ Assert, Assert ], [ string, string ] )
196
+ * Validate object: validate( object, Constraint, string ) || validate( object, Constraint, [ string, string ] )
197
+ * Validate binded object: validate( object, string ) || validate( object, [ string, string ] )
305
198
  */
306
- , init: function ( options ) {
307
- var customValidators = options.validators
308
- , customMessages = options.messages
309
- , key;
310
-
311
- for ( key in customValidators ) {
312
- this.addValidator(key, customValidators[ key ]);
313
- }
314
-
315
- for ( key in customMessages ) {
316
- this.addMessage(key, customMessages[ key ]);
317
- }
199
+ validate: function ( objectOrString, AssertsOrConstraintOrGroup, group ) {
200
+ if ( 'string' !== typeof objectOrString && 'object' !== typeof objectOrString )
201
+ throw new Error( 'You must validate an object or a string' );
202
+ // string / array validation
203
+ if ( 'string' === typeof objectOrString || _isArray(objectOrString) )
204
+ return this._validateString( objectOrString, AssertsOrConstraintOrGroup, group );
205
+ // binded object validation
206
+ if ( this.isBinded( objectOrString ) )
207
+ return this._validateBindedObject( objectOrString, AssertsOrConstraintOrGroup );
208
+ // regular object validation
209
+ return this._validateObject( objectOrString, AssertsOrConstraintOrGroup, group );
210
+ },
211
+ bind: function ( object, constraint ) {
212
+ if ( 'object' !== typeof object )
213
+ throw new Error( 'Must bind a Constraint to an object' );
214
+ object[ this.bindingKey ] = new Constraint( constraint );
215
+ return this;
216
+ },
217
+ unbind: function ( object ) {
218
+ if ( 'undefined' === typeof object._validatorjsConstraint )
219
+ return this;
220
+ delete object[ this.bindingKey ];
221
+ return this;
222
+ },
223
+ isBinded: function ( object ) {
224
+ return 'undefined' !== typeof object[ this.bindingKey ];
225
+ },
226
+ getBinded: function ( object ) {
227
+ return this.isBinded( object ) ? object[ this.bindingKey ] : null;
228
+ },
229
+ _validateString: function ( string, assert, group ) {
230
+ var result, failures = [];
231
+ if ( !_isArray( assert ) )
232
+ assert = [ assert ];
233
+ for ( var i = 0; i < assert.length; i++ ) {
234
+ if ( ! ( assert[ i ] instanceof Assert) )
235
+ throw new Error( 'You must give an Assert or an Asserts array to validate a string' );
236
+ result = assert[ i ].check( string, group );
237
+ if ( result instanceof Violation )
238
+ failures.push( result );
239
+ }
240
+ return failures.length ? failures : true;
241
+ },
242
+ _validateObject: function ( object, constraint, group ) {
243
+ if ( 'object' !== typeof constraint )
244
+ throw new Error( 'You must give a constraint to validate an object' );
245
+ if ( constraint instanceof Constraint )
246
+ return constraint.check( object, group );
247
+ return new Constraint( constraint ).check( object, group );
248
+ },
249
+ _validateBindedObject: function ( object, group ) {
250
+ return object[ this.bindingKey ].check( object, group );
318
251
  }
319
-
320
- /**
321
- * Replace %s placeholders by values
322
- *
323
- * @method formatMesssage
324
- * @param {String} message Message key
325
- * @param {Mixed} args Args passed by validators functions. Could be string, number or object
326
- * @return {String} Formatted string
327
- */
328
- , formatMesssage: function ( message, args ) {
329
-
330
- if ( 'object' === typeof args ) {
331
- for ( var i in args ) {
332
- message = this.formatMesssage( message, args[ i ] );
333
- }
334
-
335
- return message;
252
+ };
253
+ Validator.errorCode = {
254
+ must_be_a_string: 'must_be_a_string',
255
+ must_be_an_array: 'must_be_an_array',
256
+ must_be_a_number: 'must_be_a_number',
257
+ must_be_a_string_or_array: 'must_be_a_string_or_array'
258
+ };
259
+ /**
260
+ * Constraint
261
+ */
262
+ var Constraint = function ( data, options ) {
263
+ this.__class__ = 'Constraint';
264
+ this.options = options || {};
265
+ this.nodes = {};
266
+ if ( data ) {
267
+ try {
268
+ this._bootstrap( data );
269
+ } catch ( err ) {
270
+ throw new Error( 'Should give a valid mapping object to Constraint', err, data );
336
271
  }
337
-
338
- return 'string' === typeof message ? message.replace( new RegExp( '%s', 'i' ), args ) : '';
339
272
  }
340
-
341
- /**
342
- * Add / override a validator in validators list
343
- *
344
- * @method addValidator
345
- * @param {String} name Validator name.
346
- * @param {Function} fn Validator. Must return { validator: fn(), priority: int }
347
- */
348
- , addValidator: function ( name, fn ) {
349
- if ('undefined' === typeof fn().validate) {
350
- throw new Error( 'Validator `' + name + '` must have a validate method. See more here: http://parsleyjs.org/documentation.html#javascript-general' );
351
- }
352
-
353
- // add default prioirty if not given.
354
- if ('undefined' === typeof fn().priority) {
355
- fn = {
356
- validate: fn().validate
357
- , priority: 32
358
- };
359
-
360
- // Warn if possible
361
- if (window.console && window.console.warn) {
362
- window.console.warn( 'Validator `' + name + '` should have a priority. Default priority 32 given' );
273
+ return this;
274
+ };
275
+ Constraint.prototype = {
276
+ constructor: Constraint,
277
+ check: function ( object, group ) {
278
+ var result, failures = {};
279
+ // check all constraint nodes if strict validation enabled. Else, only object nodes that have a constraint
280
+ for ( var property in this.options.strict ? this.nodes : object ) {
281
+ if ( this.options.strict ? this.has( property, object ) : this.has( property ) ) {
282
+ result = this._check( property, object[ property ], group );
283
+ // check returned an array of Violations or an object mapping Violations
284
+ if ( ( _isArray( result ) && result.length > 0 ) || ( !_isArray( result ) && !_isEmptyObject( result ) ) )
285
+ failures[ property ] = result;
286
+ // in strict mode, get a violation for each constraint node not in object
287
+ } else if ( this.options.strict ) {
288
+ try {
289
+ // we trigger here a HaveProperty Assert violation to have uniform Violation object in the end
290
+ new Assert().HaveProperty( property ).validate( object );
291
+ } catch ( violation ) {
292
+ failures[ property ] = violation;
293
+ }
363
294
  }
364
295
  }
365
-
366
- this.validators[ name ] = fn;
367
- }
368
-
369
- /**
370
- * Add / override error message
371
- *
372
- * @method addMessage
373
- * @param {String} name Message name. Will automatically be binded to validator with same name
374
- * @param {String} message Message
375
- */
376
- , addMessage: function ( key, message, type ) {
377
-
378
- if ( 'undefined' !== typeof type && true === type ) {
379
- this.messages.type[ key ] = message;
380
- return;
296
+ return _isEmptyObject(failures) ? true : failures;
297
+ },
298
+ add: function ( node, object ) {
299
+ if ( object instanceof Assert || ( _isArray( object ) && object[ 0 ] instanceof Assert ) ) {
300
+ this.nodes[ node ] = object;
301
+ return this;
381
302
  }
382
-
383
- // custom types messages are a bit tricky cuz' nested ;)
384
- if ( 'type' === key ) {
385
- for ( var i in message ) {
386
- this.messages.type[ i ] = message[ i ];
387
- }
388
-
389
- return;
303
+ if ( 'object' === typeof object && !_isArray( object ) ) {
304
+ this.nodes[ node ] = object instanceof Constraint ? object : new Constraint( object );
305
+ return this;
390
306
  }
391
-
392
- this.messages[ key ] = message;
307
+ throw new Error( 'Should give an Assert, an Asserts array, a Constraint', object );
308
+ },
309
+ has: function ( node, nodes ) {
310
+ nodes = 'undefined' !== typeof nodes ? nodes : this.nodes;
311
+ return 'undefined' !== typeof nodes[ node ];
312
+ },
313
+ get: function ( node, placeholder ) {
314
+ return this.has( node ) ? this.nodes[ node ] : placeholder || null;
315
+ },
316
+ remove: function ( node ) {
317
+ var _nodes = [];
318
+ for ( var i in this.nodes )
319
+ if ( i !== node )
320
+ _nodes[ i ] = this.nodes[ i ];
321
+ this.nodes = _nodes;
322
+ return this;
323
+ },
324
+ _bootstrap: function ( data ) {
325
+ if ( data instanceof Constraint )
326
+ return this.nodes = data.nodes;
327
+ for ( var node in data )
328
+ this.add( node, data[ node ] );
329
+ },
330
+ _check: function ( node, value, group ) {
331
+ // Assert
332
+ if ( this.nodes[ node ] instanceof Assert )
333
+ return this._checkAsserts( value, [ this.nodes[ node ] ], group );
334
+ // Asserts
335
+ if ( _isArray( this.nodes[ node ] ) )
336
+ return this._checkAsserts( value, this.nodes[ node ], group );
337
+ // Constraint -> check api
338
+ if ( this.nodes[ node ] instanceof Constraint )
339
+ return this.nodes[ node ].check( value, group );
340
+ throw new Error( 'Invalid node', this.nodes[ node ] );
341
+ },
342
+ _checkAsserts: function ( value, asserts, group ) {
343
+ var result, failures = [];
344
+ for ( var i = 0; i < asserts.length; i++ ) {
345
+ result = asserts[ i ].check( value, group );
346
+ if ( 'undefined' !== typeof result && true !== result )
347
+ failures.push( result );
348
+ // Some asserts (Collection for example) could return an object
349
+ // if ( result && ! ( result instanceof Violation ) )
350
+ // return result;
351
+ //
352
+ // // Vast assert majority return Violation
353
+ // if ( result instanceof Violation )
354
+ // failures.push( result );
355
+ }
356
+ return failures;
393
357
  }
394
358
  };
395
-
396
- var ParsleyUI = function ( ParsleyInstance ) {
397
- this.init( ParsleyInstance );
359
+ /**
360
+ * Violation
361
+ */
362
+ var Violation = function ( assert, value, violation ) {
363
+ this.__class__ = 'Violation';
364
+ if ( ! ( assert instanceof Assert ) )
365
+ throw new Error( 'Should give an assertion implementing the Assert interface' );
366
+ this.assert = assert;
367
+ this.value = value;
368
+ if ( 'undefined' !== typeof violation )
369
+ this.violation = violation;
398
370
  };
399
-
400
- ParsleyUI.prototype = {
401
-
402
- constructor: ParsleyUI
403
-
404
- , init: function ( ParsleyInstance ) {
405
- this.ParsleyInstance = ParsleyInstance;
406
- this.hash = ParsleyInstance.hash;
407
- this.options = this.ParsleyInstance.options;
408
- this.errorClassHandler = this.options.errors.classHandler( this.ParsleyInstance.element, this.ParsleyInstance.isRadioOrCheckbox ) || this.ParsleyInstance.$element;
409
- this.ulErrorManagement();
410
- }
411
-
412
- /**
413
- * Manage ul error Container
414
- *
415
- * @private
416
- * @method ulErrorManagement
417
- */
418
- , ulErrorManagement: function () {
419
- this.ulError = '#' + this.hash;
420
- this.ulTemplate = $( this.options.errors.errorsWrapper ).attr( 'id', this.hash ).addClass( 'parsley-error-list' );
421
- }
422
-
423
- /**
424
- * Remove li / ul error
425
- *
426
- * @method removeError
427
- * @param {String} constraintName Method Name
428
- * @return ParsleyUI
429
- */
430
- , removeError: function ( constraintName ) {
431
- var liError = this.ulError + ' .' + constraintName
432
- , that = this;
433
-
434
- this.options.animate ? $( liError ).fadeOut( this.options.animateDuration, function () {
435
- $( this ).remove();
436
-
437
- if ( that.ulError && $( that.ulError ).children().length === 0 ) {
438
- that.removeErrors();
439
- } } ) : $( liError ).remove();
440
-
441
- return this;
371
+ Violation.prototype = {
372
+ show: function () {
373
+ var show = {
374
+ assert: this.assert.__class__,
375
+ value: this.value
376
+ };
377
+ if ( this.violation )
378
+ show.violation = this.violation;
379
+ return show;
380
+ },
381
+ __toString: function () {
382
+ if ( 'undefined' !== typeof this.violation )
383
+ this.violation = '", ' + this.getViolation().constraint + ' expected was ' + this.getViolation().expected;
384
+ return this.assert.__class__ + ' assert failed for "' + this.value + this.violation || '';
385
+ },
386
+ getViolation: function () {
387
+ var constraint, expected;
388
+ for ( constraint in this.violation )
389
+ expected = this.violation[ constraint ];
390
+ return { constraint: constraint, expected: expected };
442
391
  }
443
-
444
- /**
445
- * Add li error
446
- *
447
- * @method addError
448
- * @param {Object} { minlength: "error message for minlength constraint" }
449
- * @return ParsleyUI
450
- */
451
- , addError: function ( error ) {
452
- for ( var constraint in error ) {
453
- var liTemplate = $( this.options.errors.errorElem ).addClass( constraint );
454
-
455
- $( this.ulError ).append( this.options.animate ? $( liTemplate ).html( error[ constraint ] ).hide().fadeIn( this.options.animateDuration ) : $( liTemplate ).html( error[ constraint ] ) );
392
+ };
393
+ /**
394
+ * Assert
395
+ */
396
+ var Assert = function ( group ) {
397
+ this.__class__ = 'Assert';
398
+ this.__parentClass__ = this.__class__;
399
+ this.groups = [];
400
+ if ( 'undefined' !== typeof group )
401
+ this.addGroup( group );
402
+ return this;
403
+ };
404
+ Assert.prototype = {
405
+ construct: Assert,
406
+ check: function ( value, group ) {
407
+ if ( group && !this.hasGroup( group ) )
408
+ return;
409
+ if ( !group && this.hasGroups() )
410
+ return;
411
+ try {
412
+ return this.validate( value, group );
413
+ } catch ( violation ) {
414
+ return violation;
456
415
  }
457
-
416
+ },
417
+ hasGroup: function ( group ) {
418
+ if ( _isArray( group ) )
419
+ return this.hasOneOf( group );
420
+ // All Asserts respond to "Any" group
421
+ if ( 'Any' === group )
422
+ return true;
423
+ // Asserts with no group also respond to "Default" group. Else return false
424
+ if ( !this.hasGroups() )
425
+ return 'Default' === group;
426
+ return -1 !== this.groups.indexOf( group );
427
+ },
428
+ hasOneOf: function ( groups ) {
429
+ for ( var i = 0; i < groups.length; i++ )
430
+ if ( this.hasGroup( groups[ i ] ) )
431
+ return true;
432
+ return false;
433
+ },
434
+ hasGroups: function () {
435
+ return this.groups.length > 0;
436
+ },
437
+ addGroup: function ( group ) {
438
+ if ( _isArray( group ) )
439
+ return this.addGroups( group );
440
+ if ( !this.hasGroup( group ) )
441
+ this.groups.push( group );
458
442
  return this;
459
- }
460
-
461
- /**
462
- * Update existing error if text has changed
463
- *
464
- * @method updateError
465
- * @param {Object} { minlength: "error message for minlength constraint" }
466
- * @return ParsleyUI
467
- */
468
- , updateError: function ( error ) {
469
- for ( var constraint in error ) {
470
- if ( error[ constraint ] !== $( this.ulError + " > li." + constraint ).html() ) {
471
- this.removeError( constraint ).addError( error );
472
- }
473
- }
474
-
443
+ },
444
+ removeGroup: function ( group ) {
445
+ var _groups = [];
446
+ for ( var i = 0; i < this.groups.length; i++ )
447
+ if ( group !== this.groups[ i ] )
448
+ _groups.push( this.groups[ i ] );
449
+ this.groups = _groups;
475
450
  return this;
476
- }
477
-
478
- /**
479
- * Remove all ul / li errors
480
- *
481
- * @method removeErrors
482
- * @return ParsleyUI
483
- */
484
- , removeErrors: function () {
485
- this.options.animate ? $( this.ulError ).fadeOut( this.options.animateDuration, function () { $( this ).remove(); } ) : $( this.ulError ).remove();
486
-
451
+ },
452
+ addGroups: function ( groups ) {
453
+ for ( var i = 0; i < groups.length; i++ )
454
+ this.addGroup( groups[ i ] );
487
455
  return this;
488
- }
489
-
456
+ },
490
457
  /**
491
- * Remove ul errors and parsley error or success classes
492
- *
493
- * @method reset
494
- * @return ParsleyUI
458
+ * Asserts definitions
495
459
  */
496
- , reset: function () {
497
- this.ParsleyInstance.valid = null;
498
- this.removeErrors();
499
- this.ParsleyInstance.validatedOnce = false;
500
- this.errorClassHandler.removeClass( this.options.successClass ).removeClass( this.options.errorClass );
501
-
502
- for ( var constraint in this.constraints ) {
503
- this.constraints[ constraint ].valid = null;
504
- }
505
-
460
+ HaveProperty: function ( node ) {
461
+ this.__class__ = 'HaveProperty';
462
+ this.node = node;
463
+ this.validate = function ( object ) {
464
+ if ( 'undefined' === typeof object[ this.node ] )
465
+ throw new Violation( this, object, { value: this.node } );
466
+ return true;
467
+ };
506
468
  return this;
507
- }
508
-
509
- /**
510
- * Add li / ul errors messages
511
- *
512
- * @method manageError
513
- * @param {Object} constraint
514
- * @return ParsleyUI
515
- */
516
- , manageError: function ( constraint ) {
517
- // display ulError container if it has been removed previously (or never shown)
518
- if ( !$( this.ulError ).length ) {
519
- this.manageErrorContainer();
520
- }
521
-
522
- // TODO: refacto properly
523
- // if required constraint but field is not null, do not display
524
- if ( 'required' === constraint.name && null !== this.ParsleyInstance.getVal() && this.ParsleyInstance.getVal().length > 0 ) {
525
- return this;
526
-
527
- // if empty required field and non required constraint fails, do not display
528
- } else if ( this.ParsleyInstance.isRequired && 'required' !== constraint.name && ( null === this.ParsleyInstance.getVal() || 0 === this.ParsleyInstance.getVal().length ) ) {
529
- this.removeError( constraint.name );
530
-
531
- return this;
532
- }
533
-
534
- // TODO: refacto error name w/ proper & readable function
535
- var constraintName = constraint.name
536
- , liClass = false !== this.options.errorMessage ? 'custom-error-message' : constraintName
537
- , liError = {}
538
- , message = false !== this.options.errorMessage ? this.options.errorMessage : ( constraint.name === 'type' ?
539
- this.ParsleyInstance.Validator.messages[ constraintName ][ constraint.requirements ] : ( 'undefined' === typeof this.ParsleyInstance.Validator.messages[ constraintName ] ?
540
- this.ParsleyInstance.Validator.messages.defaultMessage : this.ParsleyInstance.Validator.formatMesssage( this.ParsleyInstance.Validator.messages[ constraintName ], constraint.requirements ) ) );
541
-
542
- liError[ liClass ] = message;
543
-
544
- // add liError if not shown. update if already exist
545
- !$( this.ulError + ' .' + liClass ).length ? this.addError( liError ) : this.updateError( liError );
546
-
469
+ },
470
+ Blank: function () {
471
+ this.__class__ = 'Blank';
472
+ this.validate = function ( value ) {
473
+ if ( 'string' !== typeof value )
474
+ throw new Violation( this, value, { value: Validator.errorCode.must_be_a_string } );
475
+ if ( '' !== value.replace( /^\s+/g, '' ).replace( /\s+$/g, '' ) )
476
+ throw new Violation( this, value );
477
+ return true;
478
+ };
547
479
  return this;
548
- }
549
-
550
- /**
551
- * Create ul error container
552
- *
553
- * @method manageErrorContainer
554
- * @return ParsleyUI
555
- */
556
- , manageErrorContainer: function () {
557
- var errorContainer = this.options.errorContainer || this.options.errors.container( this.ParsleyInstance.element, this.ParsleyInstance.isRadioOrCheckbox )
558
- , ulTemplate = this.options.animate ? this.ulTemplate.css('display', '') : this.ulTemplate;
559
-
560
- if ( 'undefined' !== typeof errorContainer ) {
561
- $( errorContainer ).append( ulTemplate );
562
- return;
563
- }
564
-
565
- !this.ParsleyInstance.isRadioOrCheckbox ? this.ParsleyInstance.$element.after( ulTemplate ) : this.ParsleyInstance.$element.parent().after( ulTemplate );
566
-
480
+ },
481
+ Callback: function ( fn ) {
482
+ this.__class__ = 'Callback';
483
+ this.arguments = Array.prototype.slice.call( arguments );
484
+ if ( 1 === this.arguments.length )
485
+ this.arguments = [];
486
+ else
487
+ this.arguments.splice( 0, 1 );
488
+ if ( 'function' !== typeof fn )
489
+ throw new Error( 'Callback must be instanciated with a function' );
490
+ this.fn = fn;
491
+ this.validate = function ( value ) {
492
+ var result = this.fn.apply( this, [ value ].concat( this.arguments ) );
493
+ if ( true !== result )
494
+ throw new Violation( this, value, { result: result } );
495
+ return true;
496
+ };
567
497
  return this;
568
- }
569
- };
570
-
571
- /**
572
- * ParsleyField class manage each form field inside a validated Parsley form.
573
- * Returns if field valid or not depending on its value and constraints
574
- * Manage field error display and behavior, event triggers and more
575
- *
576
- * @class ParsleyField
577
- * @constructor
578
- */
579
- var ParsleyField = function ( element, options, type ) {
580
- this.options = options;
581
-
582
- // if type is ParsleyFieldMultiple, just return this. used for clone
583
- if ( type === 'ParsleyFieldMultiple' ) {
498
+ },
499
+ Choice: function ( list ) {
500
+ this.__class__ = 'Choice';
501
+ if ( !_isArray( list ) && 'function' !== typeof list )
502
+ throw new Error( 'Choice must be instanciated with an array or a function' );
503
+ this.list = list;
504
+ this.validate = function ( value ) {
505
+ var list = 'function' === typeof this.list ? this.list() : this.list;
506
+ for ( var i = 0; i < list.length; i++ )
507
+ if ( value === list[ i ] )
508
+ return true;
509
+ throw new Violation( this, value, { choices: list } );
510
+ };
584
511
  return this;
585
- }
586
-
587
- this.init( element, type || 'ParsleyField' );
588
- };
589
-
590
- ParsleyField.prototype = {
591
-
592
- constructor: ParsleyField
593
-
594
- /**
595
- * Set some properties, bind constraint validators and validation events
596
- *
597
- * @method init
598
- * @param {Object} element
599
- * @param {Object} options
600
- */
601
- , init: function ( element, type ) {
602
- this.type = type;
603
- this.valid = true;
604
- this.element = element;
605
- this.validatedOnce = false;
606
- this.$element = $( element );
607
- this.val = this.$element.val();
608
- this.Validator = new Validator( this.options );
609
- this.isRequired = false;
610
- this.constraints = {};
611
-
612
- // overriden by ParsleyItemMultiple if radio or checkbox input
613
- if ( 'undefined' === typeof this.isRadioOrCheckbox ) {
614
- this.isRadioOrCheckbox = false;
615
- this.hash = this.generateHash();
616
- }
617
-
618
- // error ul dom management done only once at init
619
- this.UI = new ParsleyUI( this );
620
-
621
- // bind some html5 properties
622
- if ( this.options.useHtml5Constraints ) {
623
- this.bindHtml5Constraints();
624
- }
625
-
626
- // bind validators to field
627
- this.addConstraints();
628
-
629
- // bind parsley events if validators have been registered
630
- if ( this.hasConstraints() ) {
631
- this.bindValidationEvents();
632
- }
633
- }
634
-
635
- , setParent: function ( elem ) {
636
- this.$parent = $( elem );
637
- }
638
-
639
- , getParent: function () {
640
- return this.$parent;
641
- }
642
-
643
- /**
644
- * Bind some extra html5 types / validators
645
- *
646
- * @private
647
- * @method bindHtml5Constraints
648
- */
649
- , bindHtml5Constraints: function () {
650
- // add html5 required support + class required support
651
- if ( this.$element.hasClass( 'required' ) || this.$element.attr( 'required' ) ) {
652
- this.options.required = true;
653
- }
654
-
655
- // add html5 supported types & options
656
- var type = this.$element.attr( 'type' );
657
- if ( 'undefined' !== typeof type && new RegExp( type, 'i' ).test( 'email url number range tel' ) ) {
658
- this.options.type = 'tel' === type ? 'phone' : type;
659
-
660
- // number and range types could have min and/or max values
661
- if ( new RegExp( this.options.type, 'i' ).test( 'number range' ) ) {
662
- this.options.type = 'number';
663
-
664
- // double condition to support jQuery and Zepto.. :(
665
- if ( 'undefined' !== typeof this.$element.attr( 'min' ) && this.$element.attr( 'min' ).length ) {
666
- this.options.min = this.$element.attr( 'min' );
667
- }
668
-
669
- if ( 'undefined' !== typeof this.$element.attr( 'max' ) && this.$element.attr( 'max' ).length ) {
670
- this.options.max = this.$element.attr( 'max' );
671
- }
512
+ },
513
+ Collection: function ( constraint ) {
514
+ this.__class__ = 'Collection';
515
+ this.constraint = 'undefined' !== typeof constraint ? new Constraint( constraint ) : false;
516
+ this.validate = function ( collection, group ) {
517
+ var result, validator = new Validator(), count = 0, failures = {}, groups = this.groups.length ? this.groups : group;
518
+ if ( !_isArray( collection ) )
519
+ throw new Violation( this, array, { value: Validator.errorCode.must_be_an_array } );
520
+ for ( var i = 0; i < collection.length; i++ ) {
521
+ result = this.constraint ?
522
+ validator.validate( collection[ i ], this.constraint, groups ) :
523
+ validator.validate( collection[ i ], groups );
524
+ if ( !_isEmptyObject( result ) )
525
+ failures[ count ] = result;
526
+ count++;
672
527
  }
673
- }
674
-
675
- if ( 'string' === typeof this.$element.attr( 'pattern' ) && this.$element.attr( 'pattern' ).length ) {
676
- this.options.regexp = this.$element.attr( 'pattern' );
677
- }
678
-
679
- }
680
-
681
- /**
682
- * Attach field validators functions passed through data-api
683
- *
684
- * @private
685
- * @method addConstraints
686
- */
687
- , addConstraints: function () {
688
- for ( var constraint in this.options ) {
689
- var addConstraint = {};
690
- addConstraint[ constraint ] = this.options[ constraint ];
691
- this.addConstraint( addConstraint, true, false );
692
- }
693
- }
694
-
695
- /**
696
- * Dynamically add a new constraint to a field
697
- *
698
- * @method addConstraint
699
- * @param {Object} constraint { name: requirements }
700
- */
701
- , addConstraint: function ( constraint, doNotUpdateValidationEvents, sort ) {
702
- for ( var name in constraint ) {
703
- name = name.toLowerCase();
704
-
705
- if ( 'function' === typeof this.Validator.validators[ name ] ) {
706
- this.constraints[ name ] = {
707
- name: name
708
- , requirements: constraint[ name ]
709
- , valid: null
710
- }
711
-
712
- if ( name === 'required' ) {
713
- this.isRequired = true;
714
- }
715
-
716
- this.addCustomConstraintMessage( name );
528
+ return !_isEmptyObject( failures ) ? failures : true;
529
+ };
530
+ return this;
531
+ },
532
+ Count: function ( count ) {
533
+ this.__class__ = 'Count';
534
+ this.count = count;
535
+ this.validate = function ( array ) {
536
+ if ( !_isArray( array ) )
537
+ throw new Violation( this, array, { value: Validator.errorCode.must_be_an_array } );
538
+ var count = 'function' === typeof this.count ? this.count( array ) : this.count;
539
+ if ( isNaN( Number( count ) ) )
540
+ throw new Error( 'Count must be a valid interger', count );
541
+ if ( count !== array.length )
542
+ throw new Violation( this, array, { count: count } );
543
+ return true;
544
+ };
545
+ return this;
546
+ },
547
+ Email: function () {
548
+ this.__class__ = 'Email';
549
+ this.validate = function ( value ) {
550
+ var regExp = /^((([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;
551
+ if ( 'string' !== typeof value )
552
+ throw new Violation( this, value, { value: Validator.errorCode.must_be_a_string } );
553
+ if ( !regExp.test( value ) )
554
+ throw new Violation( this, value );
555
+ return true;
556
+ };
557
+ return this;
558
+ },
559
+ Eql: function ( eql ) {
560
+ this.__class__ = 'Eql';
561
+ if ( 'undefined' === typeof eql )
562
+ throw new Error( 'Equal must be instanciated with an Array or an Object' );
563
+ this.eql = eql;
564
+ this.validate = function ( value ) {
565
+ var eql = 'function' === typeof this.eql ? this.eql( value ) : this.eql;
566
+ if ( !expect.eql( eql, value ) )
567
+ throw new Violation( this, value, { eql: eql } );
568
+ return true;
569
+ };
570
+ return this;
571
+ },
572
+ EqualTo: function ( reference ) {
573
+ this.__class__ = 'EqualTo';
574
+ if ( 'undefined' === typeof reference )
575
+ throw new Error( 'EqualTo must be instanciated with a value or a function' );
576
+ this.reference = reference;
577
+ this.validate = function ( value ) {
578
+ var reference = 'function' === typeof this.reference ? this.reference( value ) : this.reference;
579
+ if ( reference !== value )
580
+ throw new Violation( this, value, { value: reference } );
581
+ return true;
582
+ };
583
+ return this;
584
+ },
585
+ GreaterThan: function ( threshold ) {
586
+ this.__class__ = 'GreaterThan';
587
+ if ( 'undefined' === typeof threshold )
588
+ throw new Error( 'Should give a threshold value' );
589
+ this.threshold = threshold;
590
+ this.validate = function ( value ) {
591
+ if ( '' === value || isNaN( Number( value ) ) )
592
+ throw new Violation( this, value, { value: Validator.errorCode.must_be_a_number } );
593
+ if ( this.threshold >= value )
594
+ throw new Violation( this, value, { threshold: this.threshold } );
595
+ return true;
596
+ };
597
+ return this;
598
+ },
599
+ GreaterThanOrEqual: function ( threshold ) {
600
+ this.__class__ = 'GreaterThanOrEqual';
601
+ if ( 'undefined' === typeof threshold )
602
+ throw new Error( 'Should give a threshold value' );
603
+ this.threshold = threshold;
604
+ this.validate = function ( value ) {
605
+ if ( '' === value || isNaN( Number( value ) ) )
606
+ throw new Violation( this, value, { value: Validator.errorCode.must_be_a_number } );
607
+ if ( this.threshold > value )
608
+ throw new Violation( this, value, { threshold: this.threshold } );
609
+ return true;
610
+ };
611
+ return this;
612
+ },
613
+ InstanceOf: function ( classRef ) {
614
+ this.__class__ = 'InstanceOf';
615
+ if ( 'undefined' === typeof classRef )
616
+ throw new Error( 'InstanceOf must be instanciated with a value' );
617
+ this.classRef = classRef;
618
+ this.validate = function ( value ) {
619
+ if ( true !== (value instanceof this.classRef) )
620
+ throw new Violation( this, value, { classRef: this.classRef } );
621
+ return true;
622
+ };
623
+ return this;
624
+ },
625
+ IPv4: function () {
626
+ this.__class__ = 'IPv4';
627
+ this.validate = function ( value ) {
628
+ var regExp = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
629
+ if ( 'string' !== typeof value )
630
+ throw new Violation( this, value, { value: Validator.errorCode.must_be_a_string } );
631
+ if ( !regExp.test( value ) )
632
+ throw new Violation( this, value );
633
+ return true;
634
+ };
635
+ return this;
636
+ },
637
+ Length: function ( boundaries ) {
638
+ this.__class__ = 'Length';
639
+ if ( !boundaries.min && !boundaries.max )
640
+ throw new Error( 'Lenth assert must be instanciated with a { min: x, max: y } object' );
641
+ this.min = boundaries.min;
642
+ this.max = boundaries.max;
643
+ this.validate = function ( value ) {
644
+ if ( 'string' !== typeof value && !_isArray( value ) )
645
+ throw new Violation( this, value, { value: Validator.errorCode.must_be_a_string_or_array } );
646
+ if ( 'undefined' !== typeof this.min && this.min === this.max && value.length !== this.min )
647
+ throw new Violation( this, value, { min: this.min, max: this.max } );
648
+ if ( 'undefined' !== typeof this.max && value.length > this.max )
649
+ throw new Violation( this, value, { max: this.max } );
650
+ if ( 'undefined' !== typeof this.min && value.length < this.min )
651
+ throw new Violation( this, value, { min: this.min } );
652
+ return true;
653
+ };
654
+ return this;
655
+ },
656
+ LessThan: function ( threshold ) {
657
+ this.__class__ = 'LessThan';
658
+ if ( 'undefined' === typeof threshold )
659
+ throw new Error( 'Should give a threshold value' );
660
+ this.threshold = threshold;
661
+ this.validate = function ( value ) {
662
+ if ( '' === value || isNaN( Number( value ) ) )
663
+ throw new Violation( this, value, { value: Validator.errorCode.must_be_a_number } );
664
+ if ( this.threshold <= value )
665
+ throw new Violation( this, value, { threshold: this.threshold } );
666
+ return true;
667
+ };
668
+ return this;
669
+ },
670
+ LessThanOrEqual: function ( threshold ) {
671
+ this.__class__ = 'LessThanOrEqual';
672
+ if ( 'undefined' === typeof threshold )
673
+ throw new Error( 'Should give a threshold value' );
674
+ this.threshold = threshold;
675
+ this.validate = function ( value ) {
676
+ if ( '' === value || isNaN( Number( value ) ) )
677
+ throw new Violation( this, value, { value: Validator.errorCode.must_be_a_number } );
678
+ if ( this.threshold < value )
679
+ throw new Violation( this, value, { threshold: this.threshold } );
680
+ return true;
681
+ };
682
+ return this;
683
+ },
684
+ Mac: function () {
685
+ this.__class__ = 'Mac';
686
+ this.validate = function ( value ) {
687
+ var regExp = /^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$/i;
688
+ if ( 'string' !== typeof value )
689
+ throw new Violation( this, value, { value: Validator.errorCode.must_be_a_string } );
690
+ if ( !regExp.test( value ) )
691
+ throw new Violation( this, value );
692
+ return true;
693
+ };
694
+ return this;
695
+ },
696
+ NotNull: function () {
697
+ this.__class__ = 'NotNull';
698
+ this.validate = function ( value ) {
699
+ if ( null === value || 'undefined' === typeof value )
700
+ throw new Violation( this, value );
701
+ return true;
702
+ };
703
+ return this;
704
+ },
705
+ NotBlank: function () {
706
+ this.__class__ = 'NotBlank';
707
+ this.validate = function ( value ) {
708
+ if ( 'string' !== typeof value )
709
+ throw new Violation( this, value, { value: Validator.errorCode.must_be_a_string } );
710
+ if ( '' === value.replace( /^\s+/g, '' ).replace( /\s+$/g, '' ) )
711
+ throw new Violation( this, value );
712
+ return true;
713
+ };
714
+ return this;
715
+ },
716
+ Null: function () {
717
+ this.__class__ = 'Null';
718
+ this.validate = function ( value ) {
719
+ if ( null !== value )
720
+ throw new Violation( this, value );
721
+ return true;
722
+ };
723
+ return this;
724
+ },
725
+ Range: function ( min, max ) {
726
+ this.__class__ = 'Range';
727
+ if ( 'undefined' === typeof min || 'undefined' === typeof max )
728
+ throw new Error( 'Range assert expects min and max values' );
729
+ this.min = min;
730
+ this.max = max;
731
+ this.validate = function ( value ) {
732
+ try {
733
+ // validate strings and objects with their Length
734
+ if ( ( 'string' === typeof value && isNaN( Number( value ) ) ) || _isArray( value ) )
735
+ new Assert().Length( { min: this.min, max: this.max } ).validate( value );
736
+ // validate numbers with their value
737
+ else
738
+ new Assert().GreaterThanOrEqual( this.min ).validate( value ) && new Assert().LessThanOrEqual( this.max ).validate( value );
739
+ return true;
740
+ } catch ( violation ) {
741
+ throw new Violation( this, value, violation.violation );
717
742
  }
718
- }
719
-
720
- // force field validation next check and reset validation events
721
- if ( 'undefined' === typeof doNotUpdateValidationEvents ) {
722
- this.bindValidationEvents();
723
- }
724
- }
725
-
726
- /**
727
- * Dynamically update an existing constraint to a field.
728
- * Simple API: { name: requirements }
729
- *
730
- * @method updtConstraint
731
- * @param {Object} constraint
732
- */
733
- , updateConstraint: function ( constraint, message ) {
734
- for ( var name in constraint ) {
735
- this.updtConstraint( { name: name, requirements: constraint[ name ], valid: null }, message );
736
- }
737
- }
738
-
739
- /**
740
- * Dynamically update an existing constraint to a field.
741
- * Complex API: { name: name, requirements: requirements, valid: boolean }
742
- *
743
- * @method updtConstraint
744
- * @param {Object} constraint
745
- */
746
- , updtConstraint: function ( constraint, message ) {
747
- this.constraints[ constraint.name ] = $.extend( true, this.constraints[ constraint.name ], constraint );
748
-
749
- if ( 'string' === typeof message ) {
750
- if ( constraint.name === 'type' )
751
- this.Validator.messages[ constraint.name ][ constraint.requirements ] = message ;
752
- else
753
- this.Validator.messages[ constraint.name ] = message ;
754
- }
755
-
756
- // force field validation next check and reset validation events
757
- this.bindValidationEvents();
758
- }
759
-
760
- /**
761
- * Dynamically remove an existing constraint to a field.
762
- *
763
- * @method removeConstraint
764
- * @param {String} constraintName
765
- */
766
- , removeConstraint: function ( constraintName ) {
767
- var constraintName = constraintName.toLowerCase();
768
-
769
- delete this.constraints[ constraintName ];
770
-
771
- if ( constraintName === 'required' ) {
772
- this.isRequired = false;
773
- }
774
-
775
- // if there are no more constraint, reset errors and validation state
776
- if ( !this.hasConstraints() ) {
777
- this.UI.reset();
778
- return;
779
- }
780
-
781
- this.bindValidationEvents();
782
- }
783
-
784
- /**
785
- * Add custom constraint message, passed through data-API
786
- *
787
- * @private
788
- * @method addCustomConstraintMessage
789
- * @param constraint
790
- */
791
- , addCustomConstraintMessage: function ( constraint ) {
792
- // custom message type data-type-email-message -> typeEmailMessage | data-minlength-error => minlengthMessage
793
- var customMessage = constraint
794
- + ( 'type' === constraint && 'undefined' !== typeof this.options[ constraint ] ? this.options[ constraint ].charAt( 0 ).toUpperCase() + this.options[ constraint ].substr( 1 ) : '' )
795
- + 'Message';
796
-
797
- if ( 'undefined' !== typeof this.options[ customMessage ] ) {
798
- this.Validator.addMessage( 'type' === constraint ? this.options[ constraint ] : constraint, this.options[ customMessage ], 'type' === constraint );
799
- }
800
- }
801
-
802
- /**
803
- * Bind validation events on a field
804
- *
805
- * @private
806
- * @method bindValidationEvents
807
- */
808
- , bindValidationEvents: function () {
809
- // this field has validation events, that means it has to be validated
810
- this.valid = null;
811
- this.$element.addClass( 'parsley-validated' );
812
-
813
- // remove eventually already binded events
814
- this.$element.off( '.' + this.type );
815
-
816
- // force add 'change' event if async remote validator here to have result before form submitting
817
- if ( this.options.remote && !new RegExp( 'change', 'i' ).test( this.options.trigger ) ) {
818
- this.options.trigger = !this.options.trigger ? 'change' : ' change';
819
- }
820
-
821
- // always bind keyup event, for better UX when a field is invalid
822
- var triggers = ( !this.options.trigger ? '' : this.options.trigger )
823
- + ( new RegExp( 'key', 'i' ).test( this.options.trigger ) ? '' : ' keyup' );
824
-
825
- // always bind change event, for better UX when a select is invalid
826
- if ( this.$element.is( 'select' ) ) {
827
- triggers += new RegExp( 'change', 'i' ).test( triggers ) ? '' : ' change';
828
- }
829
-
830
- // trim triggers to bind them correctly with .on()
831
- triggers = triggers.replace( /^\s+/g , '' ).replace( /\s+$/g , '' );
832
-
833
- this.$element.on( ( triggers + ' ' ).split( ' ' ).join( '.' + this.type + ' ' ), false, $.proxy( this.eventValidation, this ) );
834
- }
835
-
836
- /**
837
- * Hash management. Used for ul error
838
- *
839
- * @method generateHash
840
- * @returns {String} 5 letters unique hash
841
- */
842
- , generateHash: function () {
843
- return 'parsley-' + ( Math.random() + '' ).substring( 2 );
844
- }
845
-
846
- /**
847
- * Public getHash accessor
848
- *
849
- * @method getHash
850
- * @returns {String} hash
851
- */
852
- , getHash: function () {
853
- return this.hash;
854
- }
855
-
856
- /**
857
- * Returns field val needed for validation
858
- * Special treatment for radio & checkboxes
859
- *
860
- * @method getVal
861
- * @returns {String} val
862
- */
863
- , getVal: function () {
864
- if ('undefined' !== typeof this.$element.domApi( this.options.namespace )[ 'value' ]) {
865
- return this.$element.domApi( this.options.namespace )[ 'value' ];
866
- }
867
-
868
- return this.$element.val();
869
- }
870
-
871
- /**
872
- * Called when validation is triggered by an event
873
- * Do nothing if val.length < this.options.validationMinlength
874
- *
875
- * @method eventValidation
876
- * @param {Object} event jQuery event
877
- */
878
- , eventValidation: function ( event ) {
879
- var val = this.getVal();
880
-
881
- // do nothing on keypress event if not explicitely passed as data-trigger and if field has not already been validated once
882
- if ( event.type === 'keyup' && !/keyup/i.test( this.options.trigger ) && !this.validatedOnce ) {
883
743
  return true;
884
- }
885
-
886
- // do nothing on change event if not explicitely passed as data-trigger and if field has not already been validated once
887
- if ( event.type === 'change' && !/change/i.test( this.options.trigger ) && !this.validatedOnce ) {
744
+ };
745
+ return this;
746
+ },
747
+ Regexp: function ( regexp, flag ) {
748
+ this.__class__ = 'Regexp';
749
+ if ( 'undefined' === typeof regexp )
750
+ throw new Error( 'You must give a regexp' );
751
+ this.regexp = regexp;
752
+ this.flag = flag || '';
753
+ this.validate = function ( value ) {
754
+ if ( 'string' !== typeof value )
755
+ throw new Violation( this, value, { value: Validator.errorCode.must_be_a_string } );
756
+ if ( !new RegExp( this.regexp, this.flag ).test( value ) )
757
+ throw new Violation( this, value, { regexp: this.regexp, flag: this.flag } );
888
758
  return true;
889
- }
890
-
891
- // start validation process only if field has enough chars and validation never started
892
- if ( !this.isRadioOrCheckbox && this.getLength(val) < this.options.validationMinlength && !this.validatedOnce ) {
759
+ };
760
+ return this;
761
+ },
762
+ Required: function () {
763
+ this.__class__ = 'Required';
764
+ this.validate = function ( value ) {
765
+ if ( 'undefined' === typeof value )
766
+ throw new Violation( this, value );
767
+ try {
768
+ if ( 'string' === typeof value )
769
+ new Assert().NotNull().validate( value ) && new Assert().NotBlank().validate( value );
770
+ else if ( true === _isArray( value ) )
771
+ new Assert().Length( { min: 1 } ).validate( value );
772
+ } catch ( violation ) {
773
+ throw new Violation( this, value );
774
+ }
893
775
  return true;
894
- }
895
-
896
- this.validate();
897
- }
898
-
899
- /**
900
- * Get the length of a given value
901
- *
902
- * @method getLength
903
- * @return {int} The length of the value
904
- */
905
- , getLength: function ( val ) {
906
- return !val || !val.hasOwnProperty( 'length' ) ? 0 : val.length;
907
- }
908
-
909
- /**
910
- * Return if field verify its constraints
911
- *
912
- * @method isValid
913
- * @return {Boolean} Is field valid or not
914
- */
915
- , isValid: function () {
916
- return this.validate( false );
917
- }
918
-
919
- /**
920
- * Return if field has constraints
921
- *
922
- * @method hasConstraints
923
- * @return {Boolean} Is field has constraints or not
924
- */
925
- , hasConstraints: function () {
926
- for ( var constraint in this.constraints ) {
776
+ };
777
+ return this;
778
+ },
779
+ // Unique() or Unique ( { key: foo } )
780
+ Unique: function ( object ) {
781
+ this.__class__ = 'Unique';
782
+ if ( 'object' === typeof object )
783
+ this.key = object.key;
784
+ this.validate = function ( array ) {
785
+ var value, store = [];
786
+ if ( !_isArray( array ) )
787
+ throw new Violation( this, array, { value: Validator.errorCode.must_be_an_array } );
788
+ for ( var i = 0; i < array.length; i++ ) {
789
+ value = 'object' === typeof array[ i ] ? array[ i ][ this.key ] : array[ i ];
790
+ if ( 'undefined' === typeof value )
791
+ continue;
792
+ if ( -1 !== store.indexOf( value ) )
793
+ throw new Violation( this, array, { value: value } );
794
+ store.push( value );
795
+ }
927
796
  return true;
928
- }
929
-
930
- return false;
931
- }
932
-
933
- /**
934
- * Validate a field & display errors
935
- *
936
- * @method validate
937
- * @param {Boolean} errorBubbling set to false if you just want valid boolean without error bubbling next to fields
938
- * @return {Boolean} Is field valid or not
939
- */
940
- , validate: function ( errorBubbling ) {
941
- var val = this.getVal()
942
- , valid = null;
943
-
944
- // do not even bother trying validating a field w/o constraints
945
- if ( !this.hasConstraints() ) {
946
- return null;
947
- }
948
-
949
- // do not validate excluded fields
950
- if ( this.$element.is( this.options.excluded ) ) {
951
- return null;
952
- }
953
-
954
- // reset Parsley validation if onFieldValidate returns true, or if field is empty and not required
955
- if ( this.options.listeners.onFieldValidate( this.element, this ) || ( '' === val && !this.isRequired ) ) {
956
- this.UI.reset();
957
- return null;
958
- }
959
-
960
- // do not validate a field already validated and unchanged !
961
- if ( !this.needsValidation( val ) ) {
962
- return this.valid;
963
- }
964
-
965
- valid = this.applyValidators();
966
-
967
- if ( 'undefined' !== typeof errorBubbling ? errorBubbling : this.options.showErrors ) {
968
- this.manageValidationResult();
969
- }
970
-
971
- return valid;
972
- }
973
-
974
- /**
975
- * Check if value has changed since previous validation
976
- *
977
- * @method needsValidation
978
- * @param value
979
- * @return {Boolean}
980
- */
981
- , needsValidation: function ( val ) {
982
- if ( !this.options.validateIfUnchanged && this.valid !== null && this.val === val && this.validatedOnce ) {
983
- return false;
984
- }
985
-
986
- this.val = val;
987
- return this.validatedOnce = true;
797
+ };
798
+ return this;
988
799
  }
989
-
990
- /**
991
- * Loop through every fields validators
992
- * Adds errors after unvalid fields
993
- *
994
- * @method applyValidators
995
- * @return {Mixed} {Boolean} If field valid or not, null if not validated
996
- */
997
- , applyValidators: function () {
998
- var valid = null;
999
-
1000
- for ( var constraint in this.constraints ) {
1001
- var result = this.Validator.validators[ this.constraints[ constraint ].name ]().validate( this.val, this.constraints[ constraint ].requirements, this );
1002
-
1003
- if ( false === result ) {
1004
- valid = false;
1005
- this.constraints[ constraint ].valid = valid;
1006
- } else if ( true === result ) {
1007
- this.constraints[ constraint ].valid = true;
1008
- valid = false !== valid;
800
+ };
801
+ // expose to the world these awesome classes
802
+ exports.Assert = Assert;
803
+ exports.Validator = Validator;
804
+ exports.Violation = Violation;
805
+ exports.Constraint = Constraint;
806
+ /**
807
+ * Some useful object prototypes / functions here
808
+ */
809
+ // IE8<= compatibility
810
+ // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf
811
+ if (!Array.prototype.indexOf)
812
+ Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
813
+
814
+ if (this === null) {
815
+ throw new TypeError();
1009
816
  }
1010
- }
1011
-
1012
- // listeners' ballet
1013
- if (false === valid) {
1014
- this.options.listeners.onFieldError( this.element, this.constraints, this );
1015
- } else if (true === valid && false === this.options.listeners.onFieldSuccess( this.element, this.constraints, this )) {
1016
- // if onFieldSuccess returns (bool) false, consider that field si invalid
1017
- valid = false;
1018
- }
1019
-
1020
- return valid;
1021
- }
1022
-
1023
- /**
1024
- * Fired when all validators have be executed
1025
- * Returns true or false if field is valid or not
1026
- * Display errors messages below failed fields
1027
- * Adds parsley-success or parsley-error class on fields
1028
- *
1029
- * @method manageValidationResult
1030
- * @return {Boolean} Is field valid or not
1031
- */
1032
- , manageValidationResult: function () {
1033
- var valid = null
1034
- , errors = [];
1035
-
1036
- for ( var constraint in this.constraints ) {
1037
- if ( false === this.constraints[ constraint ].valid ) {
1038
- errors.push( this.constraints[ constraint ]);
1039
- valid = false;
1040
- } else if ( true === this.constraints[ constraint ].valid ) {
1041
- this.UI.removeError( this.constraints[ constraint ].name );
1042
- valid = false !== valid;
817
+ var t = Object(this);
818
+ var len = t.length >>> 0;
819
+ if (len === 0) {
820
+ return -1;
1043
821
  }
1044
- }
1045
-
1046
- this.valid = valid;
1047
-
1048
- if ( true === this.valid ) {
1049
- this.UI.removeErrors();
1050
- this.UI.errorClassHandler.removeClass( this.options.errorClass ).addClass( this.options.successClass );
1051
-
1052
- return true;
1053
- } else if ( false === this.valid ) {
1054
- if ( true === this.options.priorityEnabled ) {
1055
- var maxPriority = 0, constraint, priority, error, errorArr = [];
1056
- for ( var i = 0; i < errors.length; i++ ) {
1057
- error = this.Validator.validators[ errors[ i ].name ]();
1058
- priority = error.priority;
1059
- errorArr.push(errors[ i ]);
1060
-
1061
- if ( priority > maxPriority ) {
1062
- constraint = errors[ i ];
1063
- maxPriority = priority;
822
+ var n = 0;
823
+ if (arguments.length > 1) {
824
+ n = Number(arguments[1]);
825
+ if (n != n) { // shortcut for verifying if it's NaN
826
+ n = 0;
827
+ } else if (n !== 0 && n != Infinity && n != -Infinity) {
828
+ n = (n > 0 || -1) * Math.floor(Math.abs(n));
1064
829
  }
1065
- }
1066
- for ( var i = 0; i < errorArr.length; i++ ) {
1067
- if ( constraint === errorArr[ i ] ) {
1068
- this.UI.manageError( constraint );
1069
- } else {
1070
- this.UI.removeError( errorArr[ i ].name );
830
+ }
831
+ if (n >= len) {
832
+ return -1;
833
+ }
834
+ var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
835
+ for (; k < len; k++) {
836
+ if (k in t && t[k] === searchElement) {
837
+ return k;
1071
838
  }
1072
- }
1073
- } else {
1074
- for ( var i = 0; i < errors.length; i++ )
1075
- this.UI.manageError( errors[ i ] );
1076
839
  }
1077
-
1078
- this.UI.errorClassHandler.removeClass( this.options.successClass ).addClass( this.options.errorClass );
1079
- return false;
1080
- }
1081
-
1082
- // remove li error, and ul error if no more li inside
1083
- if ( this.UI.ulError && $( this.ulError ).children().length === 0 ) {
1084
- this.UI.removeErrors();
1085
- }
1086
-
1087
- return valid;
1088
- }
1089
-
1090
- /**
1091
- * Add custom listeners
1092
- *
1093
- * @param {Object} { listener: function () {} }, eg { onFormValidate: function ( valid, event, focus ) { ... } }
1094
- */
1095
- , addListener: function ( object ) {
1096
- for ( var listener in object ) {
1097
- this.options.listeners[ listener ] = object[ listener ];
1098
- }
1099
- }
1100
-
1101
- /**
1102
- * Destroy parsley field instance
1103
- *
1104
- * @private
1105
- * @method destroy
1106
- */
1107
- , destroy: function () {
1108
- this.$element.removeClass( 'parsley-validated' );
1109
- this.UI.reset();
1110
- this.$element.off( '.' + this.type ).removeData( this.type );
1111
- }
840
+ return -1;
841
+ };
842
+ // Test if object is empty, useful for Constraint violations check
843
+ var _isEmptyObject = function ( obj ) {
844
+ for ( var property in obj )
845
+ return false;
846
+ return true;
1112
847
  };
1113
-
1114
- /**
1115
- * ParsleyFieldMultiple override ParsleyField for checkbox and radio inputs
1116
- * Pseudo-heritance to manage divergent behavior from ParsleyItem in dedicated methods
1117
- *
1118
- * @class ParsleyFieldMultiple
1119
- * @constructor
1120
- */
1121
- var ParsleyFieldMultiple = function ( element, options, type ) {
1122
- this.initMultiple( element, options );
1123
- this.inherit( element, options );
1124
- this.Validator = new Validator( options );
1125
-
1126
- // call ParsleyField constructor
1127
- this.init( element, type || 'ParsleyFieldMultiple' );
848
+ var _isArray = function ( obj ) {
849
+ return Object.prototype.toString.call( obj ) === '[object Array]';
1128
850
  };
1129
-
1130
- ParsleyFieldMultiple.prototype = {
1131
-
1132
- constructor: ParsleyFieldMultiple
1133
-
1134
- /**
1135
- * Set some specific properties, call some extra methods to manage radio / checkbox
1136
- *
1137
- * @method init
1138
- * @param {Object} element
1139
- * @param {Object} options
1140
- */
1141
- , initMultiple: function ( element, options ) {
1142
- this.element = element;
1143
- this.$element = $( element );
1144
- this.group = options.group || false;
1145
- this.hash = this.getName();
1146
- this.siblings = this.group ? '[' + options.namespace + 'group="' + this.group + '"]' : 'input[name="' + this.$element.attr( 'name' ) + '"]';
1147
- this.isRadioOrCheckbox = true;
1148
- this.isRadio = this.$element.is( 'input[type=radio]' );
1149
- this.isCheckbox = this.$element.is( 'input[type=checkbox]' );
1150
- this.errorClassHandler = options.errors.classHandler( element, this.isRadioOrCheckbox ) || this.$element.parent();
1151
- }
1152
-
1153
- /**
1154
- * Set specific constraints messages, do pseudo-heritance
1155
- *
1156
- * @private
1157
- * @method inherit
1158
- * @param {Object} element
1159
- * @param {Object} options
1160
- */
1161
- , inherit: function ( element, options ) {
1162
- var clone = new ParsleyField( element, options, 'ParsleyFieldMultiple' );
1163
-
1164
- for ( var property in clone ) {
1165
- if ( 'undefined' === typeof this[ property ] ) {
1166
- this[ property ] = clone [ property ];
1167
- }
851
+ // https://github.com/LearnBoost/expect.js/blob/master/expect.js
852
+ var expect = {
853
+ eql: function ( actual, expected ) {
854
+ if ( actual === expected ) {
855
+ return true;
856
+ } else if ( 'undefined' !== typeof Buffer && Buffer.isBuffer( actual ) && Buffer.isBuffer( expected ) ) {
857
+ if ( actual.length !== expected.length ) return false;
858
+ for ( var i = 0; i < actual.length; i++ )
859
+ if ( actual[i] !== expected[i] ) return false;
860
+ return true;
861
+ } else if ( actual instanceof Date && expected instanceof Date ) {
862
+ return actual.getTime() === expected.getTime();
863
+ } else if ( typeof actual !== 'object' && typeof expected !== 'object' ) {
864
+ // loosy ==
865
+ return actual == expected;
866
+ } else {
867
+ return this.objEquiv(actual, expected);
1168
868
  }
1169
- }
1170
-
1171
- /**
1172
- * Set specific constraints messages, do pseudo-heritance
1173
- *
1174
- * @method getName
1175
- * @returns {String} radio / checkbox hash is cleaned 'name' or data-group property
1176
- */
1177
- , getName: function () {
1178
- if ( this.group ) {
1179
- return 'parsley-' + this.group;
1180
- }
1181
-
1182
- if ( 'undefined' === typeof this.$element.attr( 'name' ) ) {
1183
- throw "A radio / checkbox input must have a parsley-group attribute or a name to be Parsley validated !";
1184
- }
1185
-
1186
- return 'parsley-' + this.$element.attr( 'name' ).replace( /(:|\.|\[|\]|\$)/g, '' );
1187
- }
1188
-
1189
- /**
1190
- * Special treatment for radio & checkboxes
1191
- * Returns checked radio or checkboxes values
1192
- *
1193
- * @method getVal
1194
- * @returns {String} val
1195
- */
1196
- , getVal: function () {
1197
- if ( this.isRadio ) {
1198
- return $( this.siblings + ':checked' ).val() || '';
869
+ },
870
+ isUndefinedOrNull: function ( value ) {
871
+ return value === null || typeof value === 'undefined';
872
+ },
873
+ isArguments: function ( object ) {
874
+ return Object.prototype.toString.call(object) == '[object Arguments]';
875
+ },
876
+ keys: function ( obj ) {
877
+ if ( Object.keys )
878
+ return Object.keys( obj );
879
+ var keys = [];
880
+ for ( var i in obj )
881
+ if ( Object.prototype.hasOwnProperty.call( obj, i ) )
882
+ keys.push(i);
883
+ return keys;
884
+ },
885
+ objEquiv: function ( a, b ) {
886
+ if ( this.isUndefinedOrNull( a ) || this.isUndefinedOrNull( b ) )
887
+ return false;
888
+ if ( a.prototype !== b.prototype ) return false;
889
+ if ( this.isArguments( a ) ) {
890
+ if ( !this.isArguments( b ) )
891
+ return false;
892
+ return eql( pSlice.call( a ) , pSlice.call( b ) );
1199
893
  }
1200
-
1201
- if ( this.isCheckbox ) {
1202
- var values = [];
1203
-
1204
- $( this.siblings + ':checked' ).each( function () {
1205
- values.push( $( this ).val() );
1206
- } );
1207
-
1208
- return values;
894
+ try {
895
+ var ka = this.keys( a ), kb = this.keys( b ), key, i;
896
+ if ( ka.length !== kb.length )
897
+ return false;
898
+ ka.sort();
899
+ kb.sort();
900
+ for ( i = ka.length - 1; i >= 0; i-- )
901
+ if ( ka[ i ] != kb[ i ] )
902
+ return false;
903
+ for ( i = ka.length - 1; i >= 0; i-- ) {
904
+ key = ka[i];
905
+ if ( !this.eql( a[ key ], b[ key ] ) )
906
+ return false;
907
+ }
908
+ return true;
909
+ } catch ( e ) {
910
+ return false;
1209
911
  }
1210
- }
1211
-
1212
- /**
1213
- * Bind validation events on a field
1214
- *
1215
- * @private
1216
- * @method bindValidationEvents
1217
- */
1218
- , bindValidationEvents: function () {
1219
- // this field has validation events, that means it has to be validated
1220
- this.valid = null;
1221
- this.$element.addClass( 'parsley-validated' );
1222
-
1223
- // remove eventually already binded events
1224
- this.$element.off( '.' + this.type );
1225
-
1226
- // always bind keyup event, for better UX when a field is invalid
1227
- var self = this
1228
- , triggers = ( !this.options.trigger ? '' : this.options.trigger )
1229
- + ( new RegExp( 'change', 'i' ).test( this.options.trigger ) ? '' : ' change' );
1230
-
1231
- // trim triggers to bind them correctly with .on()
1232
- triggers = triggers.replace( /^\s+/g , '' ).replace( /\s+$/g ,'' );
1233
-
1234
- // bind trigger event on every siblings
1235
- $( this.siblings ).each(function () {
1236
- $( this ).on( triggers.split( ' ' ).join( '.' + self.type + ' ' ) , false, $.proxy( self.eventValidation, self ) );
1237
- } )
1238
- }
912
+ }
1239
913
  };
1240
-
1241
- /**
1242
- * ParsleyForm class manage Parsley validated form.
1243
- * Manage its fields and global validation
1244
- *
1245
- * @class ParsleyForm
1246
- * @constructor
1247
- */
1248
- var ParsleyForm = function ( element, options, type ) {
1249
- this.init( element, options, type || 'parsleyForm' );
914
+ // AMD Compliance
915
+ if ( "function" === typeof define && define.amd ) {
916
+ define( 'validator', [],function() { return exports; } );
917
+ }
918
+ } )( 'undefined' === typeof exports ? this[ 'undefined' !== typeof validatorjs_ns ? validatorjs_ns : 'Validator' ] = {} : exports );
919
+
920
+
921
+ var ParsleyValidator = function (validators, catalog) {
922
+ this.__class__ = 'ParsleyValidator';
923
+ this.Validator = Validator;
924
+ // Default Parsley locale is en
925
+ this.locale = 'en';
926
+ this.init(validators || {}, catalog || {});
1250
927
  };
1251
-
1252
- ParsleyForm.prototype = {
1253
-
1254
- constructor: ParsleyForm
1255
-
1256
- /* init data, bind jQuery on() actions */
1257
- , init: function ( element, options, type ) {
1258
- this.type = type;
1259
- this.items = [];
1260
- this.$element = $( element );
1261
- this.options = options;
1262
- var self = this;
1263
-
1264
- this.$element.find( options.inputs ).each( function () {
1265
- self.addItem( this );
928
+ ParsleyValidator.prototype = {
929
+ init: function (validators, catalog) {
930
+ this.catalog = catalog;
931
+ for (var name in validators)
932
+ this.addValidator(name, validators[name].fn, validators[name].priority);
933
+ $.emit('parsley:validator:init');
934
+ },
935
+ // Set new messages locale if we have dictionary loaded in ParsleyConfig.i18n
936
+ setLocale: function (locale) {
937
+ if ('undefined' === typeof this.catalog[locale])
938
+ throw new Error(locale + ' is not available in the catalog');
939
+ this.locale = locale;
940
+ return this;
941
+ },
942
+ // Add a new messages catalog for a given locale. Set locale for this catalog if set === `true`
943
+ addCatalog: function (locale, messages, set) {
944
+ if ('object' === typeof messages)
945
+ this.catalog[locale] = messages;
946
+ if (true === set)
947
+ return this.setLocale(locale);
948
+ return this;
949
+ },
950
+ // Add a specific message for a given constraint in a given locale
951
+ addMessage: function (locale, name, message) {
952
+ if (undefined === typeof this.catalog[locale])
953
+ this.catalog[locale] = {};
954
+ this.catalog[locale][name.toLowerCase()] = message;
955
+ return this;
956
+ },
957
+ validate: function (value, constraints, priority) {
958
+ return new this.Validator.Validator().validate.apply(new Validator.Validator(), arguments);
959
+ },
960
+ // Add a new validator
961
+ addValidator: function (name, fn, priority) {
962
+ this.validators[name.toLowerCase()] = function (requirements) {
963
+ return $.extend(new Validator.Assert().Callback(fn, requirements), { priority: priority });
964
+ };
965
+ return this;
966
+ },
967
+ updateValidator: function (name, fn, priority) {
968
+ return addValidator(name, fn, priority);
969
+ },
970
+ removeValidator: function (name) {
971
+ delete this.validators[name];
972
+ return this;
973
+ },
974
+ getErrorMessage: function (constraint) {
975
+ var message;
976
+ // Type constraints are a bit different, we have to match their requirements too to find right error message
977
+ if ('type' === constraint.name)
978
+ message = this.catalog[this.locale][constraint.name][constraint.requirements];
979
+ else
980
+ message = this.formatMessage(this.catalog[this.locale][constraint.name], constraint.requirements);
981
+ return '' !== message ? message : this.catalog[this.locale].defaultMessage;
982
+ },
983
+ // Kind of light `sprintf()` implementation
984
+ formatMessage: function (string, parameters) {
985
+ if ('object' === typeof parameters) {
986
+ for (var i in parameters)
987
+ string = this.formatMessage(string, parameters[i]);
988
+ return string;
989
+ }
990
+ return 'string' === typeof string ? string.replace(new RegExp('%s', 'i'), parameters) : '';
991
+ },
992
+ // Here is the Parsley default validators list.
993
+ // This is basically Validatorjs validators, with different API for some of them
994
+ // and a Parsley priority set
995
+ validators: {
996
+ notblank: function () {
997
+ return $.extend(new Validator.Assert().NotBlank(), { priority: 2 });
998
+ },
999
+ required: function () {
1000
+ return $.extend(new Validator.Assert().Required(), { priority: 512 });
1001
+ },
1002
+ type: function (type) {
1003
+ var assert;
1004
+ switch (type) {
1005
+ case 'email':
1006
+ assert = new Validator.Assert().Email();
1007
+ break;
1008
+ case 'number':
1009
+ assert = new Validator.Assert().Regexp('^-?(?:\\d+|\\d{1,3}(?:,\\d{3})+)?(?:\\.\\d+)?$');
1010
+ break;
1011
+ case 'integer':
1012
+ assert = new Validator.Assert().Regexp('^-?\\d+$');
1013
+ break;
1014
+ case 'digits':
1015
+ assert = new Validator.Assert().Regexp('^\\d+$');
1016
+ break;
1017
+ case 'alphanum':
1018
+ assert = new Validator.Assert().Regexp('^\\w+$', 'i');
1019
+ break;
1020
+ case 'url':
1021
+ assert = new Validator.Assert().Regexp('(https?:\\/\\/)?(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,4}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)', 'i');
1022
+ break;
1023
+ default:
1024
+ throw new Error('validator type `' + type + '` is not supported');
1025
+ }
1026
+ return $.extend(assert, { priority: 256 });
1027
+ },
1028
+ pattern: function (regexp) {
1029
+ var flags = '';
1030
+ // Test if RegExp is literal, if not, nothing to be done, otherwise, we need to isolate flags and pattern
1031
+ if (!!(/^\/.*\/(?:[gimy]*)$/.test(regexp))) {
1032
+ // Replace the regexp literal string with the first match group: ([gimy]*)
1033
+ // If no flag is present, this will be a blank string
1034
+ flags = regexp.replace(/.*\/([gimy]*)$/, '$1');
1035
+ // Again, replace the regexp literal string with the first match group:
1036
+ // everything excluding the opening and closing slashes and the flags
1037
+ regexp = regexp.replace(new RegExp('^/(.*?)/' + flags + '$'), '$1');
1038
+ }
1039
+ return $.extend(new Validator.Assert().Regexp(regexp, flags), { priority: 64 });
1040
+ },
1041
+ minlength: function (value) {
1042
+ return $.extend(new Validator.Assert().Length({ min: value }), {
1043
+ priority: 30,
1044
+ requirementsTransformer: function () {
1045
+ return 'string' === typeof value && !isNaN(value) ? parseInt(value, 10) : value;
1046
+ }
1047
+ });
1048
+ },
1049
+ maxlength: function (value) {
1050
+ return $.extend(new Validator.Assert().Length({ max: value }), {
1051
+ priority: 30,
1052
+ requirementsTransformer: function () {
1053
+ return 'string' === typeof value && !isNaN(value) ? parseInt(value, 10) : value;
1054
+ }
1266
1055
  });
1267
-
1268
- this.$element.on( 'submit.' + this.type , false, $.proxy( this.validate, this ) );
1269
- }
1270
-
1271
- /**
1272
- * Add custom listeners
1273
- *
1274
- * @param {Object} { listener: function () {} }, eg { onFormValidate: function ( valid, event, focus ) { ... } }
1275
- */
1276
- , addListener: function ( object ) {
1277
- for ( var listener in object ) {
1278
- if ( new RegExp( 'Field' ).test( listener ) ) {
1279
- for ( var item = 0; item < this.items.length; item++ ) {
1280
- this.items[ item ].addListener( object );
1056
+ },
1057
+ length: function (array) {
1058
+ return $.extend(new Validator.Assert().Length({ min: array[0], max: array[1] }), { priority: 32 });
1059
+ },
1060
+ mincheck: function (length) {
1061
+ return this.minlength(length);
1062
+ },
1063
+ maxcheck: function (length) {
1064
+ return this.maxlength(length);
1065
+ },
1066
+ check: function (array) {
1067
+ return this.length(array);
1068
+ },
1069
+ min: function (value) {
1070
+ return $.extend(new Validator.Assert().GreaterThanOrEqual(value), {
1071
+ priority: 30,
1072
+ requirementsTransformer: function () {
1073
+ return 'string' === typeof value && !isNaN(value) ? parseInt(value, 10) : value;
1281
1074
  }
1282
- } else {
1283
- this.options.listeners[ listener ] = object[ listener ];
1284
- }
1075
+ });
1076
+ },
1077
+ max: function (value) {
1078
+ return $.extend(new Validator.Assert().LessThanOrEqual(value), {
1079
+ priority: 30,
1080
+ requirementsTransformer: function () {
1081
+ return 'string' === typeof value && !isNaN(value) ? parseInt(value, 10) : value;
1082
+ }
1083
+ });
1084
+ },
1085
+ range: function (array) {
1086
+ return $.extend(new Validator.Assert().Range(array[0], array[1]), {
1087
+ priority: 32,
1088
+ requirementsTransformer: function () {
1089
+ for (var i = 0; i < array.length; i++)
1090
+ array[i] = 'string' === typeof array[i] && !isNaN(array[i]) ? parseInt(array[i], 10) : array[i];
1091
+ return array;
1092
+ }
1093
+ });
1094
+ },
1095
+ equalto: function (value) {
1096
+ return $.extend(new Validator.Assert().EqualTo(value), {
1097
+ priority: 256,
1098
+ requirementsTransformer: function () {
1099
+ return $(value).length ? $(value).val() : value;
1100
+ }
1101
+ });
1285
1102
  }
1286
1103
  }
1104
+ };
1287
1105
 
1288
- /**
1289
- * Adds a new parsleyItem child to ParsleyForm
1290
- *
1291
- * @method addItem
1292
- * @param elem
1293
- */
1294
- , addItem: function ( elem ) {
1295
- var ParsleyField = $( elem ).parsley( this.options );
1296
- ParsleyField.setParent( this );
1297
-
1298
- this.items.push( ParsleyField );
1299
- }
1300
-
1301
- /**
1302
- * Removes a parsleyItem child from ParsleyForm
1303
- *
1304
- * @method removeItem
1305
- * @param elem
1306
- * @return {Boolean}
1307
- */
1308
- , removeItem: function ( elem ) {
1309
- var parsleyItem = $( elem ).parsley();
1310
-
1311
- // identify & remove item if same Parsley hash
1312
- for ( var i = 0; i < this.items.length; i++ ) {
1313
- if ( this.items[ i ].hash === parsleyItem.hash ) {
1314
- this.items[ i ].destroy();
1315
- this.items.splice( i, 1 );
1316
- return true;
1106
+ var ParsleyUI = function (options) {
1107
+ this.__class__ = 'ParsleyUI';
1108
+ };
1109
+ ParsleyUI.prototype = {
1110
+ listen: function () {
1111
+ $.listen('parsley:form:init', this, this.setupForm);
1112
+ $.listen('parsley:field:init', this, this.setupField);
1113
+ $.listen('parsley:field:validated', this, this.reflow);
1114
+ $.listen('parsley:form:validated', this, this.focus);
1115
+ $.listen('parsley:field:reset', this, this.reset);
1116
+ $.listen('parsley:form:destroy', this, this.destroy);
1117
+ $.listen('parsley:field:destroy', this, this.destroy);
1118
+ return this;
1119
+ },
1120
+ reflow: function (fieldInstance) {
1121
+ // If this field has not an active UI (case for multiples) don't bother doing something
1122
+ if ('undefined' === typeof fieldInstance._ui || false === fieldInstance._ui.active)
1123
+ return;
1124
+ // Diff between two validation results
1125
+ var diff = this._diff(fieldInstance.validationResult, fieldInstance._ui.lastValidationResult);
1126
+ // Then store current validation result for next reflow
1127
+ fieldInstance._ui.lastValidationResult = fieldInstance.validationResult;
1128
+ // Field have been validated at least once if here. Useful for binded key events..
1129
+ fieldInstance._ui.validatedOnce = true;
1130
+ // Handle valid / invalid / none field class
1131
+ this.manageStatusClass(fieldInstance);
1132
+ // Add, remove, updated errors messages
1133
+ this.manageErrorsMessages(fieldInstance, diff);
1134
+ // Triggers impl
1135
+ this.actualizeTriggers(fieldInstance);
1136
+ // If field is not valid for the first time, bind keyup trigger to ease UX and quickly inform user
1137
+ if ((diff.kept.length || diff.added.length) && 'undefined' === typeof fieldInstance._ui.failedOnce)
1138
+ this.manageFailingFieldTrigger(fieldInstance);
1139
+ },
1140
+ // Returns an array of field's error message(s)
1141
+ getErrorsMessages: function (fieldInstance) {
1142
+ // No error message, field is valid
1143
+ if (true === fieldInstance.validationResult)
1144
+ return [];
1145
+ var messages = [];
1146
+ for (var i = 0; i < fieldInstance.validationResult.length; i++)
1147
+ messages.push(this._getErrorMessage(fieldInstance, fieldInstance.validationResult[i].assert));
1148
+ return messages;
1149
+ },
1150
+ manageStatusClass: function (fieldInstance) {
1151
+ if (true === fieldInstance.validationResult)
1152
+ this._successClass(fieldInstance);
1153
+ else if (fieldInstance.validationResult.length > 0)
1154
+ this._errorClass(fieldInstance);
1155
+ else
1156
+ this._resetClass(fieldInstance);
1157
+ },
1158
+ manageErrorsMessages: function (fieldInstance, diff) {
1159
+ if ('undefined' !== typeof fieldInstance.options.errorsMessagesDisabled)
1160
+ return;
1161
+ // Case where we have errorMessage option that configure an unique field error message, regardless failing validators
1162
+ if ('undefined' !== typeof fieldInstance.options.errorMessage) {
1163
+ if ((diff.added.length || diff.kept.length)) {
1164
+ if (0 === fieldInstance._ui.$errorsWrapper.find('.parsley-custom-error-message').length)
1165
+ fieldInstance._ui.$errorsWrapper
1166
+ .append($(fieldInstance.options.errorTemplate)
1167
+ .addClass('parsley-custom-error-message'));
1168
+ return fieldInstance._ui.$errorsWrapper
1169
+ .addClass('filled')
1170
+ .find('.parsley-custom-error-message')
1171
+ .html(fieldInstance.options.errorMessage);
1317
1172
  }
1318
- }
1319
-
1320
- return false;
1321
- }
1322
-
1323
- /**
1324
- * Process each form field validation
1325
- * Display errors, call custom onFormValidate() function
1326
- *
1327
- * @method validate
1328
- * @param {Object} event jQuery Event
1329
- * @return {Boolean} Is form valid or not
1330
- */
1331
- , validate: function ( event ) {
1332
- var valid = true;
1333
- this.focusedField = false;
1334
-
1335
- for ( var item = 0; item < this.items.length; item++ ) {
1336
- if ( 'undefined' !== typeof this.items[ item ] && false === this.items[ item ].validate() ) {
1337
- valid = false;
1338
-
1339
- if ( !this.focusedField && 'first' === this.options.focus || 'last' === this.options.focus ) {
1340
- this.focusedField = this.items[ item ].$element;
1173
+ return fieldInstance._ui.$errorsWrapper
1174
+ .removeClass('filled')
1175
+ .find('.parsley-custom-error-message')
1176
+ .remove();
1177
+ }
1178
+ // Show, hide, update failing constraints messages
1179
+ for (var i = 0; i < diff.removed.length; i++)
1180
+ this.removeError(fieldInstance, diff.removed[i].assert.name, true);
1181
+ for (i = 0; i < diff.added.length; i++)
1182
+ this.addError(fieldInstance, diff.added[i].assert.name, undefined, diff.added[i].assert, true);
1183
+ for (i = 0; i < diff.kept.length; i++)
1184
+ this.updateError(fieldInstance, diff.kept[i].assert.name, undefined, diff.kept[i].assert, true);
1185
+ },
1186
+ // TODO: strange API here, intuitive for manual usage with addError(pslyInstance, 'foo', 'bar')
1187
+ // but a little bit complex for above internal usage, with forced undefined parametter..
1188
+ addError: function (fieldInstance, name, message, assert, doNotUpdateClass) {
1189
+ fieldInstance._ui.$errorsWrapper
1190
+ .addClass('filled')
1191
+ .append($(fieldInstance.options.errorTemplate)
1192
+ .addClass('parsley-' + name)
1193
+ .html(message || this._getErrorMessage(fieldInstance, assert)));
1194
+ if (true !== doNotUpdateClass)
1195
+ this._errorClass(fieldInstance);
1196
+ },
1197
+ // Same as above
1198
+ updateError: function (fieldInstance, name, message, assert, doNotUpdateClass) {
1199
+ fieldInstance._ui.$errorsWrapper
1200
+ .addClass('filled')
1201
+ .find('.parsley-' + name)
1202
+ .html(message || this._getErrorMessage(fieldInstance, assert));
1203
+ if (true !== doNotUpdateClass)
1204
+ this._errorClass(fieldInstance);
1205
+ },
1206
+ // Same as above twice
1207
+ removeError: function (fieldInstance, name, doNotUpdateClass) {
1208
+ fieldInstance._ui.$errorsWrapper
1209
+ .removeClass('filled')
1210
+ .find('.parsley-' + name)
1211
+ .remove();
1212
+ // edge case possible here: remove a standard Parsley error that is still failing in fieldInstance.validationResult
1213
+ // but highly improbable cuz' manually removing a well Parsley handled error makes no sense.
1214
+ if (true !== doNotUpdateClass)
1215
+ this.manageStatusClass(fieldInstance);
1216
+ },
1217
+ focus: function (formInstance) {
1218
+ if (true === formInstance.validationResult || 'none' === formInstance.options.focus)
1219
+ return formInstance._focusedField = null;
1220
+ formInstance._focusedField = null;
1221
+ for (var i = 0; i < formInstance.fields.length; i++)
1222
+ if (true !== formInstance.fields[i].validationResult && formInstance.fields[i].validationResult.length > 0 && 'undefined' === typeof formInstance.fields[i].options.noFocus) {
1223
+ if ('first' === formInstance.options.focus) {
1224
+ formInstance._focusedField = formInstance.fields[i].$element;
1225
+ return formInstance._focusedField.focus();
1341
1226
  }
1227
+ formInstance._focusedField = formInstance.fields[i].$element;
1342
1228
  }
1229
+ if (null === formInstance._focusedField)
1230
+ return null;
1231
+ return formInstance._focusedField.focus();
1232
+ },
1233
+ _getErrorMessage: function (fieldInstance, constraint) {
1234
+ var customConstraintErrorMessage = constraint.name + 'Message';
1235
+ if ('undefined' !== typeof fieldInstance.options[customConstraintErrorMessage])
1236
+ return window.ParsleyValidator.formatMessage(fieldInstance.options[customConstraintErrorMessage], constraint.requirements);
1237
+ return window.ParsleyValidator.getErrorMessage(constraint);
1238
+ },
1239
+ _diff: function (newResult, oldResult, deep) {
1240
+ var
1241
+ added = [],
1242
+ kept = [];
1243
+ for (var i = 0; i < newResult.length; i++) {
1244
+ var found = false;
1245
+ for (var j = 0; j < oldResult.length; j++)
1246
+ if (newResult[i].assert.name === oldResult[j].assert.name) {
1247
+ found = true;
1248
+ break;
1249
+ }
1250
+ if (found)
1251
+ kept.push(newResult[i]);
1252
+ else
1253
+ added.push(newResult[i]);
1343
1254
  }
1255
+ return {
1256
+ kept: kept,
1257
+ added: added,
1258
+ removed: !deep ? this._diff(oldResult, newResult, true).added : []
1259
+ };
1260
+ },
1261
+ setupForm: function (formInstance) {
1262
+ formInstance.$element.on('submit.Parsley', false, $.proxy(formInstance.onSubmitValidate, formInstance));
1263
+ // UI could be disabled
1264
+ if (false === formInstance.options.uiEnabled)
1265
+ return;
1266
+ formInstance.$element.attr('novalidate', '');
1267
+ },
1268
+ setupField: function (fieldInstance) {
1269
+ var _ui = { active: false };
1270
+ // UI could be disabled
1271
+ if (false === fieldInstance.options.uiEnabled)
1272
+ return;
1273
+ _ui.active = true;
1274
+ // Give field its Parsley id in DOM
1275
+ fieldInstance.$element.attr(fieldInstance.options.namespace + 'id', fieldInstance.__id__);
1276
+ /** Generate important UI elements and store them in fieldInstance **/
1277
+ // $errorClassHandler is the $element that woul have parsley-error and parsley-success classes
1278
+ _ui.$errorClassHandler = this._manageClassHandler(fieldInstance);
1279
+ // $errorsWrapper is a div that would contain the various field errors, it will be appended into $errorsContainer
1280
+ _ui.errorsWrapperId = 'parsley-id-' + ('undefined' !== typeof fieldInstance.options.multiple ? 'multiple-' + fieldInstance.options.multiple : fieldInstance.__id__);
1281
+ _ui.$errorsWrapper = $(fieldInstance.options.errorsWrapper).attr('id', _ui.errorsWrapperId);
1282
+ // ValidationResult UI storage to detect what have changed bwt two validations, and update DOM accordingly
1283
+ _ui.lastValidationResult = [];
1284
+ _ui.validatedOnce = false;
1285
+ _ui.validationInformationVisible = false;
1286
+ // Store it in fieldInstance for later
1287
+ fieldInstance._ui = _ui;
1288
+ /** Mess with DOM now **/
1289
+ this._insertErrorWrapper(fieldInstance);
1290
+ // Bind triggers first time
1291
+ this.actualizeTriggers(fieldInstance);
1292
+ },
1293
+ // Determine which element will have `parsley-error` and `parsley-success` classes
1294
+ _manageClassHandler: function (fieldInstance) {
1295
+ // An element selector could be passed through DOM with `data-parsley-class-handler=#foo`
1296
+ if ('string' === typeof fieldInstance.options.classHandler && $(fieldInstance.options.classHandler).length)
1297
+ return $(fieldInstance.options.classHandler);
1298
+ // Class handled could also be determined by function given in Parsley options
1299
+ var $handler = fieldInstance.options.classHandler(fieldInstance);
1300
+ // If this function returned a valid existing DOM element, go for it
1301
+ if ('undefined' !== typeof $handler && $handler.length)
1302
+ return $handler;
1303
+ // Otherwise, if simple element (input, texatrea, select..) it will perfectly host the classes
1304
+ if ('undefined' === typeof fieldInstance.options.multiple || fieldInstance.$element.is('select'))
1305
+ return fieldInstance.$element;
1306
+ // But if multiple element (radio, checkbox), that would be their parent
1307
+ return fieldInstance.$element.parent();
1308
+ },
1309
+ _insertErrorWrapper: function (fieldInstance) {
1310
+ var $errorsContainer;
1311
+ if ('string' === typeof fieldInstance.options.errorsContainer )
1312
+ if ($(fieldInstance.options.errorsContainer + '').length)
1313
+ return $(fieldInstance.options.errorsContainer).append(fieldInstance._ui.$errorsWrapper);
1314
+ else if (window.console && window.console.warn)
1315
+ window.console.warn('The errors container `' + fieldInstance.options.errorsContainer + '` does not exist in DOM');
1316
+
1317
+ if ('function' === typeof fieldInstance.options.errorsContainer)
1318
+ $errorsContainer = fieldInstance.options.errorsContainer(fieldInstance);
1319
+ if ('undefined' !== typeof $errorsContainer && $errorsContainer.length)
1320
+ return $errorsContainer.append(fieldInstance._ui.$errorsWrapper);
1321
+ return 'undefined' === typeof fieldInstance.options.multiple ? fieldInstance.$element.after(fieldInstance._ui.$errorsWrapper) : fieldInstance.$element.parent().after(fieldInstance._ui.$errorsWrapper);
1322
+ },
1323
+ actualizeTriggers: function (fieldInstance) {
1324
+ var that = this;
1325
+ // Remove Parsley events already binded on this field
1326
+ if (fieldInstance.options.multiple)
1327
+ $('[' + fieldInstance.options.namespace + 'multiple="' + fieldInstance.options.multiple + '"]').each(function () {
1328
+ $(this).off('.Parsley');
1329
+ });
1330
+ else
1331
+ fieldInstance.$element.off('.Parsley');
1332
+ // If no trigger is set, all good
1333
+ if (false === fieldInstance.options.trigger)
1334
+ return;
1335
+ var triggers = fieldInstance.options.trigger.replace(/^\s+/g , '').replace(/\s+$/g , '');
1336
+ if ('' === triggers)
1337
+ return;
1338
+ // Bind fieldInstance.eventValidate if exists (for parsley.ajax for example), ParsleyUI.eventValidate otherwise
1339
+ if (fieldInstance.options.multiple)
1340
+ $('[' + fieldInstance.options.namespace + 'multiple="' + fieldInstance.options.multiple + '"]').each(function () {
1341
+ $(this).on(
1342
+ triggers.split(' ').join('.Parsley ') + '.Parsley',
1343
+ false,
1344
+ $.proxy('function' === typeof fieldInstance.eventValidate ? fieldInstance.eventValidate : that.eventValidate, fieldInstance));
1345
+ });
1346
+ else
1347
+ fieldInstance.$element
1348
+ .on(
1349
+ triggers.split(' ').join('.Parsley ') + '.Parsley',
1350
+ false,
1351
+ $.proxy('function' === typeof fieldInstance.eventValidate ? fieldInstance.eventValidate : this.eventValidate, fieldInstance));
1352
+ },
1353
+ // Called through $.proxy with fieldInstance. `this` context is ParsleyField
1354
+ eventValidate: function(event) {
1355
+ // For keyup, keypress, keydown.. events that could be a little bit obstrusive
1356
+ // do not validate if val length < min threshold on first validation. Once field have been validated once and info
1357
+ // about success or failure have been displayed, always validate with this trigger to reflect every yalidation change.
1358
+ if (new RegExp('key').test(event.type))
1359
+ if (!this._ui.validationInformationVisible && this.getValue().length <= this.options.validationThreshold)
1360
+ return;
1361
+ this._ui.validatedOnce = true;
1362
+ this.validate();
1363
+ },
1364
+ manageFailingFieldTrigger: function (fieldInstance) {
1365
+ fieldInstance._ui.failedOnce = true;
1366
+ // Radio and checkboxes fields must bind every field multiple
1367
+ if (fieldInstance.options.multiple)
1368
+ $('[' + fieldInstance.options.namespace + 'multiple="' + fieldInstance.options.multiple + '"]').each(function () {
1369
+ if (!new RegExp('change', 'i').test($(this).parsley().options.trigger || ''))
1370
+ return $(this).on('change.ParsleyFailedOnce', false, $.proxy(fieldInstance.validate, fieldInstance));
1371
+ });
1372
+ // Select case
1373
+ if (fieldInstance.$element.is('select'))
1374
+ if (!new RegExp('change', 'i').test(fieldInstance.options.trigger || ''))
1375
+ return fieldInstance.$element.on('change.ParsleyFailedOnce', false, $.proxy(fieldInstance.validate, fieldInstance));
1376
+ // All other inputs fields
1377
+ if (!new RegExp('keyup', 'i').test(fieldInstance.options.trigger || ''))
1378
+ return fieldInstance.$element.on('keyup.ParsleyFailedOnce', false, $.proxy(fieldInstance.validate, fieldInstance));
1379
+ },
1380
+ reset: function (parsleyInstance) {
1381
+ // Reset all event listeners
1382
+ parsleyInstance.$element.off('.Parsley');
1383
+ parsleyInstance.$element.off('.ParsleyFailedOnce');
1384
+ // Nothing to do if UI never initialized for this field
1385
+ if ('undefined' === typeof parsleyInstance._ui)
1386
+ return;
1387
+ if ('ParsleyForm' === parsleyInstance.__class__)
1388
+ return;
1389
+ // Reset all errors' li
1390
+ parsleyInstance._ui.$errorsWrapper.children().each(function () {
1391
+ $(this).remove();
1392
+ });
1393
+ // Reset validation class
1394
+ this._resetClass(parsleyInstance);
1395
+ // Reset validation flags and last validation result
1396
+ parsleyInstance._ui.validatedOnce = false;
1397
+ parsleyInstance._ui.lastValidationResult = [];
1398
+ parsleyInstance._ui.validationInformationVisible = false;
1399
+ },
1400
+ destroy: function (parsleyInstance) {
1401
+ this.reset(parsleyInstance);
1402
+ if ('ParsleyForm' === parsleyInstance.__class__)
1403
+ return;
1404
+ parsleyInstance._ui.$errorsWrapper.remove();
1405
+ delete parsleyInstance._ui;
1406
+ },
1407
+ _successClass: function (fieldInstance) {
1408
+ fieldInstance._ui.validationInformationVisible = true;
1409
+ fieldInstance._ui.$errorClassHandler.removeClass(fieldInstance.options.errorClass).addClass(fieldInstance.options.successClass);
1410
+ },
1411
+ _errorClass: function (fieldInstance) {
1412
+ fieldInstance._ui.validationInformationVisible = true;
1413
+ fieldInstance._ui.$errorClassHandler.removeClass(fieldInstance.options.successClass).addClass(fieldInstance.options.errorClass);
1414
+ },
1415
+ _resetClass: function (fieldInstance) {
1416
+ fieldInstance._ui.$errorClassHandler.removeClass(fieldInstance.options.successClass).removeClass(fieldInstance.options.errorClass);
1417
+ }
1418
+ };
1344
1419
 
1345
- // form is invalid, focus an error field depending on focus policy
1346
- if ( this.focusedField && !valid ) {
1347
- // Scroll smoothly
1348
- if ( this.options.scrollDuration > 0 ) {
1349
- var that = this,
1350
- top = this.focusedField.offset().top - $( window ).height() / 2; // Center the window on the field
1351
-
1352
- $( 'html, body' ).animate( {
1353
- scrollTop: top
1354
- },
1355
- this.options.scrollDuration,
1356
- function () {
1357
- that.focusedField.focus();
1358
- }
1359
- );
1360
- // Just focus on the field and let the browser do the rest
1361
- } else {
1362
- this.focusedField.focus();
1363
- }
1364
- }
1365
-
1366
- // if onFormValidate returns (bool) false, form won't be submitted, even if valid
1367
- var onFormValidate = this.options.listeners.onFormValidate( valid, event, this );
1368
- if ('undefined' !== typeof onFormValidate) {
1369
- return onFormValidate;
1420
+ var ParsleyOptionsFactory = function (defaultOptions, globalOptions, userOptions, namespace) {
1421
+ this.__class__ = 'OptionsFactory';
1422
+ this.__id__ = ParsleyUtils.hash(4);
1423
+ this.formOptions = null;
1424
+ this.fieldOptions = null;
1425
+ this.staticOptions = $.extend(true, {}, defaultOptions, globalOptions, userOptions, { namespace: namespace });
1426
+ };
1427
+ ParsleyOptionsFactory.prototype = {
1428
+ get: function (parsleyInstance) {
1429
+ if ('undefined' === typeof parsleyInstance.__class__)
1430
+ throw new Error('Parsley Instance expected');
1431
+ switch (parsleyInstance.__class__) {
1432
+ case 'Parsley':
1433
+ return this.staticOptions;
1434
+ case 'ParsleyForm':
1435
+ return this.getFormOptions(parsleyInstance);
1436
+ case 'ParsleyField':
1437
+ case 'ParsleyFieldMultiple':
1438
+ return this.getFieldOptions(parsleyInstance);
1439
+ default:
1440
+ throw new Error('Instance ' + parsleyInstance.__class__ + ' is not supported');
1370
1441
  }
1371
-
1372
- return valid;
1442
+ },
1443
+ getFormOptions: function (formInstance) {
1444
+ this.formOptions = ParsleyUtils.attr(formInstance.$element, this.staticOptions.namespace);
1445
+ // not deep extend, since formOptions is a 1 level deep object
1446
+ return $.extend({}, this.staticOptions, this.formOptions);
1447
+ },
1448
+ getFieldOptions: function (fieldInstance) {
1449
+ this.fieldOptions = ParsleyUtils.attr(fieldInstance.$element, this.staticOptions.namespace);
1450
+ if (null === this.formOptions && 'undefined' !== typeof fieldInstance.parent)
1451
+ this.formOptions = this.getFormOptions(fieldInstance.parent);
1452
+ // not deep extend, since formOptions and fieldOptions is a 1 level deep object
1453
+ return $.extend({}, this.staticOptions, this.formOptions, this.fieldOptions);
1373
1454
  }
1455
+ };
1374
1456
 
1375
- , isValid: function () {
1376
- for ( var item = 0; item < this.items.length; item++ ) {
1377
- if ( false === this.items[ item ].isValid() ) {
1457
+ var ParsleyForm = function (element, OptionsFactory) {
1458
+ this.__class__ = 'ParsleyForm';
1459
+ this.__id__ = ParsleyUtils.hash(4);
1460
+ if ('OptionsFactory' !== ParsleyUtils.get(OptionsFactory, '__class__'))
1461
+ throw new Error('You must give an OptionsFactory instance');
1462
+ this.OptionsFactory = OptionsFactory;
1463
+ this.$element = $(element);
1464
+ this.validationResult = null;
1465
+ this.options = this.OptionsFactory.get(this);
1466
+ };
1467
+ ParsleyForm.prototype = {
1468
+ onSubmitValidate: function (event) {
1469
+ this.validate(undefined, undefined, event);
1470
+ // prevent form submission if validation fails
1471
+ if (false === this.validationResult && event instanceof $.Event) {
1472
+ event.stopImmediatePropagation();
1473
+ event.preventDefault();
1474
+ }
1475
+ return this;
1476
+ },
1477
+ // @returns boolean
1478
+ validate: function (group, force, event) {
1479
+ this.submitEvent = event;
1480
+ this.validationResult = true;
1481
+ var fieldValidationResult = [];
1482
+ // Refresh form DOM options and form's fields that could have changed
1483
+ this._refreshFields();
1484
+ $.emit('parsley:form:validate', this);
1485
+ // loop through fields to validate them one by one
1486
+ for (var i = 0; i < this.fields.length; i++) {
1487
+ // do not validate a field if not the same as given validation group
1488
+ if (group && group !== this.fields[i].options.group)
1489
+ continue;
1490
+ fieldValidationResult = this.fields[i].validate(force);
1491
+ if (true !== fieldValidationResult && fieldValidationResult.length > 0 && this.validationResult)
1492
+ this.validationResult = false;
1493
+ }
1494
+ $.emit('parsley:form:validated', this);
1495
+ return this.validationResult;
1496
+ },
1497
+ // Iterate over refreshed fields, and stop on first failure
1498
+ isValid: function (group, force) {
1499
+ this._refreshFields();
1500
+ for (var i = 0; i < this.fields.length; i++) {
1501
+ // do not validate a field if not the same as given validation group
1502
+ if (group && group !== this.fields[i].options.group)
1503
+ continue;
1504
+ if (false === this.fields[i].isValid(force))
1378
1505
  return false;
1379
- }
1380
1506
  }
1381
-
1382
1507
  return true;
1508
+ },
1509
+ _refreshFields: function () {
1510
+ return this.actualizeOptions()._bindFields();
1511
+ },
1512
+ _bindFields: function () {
1513
+ var self = this;
1514
+ this.fields = [];
1515
+ this.fieldsMappedById = {};
1516
+ this.$element.find(this.options.inputs).each(function () {
1517
+ var fieldInstance = new window.Parsley(this, {}, self);
1518
+ // Only add valid and not excluded `ParsleyField` and `ParsleyFieldMultiple` children
1519
+ if (('ParsleyField' === fieldInstance.__class__ || 'ParsleyFieldMultiple' === fieldInstance.__class__) && !fieldInstance.$element.is(fieldInstance.options.excluded))
1520
+ if ('undefined' === typeof self.fieldsMappedById[fieldInstance.__class__ + '-' + fieldInstance.__id__]) {
1521
+ self.fieldsMappedById[fieldInstance.__class__ + '-' + fieldInstance.__id__] = fieldInstance;
1522
+ self.fields.push(fieldInstance);
1523
+ }
1524
+ });
1525
+ return this;
1383
1526
  }
1527
+ };
1384
1528
 
1385
- /**
1386
- * Remove all errors ul under invalid fields
1387
- *
1388
- * @method removeErrors
1389
- */
1390
- , removeErrors: function () {
1391
- for ( var item = 0; item < this.items.length; item++ ) {
1392
- this.items[ item ].parsley( 'reset' );
1393
- }
1394
- }
1395
-
1396
- /**
1397
- * destroy Parsley binded on the form and its fields
1398
- *
1399
- * @method destroy
1400
- */
1401
- , destroy: function () {
1402
- for ( var item = 0; item < this.items.length; item++ ) {
1403
- this.items[ item ].destroy();
1404
- }
1529
+ var ConstraintFactory = function (parsleyField, name, requirements, priority, isDomConstraint) {
1530
+ if (!new RegExp('ParsleyField').test(ParsleyUtils.get(parsleyField, '__class__')))
1531
+ throw new Error('ParsleyField or ParsleyFieldMultiple instance expected');
1532
+ if ('function' !== typeof window.ParsleyValidator.validators[name] &&
1533
+ 'Assert' !== window.ParsleyValidator.validators[name](requirements).__parentClass__)
1534
+ throw new Error('Valid validator expected');
1535
+ var getPriority = function (parsleyField, name) {
1536
+ if ('undefined' !== typeof parsleyField.options[name + 'Priority'])
1537
+ return parsleyField.options[name + 'Priority'];
1538
+ return ParsleyUtils.get(window.ParsleyValidator.validators[name](requirements), 'priority') || 2;
1539
+ };
1540
+ priority = priority || getPriority(parsleyField, name);
1541
+ // If validator have a requirementsTransformer, execute it
1542
+ if ('function' === typeof window.ParsleyValidator.validators[name](requirements).requirementsTransformer)
1543
+ requirements = window.ParsleyValidator.validators[name](requirements).requirementsTransformer();
1544
+ return $.extend(window.ParsleyValidator.validators[name](requirements), {
1545
+ name: name,
1546
+ requirements: requirements,
1547
+ priority: priority,
1548
+ groups: [priority],
1549
+ isDomConstraint: isDomConstraint || ParsleyUtils.attr(parsleyField.$element, parsleyField.options.namespace, name)
1550
+ });
1551
+ };
1405
1552
 
1406
- this.$element.off( '.' + this.type ).removeData( this.type );
1553
+ var ParsleyField = function (field, OptionsFactory, parsleyFormInstance) {
1554
+ this.__class__ = 'ParsleyField';
1555
+ this.__id__ = ParsleyUtils.hash(4);
1556
+ this.$element = $(field);
1557
+ // If we have a parent `ParsleyForm` instance given, use its `OptionsFactory`, and save parent
1558
+ if ('undefined' !== typeof parsleyFormInstance) {
1559
+ this.parent = parsleyFormInstance;
1560
+ this.OptionsFactory = this.parent.OptionsFactory;
1561
+ this.options = this.OptionsFactory.get(this);
1562
+ // Else, take the `Parsley` one
1563
+ } else {
1564
+ this.OptionsFactory = OptionsFactory;
1565
+ this.options = this.OptionsFactory.get(this);
1407
1566
  }
1408
-
1567
+ // Initialize some properties
1568
+ this.constraints = [];
1569
+ this.constraintsByName = {};
1570
+ this.validationResult = [];
1571
+ // Bind constraints
1572
+ this._bindConstraints();
1573
+ };
1574
+ ParsleyField.prototype = {
1575
+ // # Public API
1576
+ // Validate field and $.emit some events for mainly `ParsleyUI`
1577
+ // @returns validationResult:
1578
+ // - `true` if all constraint passes
1579
+ // - `[]` if not required field and empty (not validated)
1580
+ // - `[Violation, [Violation..]]` if there were validation errors
1581
+ validate: function (force) {
1582
+ this.value = this.getValue();
1583
+ // Field Validate event. `this.value` could be altered for custom needs
1584
+ $.emit('parsley:field:validate', this);
1585
+ $.emit('parsley:field:' + (this.isValid(force, this.value) ? 'success' : 'error'), this);
1586
+ // Field validated event. `this.validationResult` could be altered for custom needs too
1587
+ $.emit('parsley:field:validated', this);
1588
+ return this.validationResult;
1589
+ },
1590
+ // Just validate field. Do not trigger any event
1591
+ // Same @return as `validate()`
1592
+ isValid: function (force, value) {
1593
+ // Recompute options and rebind constraints to have latest changes
1594
+ this.refreshConstraints();
1595
+ // Sort priorities to validate more important first
1596
+ var priorities = this._getConstraintsSortedPriorities();
1597
+ // Value could be passed as argument, needed to add more power to 'parsley:field:validate'
1598
+ value = value || this.getValue();
1599
+ // If a field is empty and not required, leave it alone, it's just fine
1600
+ // Except if `data-parsley-validate-if-empty` explicitely added, useful for some custom validators
1601
+ if (0 === value.length && !this._isRequired() && 'undefined' === typeof this.options.validateIfEmpty && true !== force)
1602
+ return this.validationResult = [];
1603
+ // If we want to validate field against all constraints, just call Validator and let it do the job
1604
+ if (false === this.options.priorityEnabled)
1605
+ return true === (this.validationResult = this.validateThroughValidator(value, this.constraints, 'Any'));
1606
+ // Else, iterate over priorities one by one, and validate related asserts one by one
1607
+ for (var i = 0; i < priorities.length; i++)
1608
+ if (true !== (this.validationResult = this.validateThroughValidator(value, this.constraints, priorities[i])))
1609
+ return false;
1610
+ return true;
1611
+ },
1612
+ // @returns Parsley field computed value that could be overrided or configured in DOM
1613
+ getValue: function () {
1614
+ var value;
1615
+ // Value could be overriden in DOM
1616
+ if ('undefined' !== typeof this.options.value)
1617
+ value = this.options.value;
1618
+ else
1619
+ value = this.$element.val();
1620
+ // Handle wrong DOM or configurations
1621
+ if ('undefined' === typeof value || null === value)
1622
+ return '';
1623
+ // Use `data-parsley-trim-value="true"` to auto trim inputs entry
1624
+ if (true === this.options.trimValue)
1625
+ return value.replace(/^\s+|\s+$/g, '');
1626
+ return value;
1627
+ },
1628
+ // Actualize options that could have change since previous validation
1629
+ // Re-bind accordingly constraints (could be some new, removed or updated)
1630
+ refreshConstraints: function () {
1631
+ return this.actualizeOptions()._bindConstraints();
1632
+ },
1409
1633
  /**
1410
- * reset Parsley binded on the form and its fields
1634
+ * Add a new constraint to a field
1411
1635
  *
1412
- * @method reset
1636
+ * @method addConstraint
1637
+ * @param {String} name
1638
+ * @param {Mixed} requirements optional
1639
+ * @param {Number} priority optional
1640
+ * @param {Boolean} isDomConstraint optional
1413
1641
  */
1414
- , reset: function () {
1415
- for ( var item = 0; item < this.items.length; item++ ) {
1416
- this.items[ item ].UI.reset();
1642
+ addConstraint: function (name, requirements, priority, isDomConstraint) {
1643
+ name = name.toLowerCase();
1644
+ if ('function' === typeof window.ParsleyValidator.validators[name]) {
1645
+ var constraint = new ConstraintFactory(this, name, requirements, priority, isDomConstraint);
1646
+ // if constraint already exist, delete it and push new version
1647
+ if ('undefined' !== this.constraintsByName[constraint.name])
1648
+ this.removeConstraint(constraint.name);
1649
+ this.constraints.push(constraint);
1650
+ this.constraintsByName[constraint.name] = constraint;
1417
1651
  }
1652
+ return this;
1653
+ },
1654
+ // Remove a constraint
1655
+ removeConstraint: function (name) {
1656
+ for (var i = 0; i < this.constraints.length; i++)
1657
+ if (name === this.constraints[i].name) {
1658
+ this.constraints.splice(i, 1);
1659
+ break;
1660
+ }
1661
+ return this;
1662
+ },
1663
+ // Update a constraint (Remove + re-add)
1664
+ updateConstraint: function (name, parameters, priority) {
1665
+ return this.removeConstraint(name)
1666
+ .addConstraint(name, parameters, priority);
1667
+ },
1668
+ // # Internals
1669
+ // Internal only.
1670
+ // Bind constraints from config + options + DOM
1671
+ _bindConstraints: function () {
1672
+ var constraints = [];
1673
+ // clean all existing DOM constraints to only keep javascript user constraints
1674
+ for (var i = 0; i < this.constraints.length; i++)
1675
+ if (false === this.constraints[i].isDomConstraint)
1676
+ constraints.push(this.constraints[i]);
1677
+ this.constraints = constraints;
1678
+ // then re-add Parsley DOM-API constraints
1679
+ for (var name in this.options)
1680
+ this.addConstraint(name, this.options[name]);
1681
+ // finally, bind special HTML5 constraints
1682
+ return this._bindHtml5Constraints();
1683
+ },
1684
+ // Internal only.
1685
+ // Bind specific HTML5 constraints to be HTML5 compliant
1686
+ _bindHtml5Constraints: function () {
1687
+ // html5 required
1688
+ if (this.$element.hasClass('required') || this.$element.attr('required'))
1689
+ this.addConstraint('required', true, undefined, true);
1690
+ // html5 pattern
1691
+ if ('string' === typeof this.$element.attr('pattern'))
1692
+ this.addConstraint('pattern', this.$element.attr('pattern'), undefined, true);
1693
+ // range
1694
+ if ('undefined' !== typeof this.$element.attr('min') && 'undefined' !== typeof this.$element.attr('max'))
1695
+ this.addConstraint('range', [this.$element.attr('min'), this.$element.attr('max')], undefined, true);
1696
+ // HTML5 min
1697
+ else if ('undefined' !== typeof this.$element.attr('min'))
1698
+ this.addConstraint('min', this.$element.attr('min'), undefined, true);
1699
+ // HTML5 max
1700
+ else if ('undefined' !== typeof this.$element.attr('max'))
1701
+ this.addConstraint('max', this.$element.attr('max'), undefined, true);
1702
+ // html5 types
1703
+ var type = this.$element.attr('type');
1704
+ if ('undefined' === typeof type)
1705
+ return this;
1706
+ // Small special case here for HTML5 number, that is in fact an integer validator
1707
+ if ('number' === type)
1708
+ return this.addConstraint('type', 'integer', undefined, true);
1709
+ // Regular other HTML5 supported types
1710
+ else if (new RegExp(type, 'i').test('email url range'))
1711
+ return this.addConstraint('type', type, undefined, true);
1712
+ return this;
1713
+ },
1714
+ // Internal only.
1715
+ // Field is required if have required constraint without `false` value
1716
+ _isRequired: function () {
1717
+ if ('undefined' === typeof this.constraintsByName.required)
1718
+ return false;
1719
+ return false !== this.constraintsByName.required.requirements;
1720
+ },
1721
+ // Internal only.
1722
+ // Sort constraints by priority DESC
1723
+ _getConstraintsSortedPriorities: function () {
1724
+ var priorities = [];
1725
+ // Create array unique of priorities
1726
+ for (var i = 0; i < this.constraints.length; i++)
1727
+ if (-1 === priorities.indexOf(this.constraints[i].priority))
1728
+ priorities.push(this.constraints[i].priority);
1729
+ // Sort them by priority DESC
1730
+ priorities.sort(function (a, b) { return b - a; });
1731
+ return priorities;
1418
1732
  }
1419
1733
  };
1420
1734
 
1421
- /**
1422
- * Parsley plugin definition
1423
- * Provides an interface to access public Validator, ParsleyForm and ParsleyField functions
1424
- *
1425
- * @class Parsley
1426
- * @constructor
1427
- * @param {Mixed} Options. {Object} to configure Parsley or {String} method name to call a public class method
1428
- * @param {Function} Callback function
1429
- * @return {Mixed} public class method return
1430
- */
1431
- $.fn.parsley = function ( option, fn ) {
1432
- var namespace = { namespace: $( this ).data( 'parsleyNamespace' ) ? $( this ).data( 'parsleyNamespace' ) : ( 'undefined' !== typeof option && 'undefined' !== typeof option.namespace ? option.namespace : $.fn.parsley.defaults.namespace ) }
1433
- , options = $.extend( true, {}, $.fn.parsley.defaults, 'undefined' !== typeof window.ParsleyConfig ? window.ParsleyConfig : {}, option, this.domApi( namespace.namespace ) )
1434
- , newInstance = null
1435
- , args = Array.prototype.slice.call(arguments, 1);
1436
-
1437
- function bind ( self, type ) {
1438
- var parsleyInstance = $( self ).data( type );
1439
-
1440
- // if data never binded or we want to clone a build (for radio & checkboxes), bind it right now!
1441
- if ( !parsleyInstance ) {
1442
- switch ( type ) {
1443
- case 'parsleyForm':
1444
- parsleyInstance = new ParsleyForm( self, options, 'parsleyForm' );
1445
- break;
1446
- case 'parsleyField':
1447
- parsleyInstance = new ParsleyField( self, options, 'parsleyField' );
1448
- break;
1449
- case 'parsleyFieldMultiple':
1450
- parsleyInstance = new ParsleyFieldMultiple( self, options, 'parsleyFieldMultiple' );
1451
- break;
1452
- default:
1453
- return;
1454
- }
1455
-
1456
- $( self ).data( type, parsleyInstance );
1735
+ var ParsleyMultiple = function () {
1736
+ this.__class__ = 'ParsleyFieldMultiple';
1737
+ };
1738
+ ParsleyMultiple.prototype = {
1739
+ // Add new `$element` sibling for multiple field
1740
+ addElement: function ($element) {
1741
+ this.$elements.push($element);
1742
+ return this;
1743
+ },
1744
+ // See `ParsleyField.refreshConstraints()`
1745
+ refreshConstraints: function () {
1746
+ var fieldConstraints;
1747
+ this.constraints = [];
1748
+ // Select multiple special treatment
1749
+ if (this.$element.is('select')) {
1750
+ this.actualizeOptions()._bindConstraints();
1751
+ return this;
1457
1752
  }
1458
-
1459
- // here is our parsley public function accessor
1460
- if ( 'string' === typeof option && 'function' === typeof parsleyInstance[ option ] ) {
1461
- var response = parsleyInstance[ option ].apply( parsleyInstance, args );
1462
-
1463
- return 'undefined' !== typeof response ? response : $( self );
1753
+ // Gather all constraints for each input in the multiple group
1754
+ for (var i = 0; i < this.$elements.length; i++) {
1755
+ fieldConstraints = this.$elements[i].data('ParsleyFieldMultiple').refreshConstraints().constraints;
1756
+ for (var j = 0; j < fieldConstraints.length; j++)
1757
+ this.addConstraint(fieldConstraints[j].name, fieldConstraints[j].requirements, fieldConstraints[j].priority, fieldConstraints[j].isDomConstraint);
1464
1758
  }
1465
-
1466
- return parsleyInstance;
1467
- }
1468
-
1469
- // if a form elem is given, bind all its input children
1470
- if ( $( this ).is( 'form' ) || 'undefined' !== typeof $( this ).domApi( namespace.namespace )[ 'bind' ] ) {
1471
- newInstance = bind ( $( this ), 'parsleyForm' );
1472
-
1473
- // if it is a Parsley supported single element, bind it too, except inputs type hidden
1474
- // add here a return instance, cuz' we could call public methods on single elems with data[ option ]() above
1475
- } else if ( $( this ).is( options.inputs ) ) {
1476
- newInstance = bind( $( this ), !$( this ).is( 'input[type=radio], input[type=checkbox]' ) ? 'parsleyField' : 'parsleyFieldMultiple' );
1759
+ return this;
1760
+ },
1761
+ // See `ParsleyField.getValue()`
1762
+ getValue: function () {
1763
+ // Value could be overriden in DOM
1764
+ if ('undefined' !== typeof this.options.value)
1765
+ return this.options.value;
1766
+ // Radio input case
1767
+ if (this.$element.is('input[type=radio]'))
1768
+ return $('[' + this.options.namespace + 'multiple="' + this.options.multiple + '"]:checked').val() || '';
1769
+ // checkbox input case
1770
+ if (this.$element.is('input[type=checkbox]')) {
1771
+ var values = [];
1772
+ $('[' + this.options.namespace + 'multiple="' + this.options.multiple + '"]:checked').each(function () {
1773
+ values.push($(this).val());
1774
+ });
1775
+ return values.length ? values : [];
1776
+ }
1777
+ // Select multiple case
1778
+ if (this.$element.is('select') && null === this.$element.val())
1779
+ return [];
1780
+ // Default case that should never happen
1781
+ return this.$element.val();
1782
+ },
1783
+ _init: function (multiple) {
1784
+ this.$elements = [this.$element];
1785
+ this.options.multiple = multiple;
1786
+ return this;
1477
1787
  }
1478
-
1479
- return 'function' === typeof fn ? fn() : newInstance;
1480
1788
  };
1481
1789
 
1482
- /* PARSLEY auto-binding
1483
- * =================================================== */
1484
- $( window ).on( 'load', function () {
1485
- $( '[parsley-validate], [data-parsley-validate]' ).each( function () {
1486
- $( this ).parsley();
1487
- } );
1488
- } );
1489
-
1490
- /* PARSLEY DOM API
1491
- * =================================================== */
1492
- $.fn.domApi = function ( namespace ) {
1493
- var attribute,
1494
- obj = {}
1495
- , regex = new RegExp("^" + namespace, 'i');
1496
-
1497
- if ( 'undefined' === typeof this[ 0 ] ) {
1498
- return {};
1499
- }
1500
-
1501
- for ( var i in this[ 0 ].attributes ) {
1502
- attribute = this[ 0 ].attributes[ i ];
1503
-
1504
- if ( 'undefined' !== typeof attribute && null !== attribute && attribute.specified && regex.test( attribute.name ) ) {
1505
- obj[ camelize( attribute.name.replace( namespace, '' ) ) ] = deserializeValue( attribute.value );
1506
- }
1507
- }
1508
-
1509
- return obj;
1790
+ var
1791
+ o = $({}),
1792
+ subscribed = {};
1793
+ // $.listen(name, callback);
1794
+ // $.listen(name, context, callback);
1795
+ $.listen = function (name) {
1796
+ if ('undefined' === typeof subscribed[name])
1797
+ subscribed[name] = [];
1798
+ if ('function' === typeof arguments[1])
1799
+ return subscribed[name].push({ fn: arguments[1] });
1800
+ if ('object' === typeof arguments[1] && 'function' === typeof arguments[2])
1801
+ return subscribed[name].push({ fn: arguments[2], ctxt: arguments[1] });
1802
+ throw new Error('Wrong parameters');
1510
1803
  };
1511
-
1512
- // Zepto deserializeValue function
1513
- // "true" => true
1514
- // "false" => false
1515
- // "null" => null
1516
- // "42" => 42
1517
- // "42.5" => 42.5
1518
- // JSON => parse if valid
1519
- // String => self
1520
- var deserializeValue = function( value ) {
1521
- var num
1522
- try {
1523
- return value ?
1524
- value == "true" ||
1525
- ( value == "false" ? false :
1526
- value == "null" ? null :
1527
- !isNaN( num = Number( value ) ) ? num :
1528
- /^[\[\{]/.test( value ) ? $.parseJSON( value ) :
1529
- value )
1530
- : value;
1531
- } catch ( e ) {
1532
- return value;
1533
- }
1804
+ $.listenTo = function (instance, name, fn) {
1805
+ if ('undefined' === typeof subscribed[name])
1806
+ subscribed[name] = [];
1807
+ if (!(instance instanceof ParsleyField) && !(instance instanceof ParsleyForm))
1808
+ throw new Error('Must give Parsley instance');
1809
+ if ('string' !== typeof name || 'function' !== typeof fn)
1810
+ throw new Error('Wrong parameters');
1811
+ subscribed[name].push({ instance: instance, fn: fn });
1534
1812
  };
1535
-
1536
- // Zepto camelize function
1537
- var camelize = function ( str ) {
1538
- return str.replace( /-+(.)?/g, function ( match, chr ) {
1539
- return chr ? chr.toUpperCase() : '';
1540
- } )
1813
+ $.unsubscribe = function (name, fn) {
1814
+ if ('undefined' === typeof subscribed[name])
1815
+ return;
1816
+ if ('string' !== typeof name || 'function' !== typeof fn)
1817
+ throw new Error('Wrong arguments');
1818
+ for (var i = 0; i < subscribed[name].length; i++)
1819
+ if (subscribed[name][i].fn === fn)
1820
+ return subscribed[name].splice(i, 1);
1541
1821
  };
1542
-
1543
- // Zepto dasherize function
1544
- var dasherize = function ( str ) {
1545
- return str.replace( /::/g, '/' )
1546
- .replace( /([A-Z]+)([A-Z][a-z])/g, '$1_$2' )
1547
- .replace( /([a-z\d])([A-Z])/g, '$1_$2' )
1548
- .replace( /_/g, '-' )
1549
- .toLowerCase()
1822
+ $.unsubscribeTo = function (instance, name) {
1823
+ if ('undefined' === typeof subscribed[name])
1824
+ return;
1825
+ if (!(instance instanceof ParsleyField) && !(instance instanceof ParsleyForm))
1826
+ throw new Error('Must give Parsley instance');
1827
+ for (var i = 0; i < subscribed[name].length; i++)
1828
+ if ('undefined' !== typeof subscribed[name][i].instance && subscribed[name][i].instance.__id__ === instance.__id__)
1829
+ return subscribed[name].splice(i, 1);
1550
1830
  };
1551
-
1552
- /**
1553
- * Parsley plugin configuration
1554
- *
1555
- * @property $.fn.parsley.defaults
1556
- * @type {Object}
1557
- */
1558
- $.fn.parsley.defaults = {
1559
- // basic data-api overridable properties here..
1560
- namespace: 'parsley-' // DOM-API, default 'parsley-'. W3C valid would be 'data-parsley-' but quite ugly
1561
- , inputs: 'input, textarea, select' // Default supported inputs.
1562
- , excluded: 'input[type=hidden], input[type=file], :disabled' // Do not validate input[type=hidden] & :disabled.
1563
- , priorityEnabled: true // Will display only one error at the time depending on validators priorities
1564
- , trigger: false // $.Event() that will trigger validation. eg: keyup, change..
1565
- , animate: true // fade in / fade out error messages
1566
- , animateDuration: 300 // fadein/fadout ms time
1567
- , scrollDuration: 500 // Duration in ms time of the window scroll when focusing on invalid field (0 = no scroll)
1568
- , focus: 'first' // 'fist'|'last'|'none' which error field would have focus first on form validation
1569
- , validationMinlength: 3 // If trigger validation specified, only if value.length > validationMinlength
1570
- , successClass: 'parsley-success' // Class name on each valid input
1571
- , errorClass: 'parsley-error' // Class name on each invalid input
1572
- , errorMessage: false // Customize an unique error message showed if one constraint fails
1573
- , validators: {} // Add your custom validators functions
1574
- , showErrors: true // Set to false if you don't want Parsley to display error messages
1575
- , useHtml5Constraints: true // Set to false if you don't want Parsley to use html5 constraints
1576
- , messages: {} // Add your own error messages here
1577
-
1578
- //some quite advanced configuration here..
1579
- , validateIfUnchanged: false // false: validate once by field value change
1580
- , errors: {
1581
- classHandler: function ( elem, isRadioOrCheckbox ) {} // specify where parsley error-success classes are set
1582
- , container: function ( elem, isRadioOrCheckbox ) {} // specify an elem where errors will be **apened**
1583
- , errorsWrapper: '<ul></ul>' // do not set an id for this elem, it would have an auto-generated id
1584
- , errorElem: '<li></li>' // each field constraint fail in an li
1831
+ $.unsubscribeAll = function (name) {
1832
+ if ('undefined' === typeof subscribed[name])
1833
+ return;
1834
+ delete subscribed[name];
1835
+ };
1836
+ // $.emit(name [, arguments...]);
1837
+ // $.emit(name, instance [, arguments..]);
1838
+ $.emit = function (name, instance) {
1839
+ if ('undefined' === typeof subscribed[name])
1840
+ return;
1841
+ // loop through registered callbacks for this event
1842
+ for (var i = 0; i < subscribed[name].length; i++) {
1843
+ // if instance is not registered, simple emit
1844
+ if ('undefined' === typeof subscribed[name][i].instance) {
1845
+ subscribed[name][i].fn.apply('undefined' !== typeof subscribed[name][i].ctxt ? subscribed[name][i].ctxt : o, Array.prototype.slice.call(arguments, 1));
1846
+ continue;
1847
+ }
1848
+ // if instance registered but no instance given for the emit, continue
1849
+ if (!(instance instanceof ParsleyField) && !(instance instanceof ParsleyForm))
1850
+ continue;
1851
+ // if instance is registered and same id, emit
1852
+ if (subscribed[name][i].instance.__id__ === instance.__id__) {
1853
+ subscribed[name][i].fn.apply(o, Array.prototype.slice.call(arguments, 1));
1854
+ continue;
1855
+ }
1856
+ // if registered instance is a Form and fired one is a Field, loop over all its fields and emit if field found
1857
+ if (subscribed[name][i].instance instanceof ParsleyForm && instance instanceof ParsleyField)
1858
+ for (var j = 0; j < subscribed[name][i].instance.fields.length; j++)
1859
+ if (subscribed[name][i].instance.fields[j].__id__ === instance.__id__) {
1860
+ subscribed[name][i].fn.apply(o, Array.prototype.slice.call(arguments, 1));
1861
+ continue;
1862
+ }
1863
+ }
1864
+ };
1865
+ $.subscribed = function () { return subscribed; };
1866
+
1867
+ // ParsleyConfig definition if not already set
1868
+ window.ParsleyConfig = window.ParsleyConfig || {};
1869
+ window.ParsleyConfig.i18n = window.ParsleyConfig.i18n || {};
1870
+ // Define then the messages
1871
+ window.ParsleyConfig.i18n.en = $.extend(window.ParsleyConfig.i18n.en || {}, {
1872
+ defaultMessage: "This value seems to be invalid.",
1873
+ type: {
1874
+ email: "This value should be a valid email.",
1875
+ url: "This value should be a valid url.",
1876
+ number: "This value should be a valid number.",
1877
+ integer: "This value should be a valid integer.",
1878
+ digits: "This value should be digits.",
1879
+ alphanum: "This value should be alphanumeric."
1880
+ },
1881
+ notblank: "This value should not be blank.",
1882
+ required: "This value is required.",
1883
+ pattern: "This value seems to be invalid.",
1884
+ min: "This value should be greater than or equal to %s.",
1885
+ max: "This value should be lower than or equal to %s.",
1886
+ range: "This value should be between %s and %s.",
1887
+ minlength: "This value is too short. It should have %s characters or more.",
1888
+ maxlength: "This value is too long. It should have %s characters or less.",
1889
+ length: "This value length is invalid. It should be between %s and %s characters long.",
1890
+ mincheck: "You must select at least %s choices.",
1891
+ maxcheck: "You must select %s choices or less.",
1892
+ check: "You must select between %s and %s choices.",
1893
+ equalto: "This value should be the same."
1894
+ });
1895
+ // If file is loaded after Parsley main file, auto-load locale
1896
+ if ('undefined' !== typeof window.ParsleyValidator)
1897
+ window.ParsleyValidator.addCatalog('en', window.ParsleyConfig.i18n.en, true);
1898
+
1899
+ // Parsley.js 2.0.0
1900
+ // http://parsleyjs.org
1901
+ // (c) 20012-2014 Guillaume Potier, Wisembly
1902
+ // Parsley may be freely distributed under the MIT license.
1903
+
1904
+ // ### Parsley factory
1905
+ var Parsley = function (element, options, parsleyFormInstance) {
1906
+ this.__class__ = 'Parsley';
1907
+ this.__version__ = '2.0.0';
1908
+ this.__id__ = ParsleyUtils.hash(4);
1909
+ // Parsley must be instanciated with a DOM element or jQuery $element
1910
+ if ('undefined' === typeof element)
1911
+ throw new Error('You must give an element');
1912
+ if ('undefined' !== typeof parsleyFormInstance && 'ParsleyForm' !== parsleyFormInstance.__class__)
1913
+ throw new Error('Parent instance must be a ParsleyForm instance');
1914
+ return this.init($(element), options, parsleyFormInstance);
1915
+ };
1916
+ Parsley.prototype = {
1917
+ init: function ($element, options, parsleyFormInstance) {
1918
+ if (!$element.length)
1919
+ throw new Error('You must bind Parsley on an existing element.');
1920
+ this.$element = $element;
1921
+ // If element have already been binded, returns its saved Parsley instance
1922
+ if (this.$element.data('Parsley')) {
1923
+ var savedparsleyFormInstance = this.$element.data('Parsley');
1924
+ // If saved instance have been binded without a ParsleyForm parent and there is one given in this call, add it
1925
+ if ('undefined' !== typeof parsleyFormInstance)
1926
+ savedparsleyFormInstance.parent = parsleyFormInstance;
1927
+ return savedparsleyFormInstance;
1928
+ }
1929
+ // Handle 'static' options
1930
+ this.OptionsFactory = new ParsleyOptionsFactory(ParsleyDefaults, ParsleyUtils.get(window, 'ParsleyConfig') || {}, options, this.getNamespace(options));
1931
+ this.options = this.OptionsFactory.get(this);
1932
+ // A ParsleyForm instance is obviously a `<form>` elem but also every node that is not an input and have `data-parsley-validate` attribute
1933
+ if (this.$element.is('form') || (ParsleyUtils.attr(this.$element, this.options.namespace, 'validate') && !this.$element.is(this.options.inputs)))
1934
+ return this.bind('parsleyForm');
1935
+ // Every other supported element and not excluded element is binded as a `ParsleyField` or `ParsleyFieldMultiple`
1936
+ else if (this.$element.is(this.options.inputs) && !this.$element.is(this.options.excluded))
1937
+ return this.isMultiple() ? this.handleMultiple(parsleyFormInstance) : this.bind('parsleyField', parsleyFormInstance);
1938
+ return this;
1939
+ },
1940
+ isMultiple: function () {
1941
+ return (this.$element.is('input[type=radio], input[type=checkbox]') && 'undefined' === typeof this.options.multiple) || (this.$element.is('select') && 'undefined' !== typeof this.$element.attr('multiple'));
1942
+ },
1943
+ // Multiples fields are a real nightmare :(
1944
+ // Maybe some refacto would be appreciated here..
1945
+ handleMultiple: function (parsleyFormInstance) {
1946
+ var
1947
+ that = this,
1948
+ name,
1949
+ multiple,
1950
+ parsleyMultipleInstance;
1951
+ // Get parsleyFormInstance options if exist, mixed with element attributes
1952
+ this.options = $.extend(this.options, parsleyFormInstance ? parsleyFormInstance.OptionsFactory.get(parsleyFormInstance) : {}, ParsleyUtils.attr(this.$element, this.options.namespace));
1953
+ // Handle multiple name
1954
+ if (this.options.multiple) {
1955
+ multiple = this.options.multiple;
1956
+ } else if ('undefined' !== typeof this.$element.attr('name') && this.$element.attr('name').length) {
1957
+ multiple = name = this.$element.attr('name');
1958
+ } else if ('undefined' !== typeof this.$element.attr('id') && this.$element.attr('id').length) {
1959
+ multiple = this.$element.attr('id');
1960
+ }
1961
+ // Special select multiple input
1962
+ if (this.$element.is('select') && 'undefined' !== typeof this.$element.attr('multiple')) {
1963
+ return this.bind('parsleyFieldMultiple', parsleyFormInstance, multiple || this.__id__);
1964
+ // Else for radio / checkboxes, we need a `name` or `data-parsley-multiple` to properly bind it
1965
+ } else if ('undefined' === typeof multiple) {
1966
+ if (window.console && window.console.warn)
1967
+ window.console.warn('To be binded by Parsley, a radio, a checkbox and a multiple select input must have either a name or a multiple option.', this.$element);
1968
+ return this;
1969
+ }
1970
+ // Remove special chars
1971
+ multiple = multiple.replace(/(:|\.|\[|\]|\$)/g, '');
1972
+ // Add proper `data-parsley-multiple` to siblings if we had a name
1973
+ if ('undefined' !== typeof name)
1974
+ $('input[name="' + name + '"]').each(function () {
1975
+ if ($(this).is('input[type=radio], input[type=checkbox]'))
1976
+ $(this).attr(that.options.namespace + 'multiple', multiple);
1977
+ });
1978
+ // Check here if we don't already have a related multiple instance saved
1979
+ if ($('[' + this.options.namespace + 'multiple=' + multiple +']').length)
1980
+ for (var i = 0; i < $('[' + this.options.namespace + 'multiple=' + multiple +']').length; i++)
1981
+ if ('undefined' !== typeof $($('[' + this.options.namespace + 'multiple=' + multiple +']').get(i)).data('Parsley')) {
1982
+ parsleyMultipleInstance = $($('[' + this.options.namespace + 'multiple=' + multiple +']').get(i)).data('Parsley');
1983
+ if (!this.$element.data('ParsleyFieldMultiple')) {
1984
+ parsleyMultipleInstance.addElement(this.$element);
1985
+ this.$element.attr(this.options.namespace + 'id', parsleyMultipleInstance.__id__);
1986
+ }
1987
+ break;
1988
+ }
1989
+ // Create a secret ParsleyField instance for every multiple field. It would be stored in `data('ParsleyFieldMultiple')`
1990
+ // And would be useful later to access classic `ParsleyField` stuff while being in a `ParsleyFieldMultiple` instance
1991
+ this.bind('parsleyField', parsleyFormInstance, multiple, true);
1992
+ return parsleyMultipleInstance || this.bind('parsleyFieldMultiple', parsleyFormInstance, multiple);
1993
+ },
1994
+ // Retrieve namespace used for DOM-API
1995
+ getNamespace: function (options) {
1996
+ // `data-parsley-namespace=<namespace>`
1997
+ if ('undefined' !== typeof this.$element.data('parsleyNamespace'))
1998
+ return this.$element.data('parsleyNamespace');
1999
+ if ('undefined' !== typeof ParsleyUtils.get(options, 'namespace'))
2000
+ return options.namespace;
2001
+ if ('undefined' !== typeof ParsleyUtils.get(window, 'ParsleyConfig.namespace'))
2002
+ return window.ParsleyConfig.namespace;
2003
+ return ParsleyDefaults.namespace;
2004
+ },
2005
+ // Return proper `ParsleyForm`, `ParsleyField` or `ParsleyFieldMultiple`
2006
+ bind: function (type, parentParsleyFormInstance, multiple, doNotStore) {
2007
+ var parsleyInstance;
2008
+ switch (type) {
2009
+ case 'parsleyForm':
2010
+ parsleyInstance = $.extend(
2011
+ new ParsleyForm(this.$element, this.OptionsFactory),
2012
+ new ParsleyAbstract(),
2013
+ window.ParsleyExtend
2014
+ )._bindFields();
2015
+ break;
2016
+ case 'parsleyField':
2017
+ parsleyInstance = $.extend(
2018
+ new ParsleyField(this.$element, this.OptionsFactory, parentParsleyFormInstance),
2019
+ new ParsleyAbstract(),
2020
+ window.ParsleyExtend
2021
+ );
2022
+ break;
2023
+ case 'parsleyFieldMultiple':
2024
+ parsleyInstance = $.extend(
2025
+ new ParsleyField(this.$element, this.OptionsFactory, parentParsleyFormInstance),
2026
+ new ParsleyAbstract(),
2027
+ new ParsleyMultiple(),
2028
+ window.ParsleyExtend
2029
+ )._init(multiple);
2030
+ break;
2031
+ default:
2032
+ throw new Error(type + 'is not a supported Parsley type');
2033
+ }
2034
+ if ('undefined' !== typeof multiple)
2035
+ ParsleyUtils.setAttr(this.$element, this.options.namespace, 'multiple', multiple);
2036
+ if ('undefined' !== typeof doNotStore) {
2037
+ this.$element.data('ParsleyFieldMultiple', parsleyInstance);
2038
+ return parsleyInstance;
2039
+ }
2040
+ // Store instance if `ParsleyForm`, `ParsleyField` or `ParsleyFieldMultiple`
2041
+ if (new RegExp('ParsleyF', 'i').test(parsleyInstance.__class__)) {
2042
+ // Store for later access the freshly binded instance in DOM element itself using jQuery `data()`
2043
+ this.$element.data('Parsley', parsleyInstance);
2044
+ // Tell the world we got a new ParsleyForm or ParsleyField instance!
2045
+ $.emit('parsley:' + ('parsleyForm' === type ? 'form' : 'field') + ':init', parsleyInstance);
1585
2046
  }
1586
- , listeners: {
1587
- onFieldValidate: function ( elem, ParsleyField ) { return false; } // Executed on validation. Return true to ignore field validation
1588
- , onFormValidate: function ( isFormValid, event, ParsleyForm ) {} // Executed once on form validation. Return (bool) false to block submit, even if valid
1589
- , onFieldError: function ( elem, constraints, ParsleyField ) {} // Executed when a field is detected as invalid
1590
- , onFieldSuccess: function ( elem, constraints, ParsleyField ) {} // Executed when a field passes validation
2047
+ return parsleyInstance;
1591
2048
  }
1592
2049
  };
1593
-
1594
- // This plugin works with jQuery or Zepto (with data extension built for Zepto.)
1595
- } ( window.jQuery || window.Zepto );
2050
+ // ### jQuery API
2051
+ // `$('.elem').parsley(options)` or `$('.elem').psly(options)`
2052
+ $.fn.parsley = $.fn.psly = function (options) {
2053
+ if (this.length > 1) {
2054
+ var instances = [];
2055
+ this.each(function () {
2056
+ instances.push($(this).parsley(options));
2057
+ });
2058
+ return instances;
2059
+ }
2060
+ // Return undefined if applied to non existing DOM element
2061
+ if (!$(this).length) {
2062
+ if (window.console && window.console.warn)
2063
+ window.console.warn('You must bind Parsley on an existing element.');
2064
+ return;
2065
+ }
2066
+ return new Parsley(this, options);
2067
+ };
2068
+ // ### ParsleyUI
2069
+ // UI is a class apart that only listen to some events and them modify DOM accordingly
2070
+ // Could be overriden by defining a `window.ParsleyConfig.ParsleyUI` appropriate class (with `listen()` method basically)
2071
+ window.ParsleyUI = 'function' === typeof ParsleyUtils.get(window, 'ParsleyConfig.ParsleyUI') ?
2072
+ new window.ParsleyConfig.ParsleyUI().listen() : new ParsleyUI().listen();
2073
+ // ### ParsleyField and ParsleyForm extension
2074
+ // Ensure that defined if not already the case
2075
+ if ('undefined' === typeof window.ParsleyExtend)
2076
+ window.ParsleyExtend = {};
2077
+ // ### ParsleyConfig
2078
+ // Ensure that defined if not already the case
2079
+ if ('undefined' === typeof window.ParsleyConfig)
2080
+ window.ParsleyConfig = {};
2081
+ // ### Globals
2082
+ window.Parsley = window.psly = Parsley;
2083
+ window.ParsleyUtils = ParsleyUtils;
2084
+ window.ParsleyValidator = new ParsleyValidator(window.ParsleyConfig.validators, window.ParsleyConfig.i18n);
2085
+ // ### PARSLEY auto-binding
2086
+ // Prevent it by setting `ParsleyConfig.autoBind` to `false`
2087
+ if (false !== ParsleyUtils.get(window, 'ParsleyConfig.autoBind'))
2088
+ $(document).ready(function () {
2089
+ // Works only on `data-parsley-validate`.
2090
+ if ($('[data-parsley-validate]').length)
2091
+ $('[data-parsley-validate]').parsley();
2092
+ });
2093
+
2094
+ // AMD Compliance
2095
+ if ('function' === typeof define && define.amd)
2096
+ define('parsley', function() { return window.Parsley; } );
2097
+ })(window.jQuery);