parsley-rails 1.1.18.0 → 1.2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d1da95f8fd31a1fbd3204c03c48d00b71008c9b7
4
- data.tar.gz: 5eba7457357f6cb96f3631491ffaf08119e5bc0c
3
+ metadata.gz: 8f4d0c0d9261762aa5ac043ab0e05123dddf0234
4
+ data.tar.gz: 068a8f50f49b2e1d0c43d31fda81852c3323ef9d
5
5
  SHA512:
6
- metadata.gz: 6df8668cd02350c3fc25745d7bb50251be8a03bb5dd6b0e428b90303bb5dc3bb6c9d2c46f4d53a20eaa72d41dd4c30b0cad5df89b0d50260dee2e62a9d610921
7
- data.tar.gz: 39e18ceebdfd7c98cd68af9dd375f22915880812c569827fd26183bed118acab6452165a1c795cef09e9fcb98839fd8fc44ba5c1f7c9626ce6c975b0c0dd6d6a
6
+ metadata.gz: 760f5fdda70bc1ec7cef59e6ae8f10a62f98b34d9770348ee46da1fd912bf31e9405592e0af59629d9f5e257e62b48b53e9709415fa0b82dbae6f392a6cf5b4d
7
+ data.tar.gz: 50a8cad8e8164b386afe55334783b1c499127d1f8f0e548e3657ce589676b65b57416852e9ebb31ea59355fd1e9581132586cf5af259e04410c19d0273c961f0
@@ -1,5 +1,5 @@
1
1
  module Parsley
2
2
  module Rails
3
- VERSION = "1.1.18.0"
3
+ VERSION = "1.2.0.0"
4
4
  end
5
5
  end
