parsley-rails 1.1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,32 @@
1
+ window.ParsleyConfig = window.ParsleyConfig || {};
2
+
3
+ (function ($) {
4
+ window.ParsleyConfig = $.extend( true, {}, window.ParsleyConfig, {
5
+ messages: {
6
+ // parsley //////////////////////////////////////
7
+ defaultMessage: "Þetta gildi virðist vera ógilt."
8
+ , type: {
9
+ email: "Þetta ætti að vera gilt netfang."
10
+ , url: "Þetta ætti að vera gild vefslóð."
11
+ , urlstrict: "Þetta ætti að vera gild vefslóð."
12
+ , number: "Þetta ætti að vera gild tala."
13
+ , digits: "Þetta ætti að innihalda tölur."
14
+ , dateIso: "Þetta ætti að vera gild dagsetning (ÁÁÁÁ-MM-DD)."
15
+ , alphanum: "Þetta ætti að vera innihalda tölur og bókstafi."
16
+ }
17
+ , notnull: "Þetta gildi ætti ekki að vera autt."
18
+ , notblank: "Þetta gildi ætti ekki að vera tómt."
19
+ , required: "Þetta gildi er nauðsynlegt að fylla út."
20
+ , regexp: "Þetta gildi virðist vera ógilt."
21
+ , min: "Þetta gildi ætti að vera stærra en %s."
22
+ , max: "Þetta gildi ætti að vera minna en %s."
23
+ , range: "Þetta gildi ætti að vera milli %s og %s."
24
+ , minlength: "Þetta gildi er of stutt. Það ætti að innihalda %s stafi eða fleiri."
25
+ , maxlength: "Þetta gildi er of langt. Það ætti að innihalda %s stafi eða færri."
26
+ , rangelength: "Þetta gildi er ógilt. Það ætti að vera %s-%s stafir að lengd."
27
+ , equalto: "Þetta gildi ætti að vera eins."
28
+
29
+ // parsley.extend ///////////////////////////////
30
+ }
31
+ });
32
+ }(window.jQuery || window.Zepto));
@@ -0,0 +1,37 @@
1
+ window.ParsleyConfig = window.ParsleyConfig || {};
2
+
3
+ (function ($) {
4
+ window.ParsleyConfig = $.extend( true, {}, window.ParsleyConfig, {
5
+ messages: {
6
+ // parsley //////////////////////////////////////
7
+ defaultMessage: "Deze waarde lijkt onjuist."
8
+ , type: {
9
+ email: "Dit lijkt geen geldig e-mail adres te zijn."
10
+ , url: "Dit lijkt geen geldige URL te zijn."
11
+ , urlstrict: "Dit is geen geldige URL."
12
+ , number: "Deze waarde moet een nummer zijn."
13
+ , digits: "Deze waarde moet numeriek zijn."
14
+ , dateIso: "Deze waarde moet een datum in het volgende formaat zijn: (YYYY-MM-DD)."
15
+ , alphanum: "Deze waarde moet alfanumeriek zijn."
16
+ }
17
+ , notnull: "Deze waarde mag niet leeg zijn."
18
+ , notblank: "Deze waarde mag niet leeg zijn."
19
+ , required: "Dit veld is verplicht"
20
+ , regexp: "Deze waarde lijkt onjuist te zijn."
21
+ , min: "Deze waarde mag niet lager zijn dan %s."
22
+ , max: "Deze waarde mag niet groter zijn dan %s."
23
+ , range: "Deze waarde moet tussen %s en %s liggen."
24
+ , minlength: "Deze tekst is te kort. Deze moet uit minimaal %s karakters bestaan."
25
+ , maxlength: "Deze waarde is te lang. Deze mag maximaal %s karakters lang zijn."
26
+ , rangelength: "Deze waarde moet tussen %s en %s karakters lang zijn."
27
+ , equalto: "Deze waardes moeten identiek zijn."
28
+
29
+ // parsley.extend ///////////////////////////////
30
+ , minwords: "Deze waarde moet minstens %s woorden bevatten."
31
+ , maxwords: "Deze waarde mag maximaal %s woorden bevatten."
32
+ , rangewords: "Deze waarde moet tussen de %s en %s woorden bevatten."
33
+ , greaterthan: "Deze waarde moet groter dan %s zijn."
34
+ , lessthan: "Deze waarde moet kleiner dan %s zijn."
35
+ }
36
+ });
37
+ }(window.jQuery || window.Zepto));
@@ -0,0 +1,32 @@
1
+ window.ParsleyConfig = window.ParsleyConfig || {};
2
+
3
+ (function ($) {
4
+ window.ParsleyConfig = $.extend( true, {}, window.ParsleyConfig, {
5
+ messages: {
6
+ // parsley //////////////////////////////////////
7
+ defaultMessage: "Denne verdien er ikke gyldig."
8
+ , type: {
9
+ email: "Denne verdien må være en gyldig e-post."
10
+ , url: "Denne verdien må være en gyldig nettadresse."
11
+ , urlstrict: "Denne verdien må være en gyldig nettadresse."
12
+ , number: "Denne verdien må være et gyldig tall."
13
+ , digits: "Denne verdien må være tall."
14
+ , dateIso: "Denne verdien må være en gyldig dato (YYYY-MM-DD)."
15
+ , alphanum: "Denne verdien må være alfanumerisk(tall eller bokstaver)."
16
+ }
17
+ , notnull: "Denne verdien kan ikke være tom."
18
+ , notblank: "Denne verdien kan ikke være blank."
19
+ , required: "Dette feltet er obligatorisk."
20
+ , regexp: "Denne verdien er ikke gyldig."
21
+ , min: "Denne verdien må være større enn %s."
22
+ , max: "Denne verdien må være mindre enn %s."
23
+ , range: "Denne verdien må være mellom %s og %s."
24
+ , minlength: "Denne verdien er for kort. Den må være minst %s tegn."
25
+ , maxlength: "Denne verdien er for lang. Den må ikke være lenger enn %s tegn."
26
+ , rangelength: "Denne verdien har feil lengde. Lengden må være mellom %s og %s tegn."
27
+ , equalto: "Denne verdien må være lik."
28
+
29
+ // parsley.extend ///////////////////////////////
30
+ }
31
+ });
32
+ }(window.jQuery || window.Zepto));
@@ -0,0 +1,40 @@
1
+ window.ParsleyConfig = window.ParsleyConfig || {};
2
+
3
+ (function ($) {
4
+ window.ParsleyConfig = $.extend( true, {}, window.ParsleyConfig, {
5
+ messages: {
6
+ // parsley ////////PL/by/Tymek///////////////////
7
+ defaultMessage: "Wartość wygląda na nieprawidłową"
8
+ , type: {
9
+ email: "Wpisz poprawny adres e-mail"
10
+ , url: "Wpisz poprawny adres URL"
11
+ , urlstrict: "Wpisz poprawny adres URL"
12
+ , number: "Wpisz poprawną liczbę"
13
+ , digits: "Dozwolone jedynie cyfry"
14
+ , dateIso: "Wpisz poprawny format daty (RRRR-MM-DD)"
15
+ , alphanum: "Dozwolone jedynie znaki alfanumeryczne"
16
+ }
17
+ , notnull: "Wartość musi być różna od zera"
18
+ , notblank: "Pole nie może pozostać puste"
19
+ , required: "Pole wymagane"
20
+ , regexp: "Wartość wygląda na nieprawidłową"
21
+ , min: "Wartość powinna być większa od %s"
22
+ , max: "Wartość powinna być mniejsza od %s"
23
+ , range: "Wartość powinna być większa od %s i mniejsza od %s"
24
+ , minlength: "Ilość znaków powinna wynosić %s lub więcej"
25
+ , maxlength: "Ilość znaków powinna wynosić %s lub mniej"
26
+ , rangelength: "Ilość znaków powinna wynosić od %s do %s"
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ść musi być identyczna"
31
+
32
+ // parsley.extend ///////////////////////////////
33
+ , minwords: "Wpisz więcej niż %s wyrazów"
34
+ , maxwords: "Wartość nie może przekraczać %s wyrazów"
35
+ , rangewords: "Wartość musi zawierać od %s do %s wyrazów"
36
+ , greaterthan: "Wartość musi być większa niż %s"
37
+ , lessthan: "Wartość musi być mniejsza niż %s"
38
+ }
39
+ });
40
+ }(window.jQuery || window.Zepto));
@@ -0,0 +1,32 @@
1
+ window.ParsleyConfig = window.ParsleyConfig || {};
2
+
3
+ (function ($) {
4
+ window.ParsleyConfig = $.extend( true, {}, window.ParsleyConfig, {
5
+ messages: {
6
+ defaultMessage: "Este valor parece estar inválido."
7
+ , type: {
8
+ email: "Este valor deve ser um e-mail válido."
9
+ , url: "Este valor deve ser uma URL válida."
10
+ , urlstrict: "Este valor deve ser uma URL válida."
11
+ , number: "Este valor deve ser um número válido."
12
+ , digits: "Este valor deve ser um dígito válido."
13
+ , dateIso: "Este valor deve ser uma data válida (YYYY-MM-DD)."
14
+ , alphanum: "Este valor deve ser alfanumérico."
15
+ }
16
+ , notnull: "Este valor não deve ser nulo."
17
+ , notblank: "Este valor não deve ser branco."
18
+ , required: "Este valor é obrigatório."
19
+ , regexp: "Este valor parece estar inválido."
20
+ , min: "Este valor deve ser maior que %s."
21
+ , max: "Este valor deve ser menor que %s."
22
+ , range: "Este valor deve estar entre %s e %s."
23
+ , minlength: "Este valor é muito pequeno. Ele deve ter %s caracteres ou mais."
24
+ , maxlength: "Este valor é muito grande. Ele deve ter %s caracteres ou menos."
25
+ , rangelength: "O tamanho deste valor é inválido. Ele deve possuir entre %s e %s caracteres."
26
+ , equalto: "Este valor deve ser o mesmo."
27
+ , minwords: "Este valor deve possuir no mínimo %s palavras."
28
+ , maxwords: "Este valor deve possuir no máximo %s palavras."
29
+ , rangewords: "Este valor deve possuir entre %s e %s palavras."
30
+ }
31
+ });
32
+ }(window.jQuery || window.Zepto));
@@ -0,0 +1,35 @@
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
+ }
17
+ , notnull: "Поле должно быть не нулевым."
18
+ , notblank: "Поле не должно быть пустым."
19
+ , required: "Поле обязательно для заполнения."
20
+ , regexp: "Поле заполнено некорректно."
21
+ , min: "Значение поля должно быть больше %s."
22
+ , max: "Значение поля должно быть меньше %s."
23
+ , range: "Значение поля должно быть между %s и %s."
24
+ , minlength: "В поле должно быть минимум %s символов(а)."
25
+ , maxlength: "В поле должно быть не больше %s символов(а)."
26
+ , rangelength: "В поле должно быть от %s до %s символов(а)."
27
+ , mincheck: "Необходимо выбрать не менее %s пунктов(а)."
28
+ , maxcheck: "Необходимо выбрать не более %s пунктов(а)."
29
+ , rangecheck: "Необходимо выбрать от %s до %s пунктов."
30
+ , equalto: "Значения полей должны быть одинаковыми."
31
+
32
+ // parsley.extend ///////////////////////////////
33
+ }
34
+ });
35
+ }(window.jQuery || window.Zepto));
@@ -0,0 +1,36 @@
1
+ window.ParsleyConfig = window.ParsleyConfig || {};
2
+
3
+ (function ($) {
4
+ window.ParsleyConfig = $.extend( true, {}, window.ParsleyConfig, {
5
+ messages: {
6
+ // parsley //////////////////////////////////////
7
+ defaultMessage: "Thông tin này không hợp lệ."
8
+ , type: {
9
+ email: "Email không hợp lệ."
10
+ , url: "Url không hợp lệ."
11
+ , urlstrict: "Yêu cầu nhập địa chỉ url."
12
+ , number: "Yêu cầu nhập giá trị kiểu số."
13
+ , digits: "Yêu cầu nhập vào các chữ số."
14
+ , dateIso: "Yêu cầu nhập ngày tháng theo chuẩn sau (YYYY-MM-DD)."
15
+ , alphanum: "Yêu cầu nhập chữ cái hoặc chữ số."
16
+ }
17
+ , notnull: "Thông tin này chưa nhập."
18
+ , notblank: "Thông tin này không được để trống."
19
+ , required: "Thông tin này là bắt buộc."
20
+ , regexp: "Thông tin này không hợp lệ."
21
+ , min: "Giá trị này phải lớn hơn %s."
22
+ , max: "Giá trị này phải nhỏ hơn %s."
23
+ , range: "Giá trị này phải nằm trong khoảng từ %s đến %s."
24
+ , minlength: "Chuỗi nhập vào quá ngắn. Yêu cầu tối thiểu %s ký tự."
25
+ , maxlength: "Chuỗi nhập vào quá dài. Yêu cầu tối đa %s ký tự."
26
+ , rangelength: "Chuỗi nhập vào không hợp lệ. Yêu cầu độ dài trong khoảng từ %s đến %s ký tự."
27
+ , mincheck: "Không được chọn ít hơn %s lựa chọn."
28
+ , maxcheck: "Không được chọn nhiều hơn %s lựa chọn."
29
+ , rangecheck: "Phải chọn trong khoảng từ %s đến %s lựa chọn."
30
+ , equalto: "Giá trị phải trùng khớp."
31
+
32
+ // parsley.extend ///////////////////////////////
33
+ }
34
+ });
35
+ }(window.jQuery || window.Zepto));
36
+
@@ -0,0 +1,1276 @@
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
5
+ */
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
+ }
36
+ , notnull: "This value should not be null."
37
+ , notblank: "This value should not be blank."
38
+ , required: "This value is required."
39
+ , regexp: "This value seems to be invalid."
40
+ , min: "This value should be greater than %s."
41
+ , max: "This value should be lower than %s."
42
+ , range: "This value should be between %s and %s."
43
+ , minlength: "This value is too short. It should have %s characters or more."
44
+ , maxlength: "This value is too long. It should have %s characters or less."
45
+ , rangelength: "This value length is invalid. It should be between %s and %s characters long."
46
+ , mincheck: "You must select at least %s choices."
47
+ , maxcheck: "You must select %s choices or less."
48
+ , rangecheck: "You must select between %s and %s choices."
49
+ , equalto: "This value should be the same."
50
+ },
51
+
52
+ this.init( options );
53
+ };
54
+
55
+ Validator.prototype = {
56
+
57
+ constructor: Validator
58
+
59
+ /**
60
+ * Validator list. Built-in validators functions
61
+ *
62
+ * @property validators
63
+ * @type {Object}
64
+ */
65
+ , validators: {
66
+ notnull: function ( val ) {
67
+ return val.length > 0;
68
+ }
69
+
70
+ , notblank: function ( val ) {
71
+ return null !== val && '' !== val.replace( /^\s+/g, '' ).replace( /\s+$/g, '' );
72
+ }
73
+
74
+ // Works on all inputs. val is object for checkboxes
75
+ , required: function ( val ) {
76
+
77
+ // for checkboxes and select multiples. Check there is at least one required value
78
+ if ( 'object' === typeof val ) {
79
+ for ( var i in val ) {
80
+ if ( this.required( val[ i ] ) ) {
81
+ return true;
82
+ }
83
+ }
84
+
85
+ return false;
86
+ }
87
+
88
+ return this.notnull( val ) && this.notblank( val );
89
+ }
90
+
91
+ , type: function ( val, type ) {
92
+ var regExp;
93
+
94
+ switch ( type ) {
95
+ case 'number':
96
+ regExp = /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/;
97
+ break;
98
+ case 'digits':
99
+ regExp = /^\d+$/;
100
+ break;
101
+ case 'alphanum':
102
+ regExp = /^\w+$/;
103
+ break;
104
+ case 'email':
105
+ 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;
106
+ break;
107
+ case 'url':
108
+ val = new RegExp( '(https?|s?ftp|git)', 'i' ).test( val ) ? val : 'http://' + val;
109
+ /* falls through */
110
+ case 'urlstrict':
111
+ 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;
112
+ break;
113
+ case 'dateIso':
114
+ regExp = /^(\d{4})\D?(0[1-9]|1[0-2])\D?([12]\d|0[1-9]|3[01])$/;
115
+ break;
116
+ default:
117
+ return false;
118
+ }
119
+
120
+ // test regExp if not null
121
+ return '' !== val ? regExp.test( val ) : false;
122
+ }
123
+
124
+ , regexp: function ( val, regExp ) {
125
+ return new RegExp( regExp, 'i' ).test( val );
126
+ }
127
+
128
+ , minlength: function ( val, min ) {
129
+ return val.length >= min;
130
+ }
131
+
132
+ , maxlength: function ( val, max ) {
133
+ return val.length <= max;
134
+ }
135
+
136
+ , rangelength: function ( val, arrayRange ) {
137
+ return this.minlength( val, arrayRange[ 0 ] ) && this.maxlength( val, arrayRange[ 1 ] );
138
+ }
139
+
140
+ , min: function ( val, min ) {
141
+ return Number( val ) >= min;
142
+ }
143
+
144
+ , max: function ( val, max ) {
145
+ return Number( val ) <= max;
146
+ }
147
+
148
+ , range: function ( val, arrayRange ) {
149
+ return val >= arrayRange[ 0 ] && val <= arrayRange[ 1 ];
150
+ }
151
+
152
+ , equalto: function ( val, elem, self ) {
153
+ self.options.validateIfUnchanged = true;
154
+
155
+ return val === $( elem ).val();
156
+ }
157
+
158
+ , remote: function ( val, url, self ) {
159
+ var result = null
160
+ , data = {}
161
+ , dataType = {};
162
+
163
+ data[ self.$element.attr( 'name' ) ] = val;
164
+
165
+ if ( 'undefined' !== typeof self.options.remoteDatatype ) {
166
+ dataType = { dataType: self.options.remoteDatatype };
167
+ }
168
+
169
+ var manage = function ( isConstraintValid, message ) {
170
+ // remove error message because ajax response message could change depending on the sent value !
171
+ self.removeError( 'remote' );
172
+
173
+ self.updtConstraint( { name: 'remote', isValid: isConstraintValid }, message );
174
+ self.manageValidationResult();
175
+ };
176
+
177
+ // transform string response into object
178
+ var handleResponse = function ( response ) {
179
+ if ( 'object' === typeof response ) {
180
+ return response;
181
+ }
182
+
183
+ try {
184
+ response = $.parseJSON( response );
185
+ } catch ( err ) {}
186
+
187
+ return response;
188
+ }
189
+
190
+ var manageErrorMessage = function ( response ) {
191
+ return 'object' === typeof response && null !== response ? ( 'undefined' !== typeof response.error ? response.error : ( 'undefined' !== typeof response.message ? response.message : null ) ) : null;
192
+ }
193
+
194
+ $.ajax( $.extend( {}, {
195
+ url: url
196
+ , data: data
197
+ , async: self.async
198
+ , method: self.options.remoteMethod || 'GET'
199
+ , success: function ( response ) {
200
+ response = handleResponse( response );
201
+ manage( 1 === response || true === response || ( 'object' === typeof response && null !== response && 'undefined' !== typeof response.success ), manageErrorMessage( response )
202
+ );
203
+ }
204
+ , error: function ( response ) {
205
+ response = handleResponse( response );
206
+ manage( false, manageErrorMessage( response ) );
207
+ }
208
+ }, dataType ) );
209
+
210
+ if ( self.async ) {
211
+ manage( result );
212
+ }
213
+
214
+ return result;
215
+ }
216
+
217
+ /**
218
+ * Aliases for checkboxes constraints
219
+ */
220
+ , mincheck: function ( obj, val ) {
221
+ return this.minlength( obj, val );
222
+ }
223
+
224
+ , maxcheck: function ( obj, val ) {
225
+ return this.maxlength( obj, val);
226
+ }
227
+
228
+ , rangecheck: function ( obj, arrayRange ) {
229
+ return this.rangelength( obj, arrayRange );
230
+ }
231
+ }
232
+
233
+ /*
234
+ * Register custom validators and messages
235
+ */
236
+ , init: function ( options ) {
237
+ var customValidators = options.validators
238
+ , customMessages = options.messages;
239
+
240
+ var key;
241
+ for ( key in customValidators ) {
242
+ this.addValidator(key, customValidators[ key ]);
243
+ }
244
+
245
+ for ( key in customMessages ) {
246
+ this.addMessage(key, customMessages[ key ]);
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Replace %s placeholders by values
252
+ *
253
+ * @method formatMesssage
254
+ * @param {String} message Message key
255
+ * @param {Mixed} args Args passed by validators functions. Could be string, number or object
256
+ * @return {String} Formatted string
257
+ */
258
+ , formatMesssage: function ( message, args ) {
259
+
260
+ if ( 'object' === typeof args ) {
261
+ for ( var i in args ) {
262
+ message = this.formatMesssage( message, args[ i ] );
263
+ }
264
+
265
+ return message;
266
+ }
267
+
268
+ return 'string' === typeof message ? message.replace( new RegExp( '%s', 'i' ), args ) : '';
269
+ }
270
+
271
+ /**
272
+ * Add / override a validator in validators list
273
+ *
274
+ * @method addValidator
275
+ * @param {String} name Validator name. Will automatically bindable through data-name=''
276
+ * @param {Function} fn Validator function. Must return {Boolean}
277
+ */
278
+ , addValidator: function ( name, fn ) {
279
+ this.validators[ name ] = fn;
280
+ }
281
+
282
+ /**
283
+ * Add / override error message
284
+ *
285
+ * @method addMessage
286
+ * @param {String} name Message name. Will automatically be binded to validator with same name
287
+ * @param {String} message Message
288
+ */
289
+ , addMessage: function ( key, message, type ) {
290
+
291
+ if ( 'undefined' !== typeof type && true === type ) {
292
+ this.messages.type[ key ] = message;
293
+ return;
294
+ }
295
+
296
+ // custom types messages are a bit tricky cuz' nested ;)
297
+ if ( 'type' === key ) {
298
+ for ( var i in message ) {
299
+ this.messages.type[ i ] = message[ i ];
300
+ }
301
+
302
+ return;
303
+ }
304
+
305
+ this.messages[ key ] = message;
306
+ }
307
+ };
308
+
309
+ /**
310
+ * ParsleyField class manage each form field inside a validated Parsley form.
311
+ * Returns if field valid or not depending on its value and constraints
312
+ * Manage field error display and behavior, event triggers and more
313
+ *
314
+ * @class ParsleyField
315
+ * @constructor
316
+ */
317
+ var ParsleyField = function ( element, options, type ) {
318
+ this.options = options;
319
+ this.Validator = new Validator( options );
320
+ this.init( element, type || 'ParsleyField' );
321
+ };
322
+
323
+ ParsleyField.prototype = {
324
+
325
+ constructor: ParsleyField
326
+
327
+ /**
328
+ * Set some properties, bind constraint validators and validation events
329
+ *
330
+ * @method init
331
+ * @param {Object} element
332
+ * @param {Object} options
333
+ */
334
+ , init: function ( element, type ) {
335
+ this.type = type;
336
+ this.isValid = true;
337
+ this.element = element;
338
+ this.validatedOnce = false;
339
+ this.$element = $( element );
340
+ this.val = this.$element.val();
341
+ this.isRequired = false;
342
+ this.constraints = [];
343
+
344
+ // overriden by ParsleyItemMultiple if radio or checkbox input
345
+ if ( 'undefined' === typeof this.isRadioOrCheckbox ) {
346
+ this.isRadioOrCheckbox = false;
347
+ this.hash = this.generateHash();
348
+ this.errorClassHandler = this.options.errors.classHandler( element, this.isRadioOrCheckbox ) || this.$element;
349
+ }
350
+
351
+ // error ul dom management done only once at init
352
+ this.ulErrorManagement();
353
+
354
+ // bind some html5 properties
355
+ this.bindHtml5Constraints();
356
+
357
+ // bind validators to field
358
+ this.addConstraints();
359
+
360
+ // bind parsley events if validators have been registered
361
+ if ( this.constraints.length ) {
362
+ this.bindValidationEvents();
363
+ }
364
+ }
365
+
366
+ , setParent: function ( elem ) {
367
+ this.$parent = $( elem );
368
+ }
369
+
370
+ , getParent: function () {
371
+ return this.$parent;
372
+ }
373
+
374
+ /**
375
+ * Bind some extra html5 types / validators
376
+ *
377
+ * @private
378
+ * @method bindHtml5Constraints
379
+ */
380
+ , bindHtml5Constraints: function () {
381
+ // add html5 required support + class required support
382
+ if ( this.$element.hasClass( 'required' ) || this.$element.attr( 'required' ) ) {
383
+ this.options.required = true;
384
+ }
385
+
386
+ // add html5 supported types & options
387
+ if ( 'undefined' !== typeof this.$element.attr( 'type' ) && new RegExp( this.$element.attr( 'type' ), 'i' ).test( 'email url number range' ) ) {
388
+ this.options.type = this.$element.attr( 'type' );
389
+
390
+ // number and range types could have min and/or max values
391
+ if ( new RegExp( this.options.type, 'i' ).test( 'number range' ) ) {
392
+ this.options.type = 'number';
393
+
394
+ // double condition to support jQuery and Zepto.. :(
395
+ if ( 'undefined' !== typeof this.$element.attr( 'min' ) && this.$element.attr( 'min' ).length ) {
396
+ this.options.min = this.$element.attr( 'min' );
397
+ }
398
+
399
+ if ( 'undefined' !== typeof this.$element.attr( 'max' ) && this.$element.attr( 'max' ).length ) {
400
+ this.options.max = this.$element.attr( 'max' );
401
+ }
402
+ }
403
+ }
404
+ }
405
+
406
+ /**
407
+ * Attach field validators functions passed through data-api
408
+ *
409
+ * @private
410
+ * @method addConstraints
411
+ */
412
+ , addConstraints: function () {
413
+ for ( var constraint in this.options ) {
414
+ var addConstraint = {};
415
+ addConstraint[ constraint ] = this.options[ constraint ];
416
+ this.addConstraint( addConstraint, true );
417
+ }
418
+ }
419
+
420
+ /**
421
+ * Dynamically add a new constraint to a field
422
+ *
423
+ * @method addConstraint
424
+ * @param {Object} constraint { name: requirements }
425
+ */
426
+ , addConstraint: function ( constraint, doNotUpdateValidationEvents ) {
427
+ for ( var name in constraint ) {
428
+ name = name.toLowerCase();
429
+
430
+ if ( 'function' === typeof this.Validator.validators[ name ] ) {
431
+ this.constraints.push( {
432
+ name: name
433
+ , requirements: constraint[ name ]
434
+ , isValid: null
435
+ } );
436
+
437
+ if ( name === 'required' ) {
438
+ this.isRequired = true;
439
+ }
440
+
441
+ this.addCustomConstraintMessage( name );
442
+ }
443
+ }
444
+
445
+ // force field validation next check and reset validation events
446
+ if ( 'undefined' === typeof doNotUpdateValidationEvents ) {
447
+ this.bindValidationEvents();
448
+ }
449
+ }
450
+
451
+ /**
452
+ * Dynamically update an existing constraint to a field.
453
+ * Simple API: { name: requirements }
454
+ *
455
+ * @method updtConstraint
456
+ * @param {Object} constraint
457
+ */
458
+ , updateConstraint: function ( constraint, message ) {
459
+ for ( var name in constraint ) {
460
+ this.updtConstraint( { name: name, requirements: constraint[ name ], isValid: null }, message );
461
+ }
462
+ }
463
+
464
+ /**
465
+ * Dynamically update an existing constraint to a field.
466
+ * Complex API: { name: name, requirements: requirements, isValid: boolean }
467
+ *
468
+ * @method updtConstraint
469
+ * @param {Object} constraint
470
+ */
471
+ , updtConstraint: function ( constraint, message ) {
472
+ for ( var i in this.constraints ) {
473
+ if ( this.constraints[ i ].name === constraint.name ) {
474
+ this.constraints[ i ] = $.extend( true, this.constraints[ i ], constraint );
475
+ if ( 'string' === typeof message ) {
476
+ this.Validator.messages[ this.constraints[ i ].name ] = message ;
477
+ }
478
+ }
479
+ }
480
+
481
+ // force field validation next check and reset validation events
482
+ this.bindValidationEvents();
483
+ }
484
+
485
+ /**
486
+ * Dynamically remove an existing constraint to a field.
487
+ *
488
+ * @method removeConstraint
489
+ * @param {String} constraintName
490
+ */
491
+ , removeConstraint: function ( constraintName ) {
492
+ var constraintName = constraintName.toLowerCase()
493
+ , updatedConstraints = [];
494
+
495
+ for ( var constraint in this.constraints ) {
496
+ if ( this.constraints[ constraint ].name !== constraintName ) {
497
+ updatedConstraints.push( this.constraints[ constraint ] );
498
+ }
499
+ }
500
+
501
+ if ( constraintName === 'required' ) {
502
+ this.isRequired = false;
503
+ }
504
+
505
+ this.constraints = updatedConstraints;
506
+
507
+ // if there are no more constraint, destroy parsley instance for this field
508
+ if ( updatedConstraints.length === 0 ) {
509
+ // in a form context, remove item from parent
510
+ if ( 'ParsleyForm' === typeof this.getParent() ) {
511
+ this.getParent().removeItem( this.$element );
512
+ return;
513
+ }
514
+
515
+ this.destroy();
516
+ return;
517
+ }
518
+
519
+ this.bindValidationEvents();
520
+ }
521
+
522
+ /**
523
+ * Add custom constraint message, passed through data-API
524
+ *
525
+ * @private
526
+ * @method addCustomConstraintMessage
527
+ * @param constraint
528
+ */
529
+ , addCustomConstraintMessage: function ( constraint ) {
530
+ // custom message type data-type-email-message -> typeEmailMessage | data-minlength-error => minlengthMessage
531
+ var customMessage = constraint
532
+ + ( 'type' === constraint && 'undefined' !== typeof this.options[ constraint ] ? this.options[ constraint ].charAt( 0 ).toUpperCase() + this.options[ constraint ].substr( 1 ) : '' )
533
+ + 'Message';
534
+
535
+ if ( 'undefined' !== typeof this.options[ customMessage ] ) {
536
+ this.Validator.addMessage( 'type' === constraint ? this.options[ constraint ] : constraint, this.options[ customMessage ], 'type' === constraint );
537
+ }
538
+ }
539
+
540
+ /**
541
+ * Bind validation events on a field
542
+ *
543
+ * @private
544
+ * @method bindValidationEvents
545
+ */
546
+ , bindValidationEvents: function () {
547
+ // this field has validation events, that means it has to be validated
548
+ this.isValid = null;
549
+ this.$element.addClass( 'parsley-validated' );
550
+
551
+ // remove eventually already binded events
552
+ this.$element.off( '.' + this.type );
553
+
554
+ // alaways bind keyup event, for better UX when a field is invalid
555
+ var triggers = ( !this.options.trigger ? '' : this.options.trigger + ' ' )
556
+ + ( new RegExp( 'key', 'i' ).test( this.options.trigger ) ? '' : 'keyup' );
557
+
558
+ // force add 'change' event if async remote validator here to have result before form submitting
559
+ if ( this.options.remote ) {
560
+ triggers += new RegExp( 'change', 'i' ).test( triggers ) ? '' : ' change';
561
+ }
562
+
563
+ this.$element.on( ( triggers + ' ' ).split( ' ' ).join( '.' + this.type + ' ' ), false, $.proxy( this.eventValidation, this ) );
564
+ }
565
+
566
+ /**
567
+ * Hash management. Used for ul error
568
+ *
569
+ * @method generateHash
570
+ * @returns {String} 5 letters unique hash
571
+ */
572
+ , generateHash: function () {
573
+ return 'parsley-' + ( Math.random() + '' ).substring( 2 );
574
+ }
575
+
576
+ /**
577
+ * Public getHash accessor
578
+ *
579
+ * @method getHash
580
+ * @returns {String} hash
581
+ */
582
+ , getHash: function () {
583
+ return this.hash;
584
+ }
585
+
586
+ /**
587
+ * Returns field val needed for validation
588
+ * Special treatment for radio & checkboxes
589
+ *
590
+ * @method getVal
591
+ * @returns {String} val
592
+ */
593
+ , getVal: function () {
594
+ return this.$element.val();
595
+ }
596
+
597
+ /**
598
+ * Called when validation is triggered by an event
599
+ * Do nothing if val.length < this.options.validationMinlength
600
+ *
601
+ * @method eventValidation
602
+ * @param {Object} event jQuery event
603
+ */
604
+ , eventValidation: function ( event ) {
605
+ var val = this.getVal();
606
+
607
+ // do nothing on keypress event if not explicitely passed as data-trigger and if field has not already been validated once
608
+ if ( event.type === 'keyup' && !/keyup/i.test( this.options.trigger ) && !this.validatedOnce ) {
609
+ return true;
610
+ }
611
+
612
+ // start validation process only if field has enough chars and validation never started
613
+ if ( !this.isRadioOrCheckbox && val.length < this.options.validationMinlength && !this.validatedOnce ) {
614
+ return true;
615
+ }
616
+
617
+ this.validate( true, false );
618
+ }
619
+
620
+ /**
621
+ * Return if field verify its constraints
622
+ *
623
+ * @method isValid
624
+ * @return {Boolean} Is field valid or not
625
+ */
626
+ , isFieldValid: function () {
627
+ return this.validate( false, false );
628
+ }
629
+
630
+ /**
631
+ * Validate a field & display errors
632
+ *
633
+ * @method validate
634
+ * @param {Boolean} errorBubbling set to false if you just want isValid boolean without error bubbling next to fields
635
+ * @param {Boolean} async if false, wait ajax calls returns
636
+ * @return {Boolean} Is field valid or not
637
+ */
638
+ , validate: function ( errorBubbling, async ) {
639
+ var val = this.getVal()
640
+ , isValid = null;
641
+
642
+ // reset Parsley validation if onFieldValidate returns true, or if field is empty and not required
643
+ if ( this.options.listeners.onFieldValidate( this.element, this ) || ( '' === val && !this.isRequired ) ) {
644
+ this.reset();
645
+ return null;
646
+ }
647
+
648
+ // do not validate a field already validated and unchanged !
649
+ if ( !this.needsValidation( val ) ) {
650
+ return this.isValid;
651
+ }
652
+
653
+ this.errorBubbling = 'undefined' !== typeof errorBubbling ? errorBubbling : true;
654
+ this.async = 'undefined' !== typeof async ? async : true;
655
+
656
+ isValid = this.applyValidators();
657
+
658
+ if ( this.errorBubbling ) {
659
+ this.manageValidationResult();
660
+ }
661
+
662
+ return isValid;
663
+ }
664
+
665
+ /**
666
+ * Check if value has changed since previous validation
667
+ *
668
+ * @method needsValidation
669
+ * @param value
670
+ * @return {Boolean}
671
+ */
672
+ , needsValidation: function ( val ) {
673
+ if ( !this.options.validateIfUnchanged && this.isValid !== null && this.val === val && this.validatedOnce ) {
674
+ return false;
675
+ }
676
+
677
+ this.val = val;
678
+ return this.validatedOnce = true;
679
+ }
680
+
681
+ /**
682
+ * Loop through every fields validators
683
+ * Adds errors after unvalid fields
684
+ *
685
+ * @method applyValidators
686
+ * @return {Mixed} {Boolean} If field valid or not, null if not validated
687
+ */
688
+ , applyValidators: function () {
689
+ var isValid = null;
690
+
691
+ for ( var constraint = 0; constraint < this.constraints.length; constraint++ ) {
692
+ var result = this.Validator.validators[ this.constraints[ constraint ].name ]( this.val, this.constraints[ constraint ].requirements, this );
693
+
694
+ if ( false === result ) {
695
+ isValid = false;
696
+ this.constraints[ constraint ].isValid = isValid;
697
+ } else if ( true === result ) {
698
+ this.constraints[ constraint ].isValid = true;
699
+ isValid = false !== isValid;
700
+ }
701
+ }
702
+
703
+ return isValid;
704
+ }
705
+
706
+ /**
707
+ * Fired when all validators have be executed
708
+ * Returns true or false if field is valid or not
709
+ * Display errors messages below faild fields
710
+ * Adds parsley-success or parsley-error class on fields
711
+ *
712
+ * @method manageValidationResult
713
+ * @return {Boolean} Is field valid or not
714
+ */
715
+ , manageValidationResult: function () {
716
+ var isValid = null;
717
+
718
+ for ( var constraint = 0; constraint < this.constraints.length; constraint++ ) {
719
+ if ( false === this.constraints[ constraint ].isValid ) {
720
+ this.manageError( this.constraints[ constraint ] );
721
+ isValid = false;
722
+ } else if ( true === this.constraints[ constraint ].isValid ) {
723
+ this.removeError( this.constraints[ constraint ].name );
724
+ isValid = false !== isValid;
725
+ }
726
+ }
727
+
728
+ this.isValid = isValid;
729
+
730
+ if ( true === this.isValid ) {
731
+ this.removeErrors();
732
+ this.errorClassHandler.removeClass( this.options.errorClass ).addClass( this.options.successClass );
733
+ this.options.listeners.onFieldSuccess( this.element, this.constraints, this );
734
+ return true;
735
+ } else if ( false === this.isValid ) {
736
+ this.errorClassHandler.removeClass( this.options.successClass ).addClass( this.options.errorClass );
737
+ this.options.listeners.onFieldError( this.element, this.constraints, this );
738
+ return false;
739
+ }
740
+
741
+ return isValid;
742
+ }
743
+
744
+ /**
745
+ * Manage ul error Container
746
+ *
747
+ * @private
748
+ * @method ulErrorManagement
749
+ */
750
+ , ulErrorManagement: function () {
751
+ this.ulError = '#' + this.hash;
752
+ this.ulTemplate = $( this.options.errors.errorsWrapper ).attr( 'id', this.hash ).addClass( 'parsley-error-list' );
753
+ }
754
+
755
+ /**
756
+ * Remove li / ul error
757
+ *
758
+ * @method removeError
759
+ * @param {String} constraintName Method Name
760
+ */
761
+ , removeError: function ( constraintName ) {
762
+ var liError = this.ulError + ' .' + constraintName;
763
+
764
+ this.options.animate ? $( liError ).fadeOut( this.options.animateDuration, function () { $( this ).remove() } ) : $( liError ).remove();
765
+
766
+ // remove li error, and ul error if no more li inside
767
+ if ( this.ulError && $( this.ulError ).children().length === 0 ) {
768
+ this.removeErrors();
769
+ }
770
+ }
771
+
772
+ /**
773
+ * Add li error
774
+ *
775
+ * @method addError
776
+ * @param {Object} { minlength: "error message for minlength constraint" }
777
+ */
778
+ , addError: function ( error ) {
779
+ for ( var constraint in error ) {
780
+ var liTemplate = $( this.options.errors.errorElem ).addClass( constraint );
781
+
782
+ $( this.ulError ).append( this.options.animate ? $( liTemplate ).text( error[ constraint ] ).hide().fadeIn( this.options.animateDuration ) : $( liTemplate ).text( error[ constraint ] ) );
783
+ }
784
+ }
785
+
786
+ /**
787
+ * Remove all ul / li errors
788
+ *
789
+ * @method removeErrors
790
+ */
791
+ , removeErrors: function () {
792
+ this.options.animate ? $( this.ulError ).fadeOut( this.options.animateDuration, function () { $( this ).remove(); } ) : $( this.ulError ).remove();
793
+ }
794
+
795
+ /**
796
+ * Remove ul errors and parsley error or success classes
797
+ *
798
+ * @method reset
799
+ */
800
+ , reset: function () {
801
+ this.isValid = null;
802
+ this.removeErrors();
803
+ this.errorClassHandler.removeClass( this.options.successClass ).removeClass( this.options.errorClass );
804
+ return this;
805
+ }
806
+
807
+ /**
808
+ * Add li / ul errors messages
809
+ *
810
+ * @method manageError
811
+ * @param {Object} constraint
812
+ */
813
+ , manageError: function ( constraint ) {
814
+ // display ulError container if it has been removed previously (or never shown)
815
+ if ( !$( this.ulError ).length ) {
816
+ this.manageErrorContainer();
817
+ }
818
+
819
+ // TODO: refacto error name w/ proper & readable function
820
+ var constraintName = constraint.name
821
+ , liClass = false !== this.options.errorMessage ? 'custom-error-message' : constraintName
822
+ , liError = {}
823
+ , message = false !== this.options.errorMessage ? this.options.errorMessage : ( constraint.name === 'type' ?
824
+ this.Validator.messages[ constraintName ][ constraint.requirements ] : ( 'undefined' === typeof this.Validator.messages[ constraintName ] ?
825
+ this.Validator.messages.defaultMessage : this.Validator.formatMesssage( this.Validator.messages[ constraintName ], constraint.requirements ) ) );
826
+
827
+ // add liError if not shown. Do not add more than once custom errorMessage if exist
828
+ if ( !$( this.ulError + ' .' + liClass ).length ) {
829
+ liError[ liClass ] = message;
830
+ this.addError( liError );
831
+ }
832
+ }
833
+
834
+ /**
835
+ * Create ul error container
836
+ *
837
+ * @method manageErrorContainer
838
+ */
839
+ , manageErrorContainer: function () {
840
+ var errorContainer = this.options.errors.container( this.element, this.isRadioOrCheckbox )
841
+ , ulTemplate = this.options.animate ? this.ulTemplate.show() : this.ulTemplate;
842
+
843
+ if ( 'undefined' !== typeof errorContainer ) {
844
+ $( errorContainer ).append( ulTemplate );
845
+ return;
846
+ }
847
+
848
+ !this.isRadioOrCheckbox ? this.$element.after( ulTemplate ) : this.$element.parent().after( ulTemplate );
849
+ }
850
+
851
+ /**
852
+ * Add custom listeners
853
+ *
854
+ * @param {Object} { listener: function () {} }, eg { onFormSubmit: function ( isValid, event, focus ) { ... } }
855
+ */
856
+ , addListener: function ( object ) {
857
+ for ( var listener in object ) {
858
+ this.options.listeners[ listener ] = object[ listener ];
859
+ }
860
+ }
861
+
862
+ /**
863
+ * Destroy parsley field instance
864
+ *
865
+ * @private
866
+ * @method destroy
867
+ */
868
+ , destroy: function () {
869
+ this.$element.removeClass( 'parsley-validated' );
870
+ this.errorClassHandler.removeClass( this.options.errorClass ).removeClass( this.options.successClass );
871
+ this.reset().$element.off( '.' + this.type ).removeData( this.type );
872
+ }
873
+ };
874
+
875
+ /**
876
+ * ParsleyFieldMultiple override ParsleyField for checkbox and radio inputs
877
+ * Pseudo-heritance to manage divergent behavior from ParsleyItem in dedicated methods
878
+ *
879
+ * @class ParsleyFieldMultiple
880
+ * @constructor
881
+ */
882
+ var ParsleyFieldMultiple = function ( element, options, type ) {
883
+ this.initMultiple( element, options );
884
+ this.inherit( element, options );
885
+ this.Validator = new Validator( options );
886
+
887
+ // call ParsleyField constructor
888
+ this.init( element, type || 'ParsleyFieldMultiple' );
889
+ };
890
+
891
+ ParsleyFieldMultiple.prototype = {
892
+
893
+ constructor: ParsleyFieldMultiple
894
+
895
+ /**
896
+ * Set some specific properties, call some extra methods to manage radio / checkbox
897
+ *
898
+ * @method init
899
+ * @param {Object} element
900
+ * @param {Object} options
901
+ */
902
+ , initMultiple: function ( element, options ) {
903
+ this.element = element;
904
+ this.$element = $( element );
905
+ this.group = options.group || false;
906
+ this.hash = this.getName();
907
+ this.siblings = this.group ? '[data-group="' + this.group + '"]' : 'input[name="' + this.$element.attr( 'name' ) + '"]';
908
+ this.isRadioOrCheckbox = true;
909
+ this.isRadio = this.$element.is( 'input[type=radio]' );
910
+ this.isCheckbox = this.$element.is( 'input[type=checkbox]' );
911
+ this.errorClassHandler = options.errors.classHandler( element, this.isRadioOrCheckbox ) || this.$element.parent();
912
+ }
913
+
914
+ /**
915
+ * Set specific constraints messages, do pseudo-heritance
916
+ *
917
+ * @private
918
+ * @method inherit
919
+ * @param {Object} element
920
+ * @param {Object} options
921
+ */
922
+ , inherit: function ( element, options ) {
923
+ var clone = new ParsleyField( element, options );
924
+
925
+ for ( var property in clone ) {
926
+ if ( 'undefined' === typeof this[ property ] ) {
927
+ this[ property ] = clone [ property ];
928
+ }
929
+ }
930
+ }
931
+
932
+ /**
933
+ * Set specific constraints messages, do pseudo-heritance
934
+ *
935
+ * @method getName
936
+ * @returns {String} radio / checkbox hash is cleaned 'name' or data-group property
937
+ */
938
+ , getName: function () {
939
+ if ( this.group ) {
940
+ return 'parsley-' + this.group;
941
+ }
942
+
943
+ if ( 'undefined' === typeof this.$element.attr( 'name' ) ) {
944
+ throw "A radio / checkbox input must have a data-group attribute or a name to be Parsley validated !";
945
+ }
946
+
947
+ return 'parsley-' + this.$element.attr( 'name' ).replace( /(:|\.|\[|\])/g, '' );
948
+ }
949
+
950
+ /**
951
+ * Special treatment for radio & checkboxes
952
+ * Returns checked radio or checkboxes values
953
+ *
954
+ * @method getVal
955
+ * @returns {String} val
956
+ */
957
+ , getVal: function () {
958
+ if ( this.isRadio ) {
959
+ return $( this.siblings + ':checked' ).val() || '';
960
+ }
961
+
962
+ if ( this.isCheckbox ) {
963
+ var values = [];
964
+
965
+ $( this.siblings + ':checked' ).each( function () {
966
+ values.push( $( this ).val() );
967
+ } );
968
+
969
+ return values;
970
+ }
971
+ }
972
+
973
+ /**
974
+ * Bind validation events on a field
975
+ *
976
+ * @private
977
+ * @method bindValidationEvents
978
+ */
979
+ , bindValidationEvents: function () {
980
+ // this field has validation events, that means it has to be validated
981
+ this.isValid = null;
982
+ this.$element.addClass( 'parsley-validated' );
983
+
984
+ // remove eventually already binded events
985
+ this.$element.off( '.' + this.type );
986
+
987
+ // alaways bind keyup event, for better UX when a field is invalid
988
+ var self = this
989
+ , triggers = ( !this.options.trigger ? '' : this.options.trigger + ' ' )
990
+ + ( new RegExp( 'change', 'i' ).test( this.options.trigger ) ? '' : 'change' );
991
+
992
+ // bind trigger event on every siblings
993
+ $( this.siblings ).each(function () {
994
+ $( this ).on( triggers.split( ' ' ).join( '.' + self.type + ' ' ), false, $.proxy( self.eventValidation, self ) );
995
+ } )
996
+ }
997
+
998
+ /**
999
+ * Called when validation is triggered by an event
1000
+ * Do nothing if never validated. validate on change otherwise
1001
+ *
1002
+ * @method eventValidation
1003
+ * @param {Object} event jQuery event
1004
+ */
1005
+ , eventValidation: function ( event ) {
1006
+ // start validation process only if field has enough chars and validation never started
1007
+ if ( !this.validatedOnce ) {
1008
+ return true;
1009
+ }
1010
+
1011
+ this.validate( true, false );
1012
+ }
1013
+ };
1014
+
1015
+ /**
1016
+ * ParsleyForm class manage Parsley validated form.
1017
+ * Manage its fields and global validation
1018
+ *
1019
+ * @class ParsleyForm
1020
+ * @constructor
1021
+ */
1022
+ var ParsleyForm = function ( element, options, type ) {
1023
+ this.init( element, options, type || 'parsleyForm' );
1024
+ };
1025
+
1026
+ ParsleyForm.prototype = {
1027
+
1028
+ constructor: ParsleyForm
1029
+
1030
+ /* init data, bind jQuery on() actions */
1031
+ , init: function ( element, options, type ) {
1032
+ this.type = type;
1033
+ this.items = [];
1034
+ this.$element = $( element );
1035
+ this.options = options;
1036
+ var self = this;
1037
+
1038
+ this.$element.find( options.inputs ).each( function () {
1039
+ self.addItem( this );
1040
+ });
1041
+
1042
+ this.$element.on( 'submit.' + this.type , false, $.proxy( this.validate, this ) );
1043
+ }
1044
+
1045
+ /**
1046
+ * Add custom listeners
1047
+ *
1048
+ * @param {Object} { listener: function () {} }, eg { onFormSubmit: function ( isValid, event, focus ) { ... } }
1049
+ */
1050
+ , addListener: function ( object ) {
1051
+ for ( var listener in object ) {
1052
+ if ( new RegExp( 'Field' ).test( listener ) ) {
1053
+ for ( var item = 0; item < this.items.length; item++ ) {
1054
+ this.items[ item ].addListener( object );
1055
+ }
1056
+ } else {
1057
+ this.options.listeners[ listener ] = object[ listener ];
1058
+ }
1059
+ }
1060
+ }
1061
+
1062
+ /**
1063
+ * Adds a new parsleyItem child to ParsleyForm
1064
+ *
1065
+ * @method addItem
1066
+ * @param elem
1067
+ */
1068
+ , addItem: function ( elem ) {
1069
+ if ( $( elem ).is( this.options.excluded ) ) {
1070
+ return false;
1071
+ }
1072
+
1073
+ var ParsleyField = $( elem ).parsley( this.options );
1074
+ ParsleyField.setParent( this );
1075
+
1076
+ this.items.push( ParsleyField );
1077
+ }
1078
+
1079
+ /**
1080
+ * Removes a parsleyItem child from ParsleyForm
1081
+ *
1082
+ * @method removeItem
1083
+ * @param elem
1084
+ * @return {Boolean}
1085
+ */
1086
+ , removeItem: function ( elem ) {
1087
+ var parsleyItem = $( elem ).parsley();
1088
+
1089
+ // identify & remove item if same Parsley hash
1090
+ for ( var i = 0; i < this.items.length; i++ ) {
1091
+ if ( this.items[ i ].hash === parsleyItem.hash ) {
1092
+ this.items[ i ].destroy();
1093
+ this.items.splice( i, 1 );
1094
+ return true;
1095
+ }
1096
+ }
1097
+
1098
+ return false;
1099
+ }
1100
+
1101
+ /**
1102
+ * Process each form field validation
1103
+ * Display errors, call custom onFormSubmit() function
1104
+ *
1105
+ * @method validate
1106
+ * @param {Object} event jQuery Event
1107
+ * @return {Boolean} Is form valid or not
1108
+ */
1109
+ , validate: function ( event ) {
1110
+ var isValid = true;
1111
+ this.focusedField = false;
1112
+
1113
+ for ( var item = 0; item < this.items.length; item++ ) {
1114
+ if ( 'undefined' !== typeof this.items[ item ] && false === this.items[ item ].validate() ) {
1115
+ isValid = false;
1116
+
1117
+ if ( !this.focusedField && 'first' === this.options.focus || 'last' === this.options.focus ) {
1118
+ this.focusedField = this.items[ item ].$element;
1119
+ }
1120
+ }
1121
+ }
1122
+
1123
+ // form is invalid, focus an error field depending on focus policy
1124
+ if ( this.focusedField && !isValid ) {
1125
+ this.focusedField.focus();
1126
+ }
1127
+
1128
+ this.options.listeners.onFormSubmit( isValid, event, this );
1129
+
1130
+ return isValid;
1131
+ }
1132
+
1133
+ /**
1134
+ * Remove all errors ul under invalid fields
1135
+ *
1136
+ * @method removeErrors
1137
+ */
1138
+ , removeErrors: function () {
1139
+ for ( var item = 0; item < this.items.length; item++ ) {
1140
+ this.items[ item ].parsley( 'reset' );
1141
+ }
1142
+ }
1143
+
1144
+ /**
1145
+ * destroy Parsley binded on the form and its fields
1146
+ *
1147
+ * @method destroy
1148
+ */
1149
+ , destroy: function () {
1150
+ for ( var item = 0; item < this.items.length; item++ ) {
1151
+ this.items[ item ].destroy();
1152
+ }
1153
+
1154
+ this.$element.off( '.' + this.type ).removeData( this.type );
1155
+ }
1156
+
1157
+ /**
1158
+ * reset Parsley binded on the form and its fields
1159
+ *
1160
+ * @method reset
1161
+ */
1162
+ , reset: function () {
1163
+ for ( var item = 0; item < this.items.length; item++ ) {
1164
+ this.items[ item ].reset();
1165
+ }
1166
+ }
1167
+ };
1168
+
1169
+ /**
1170
+ * Parsley plugin definition
1171
+ * Provides an interface to access public Validator, ParsleyForm and ParsleyField functions
1172
+ *
1173
+ * @class Parsley
1174
+ * @constructor
1175
+ * @param {Mixed} Options. {Object} to configure Parsley or {String} method name to call a public class method
1176
+ * @param {Function} Callback function
1177
+ * @return {Mixed} public class method return
1178
+ */
1179
+ $.fn.parsley = function ( option, fn ) {
1180
+ var options = $.extend( true, {}, $.fn.parsley.defaults, 'undefined' !== typeof window.ParsleyConfig ? window.ParsleyConfig : {}, option, this.data() )
1181
+ , newInstance = null;
1182
+
1183
+ function bind ( self, type ) {
1184
+ var parsleyInstance = $( self ).data( type );
1185
+
1186
+ // if data never binded or we want to clone a build (for radio & checkboxes), bind it right now!
1187
+ if ( !parsleyInstance ) {
1188
+ switch ( type ) {
1189
+ case 'parsleyForm':
1190
+ parsleyInstance = new ParsleyForm( self, options, 'parsleyForm' );
1191
+ break;
1192
+ case 'parsleyField':
1193
+ parsleyInstance = new ParsleyField( self, options, 'parsleyField' );
1194
+ break;
1195
+ case 'parsleyFieldMultiple':
1196
+ parsleyInstance = new ParsleyFieldMultiple( self, options, 'parsleyFieldMultiple' );
1197
+ break;
1198
+ default:
1199
+ return;
1200
+ }
1201
+
1202
+ $( self ).data( type, parsleyInstance );
1203
+ }
1204
+
1205
+ // here is our parsley public function accessor
1206
+ if ( 'string' === typeof option && 'function' === typeof parsleyInstance[ option ] ) {
1207
+ var response = parsleyInstance[ option ]( fn );
1208
+
1209
+ return 'undefined' !== typeof response ? response : $( self );
1210
+ }
1211
+
1212
+ return parsleyInstance;
1213
+ }
1214
+
1215
+ // if a form elem is given, bind all its input children
1216
+ if ( $( this ).is( 'form' ) ) {
1217
+ newInstance = bind ( $( this ), 'parsleyForm' );
1218
+
1219
+ // if it is a Parsley supported single element, bind it too, except inputs type hidden
1220
+ // add here a return instance, cuz' we could call public methods on single elems with data[ option ]() above
1221
+ } else if ( $( this ).is( options.inputs ) && !$( this ).is( options.excluded ) ) {
1222
+ newInstance = bind( $( this ), !$( this ).is( 'input[type=radio], input[type=checkbox]' ) ? 'parsleyField' : 'parsleyFieldMultiple' );
1223
+ }
1224
+
1225
+ return 'function' === typeof fn ? fn() : newInstance;
1226
+ };
1227
+
1228
+ $.fn.parsley.Constructor = ParsleyForm;
1229
+
1230
+ /**
1231
+ * Parsley plugin configuration
1232
+ *
1233
+ * @property $.fn.parsley.defaults
1234
+ * @type {Object}
1235
+ */
1236
+ $.fn.parsley.defaults = {
1237
+ // basic data-api overridable properties here..
1238
+ inputs: 'input, textarea, select' // Default supported inputs.
1239
+ , excluded: 'input[type=hidden], :disabled' // Do not validate input[type=hidden] & :disabled.
1240
+ , trigger: false // $.Event() that will trigger validation. eg: keyup, change..
1241
+ , animate: true // fade in / fade out error messages
1242
+ , animateDuration: 300 // fadein/fadout ms time
1243
+ , focus: 'first' // 'fist'|'last'|'none' which error field would have focus first on form validation
1244
+ , validationMinlength: 3 // If trigger validation specified, only if value.length > validationMinlength
1245
+ , successClass: 'parsley-success' // Class name on each valid input
1246
+ , errorClass: 'parsley-error' // Class name on each invalid input
1247
+ , errorMessage: false // Customize an unique error message showed if one constraint fails
1248
+ , validators: {} // Add your custom validators functions
1249
+ , messages: {} // Add your own error messages here
1250
+
1251
+ //some quite advanced configuration here..
1252
+ , validateIfUnchanged: false // false: validate once by field value change
1253
+ , errors: {
1254
+ classHandler: function ( elem, isRadioOrCheckbox ) {} // specify where parsley error-success classes are set
1255
+ , container: function ( elem, isRadioOrCheckbox ) {} // specify an elem where errors will be **apened**
1256
+ , errorsWrapper: '<ul></ul>' // do not set an id for this elem, it would have an auto-generated id
1257
+ , errorElem: '<li></li>' // each field constraint fail in an li
1258
+ }
1259
+ , listeners: {
1260
+ onFieldValidate: function ( elem, ParsleyForm ) { return false; } // Executed on validation. Return true to ignore field validation
1261
+ , onFormSubmit: function ( isFormValid, event, ParsleyForm ) {} // Executed once on form validation
1262
+ , onFieldError: function ( elem, constraints, ParsleyField ) {} // Executed when a field is detected as invalid
1263
+ , onFieldSuccess: function ( elem, constraints, ParsleyField ) {} // Executed when a field passes validation
1264
+ }
1265
+ };
1266
+
1267
+ /* PARSLEY auto-bind DATA-API + Global config retrieving
1268
+ * =================================================== */
1269
+ $( window ).on( 'load', function () {
1270
+ $( '[data-validate="parsley"]' ).each( function () {
1271
+ $( this ).parsley();
1272
+ } );
1273
+ } );
1274
+
1275
+ // This plugin works with jQuery or Zepto (with data extension built for Zepto.)
1276
+ }(window.jQuery || window.Zepto);