parsley-rails 1.1.18.0 → 1.2.0.0

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