@@ -3,98 +3,135 @@ window.ParsleyConfig = window.ParsleyConfig || {};
3
3
  (function ($) {
4
4
  window.ParsleyConfig = $.extend( true, {}, window.ParsleyConfig, {
5
5
  validators: {
6
- minwords: function ( val, nbWords ) {
7
- val = val.replace( /(^\s*)|(\s*$)/gi, "" );
8
- val = val.replace( /[ ]{2,}/gi, " " );
9
- val = val.replace( /\n /, "\n" );
10
- val = val.split(' ').length;
11
-
12
- return val >= nbWords;
6
+ minwords: function () {
7
+ return {
8
+ validate: function ( val, nbWords ) {
9
+ val = val.replace( /(^\s*)|(\s*$)/gi, "" );
10
+ val = val.replace( /[ ]{2,}/gi, " " );
11
+ val = val.replace( /\n /, "\n" );
12
+ val = val.split(' ').length;
13
+
14
+ return val >= nbWords;
15
+ }
16
+ , priority: 32
17
+ }
13
18
  }
14
-
15
- , maxwords : function ( val, nbWords ) {
16
- val = val.replace( /(^\s*)|(\s*$)/gi, "" );
17
- val = val.replace( /[ ]{2,}/gi, " " );
18
- val = val.replace( /\n /, "\n" );
19
- val = val.split(' ').length;
20
-
21
- return val <= nbWords;
19
+ , maxwords : function () {
20
+ return {
21
+ validate: function ( val, nbWords ) {
22
+ val = val.replace( /(^\s*)|(\s*$)/gi, "" );
23
+ val = val.replace( /[ ]{2,}/gi, " " );
24
+ val = val.replace( /\n /, "\n" );
25
+ val = val.split(' ').length;
26
+
27
+ return val <= nbWords;
28
+ }
29
+ , priority: 32
30
+ }
22
31
  }
23
-
24
- , rangewords: function ( val, obj ) {
25
- val = val.replace( /(^\s*)|(\s*$)/gi, "" );
26
- val = val.replace( /[ ]{2,}/gi, " " );
27
- val = val.replace( /\n /, "\n" );
28
- val = val.split(' ').length;
29
-
30
- return val >= obj[0] && val <= obj[1];
32
+ , rangewords: function () {
33
+ var that = this;
34
+ return {
35
+ validate: function ( val, arrayRange) {
36
+ return that.minwords().validate( val, arrayRange[0] ) && that.maxwords().validate( val, arrayRange[1] );
37
+ }
38
+ , priority: 32
39
+ }
31
40
  }
41
+ , greaterthan: function () {
42
+ return {
43
+ validate: function ( val, elem, self ) {
44
+ self.options.validateIfUnchanged = true;
32
45
 
33
- , greaterthan: function ( val, elem, self ) {
34
- self.options.validateIfUnchanged = true;
35
-
36
- return new Number(val) > new Number($( elem ).val());
46
+ return new Number(val) > new Number($( elem ).val());
47
+ }
48
+ , priority: 32
49
+ }
37
50
  }
51
+ , lessthan: function () {
52
+ return {
53
+ validate: function ( val, elem, self ) {
54
+ self.options.validateIfUnchanged = true;
38
55
 
39
- , lessthan: function ( val, elem, self ) {
40
- self.options.validateIfUnchanged = true;
41
-
42
- return new Number(val) < new Number($( elem ).val());
56
+ return new Number(val) < new Number($( elem ).val());
57
+ }
58
+ , priority: 32
59
+ }
43
60
  }
44
-
45
- , beforedate: function ( val, elem, self) {
46
- return Date.parse(val) < Date.parse($(elem).val());
61
+ , beforedate: function () {
62
+ return {
63
+ validate: function ( val, elem, self) {
64
+ return Date.parse(val) < Date.parse($(elem).val());
65
+ }
66
+ , priority: 32
67
+ }
47
68
  }
48
-
49
- , afterdate: function ( val, elem, self) {
50
- return Date.parse($(elem).val()) < Date.parse(val);
69
+ , afterdate: function () {
70
+ return {
71
+ validate: function ( val, elem, self) {
72
+ return Date.parse($(elem).val()) < Date.parse(val);
73
+ }
74
+ , priority: 32
75
+ }
51
76
  }
77
+ , inlist: function () {
78
+ return {
79
+ validate: function ( val, list, self ) {
80
+ var delimiter = self.options.inlistDelimiter || ',';
81
+ var listItems = (list + "").split(new RegExp("\\s*\\" + delimiter + "\\s*"));
52
82
 
53
- , inlist: function ( val, list, self ) {
54
- var delimiter = self.options.inlistDelimiter || ',';
55
- var listItems = (list + "").split(new RegExp("\\s*\\" + delimiter + "\\s*"));
56
-
57
- return (listItems.indexOf(val.trim()) !== -1);
83
+ return (listItems.indexOf(val.trim()) !== -1);
84
+ }
85
+ , priority: 32
86
+ }
58
87
  }
59
-
60
- , luhn: function ( val, elem, self) {
61
- val = val.replace(/[ -]/g, '');
62
- var digit, n, sum, _j, _len1, _ref2;
63
- sum = 0;
64
- _ref2 = val.split('').reverse();
65
- for (n = _j = 0, _len1 = _ref2.length; _j < _len1; n = ++_j) {
66
- digit = _ref2[n];
67
- digit = +digit;
68
- if (n % 2) {
69
- digit *= 2;
70
- if (digit < 10) {
71
- sum += digit;
72
- } else {
73
- sum += digit - 9;
88
+ , luhn: function () {
89
+ return {
90
+ validate: function ( val, elem, self) {
91
+ val = val.replace(/[ -]/g, '');
92
+ var digit, n, sum, _j, _len1, _ref2;
93
+ sum = 0;
94
+ _ref2 = val.split('').reverse();
95
+ for (n = _j = 0, _len1 = _ref2.length; _j < _len1; n = ++_j) {
96
+ digit = _ref2[n];
97
+ digit = +digit;
98
+ if (n % 2) {
99
+ digit *= 2;
100
+ if (digit < 10) {
101
+ sum += digit;
102
+ } else {
103
+ sum += digit - 9;
104
+ }
105
+ } else {
106
+ sum += digit;
107
+ }
74
108
  }
75
- } else {
76
- sum += digit;
109
+ return sum % 10 === 0;
77
110
  }
111
+ , priority: 32
78
112
  }
79
- return sum % 10 === 0;
80
113
  }
81
-
82
- , americandate: function ( val, elem, self) {
83
- if(!/^([01]?[0-9])[\.\/-]([0-3]?[0-9])[\.\/-]([0-9]{4}|[0-9]{2})$/.test(val)) {
84
- return false;
85
- }
86
- var parts = val.split(/[.\/-]+/);
87
- var day = parseInt(parts[1], 10);
88
- var month = parseInt(parts[0], 10);
89
- var year = parseInt(parts[2], 10);
90
- if(year == 0 || month == 0 || month > 12) {
91
- return false;
92
- }
93
- var monthLength = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
94
- if(year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)) {
95
- monthLength[1] = 29;
114
+ , americandate: function () {
115
+ return {
116
+ validate: function ( val, elem, self) {
117
+ if ( !/^([01]?[0-9])[\.\/-]([0-3]?[0-9])[\.\/-]([0-9]{4}|[0-9]{2})$/.test( val ) ) {
118
+ return false;
119
+ }
120
+ var parts = val.split(/[.\/-]+/);
121
+ var day = parseInt(parts[1], 10);
122
+ var month = parseInt(parts[0], 10);
123
+ var year = parseInt(parts[2], 10);
124
+ if ( year == 0 || month == 0 || month > 12 ) {
125
+ return false;
126
+ }
127
+ var monthLength = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
128
+ if ( year % 400 == 0 || ( year % 100 != 0 && year % 4 == 0 ) ) {
129
+ monthLength[1] = 29;
130
+ }
131
+ return day > 0 && day <= monthLength[month - 1];
132
+ }
133
+ , priority: 32
96
134
  }
97
- return day > 0 && day <= monthLength[month - 1];
98
135
  }
99
136
  }
100
137
  , messages: {
@@ -25,9 +25,9 @@ window.ParsleyConfig = window.ParsleyConfig || {};
25
25
  , minlength: "Tato položka je příliš krátká. Musí mít %s nebo více znaků."
26
26
  , maxlength: "Tato položka je příliš dlouhá. Musí mít %s nebo méně znaků."
27
27
  , rangelength: "Tato položka je mimo rozsah. Musí být rozmezí %s a %s znaků."
28
- , mincheck: "Je nutné vybrat nejméně %s možností."
29
- , maxcheck: "Je nutné vybrat nejvýše %s možností."
30
- , rangecheck: "Je nutné vybrat %s až %s možností."
28
+ , mincheck: "Je nutné vybrat nejméně %s možností."
29
+ , maxcheck: "Je nutné vybrat nejvýše %s možností."
30
+ , rangecheck: "Je nutné vybrat %s až %s možností."
31
31
  , equalto: "Tato položka by měla být stejná."
32
32
 
33
33
  // parsley.extend ///////////////////////////////
@@ -36,10 +36,10 @@ window.ParsleyConfig = window.ParsleyConfig || {};
36
36
  , rangewords: "Tato položka musí obsahovat %s až %s slov."
37
37
  , greaterthan: "Tato položka musí být větší než %s."
38
38
  , lessthan: "Tato položka musí být menší než %s."
39
- , beforedate: "Toto datum musí být před %s."
40
- , afterdate: "Toto datum musí být po %s."
41
- , luhn: "Tato hodnota by měla projít Luhnovým testem."
42
- , americandate: "Toto datum by mělo být ve formátu MM/DD/YYYY."
39
+ , beforedate: "Toto datum musí být před %s."
40
+ , afterdate: "Toto datum musí být po %s."
41
+ , luhn: "Tato hodnota by měla projít Luhnovým testem."
42
+ , americandate: "Toto datum by mělo být ve formátu MM/DD/YYYY."
43
43
  }
44
44
  });
45
45
  }(window.jQuery || window.Zepto));
@@ -25,7 +25,6 @@ window.ParsleyConfig = window.ParsleyConfig || {};
25
25
  , maxlength: "Mae'r gwerth hwn yn rhy hir. Dylai fod yna %s neu llai o lythrennau"
26
26
  , rangelength: "Mae'r gwerth hyd yn annilys. Dylai fod yna rhwng %s a %s o lythrennau"
27
27
  , equalto: "Dylai'r gwerth hwn fod yr un fath."
28
-
29
28
 
30
29
  // parsley.extend ///////////////////////////////
31
30
  , minwords: "Dylai'r gwerth hwn cynnwys o leiaf %s gair."
@@ -36,4 +35,4 @@ window.ParsleyConfig = window.ParsleyConfig || {};
36
35
 
37
36
  }
38
37
  });
39
- }(window.jQuery || window.Zepto));
38
+ }(window.jQuery || window.Zepto));
@@ -32,7 +32,7 @@ window.ParsleyConfig = window.ParsleyConfig || {};
32
32
  , rangewords: "Questo valore deve contenere tra %s e %s parole."
33
33
  , greaterthan: "Questo valore deve essere maggiore di %s."
34
34
  , lessthan: "Questo valore deve essere minore di %s."
35
- , beforedate: "Questa data deve essere anteriore al %s."
35
+ , beforedate: "Questa data deve essere anteriore al %s."
36
36
  , afterdate: "Questa data deve essere posteriore al %s."
37
37
  , luhn: "Questo valore deve superare il test di Luhn."
38
38
  }
@@ -31,14 +31,14 @@ window.ParsleyConfig = window.ParsleyConfig || {};
31
31
  , equalto: "Denne verdien må være lik."
32
32
 
33
33
  // parsley.extend ///////////////////////////////
34
- , minwords: "Denne verdien må inneholde minst %s ord."
35
- , maxwords: "Denne verdien kan ikke inneholde mer enn %s ord."
36
- , rangewords: "Denne verdien må ha mellom %s og %s ord."
37
- , greaterthan: "Denne verdien må være større enn %s."
38
- , lessthan: "Denne verdien må være mindre enn %s."
39
- , beforedate: "Datoen må være før %s."
40
- , afterdate: "Datoen må være etter %s."
41
- , americandate: "Datoen må være på gyldig format (MM/DD/YYYY)."
34
+ , minwords: "Denne verdien må inneholde minst %s ord."
35
+ , maxwords: "Denne verdien kan ikke inneholde mer enn %s ord."
36
+ , rangewords: "Denne verdien må ha mellom %s og %s ord."
37
+ , greaterthan: "Denne verdien må være større enn %s."
38
+ , lessthan: "Denne verdien må være mindre enn %s."
39
+ , beforedate: "Datoen må være før %s."
40
+ , afterdate: "Datoen må være etter %s."
41
+ , americandate: "Datoen må være på gyldig format (MM/DD/YYYY)."
42
42
  }
43
43
  });
44
44
  }(window.jQuery || window.Zepto));
