parsley-rails 1.2.4.0 → 2.0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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);