@@ -12,8 +12,8 @@ window.ParsleyConfig = window.ParsleyConfig || {};
12
12
  , number: "Поле должно быть числом."
13
13
  , digits: "Поле должно содержать только цифры."
14
14
  , dateIso: "Поле должно быть датой в формате (ГГГГ-ММ-ДД)."
15
- , alphanum: "Поле должно содержать только цифры и буквы"
16
- , phone: "Поле должно содержать корректный номер телефона"
15
+ , alphanum: "Поле должно содержать только цифры и буквы."
16
+ , phone: "Поле должно содержать корректный номер телефона."
17
17
  }
18
18
  , notnull: "Поле должно быть не нулевым."
19
19
  , notblank: "Поле не должно быть пустым."
@@ -4,7 +4,7 @@ window.ParsleyConfig = window.ParsleyConfig || {};
4
4
  window.ParsleyConfig = $.extend( true, {}, window.ParsleyConfig, {
5
5
  messages: {
6
6
  // parsley //////////////////////////////////////
7
- defaultMessage: "Thông tin này không hợp lệ."
7
+ defaultMessage: "Thông tin này không hợp lệ."
8
8
  , type: {
9
9
  email: "Email không hợp lệ."
10
10
  , url: "Url không hợp lệ."
@@ -33,4 +33,3 @@ window.ParsleyConfig = window.ParsleyConfig || {};
33
33
  }
34
34
  });
35
35
  }(window.jQuery || window.Zepto));
36
-
@@ -64,170 +64,232 @@
64
64
  * @type {Object}
65
65
  */
66
66
  , validators: {
67
- notnull: function ( val ) {
68
- return val.length > 0;
67
+ notnull: function () {
68
+ return {
69
+ validate: function ( val ) {
70
+ return val.length > 0;
71
+ }
72
+ , priority: 2
73
+ }
69
74
  }
70
-
71
- , notblank: function ( val ) {
72
- return 'string' === typeof val && '' !== val.replace( /^\s+/g, '' ).replace( /\s+$/g, '' );
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
+ }
73
82
  }
74
-
75
- // Works on all inputs. val is object for checkboxes
76
- , required: function ( val ) {
77
-
78
- // for checkboxes and select multiples. Check there is at least one required value
79
- if ( 'object' === typeof val ) {
80
- for ( var i in val ) {
81
- if ( this.required( val[ i ] ) ) {
82
- return true;
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;
83
96
  }
84
- }
85
97
 
86
- return false;
98
+ return that.notnull().validate( val ) && that.notblank().validate( val );
99
+ }
100
+ , priority: 512
87
101
  }
88
-
89
- return this.notnull( val ) && this.notblank( val );
90
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
+ }
91
136
 
92
- , type: function ( val, type ) {
93
- var regExp;
94
-
95
- switch ( type ) {
96
- case 'number':
97
- regExp = /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/;
98
- break;
99
- case 'digits':
100
- regExp = /^\d+$/;
101
- break;
102
- case 'alphanum':
103
- regExp = /^\w+$/;
104
- break;
105
- case 'email':
106
- 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;
107
- break;
108
- case 'url':
109
- val = new RegExp( '(https?|s?ftp|git)', 'i' ).test( val ) ? val : 'http://' + val;
110
- /* falls through */
111
- case 'urlstrict':
112
- 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;
113
- break;
114
- case 'dateIso':
115
- regExp = /^(\d{4})\D?(0[1-9]|1[0-2])\D?([12]\d|0[1-9]|3[01])$/;
116
- break;
117
- case 'phone':
118
- regExp = /^((\+\d{1,3}(-| )?\(?\d\)?(-| )?\d{1,5})|(\(?\d{2,6}\)?))(-| )?(\d{3,4})(-| )?(\d{4})(( x| ext)\d{1,5}){0,1}$/;
119
- break;
120
- default:
121
- return false;
137
+ // test regExp if not null
138
+ return '' !== val ? regExp.test( val ) : false;
139
+ }
140
+ , priority: 256
122
141
  }
123
-
124
- // test regExp if not null
125
- return '' !== val ? regExp.test( val ) : false;
126
142
  }
127
-
128
- , regexp: function ( val, regExp, self ) {
129
- return new RegExp( regExp, self.options.regexpFlag || '' ).test( val );
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
+ }
130
150
  }
131
-
132
- , minlength: function ( val, min ) {
133
- return val.length >= min;
151
+ , minlength: function () {
152
+ return {
153
+ validate: function ( val, min ) {
154
+ return val.length >= min;
155
+ }
156
+ , priority: 32
157
+ }
134
158
  }
135
-
136
- , maxlength: function ( val, max ) {
137
- return val.length <= max;
159
+ , maxlength: function () {
160
+ return {
161
+ validate: function ( val, max ) {
162
+ return val.length <= max;
163
+ }
164
+ , priority: 32
165
+ }
138
166
  }
139
-
140
- , rangelength: function ( val, arrayRange ) {
141
- return this.minlength( val, arrayRange[ 0 ] ) && this.maxlength( val, arrayRange[ 1 ] );
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
+ }
142
175
  }
143
-
144
- , min: function ( val, min ) {
145
- return Number( val ) >= min;
176
+ , min: function () {
177
+ return {
178
+ validate: function ( val, min ) {
179
+ return Number( val ) >= min;
180
+ }
181
+ , priority: 32
182
+ }
146
183
  }
147
-
148
- , max: function ( val, max ) {
149
- return Number( val ) <= max;
184
+ , max: function () {
185
+ return {
186
+ validate: function ( val, max ) {
187
+ return Number( val ) <= max;
188
+ }
189
+ , priority: 32
190
+ }
150
191
  }
151
-
152
- , range: function ( val, arrayRange ) {
153
- return val >= arrayRange[ 0 ] && val <= arrayRange[ 1 ];
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
+ }
154
200
  }
155
-
156
- , equalto: function ( val, elem, self ) {
157
- self.options.validateIfUnchanged = true;
158
-
159
- return val === $( elem ).val();
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
+ }
160
209
  }
210
+ , remote: function () {
211
+ return {
212
+ validate: function ( val, url, self ) {
213
+ var result = null
214
+ , data = {}
215
+ , dataType = {};
161
216
 
162
- , remote: function ( val, url, self ) {
163
- var result = null
164
- , data = {}
165
- , dataType = {};
217
+ data[ self.$element.attr( 'name' ) ] = val;
166
218
 
167
- data[ self.$element.attr( 'name' ) ] = val;
219
+ if ( 'undefined' !== typeof self.options.remoteDatatype )
220
+ dataType = { dataType: self.options.remoteDatatype };
168
221
 
169
- if ( 'undefined' !== typeof self.options.remoteDatatype ) {
170
- dataType = { dataType: self.options.remoteDatatype };
171
- }
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
+ }
172
227
 
173
- var manage = function ( isConstraintValid, message ) {
174
- // remove error message if we got a server message, different from previous message
175
- if ( 'undefined' !== typeof message && 'undefined' !== typeof self.Validator.messages.remote && message !== self.Validator.messages.remote ) {
176
- $( self.ulError + ' .remote' ).remove();
177
- }
178
-
179
- self.updtConstraint( { name: 'remote', valid: isConstraintValid }, message );
180
- self.manageValidationResult();
181
- };
228
+ self.updtConstraint( { name: 'remote', valid: isConstraintValid }, message );
229
+ self.manageValidationResult();
230
+ };
182
231
 
183
- // transform string response into object
184
- var handleResponse = function ( response ) {
185
- if ( 'object' === typeof response ) {
186
- return response;
187
- }
232
+ // transform string response into object
233
+ var handleResponse = function ( response ) {
234
+ if ( 'object' === typeof response ) {
235
+ return response;
236
+ }
188
237
 
189
- try {
190
- response = $.parseJSON( response );
191
- } catch ( err ) {}
238
+ try {
239
+ response = $.parseJSON( response );
240
+ } catch ( err ) {}
192
241
 
193
- return response;
194
- }
242
+ return response;
243
+ }
195
244
 
196
- var manageErrorMessage = function ( response ) {
197
- return 'object' === typeof response && null !== response ? ( 'undefined' !== typeof response.error ? response.error : ( 'undefined' !== typeof response.message ? response.message : null ) ) : null;
198
- }
245
+ var manageErrorMessage = function ( response ) {
246
+ return 'object' === typeof response && null !== response ? ( 'undefined' !== typeof response.error ? response.error : ( 'undefined' !== typeof response.message ? response.message : null ) ) : null;
247
+ }
199
248
 
200
- $.ajax( $.extend( {}, {
201
- url: url
202
- , data: data
203
- , type: self.options.remoteMethod || 'GET'
204
- , success: function ( response ) {
205
- response = handleResponse( response );
206
- manage( 1 === response || true === response || ( 'object' === typeof response && null !== response && 'undefined' !== typeof response.success ), manageErrorMessage( response )
207
- );
208
- }
209
- , error: function ( response ) {
210
- response = handleResponse( response );
211
- manage( false, manageErrorMessage( response ) );
249
+ $.ajax( $.extend( {}, {
250
+ url: url
251
+ , data: data
252
+ , type: self.options.remoteMethod || 'GET'
253
+ , success: function ( response ) {
254
+ response = handleResponse( response );
255
+ manage( 1 === response || true === response || ( 'object' === typeof response && null !== response && 'undefined' !== typeof response.success ), manageErrorMessage( response )
256
+ );
257
+ }
258
+ , error: function ( response ) {
259
+ response = handleResponse( response );
260
+ manage( false, manageErrorMessage( response ) );
261
+ }
262
+ }, dataType ) );
263
+
264
+ return result;
212
265
  }
213
- }, dataType ) );
214
-
215
- return result;
266
+ , priority: 64
267
+ }
216
268
  }
217
269
 
218
270
  /**
219
271
  * Aliases for checkboxes constraints
220
272
  */
221
- , mincheck: function ( obj, val ) {
222
- return this.minlength( obj, val );
273
+ , mincheck: function () {
274
+ var that = this;
275
+ return {
276
+ validate: function ( obj, val ) { return that.minlength().validate( obj, val ) }
277
+ , priority: 32
278
+ }
223
279
  }
224
-
225
- , maxcheck: function ( obj, val ) {
226
- return this.maxlength( obj, val);
280
+ , maxcheck: function () {
281
+ var that = this;
282
+ return {
283
+ validate: function ( obj, val ) { return that.maxlength().validate( obj, val ) }
284
+ , priority: 32
285
+ }
227
286
  }
228
-
229
- , rangecheck: function ( obj, arrayRange ) {
230
- return this.rangelength( obj, arrayRange );
287
+ , rangecheck: function () {
288
+ var that = this;
289
+ return {
290
+ validate: function ( obj, arrayRange ) { return that.rangelength().validate( obj, arrayRange ) }
291
+ , priority: 32
292
+ }
231
293
  }
232
294
  }
233
295
 
@@ -236,9 +298,9 @@
236
298
  */
237
299
  , init: function ( options ) {
238
300
  var customValidators = options.validators
239
- , customMessages = options.messages;
301
+ , customMessages = options.messages
302
+ , key;
240
303
 
241
- var key;
242
304
  for ( key in customValidators ) {
243
305
  this.addValidator(key, customValidators[ key ]);
244
306
  }
@@ -274,7 +336,7 @@
274
336
  *
275
337
  * @method addValidator
276
338
  * @param {String} name Validator name. Will automatically bindable through data-name=''
277
- * @param {Function} fn Validator function. Must return {Boolean}
339
+ * @param {Function} fn Validator function. Must return { validator: fn(), priority: int }
278
340
  */
279
341
  , addValidator: function ( name, fn ) {
280
342
  this.validators[ name ] = fn;
@@ -307,6 +369,147 @@
307
369
  }
308
370
  };
309
371
 
372
+ var ParsleyUI = function ( ParsleyInstance ) {
373
+ this.init( ParsleyInstance );
374
+ };
375
+
376
+ ParsleyUI.prototype = {
377
+
378
+ constructor: ParsleyUI
379
+
380
+ , init: function ( ParsleyInstance ) {
381
+ this.ParsleyInstance = ParsleyInstance;
382
+ this.hash = ParsleyInstance.hash;
383
+ this.options = this.ParsleyInstance.options;
384
+ this.errorClassHandler = this.options.errors.classHandler( this.ParsleyInstance.element, this.ParsleyInstance.isRadioOrCheckbox ) || this.ParsleyInstance.$element;
385
+ this.ulErrorManagement();
386
+ }
387
+
388
+ /**
389
+ * Manage ul error Container
390
+ *
391
+ * @private
392
+ * @method ulErrorManagement
393
+ */
394
+ , ulErrorManagement: function () {
395
+ this.ulError = '#' + this.hash;
396
+ this.ulTemplate = $( this.options.errors.errorsWrapper ).attr( 'id', this.hash ).addClass( 'parsley-error-list' );
397
+ }
398
+
399
+ /**
400
+ * Remove li / ul error
401
+ *
402
+ * @method removeError
403
+ * @param {String} constraintName Method Name
404
+ */
405
+ , removeError: function ( constraintName ) {
406
+ var liError = this.ulError + ' .' + constraintName
407
+ , that = this;
408
+
409
+ this.options.animate ? $( liError ).fadeOut( this.options.animateDuration, function () {
410
+ $( this ).remove();
411
+
412
+ if ( that.ulError && $( that.ulError ).children().length === 0 ) {
413
+ that.removeErrors();
414
+ } } ) : $( liError ).remove();
415
+ }
416
+
417
+ /**
418
+ * Add li error
419
+ *
420
+ * @method addError
421
+ * @param {Object} { minlength: "error message for minlength constraint" }
422
+ */
423
+ , addError: function ( error ) {
424
+ for ( var constraint in error ) {
425
+ var liTemplate = $( this.options.errors.errorElem ).addClass( constraint );
426
+
427
+ $( this.ulError ).append( this.options.animate ? $( liTemplate ).html( error[ constraint ] ).hide().fadeIn( this.options.animateDuration ) : $( liTemplate ).html( error[ constraint ] ) );
428
+ }
429
+ }
430
+
431
+ /**
432
+ * Remove all ul / li errors
433
+ *
434
+ * @method removeErrors
435
+ */
436
+ , removeErrors: function () {
437
+ this.options.animate ? $( this.ulError ).fadeOut( this.options.animateDuration, function () { $( this ).remove(); } ) : $( this.ulError ).remove();
438
+ }
439
+
440
+ /**
441
+ * Remove ul errors and parsley error or success classes
442
+ *
443
+ * @method reset
444
+ */
445
+ , reset: function () {
446
+ this.ParsleyInstance.valid = null;
447
+ this.removeErrors();
448
+ this.ParsleyInstance.validatedOnce = false;
449
+ this.errorClassHandler.removeClass( this.options.successClass ).removeClass( this.options.errorClass );
450
+
451
+ for ( var constraint in this.constraints ) {
452
+ this.constraints[ constraint ].valid = null;
453
+ }
454
+
455
+ return this;
456
+ }
457
+
458
+ /**
459
+ * Add li / ul errors messages
460
+ *
461
+ * @method manageError
462
+ * @param {Object} constraint
463
+ */
464
+ , manageError: function ( constraint ) {
465
+ // display ulError container if it has been removed previously (or never shown)
466
+ if ( !$( this.ulError ).length ) {
467
+ this.manageErrorContainer();
468
+ }
469
+
470
+ // TODO: refacto properly
471
+ // if required constraint but field is not null, do not display
472
+ if ( 'required' === constraint.name && null !== this.ParsleyInstance.getVal() && this.ParsleyInstance.getVal().length > 0 ) {
473
+ return;
474
+ // if empty required field and non required constraint fails, do not display
475
+ } else if ( this.ParsleyInstance.isRequired && 'required' !== constraint.name && ( null === this.ParsleyInstance.getVal() || 0 === this.ParsleyInstance.getVal().length ) ) {
476
+ this.removeError( constraint.name );
477
+ return;
478
+ }
479
+
480
+ // TODO: refacto error name w/ proper & readable function
481
+ var constraintName = constraint.name
482
+ , liClass = false !== this.options.errorMessage ? 'custom-error-message' : constraintName
483
+ , liError = {}
484
+ , message = false !== this.options.errorMessage ? this.options.errorMessage : ( constraint.name === 'type' ?
485
+ this.ParsleyInstance.Validator.messages[ constraintName ][ constraint.requirements ] : ( 'undefined' === typeof this.ParsleyInstance.Validator.messages[ constraintName ] ?
486
+ this.ParsleyInstance.Validator.messages.defaultMessage : this.ParsleyInstance.Validator.formatMesssage( this.ParsleyInstance.Validator.messages[ constraintName ], constraint.requirements ) ) );
487
+
488
+ // add liError if not shown. Do not add more than once custom errorMessage if exist
489
+ if ( !$( this.ulError + ' .' + liClass ).length ) {
490
+ liError[ liClass ] = message;
491
+ this.addError( liError );
492
+ }
493
+ }
494
+
495
+ /**
496
+ * Create ul error container
497
+ *
498
+ * @method manageErrorContainer
499
+ */
500
+ , manageErrorContainer: function () {
501
+ var errorContainer = this.options.errorContainer || this.options.errors.container( this.element, this.ParsleyInstance.isRadioOrCheckbox )
502
+ , ulTemplate = this.options.animate ? this.ulTemplate.show() : this.ulTemplate;
503
+
504
+ if ( 'undefined' !== typeof errorContainer ) {
505
+ $( errorContainer ).append( ulTemplate );
506
+ return;
507
+ }
508
+
509
+ !this.ParsleyInstance.isRadioOrCheckbox ? this.ParsleyInstance.$element.after( ulTemplate ) : this.ParsleyInstance.$element.parent().after( ulTemplate );
510
+ }
511
+ };
512
+
310
513
  /**
311
514
  * ParsleyField class manage each form field inside a validated Parsley form.
312
515
  * Returns if field valid or not depending on its value and constraints
@@ -317,7 +520,6 @@
317
520
  */
318
521
  var ParsleyField = function ( element, options, type ) {
319
522
  this.options = options;
320
- this.Validator = new Validator( options );
321
523
 
322
524
  // if type is ParsleyFieldMultiple, just return this. used for clone
323
525
  if ( type === 'ParsleyFieldMultiple' ) {
@@ -345,6 +547,7 @@
345
547
  this.validatedOnce = false;
346
548
  this.$element = $( element );
347
549
  this.val = this.$element.val();
550
+ this.Validator = new Validator( this.options );
348
551
  this.isRequired = false;
349
552
  this.constraints = {};
350
553
 
@@ -352,11 +555,10 @@
352
555
  if ( 'undefined' === typeof this.isRadioOrCheckbox ) {
353
556
  this.isRadioOrCheckbox = false;
354
557
  this.hash = this.generateHash();
355
- this.errorClassHandler = this.options.errors.classHandler( element, this.isRadioOrCheckbox ) || this.$element;
356
558
  }
357
559
 
358
560
  // error ul dom management done only once at init
359
- this.ulErrorManagement();
561
+ this.UI = new ParsleyUI( this );
360
562
 
361
563
  // bind some html5 properties
362
564
  this.bindHtml5Constraints();
@@ -425,7 +627,7 @@
425
627
  for ( var constraint in this.options ) {
426
628
  var addConstraint = {};
427
629
  addConstraint[ constraint ] = this.options[ constraint ];
428
- this.addConstraint( addConstraint, true );
630
+ this.addConstraint( addConstraint, true, false );
429
631
  }
430
632
  }
431
633
 
@@ -435,7 +637,7 @@
435
637
  * @method addConstraint
436
638
  * @param {Object} constraint { name: requirements }
437
639
  */
438
- , addConstraint: function ( constraint, doNotUpdateValidationEvents ) {
640
+ , addConstraint: function ( constraint, doNotUpdateValidationEvents, sort ) {
439
641
  for ( var name in constraint ) {
440
642
  name = name.toLowerCase();
441
643
 
@@ -601,7 +803,11 @@
601
803
  * @returns {String} val
602
804
  */
603
805
  , getVal: function () {
604
- return this.$element.data('value') || this.$element.val();
806
+ if ('undefined' !== typeof this.$element.domApi()[ 'value' ]) {
807
+ return this.$element.domApi()[ 'value' ];
808
+ }
809
+
810
+ return this.$element.val();
605
811
  }
606
812
 
607
813
  /**
@@ -683,9 +889,14 @@
683
889
  return null;
684
890
  }
685
891
 
892
+ // do not validate excluded fields
893
+ if ( this.$element.is( this.options.excluded ) ) {
894
+ return null;
895
+ }
896
+
686
897
  // reset Parsley validation if onFieldValidate returns true, or if field is empty and not required
687
898
  if ( this.options.listeners.onFieldValidate( this.element, this ) || ( '' === val && !this.isRequired ) ) {
688
- this.reset();
899
+ this.UI.reset();
689
900
  return null;
690
901
  }
691
902
 
@@ -730,23 +941,25 @@
730
941
  var valid = null;
731
942
 
732
943
  for ( var constraint in this.constraints ) {
733
- var result = this.Validator.validators[ this.constraints[ constraint ].name ]( this.val, this.constraints[ constraint ].requirements, this );
944
+ var result = this.Validator.validators[ this.constraints[ constraint ].name ]().validate( this.val, this.constraints[ constraint ].requirements, this );
734
945
 
735
946
  if ( false === result ) {
736
947
  valid = false;
737
948
  this.constraints[ constraint ].valid = valid;
738
- this.options.listeners.onFieldError( this.element, this.constraints, this );
739
949
  } else if ( true === result ) {
740
950
  this.constraints[ constraint ].valid = true;
741
951
  valid = false !== valid;
742
-
743
- // if onFieldSuccess returns (bool) false, consider that field si invalid
744
- if (false === this.options.listeners.onFieldSuccess( this.element, this.constraints, this )) {
745
- valid = false;
746
- }
747
952
  }
748
953
  }
749
954
 
955
+ // listeners' ballet
956
+ if (false === valid) {
957
+ this.options.listeners.onFieldError( this.element, this.constraints, this );
958
+ } else if (true === valid && false === this.options.listeners.onFieldSuccess( this.element, this.constraints, this )) {
959
+ // if onFieldSuccess returns (bool) false, consider that field si invalid
960
+ valid = false;
961
+ }
962
+
750
963
  return valid;
751
964
  }
752
965
 
@@ -760,14 +973,16 @@
760
973
  * @return {Boolean} Is field valid or not
761
974
  */
762
975
  , manageValidationResult: function () {
763
- var valid = null;
976
+ var valid = null
977
+ , errors = [];
764
978
 
765
979
  for ( var constraint in this.constraints ) {
766
980
  if ( false === this.constraints[ constraint ].valid ) {
767
- this.manageError( this.constraints[ constraint ] );
981
+ errors.push( this.constraints[ constraint ]);
982
+ // this.UI.manageError( this.constraints[ constraint ] );
768
983
  valid = false;
769
984
  } else if ( true === this.constraints[ constraint ].valid ) {
770
- this.removeError( this.constraints[ constraint ].name );
985
+ this.UI.removeError( this.constraints[ constraint ].name );
771
986
  valid = false !== valid;
772
987
  }
773
988
  }
@@ -775,149 +990,38 @@
775
990
  this.valid = valid;
776
991
 
777
992
  if ( true === this.valid ) {
778
- this.removeErrors();
779
- this.errorClassHandler.removeClass( this.options.errorClass ).addClass( this.options.successClass );
993
+ this.UI.removeErrors();
994
+ this.UI.errorClassHandler.removeClass( this.options.errorClass ).addClass( this.options.successClass );
995
+
780
996
  return true;
781
997
  } else if ( false === this.valid ) {
782
- this.errorClassHandler.removeClass( this.options.successClass ).addClass( this.options.errorClass );
998
+ if ( true === this.options.priorityEnabled ) {
999
+ var maxPriority = 0, constraint;
1000
+ for ( var i = 0; i < errors.length; i++ )
1001
+ if ( this.Validator.validators[ errors[ i ].name ]().priority > maxPriority )
1002
+ constraint = errors[ i ];
1003
+ this.UI.manageError( constraint );
1004
+ } else {
1005
+ for ( var i = 0; i < errors.length; i++ )
1006
+ this.UI.manageError( errors[ i ] );
1007
+ }
1008
+
1009
+ this.UI.errorClassHandler.removeClass( this.options.successClass ).addClass( this.options.errorClass );
783
1010
  return false;
784
1011
  }
785
1012
 
786
1013
  // remove li error, and ul error if no more li inside
787
- if ( this.ulError && $( this.ulError ).children().length === 0 ) {
788
- this.removeErrors();
1014
+ if ( this.UI.ulError && $( this.ulError ).children().length === 0 ) {
1015
+ this.UI.removeErrors();
789
1016
  }
790
1017
 
791
1018
  return valid;
792
1019
  }
793
1020
 
794
- /**
795
- * Manage ul error Container
796
- *
797
- * @private
798
- * @method ulErrorManagement
799
- */
800
- , ulErrorManagement: function () {
801
- this.ulError = '#' + this.hash;
802
- this.ulTemplate = $( this.options.errors.errorsWrapper ).attr( 'id', this.hash ).addClass( 'parsley-error-list' );
803
- }
804
-
805
- /**
806
- * Remove li / ul error
807
- *
808
- * @method removeError
809
- * @param {String} constraintName Method Name
810
- */
811
- , removeError: function ( constraintName ) {
812
- var liError = this.ulError + ' .' + constraintName
813
- , that = this;
814
-
815
- this.options.animate ? $( liError ).fadeOut( this.options.animateDuration, function () {
816
- $( this ).remove();
817
-
818
- if ( that.ulError && $( that.ulError ).children().length === 0 ) {
819
- that.removeErrors();
820
- } } ) : $( liError ).remove();
821
- }
822
-
823
- /**
824
- * Add li error
825
- *
826
- * @method addError
827
- * @param {Object} { minlength: "error message for minlength constraint" }
828
- */
829
- , addError: function ( error ) {
830
- for ( var constraint in error ) {
831
- var liTemplate = $( this.options.errors.errorElem ).addClass( constraint );
832
-
833
- $( this.ulError ).append( this.options.animate ? $( liTemplate ).html( error[ constraint ] ).hide().fadeIn( this.options.animateDuration ) : $( liTemplate ).html( error[ constraint ] ) );
834
- }
835
- }
836
-
837
- /**
838
- * Remove all ul / li errors
839
- *
840
- * @method removeErrors
841
- */
842
- , removeErrors: function () {
843
- this.options.animate ? $( this.ulError ).fadeOut( this.options.animateDuration, function () { $( this ).remove(); } ) : $( this.ulError ).remove();
844
- }
845
-
846
- /**
847
- * Remove ul errors and parsley error or success classes
848
- *
849
- * @method reset
850
- */
851
- , reset: function () {
852
- this.valid = null;
853
- this.removeErrors();
854
- this.validatedOnce = false;
855
- this.errorClassHandler.removeClass( this.options.successClass ).removeClass( this.options.errorClass );
856
-
857
- for ( var constraint in this.constraints ) {
858
- this.constraints[ constraint ].valid = null;
859
- }
860
-
861
- return this;
862
- }
863
-
864
- /**
865
- * Add li / ul errors messages
866
- *
867
- * @method manageError
868
- * @param {Object} constraint
869
- */
870
- , manageError: function ( constraint ) {
871
- // display ulError container if it has been removed previously (or never shown)
872
- if ( !$( this.ulError ).length ) {
873
- this.manageErrorContainer();
874
- }
875
-
876
- // TODO: refacto properly
877
- // if required constraint but field is not null, do not display
878
- if ( 'required' === constraint.name && null !== this.getVal() && this.getVal().length > 0 ) {
879
- return;
880
- // if empty required field and non required constraint fails, do not display
881
- } else if ( this.isRequired && 'required' !== constraint.name && ( null === this.getVal() || 0 === this.getVal().length ) ) {
882
- return;
883
- }
884
-
885
- // TODO: refacto error name w/ proper & readable function
886
- var constraintName = constraint.name
887
- , liClass = false !== this.options.errorMessage ? 'custom-error-message' : constraintName
888
- , liError = {}
889
- , message = false !== this.options.errorMessage ? this.options.errorMessage : ( constraint.name === 'type' ?
890
- this.Validator.messages[ constraintName ][ constraint.requirements ] : ( 'undefined' === typeof this.Validator.messages[ constraintName ] ?
891
- this.Validator.messages.defaultMessage : this.Validator.formatMesssage( this.Validator.messages[ constraintName ], constraint.requirements ) ) );
892
-
893
- // add liError if not shown. Do not add more than once custom errorMessage if exist
894
- if ( !$( this.ulError + ' .' + liClass ).length ) {
895
- liError[ liClass ] = message;
896
- this.addError( liError );
897
- }
898
- }
899
-
900
- /**
901
- * Create ul error container
902
- *
903
- * @method manageErrorContainer
904
- */
905
- , manageErrorContainer: function () {
906
- var errorContainer = this.options.errorContainer || this.options.errors.container( this.element, this.isRadioOrCheckbox )
907
- , ulTemplate = this.options.animate ? this.ulTemplate.show() : this.ulTemplate;
908
-
909
- if ( 'undefined' !== typeof errorContainer ) {
910
- $( errorContainer ).append( ulTemplate );
911
- return;
912
- }
913
-
914
- !this.isRadioOrCheckbox ? this.$element.after( ulTemplate ) : this.$element.parent().after( ulTemplate );
915
- }
916
-
917
1021
  /**
918
1022
  * Add custom listeners
919
1023
  *
920
- * @param {Object} { listener: function () {} }, eg { onFormSubmit: function ( valid, event, focus ) { ... } }
1024
+ * @param {Object} { listener: function () {} }, eg { onFormValidate: function ( valid, event, focus ) { ... } }
921
1025
  */
922
1026
  , addListener: function ( object ) {
923
1027
  for ( var listener in object ) {
@@ -933,7 +1037,8 @@
933
1037
  */
934
1038
  , destroy: function () {
935
1039
  this.$element.removeClass( 'parsley-validated' );
936
- this.reset().$element.off( '.' + this.type ).removeData( this.type );
1040
+ this.UI.reset();
1041
+ this.$element.off( '.' + this.type ).removeData( this.type );
937
1042
  }
938
1043
  };
939
1044
 
@@ -969,7 +1074,7 @@
969
1074
  this.$element = $( element );
970
1075
  this.group = options.group || false;
971
1076
  this.hash = this.getName();
972
- this.siblings = this.group ? '[data-group="' + this.group + '"]' : 'input[name="' + this.$element.attr( 'name' ) + '"]';
1077
+ this.siblings = this.group ? '[parsley-group="' + this.group + '"]' : 'input[name="' + this.$element.attr( 'name' ) + '"]';
973
1078
  this.isRadioOrCheckbox = true;
974
1079
  this.isRadio = this.$element.is( 'input[type=radio]' );
975
1080
  this.isCheckbox = this.$element.is( 'input[type=checkbox]' );
@@ -1006,7 +1111,7 @@
1006
1111
  }
1007
1112
 
1008
1113
  if ( 'undefined' === typeof this.$element.attr( 'name' ) ) {
1009
- throw "A radio / checkbox input must have a data-group attribute or a name to be Parsley validated !";
1114
+ throw "A radio / checkbox input must have a parsley-group attribute or a name to be Parsley validated !";
1010
1115
  }
1011
1116
 
1012
1117
  return 'parsley-' + this.$element.attr( 'name' ).replace( /(:|\.|\[|\])/g, '' );
@@ -1097,7 +1202,7 @@
1097
1202
  /**
1098
1203
  * Add custom listeners
1099
1204
  *
1100
- * @param {Object} { listener: function () {} }, eg { onFormSubmit: function ( valid, event, focus ) { ... } }
1205
+ * @param {Object} { listener: function () {} }, eg { onFormValidate: function ( valid, event, focus ) { ... } }
1101
1206
  */
1102
1207
  , addListener: function ( object ) {
1103
1208
  for ( var listener in object ) {
@@ -1152,7 +1257,7 @@
1152
1257
 
1153
1258
  /**
1154
1259
  * Process each form field validation
1155
- * Display errors, call custom onFormSubmit() function
1260
+ * Display errors, call custom onFormValidate() function
1156
1261
  *
1157
1262
  * @method validate
1158
1263
  * @param {Object} event jQuery Event
@@ -1174,13 +1279,29 @@
1174
1279
 
1175
1280
  // form is invalid, focus an error field depending on focus policy
1176
1281
  if ( this.focusedField && !valid ) {
1177
- this.focusedField.focus();
1282
+ // Scroll smoothly
1283
+ if ( this.options.scrollDuration > 0 ) {
1284
+ var that = this,
1285
+ top = this.focusedField.offset().top - $( window ).height() / 2; // Center the window on the field
1286
+
1287
+ $( 'html, body' ).animate( {
1288
+ scrollTop: top
1289
+ },
1290
+ this.options.scrollDuration,
1291
+ function () {
1292
+ that.focusedField.focus();
1293
+ }
1294
+ );
1295
+ // Just focus on the field and let the browser do the rest
1296
+ } else {
1297
+ this.focusedField.focus();
1298
+ }
1178
1299
  }
1179
1300
 
1180
- // if onFormSubmit returns (bool) false, form won't be submitted, even if valid
1181
- var onFormSubmit = this.options.listeners.onFormSubmit( valid, event, this );
1182
- if ('undefined' !== typeof onFormSubmit) {
1183
- return onFormSubmit;
1301
+ // if onFormValidate returns (bool) false, form won't be submitted, even if valid
1302
+ var onFormValidate = this.options.listeners.onFormValidate( valid, event, this );
1303
+ if ('undefined' !== typeof onFormValidate) {
1304
+ return onFormValidate;
1184
1305
  }
1185
1306
 
1186
1307
  return valid;
@@ -1227,7 +1348,7 @@
1227
1348
  */
1228
1349
  , reset: function () {
1229
1350
  for ( var item = 0; item < this.items.length; item++ ) {
1230
- this.items[ item ].reset();
1351
+ this.items[ item ].UI.reset();
1231
1352
  }
1232
1353
  }
1233
1354
  };
@@ -1243,7 +1364,7 @@
1243
1364
  * @return {Mixed} public class method return
1244
1365
  */
1245
1366
  $.fn.parsley = function ( option, fn ) {
1246
- var options = $.extend( true, {}, $.fn.parsley.defaults, 'undefined' !== typeof window.ParsleyConfig ? window.ParsleyConfig : {}, option, this.data() )
1367
+ var options = $.extend( true, {}, $.fn.parsley.defaults, 'undefined' !== typeof window.ParsleyConfig ? window.ParsleyConfig : {}, option, this.domApi() )
1247
1368
  , newInstance = null;
1248
1369
 
1249
1370
  function bind ( self, type ) {
@@ -1279,12 +1400,12 @@
1279
1400
  }
1280
1401
 
1281
1402
  // if a form elem is given, bind all its input children
1282
- if ( $( this ).is( 'form' ) || true === $( this ).data( 'bind' ) ) {
1403
+ if ( $( this ).is( 'form' ) || 'undefined' !== typeof $( this ).domApi()[ 'bind' ] ) {
1283
1404
  newInstance = bind ( $( this ), 'parsleyForm' );
1284
1405
 
1285
1406
  // if it is a Parsley supported single element, bind it too, except inputs type hidden
1286
1407
  // add here a return instance, cuz' we could call public methods on single elems with data[ option ]() above
1287
- } else if ( $( this ).is( options.inputs ) && !$( this ).is( options.excluded ) ) {
1408
+ } else if ( $( this ).is( options.inputs ) ) {
1288
1409
  newInstance = bind( $( this ), !$( this ).is( 'input[type=radio], input[type=checkbox]' ) ? 'parsleyField' : 'parsleyFieldMultiple' );
1289
1410
  }
1290
1411
 
@@ -1293,6 +1414,67 @@
1293
1414
 
1294
1415
  $.fn.parsley.Constructor = ParsleyForm;
1295
1416
 
1417
+ /* PARSLEY auto-binding
1418
+ * =================================================== */
1419
+ $( window ).on( 'load', function () {
1420
+ $( '[parsley-validate]' ).each( function () {
1421
+ $( this ).parsley();
1422
+ } );
1423
+ } );
1424
+
1425
+ /* PARSLEY DOM API
1426
+ * =================================================== */
1427
+ $.fn.domApi = function () {
1428
+ var obj = {};
1429
+ $.each( this[0].attributes, function () {
1430
+ if ( this.specified && /^parsley-/i.test( this.name ) ) {
1431
+ obj[ camelize( this.name.replace( 'parsley-', '' ) ) ] = deserializeValue( this.value );
1432
+ }
1433
+ } );
1434
+
1435
+ return obj;
1436
+ };
1437
+
1438
+ // Zepto deserializeValue function
1439
+ // "true" => true
1440
+ // "false" => false
1441
+ // "null" => null
1442
+ // "42" => 42
1443
+ // "42.5" => 42.5
1444
+ // JSON => parse if valid
1445
+ // String => self
1446
+ var deserializeValue = function( value ) {
1447
+ var num
1448
+ try {
1449
+ return value ?
1450
+ value == "true" ||
1451
+ ( value == "false" ? false :
1452
+ value == "null" ? null :
1453
+ !isNaN( num = Number( value ) ) ? num :
1454
+ /^[\[\{]/.test( value ) ? $.parseJSON( value ) :
1455
+ value )
1456
+ : value;
1457
+ } catch ( e ) {
1458
+ return value;
1459
+ }
1460
+ };
1461
+
1462
+ // Zepto camelize function
1463
+ var camelize = function ( str ) {
1464
+ return str.replace( /-+(.)?/g, function ( match, chr ) {
1465
+ return chr ? chr.toUpperCase() : '';
1466
+ } )
1467
+ };
1468
+
1469
+ // Zepto dasherize function
1470
+ var dasherize = function ( str ) {
1471
+ return str.replace( /::/g, '/' )
1472
+ .replace( /([A-Z]+)([A-Z][a-z])/g, '$1_$2' )
1473
+ .replace( /([a-z\d])([A-Z])/g, '$1_$2' )
1474
+ .replace( /_/g, '-' )
1475
+ .toLowerCase()
1476
+ };
1477
+
1296
1478
  /**
1297
1479
  * Parsley plugin configuration
1298
1480
  *
@@ -1301,11 +1483,13 @@
1301
1483
  */
1302
1484
  $.fn.parsley.defaults = {
1303
1485
  // basic data-api overridable properties here..
1304
- inputs: 'input, textarea, select' // Default supported inputs.
1486
+ inputs: 'input, textarea, select' // Default supported inputs.
1305
1487
  , excluded: 'input[type=hidden], input[type=file], :disabled' // Do not validate input[type=hidden] & :disabled.
1488
+ , priorityEnabled: true // Will display only one error at the time depending on validators priorities
1306
1489
  , trigger: false // $.Event() that will trigger validation. eg: keyup, change..
1307
1490
  , animate: true // fade in / fade out error messages
1308
1491
  , animateDuration: 300 // fadein/fadout ms time
1492
+ , scrollDuration: 500 // Duration in ms time of the window scroll when focusing on invalid field (0 = no scroll)
1309
1493
  , focus: 'first' // 'fist'|'last'|'none' which error field would have focus first on form validation
1310
1494
  , validationMinlength: 3 // If trigger validation specified, only if value.length > validationMinlength
1311
1495
  , successClass: 'parsley-success' // Class name on each valid input
@@ -1325,19 +1509,11 @@
1325
1509
  }
1326
1510
  , listeners: {
1327
1511
  onFieldValidate: function ( elem, ParsleyForm ) { return false; } // Executed on validation. Return true to ignore field validation
1328
- , onFormSubmit: function ( isFormValid, event, ParsleyForm ) {} // Executed once on form validation. Return (bool) false to block submit, even if valid
1512
+ , onFormValidate: function ( isFormValid, event, ParsleyForm ) {} // Executed once on form validation. Return (bool) false to block submit, even if valid
1329
1513
  , onFieldError: function ( elem, constraints, ParsleyField ) {} // Executed when a field is detected as invalid
1330
1514
  , onFieldSuccess: function ( elem, constraints, ParsleyField ) {} // Executed when a field passes validation
1331
1515
  }
1332
1516
  };
1333
1517
 
1334
- /* PARSLEY auto-bind DATA-API + Global config retrieving
1335
- * =================================================== */
1336
- $( window ).on( 'load', function () {
1337
- $( '[data-validate="parsley"]' ).each( function () {
1338
- $( this ).parsley();
1339
- } );
1340
- } );
1341
-
1342
1518
  // This plugin works with jQuery or Zepto (with data extension built for Zepto.)
1343
- }(window.jQuery || window.Zepto);
1519
+ } ( window.jQuery || window.Zepto );