From 5008cae28f6a1303b81894282adcc2e11ca96c0a Mon Sep 17 00:00:00 2001 From: James O'Toole Date: Sun, 15 Feb 2015 01:31:54 +0000 Subject: [PATCH 01/25] Incorrect attribute corrected, data-parsley-regex should be data-parsley-pattern --- wtformsparsleyjs/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wtformsparsleyjs/core.py b/wtformsparsleyjs/core.py index bf28bff..a46dc27 100644 --- a/wtformsparsleyjs/core.py +++ b/wtformsparsleyjs/core.py @@ -81,7 +81,7 @@ def _equal_to_kwargs(kwargs, vali): def _ip_address_kwargs(kwargs): # Regexp from http://stackoverflow.com/a/4460645 - kwargs[u'data-parsley-regexp'] =\ + kwargs[u'data-parsley-pattern'] =\ r'^\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' \ r'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' \ r'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' \ From c6ae2ae252ec1b150c0d88666a6609f1193f268b Mon Sep 17 00:00:00 2001 From: James O'Toole Date: Sun, 15 Feb 2015 01:37:57 +0000 Subject: [PATCH 02/25] Incorrect attribute corrected, data-parsley-regex should be data-parsley-pattern --- wtformsparsleyjs/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wtformsparsleyjs/core.py b/wtformsparsleyjs/core.py index a46dc27..1bf5ddb 100644 --- a/wtformsparsleyjs/core.py +++ b/wtformsparsleyjs/core.py @@ -29,7 +29,7 @@ def parsley_kwargs(field, kwargs): Generate *kwargs* from the validators present for the widget. - Note that the regex validation relies on the regex pattern being compatible with + Note that the regex validation relies on the eegex pattern being compatible with both ECMA script and Python. The regex is not converted in any way. It's possible to simply supply your own "parsley-regexp" keyword to the field to explicitly provide the ECMA script regex. @@ -115,7 +115,7 @@ def _regexp_kwargs(kwargs, vali): regex_string = vali.regex.pattern else: regex_string = vali.regex - kwargs[u'data-parsley-regexp'] = regex_string + kwargs[u'data-parsley-pattern'] = regex_string def _url_kwargs(kwargs): kwargs[u'data-parsley-type'] = u'url' From 7e62b619914a277abf4a529f5607dddc782bdb5b Mon Sep 17 00:00:00 2001 From: James O'Toole Date: Sun, 15 Feb 2015 01:43:09 +0000 Subject: [PATCH 03/25] Implemented MacAddress validator, regex used is the same as used by WTForms, it is EMCAScript and Python compatible --- wtformsparsleyjs/core.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/wtformsparsleyjs/core.py b/wtformsparsleyjs/core.py index 1bf5ddb..e5504d2 100644 --- a/wtformsparsleyjs/core.py +++ b/wtformsparsleyjs/core.py @@ -59,6 +59,8 @@ def parsley_kwargs(field, kwargs): _url_kwargs(new_kwargs) if isinstance(vali, AnyOf): _anyof_kwargs(new_kwargs, vali) + if isinstance(vali, MacAddress): + _mac_address_kwargs(new_kwargs) if isinstance(vali, Optional): pass @@ -147,6 +149,9 @@ def _trigger_kwargs(kwargs, trigger=u'change'): def _message_kwargs(kwargs, message): kwargs[u'data-parsley-error-message'] = message +def _mac_addresss_kwargs(kwargs): + kwargs[u'data-parsley-pattern'] = '^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$' + class ParsleyInputMixin(Input): def __call__(self, field, **kwargs): From 830743eb6e29f047c51a7eacf60cdfeb8432bd58 Mon Sep 17 00:00:00 2001 From: James O'Toole Date: Sun, 15 Feb 2015 01:44:47 +0000 Subject: [PATCH 04/25] Added forgotten import --- wtformsparsleyjs/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wtformsparsleyjs/core.py b/wtformsparsleyjs/core.py index e5504d2..3edf14a 100644 --- a/wtformsparsleyjs/core.py +++ b/wtformsparsleyjs/core.py @@ -4,7 +4,7 @@ import re import copy from wtforms.validators import Length, NumberRange, Email, EqualTo, IPAddress, \ - Regexp, URL, AnyOf, Optional, InputRequired + Regexp, URL, AnyOf, Optional, InputRequired, MacAddress try: from wtforms.validators import DataRequired except ImportError: From 28d1e84eafaab93875527203c1bc265585d417d9 Mon Sep 17 00:00:00 2001 From: James O'Toole Date: Sun, 15 Feb 2015 01:48:28 +0000 Subject: [PATCH 05/25] Implemented UUID validator, regex is the same as used by WTForms --- wtformsparsleyjs/core.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/wtformsparsleyjs/core.py b/wtformsparsleyjs/core.py index 3edf14a..a8443ef 100644 --- a/wtformsparsleyjs/core.py +++ b/wtformsparsleyjs/core.py @@ -4,7 +4,7 @@ import re import copy from wtforms.validators import Length, NumberRange, Email, EqualTo, IPAddress, \ - Regexp, URL, AnyOf, Optional, InputRequired, MacAddress + Regexp, URL, AnyOf, Optional, InputRequired, MacAddress, UUID try: from wtforms.validators import DataRequired except ImportError: @@ -61,6 +61,8 @@ def parsley_kwargs(field, kwargs): _anyof_kwargs(new_kwargs, vali) if isinstance(vali, MacAddress): _mac_address_kwargs(new_kwargs) + if isinstance(vali, UUID): + _uuid_kwargs(new_kwargs) if isinstance(vali, Optional): pass @@ -141,6 +143,11 @@ def _anyof_kwargs(kwargs, vali): delimiter = _string_seq_delimiter(vali, kwargs) kwargs[u'data-parsley-inlist'] = delimiter.join(vali.values) +def _mac_addresss_kwargs(kwargs): + kwargs[u'data-parsley-pattern'] = '^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$' + +def _uuid_kwargs(kwargs): + kwargs[u'data-parsley-pattern'] = '^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$' def _trigger_kwargs(kwargs, trigger=u'change'): kwargs[u'data-parsley-trigger'] = trigger @@ -149,10 +156,6 @@ def _trigger_kwargs(kwargs, trigger=u'change'): def _message_kwargs(kwargs, message): kwargs[u'data-parsley-error-message'] = message -def _mac_addresss_kwargs(kwargs): - kwargs[u'data-parsley-pattern'] = '^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$' - - class ParsleyInputMixin(Input): def __call__(self, field, **kwargs): kwargs = parsley_kwargs(field, kwargs) From 4be20fe74dd5be64fb9fc7d353e5d7ffaeaeb34c Mon Sep 17 00:00:00 2001 From: James O'Toole Date: Sun, 15 Feb 2015 02:03:37 +0000 Subject: [PATCH 06/25] Implemented the NoneOf validator, using a custom parsley validator --- scripts/parsley-noneof.js | 5 +++++ wtformsparsleyjs/core.py | 10 ++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 scripts/parsley-noneof.js diff --git a/scripts/parsley-noneof.js b/scripts/parsley-noneof.js new file mode 100644 index 0000000..0b7bf7e --- /dev/null +++ b/scripts/parsley-noneof.js @@ -0,0 +1,5 @@ +$(document).ready(function() { + window.ParsleyValidator.addValidator('noneof', function (value, array) { + return array.indexOf(value) === -1; + }, 32).addMessage('en', 'noneof', 'You have entered a value which is not allowed.') +}); diff --git a/wtformsparsleyjs/core.py b/wtformsparsleyjs/core.py index a8443ef..1c64656 100644 --- a/wtformsparsleyjs/core.py +++ b/wtformsparsleyjs/core.py @@ -4,7 +4,7 @@ import re import copy from wtforms.validators import Length, NumberRange, Email, EqualTo, IPAddress, \ - Regexp, URL, AnyOf, Optional, InputRequired, MacAddress, UUID + Regexp, URL, AnyOf, Optional, InputRequired, MacAddress, UUID, NoneOf try: from wtforms.validators import DataRequired except ImportError: @@ -63,6 +63,8 @@ def parsley_kwargs(field, kwargs): _mac_address_kwargs(new_kwargs) if isinstance(vali, UUID): _uuid_kwargs(new_kwargs) + if isinstance(vali, NoneOf): + _none_of_kwargs(new_kwargs, vali) if isinstance(vali, Optional): pass @@ -149,10 +151,14 @@ def _mac_addresss_kwargs(kwargs): def _uuid_kwargs(kwargs): kwargs[u'data-parsley-pattern'] = '^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$' +def _none_of_kwargs(kwargs, vali): + delimiter = _string_seq_delimiter(vali, kwargs) + #data-parsley-noneof is a custom validator, it can be found in scripts/parsley-noneof.js + kwargs[u'data-parsley-noneof'] = delimiter.join(vali.values) + def _trigger_kwargs(kwargs, trigger=u'change'): kwargs[u'data-parsley-trigger'] = trigger - def _message_kwargs(kwargs, message): kwargs[u'data-parsley-error-message'] = message From cc156b1f842040c75452ecb71e970d1f2db43178 Mon Sep 17 00:00:00 2001 From: James O'Toole Date: Sun, 15 Feb 2015 04:27:06 +0000 Subject: [PATCH 07/25] Implemented validation for DateField and DateTimeField, uses a custom validation script, which currently need some work. --- scripts/parsley-datefield.js | 32 ++++++++++++++++++++++++++++++++ wtformsparsleyjs/core.py | 21 ++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 scripts/parsley-datefield.js diff --git a/scripts/parsley-datefield.js b/scripts/parsley-datefield.js new file mode 100644 index 0000000..892617d --- /dev/null +++ b/scripts/parsley-datefield.js @@ -0,0 +1,32 @@ +$(document).ready(function() { + window.ParsleyValidator.addValidator('datefield', function (str, format) { + /** + * Modified version of micro-strptime.js. + * https://github.com/cho45/micro-strptime.js + */ + if (!format) throw Error("Missing format"); + fds = { + '%': '%', + 'A': '[a-z]+', + 'B': '[a-z]+', + 'Y': '[0-9]{4}', + 'm': '[0-9]{0,2}', + 'd': '[0-9]{0,2}', + 'H': '[0-9]{0,2}', + 'M': '[0-9]{0,2}', + 'S': '[0-9]{0,2}', + 's': '[0-9]+', + 'Z': 'UTC|Z|[+-][0-9][0-9]:?[0-9][0-9]', + 'I': '[0-9]{0,2}', + 'p': 'AM|PM' + }; + // Create a regular expression from the format string, that matches a string of that format. + var re = new RegExp(format.replace(/%(?:([a-zA-Z%])|('[^']+')|("[^"]+"))/g, function (_, a, b, c) { + var fd = a || b || c; + var d = fds[fd]; + if (!d) throw Error("Unknown format descripter: " + fd); + return '(' + d + ')'; + }), 'i'); + return re.test(str); + }, 32).addMessage('en', 'datefield', 'The input needs to be in the correct date format.') +}); diff --git a/wtformsparsleyjs/core.py b/wtformsparsleyjs/core.py index 1c64656..5afcf95 100644 --- a/wtformsparsleyjs/core.py +++ b/wtformsparsleyjs/core.py @@ -20,7 +20,8 @@ from wtforms.fields import StringField as _StringField, BooleanField as _Boolean DecimalField as _DecimalField, IntegerField as _IntegerField, \ FloatField as _FloatField, PasswordField as _PasswordField, \ SelectField as _SelectField, TextAreaField as _TextAreaField, \ - RadioField as _RadioField + RadioField as _RadioField, DateField as _DateField, \ + DateTimeField as _DateTimeField def parsley_kwargs(field, kwargs): @@ -39,6 +40,10 @@ def parsley_kwargs(field, kwargs): one. Do check if the behaviour suits your needs. """ new_kwargs = copy.deepcopy(kwargs) + + if isinstance(field, DateField) or isinstance(field, DateTimeField): + _date_kwargs(new_kwargs, field) + for vali in field.validators: if isinstance(vali, Email): _email_kwargs(new_kwargs) @@ -162,6 +167,9 @@ def _trigger_kwargs(kwargs, trigger=u'change'): def _message_kwargs(kwargs, message): kwargs[u'data-parsley-error-message'] = message +def _date_kwargs(kwargs, field): + kwargs[u'data-parsley-datefield'] = field.format + class ParsleyInputMixin(Input): def __call__(self, field, **kwargs): kwargs = parsley_kwargs(field, kwargs) @@ -257,3 +265,14 @@ class TextAreaField(_TextAreaField): class SelectField(_SelectField): def __init__(self, *args, **kwargs): super(SelectField, self).__init__(widget=Select(), *args, **kwargs) + + +class DateTimeField(_DateTimeField): + def __init__(self, *args, **kwargs): + super(DateTimeField, self).__init__(widget=TextInput(), *args, **kwargs) + + +class DateField(_DateField): + def __init__(self, *args, **kwargs): + super(DateField, self).__init__(widget=TextInput(), *args, **kwargs) + From fd8efee64bca17ece3f3356d74aee10f8051f9f7 Mon Sep 17 00:00:00 2001 From: James O'Toole Date: Sun, 15 Feb 2015 04:47:40 +0000 Subject: [PATCH 08/25] Implemented default validation for FloatField, DecimalField and IntegerField, using data-parsley-type's number and integer. --- wtformsparsleyjs/core.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/wtformsparsleyjs/core.py b/wtformsparsleyjs/core.py index 5afcf95..8f4d10c 100644 --- a/wtformsparsleyjs/core.py +++ b/wtformsparsleyjs/core.py @@ -43,6 +43,10 @@ def parsley_kwargs(field, kwargs): if isinstance(field, DateField) or isinstance(field, DateTimeField): _date_kwargs(new_kwargs, field) + if isinstance(field, IntegerField): + _integer_kwargs(new_kwargs) + if isinstance(field, DecimalField) or isinstance(field, FloatField): + _number_kwargs(new_kwargs) for vali in field.validators: if isinstance(vali, Email): @@ -170,6 +174,12 @@ def _message_kwargs(kwargs, message): def _date_kwargs(kwargs, field): kwargs[u'data-parsley-datefield'] = field.format +def _integer_kwargs(kwargs): + kwargs[u'data-parsley-type'] = "integer" + +def _number_kwargs(kwargs): + kwargs[u'data-parsley-type'] = "number" + class ParsleyInputMixin(Input): def __call__(self, field, **kwargs): kwargs = parsley_kwargs(field, kwargs) From 87b9cf2a1de3c77632ec60d85666ba9fae418e29 Mon Sep 17 00:00:00 2001 From: James O'Toole Date: Sun, 15 Feb 2015 04:59:17 +0000 Subject: [PATCH 09/25] Improved data-parsley-datefield validator, fixed previous issue of '%Y-%m-%d' being matched by '1996-2-'. --- scripts/parsley-datefield.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/scripts/parsley-datefield.js b/scripts/parsley-datefield.js index 892617d..87f0e9f 100644 --- a/scripts/parsley-datefield.js +++ b/scripts/parsley-datefield.js @@ -10,14 +10,14 @@ $(document).ready(function() { 'A': '[a-z]+', 'B': '[a-z]+', 'Y': '[0-9]{4}', - 'm': '[0-9]{0,2}', - 'd': '[0-9]{0,2}', - 'H': '[0-9]{0,2}', - 'M': '[0-9]{0,2}', - 'S': '[0-9]{0,2}', + 'm': '[0-9]{1,2}', + 'd': '[0-9]{1,2}', + 'H': '[0-9]{1,2}', + 'M': '[0-9]{1,2}', + 'S': '[0-9]{1,2}', 's': '[0-9]+', 'Z': 'UTC|Z|[+-][0-9][0-9]:?[0-9][0-9]', - 'I': '[0-9]{0,2}', + 'I': '[0-9]{1,2}', 'p': 'AM|PM' }; // Create a regular expression from the format string, that matches a string of that format. @@ -27,6 +27,7 @@ $(document).ready(function() { if (!d) throw Error("Unknown format descripter: " + fd); return '(' + d + ')'; }), 'i'); + console.log(re) return re.test(str); }, 32).addMessage('en', 'datefield', 'The input needs to be in the correct date format.') }); From 69acc56916be0c50f2aceab9454168fa68d9e1f8 Mon Sep 17 00:00:00 2001 From: James O'Toole Date: Mon, 16 Feb 2015 21:21:36 +0000 Subject: [PATCH 10/25] Added file field --- wtformsparsleyjs/core.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/wtformsparsleyjs/core.py b/wtformsparsleyjs/core.py index 8f4d10c..57a392e 100644 --- a/wtformsparsleyjs/core.py +++ b/wtformsparsleyjs/core.py @@ -11,17 +11,17 @@ except ImportError: # wtforms < 2.x from wtforms.validators import Required as DataRequired -from wtforms import StringField from wtforms.widgets import TextInput as _TextInput, PasswordInput as _PasswordInput, \ CheckboxInput as _CheckboxInput, Select as _Select, TextArea as _TextArea, \ ListWidget as _ListWidget, HiddenInput as _HiddenInput, RadioInput as _RadioInput, \ - Input + FileInput as _FileInput, Input + from wtforms.fields import StringField as _StringField, BooleanField as _BooleanField, \ DecimalField as _DecimalField, IntegerField as _IntegerField, \ FloatField as _FloatField, PasswordField as _PasswordField, \ SelectField as _SelectField, TextAreaField as _TextAreaField, \ RadioField as _RadioField, DateField as _DateField, \ - DateTimeField as _DateTimeField + DateTimeField as _DateTimeField, FileField as _FileField def parsley_kwargs(field, kwargs): @@ -225,6 +225,10 @@ class ListWidget(_ListWidget): return super(ListWidget, self).__call__(field, **kwargs) +def FileInput(_FileInput, ParsleyInputMixin): + pass + + class StringField(_StringField): def __init__(self, *args, **kwargs): super(StringField, self).__init__(widget=TextInput(), *args, **kwargs) @@ -286,3 +290,8 @@ class DateField(_DateField): def __init__(self, *args, **kwargs): super(DateField, self).__init__(widget=TextInput(), *args, **kwargs) + +class FileField(_FileField): + def __init__(self, *args, **kwargs): + super(FileField, self).__init__(widget=FileInput(), *args, **kwargs) + From 1378b29a2c982f37602b77651b94433adc836bcb Mon Sep 17 00:00:00 2001 From: James O'Toole Date: Tue, 17 Feb 2015 06:24:21 +0000 Subject: [PATCH 11/25] Added support for SelectMultipleField --- wtformsparsleyjs/core.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/wtformsparsleyjs/core.py b/wtformsparsleyjs/core.py index 57a392e..4da8012 100644 --- a/wtformsparsleyjs/core.py +++ b/wtformsparsleyjs/core.py @@ -21,7 +21,8 @@ from wtforms.fields import StringField as _StringField, BooleanField as _Boolean FloatField as _FloatField, PasswordField as _PasswordField, \ SelectField as _SelectField, TextAreaField as _TextAreaField, \ RadioField as _RadioField, DateField as _DateField, \ - DateTimeField as _DateTimeField, FileField as _FileField + DateTimeField as _DateTimeField, FileField as _FileField, \ + SelectMultipleField as _SelectMultipleField def parsley_kwargs(field, kwargs): @@ -295,3 +296,8 @@ class FileField(_FileField): def __init__(self, *args, **kwargs): super(FileField, self).__init__(widget=FileInput(), *args, **kwargs) + +class SelectMultipleField(_SelectMultipleField): + def __init__(self, *args, **kwargs): + super(SelectMultipleField, self).__init__(widget=Select(), *args, **kwargs) + From b77c2c40a81833655a726a4850d3cf1aea50c76f Mon Sep 17 00:00:00 2001 From: James O'Toole Date: Tue, 17 Feb 2015 11:46:58 +0000 Subject: [PATCH 12/25] Removed error, FileInput defined as function not class --- wtformsparsleyjs/core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wtformsparsleyjs/core.py b/wtformsparsleyjs/core.py index 4da8012..af08483 100644 --- a/wtformsparsleyjs/core.py +++ b/wtformsparsleyjs/core.py @@ -181,6 +181,7 @@ def _integer_kwargs(kwargs): def _number_kwargs(kwargs): kwargs[u'data-parsley-type'] = "number" + class ParsleyInputMixin(Input): def __call__(self, field, **kwargs): kwargs = parsley_kwargs(field, kwargs) @@ -226,7 +227,7 @@ class ListWidget(_ListWidget): return super(ListWidget, self).__call__(field, **kwargs) -def FileInput(_FileInput, ParsleyInputMixin): +class FileInput(_FileInput, ParsleyInputMixin): pass From b41c1726f2e7d219585b0fc068a2d19557f370bc Mon Sep 17 00:00:00 2001 From: James O'Toole Date: Tue, 17 Feb 2015 11:48:25 +0000 Subject: [PATCH 13/25] Spelling error, _mac_addresss_kwargs => _mac_adress_kwargs --- wtformsparsleyjs/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wtformsparsleyjs/core.py b/wtformsparsleyjs/core.py index af08483..2bbac25 100644 --- a/wtformsparsleyjs/core.py +++ b/wtformsparsleyjs/core.py @@ -155,7 +155,7 @@ def _anyof_kwargs(kwargs, vali): delimiter = _string_seq_delimiter(vali, kwargs) kwargs[u'data-parsley-inlist'] = delimiter.join(vali.values) -def _mac_addresss_kwargs(kwargs): +def _mac_address_kwargs(kwargs): kwargs[u'data-parsley-pattern'] = '^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$' def _uuid_kwargs(kwargs): From be67c5cf6994513828b691e09b314ceaf1333f0e Mon Sep 17 00:00:00 2001 From: James O'Toole Date: Tue, 17 Feb 2015 12:14:06 +0000 Subject: [PATCH 14/25] Fixed FileInput and TextArea not rendering kwarg attributes --- wtformsparsleyjs/core.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/wtformsparsleyjs/core.py b/wtformsparsleyjs/core.py index 2bbac25..88661de 100644 --- a/wtformsparsleyjs/core.py +++ b/wtformsparsleyjs/core.py @@ -153,6 +153,7 @@ def _string_seq_delimiter(vali, kwargs): def _anyof_kwargs(kwargs, vali): delimiter = _string_seq_delimiter(vali, kwargs) + # This is out of date, inlist is a 1.x validator, have yet to find a 2.x alternative, may have to write a custom validator kwargs[u'data-parsley-inlist'] = delimiter.join(vali.values) def _mac_address_kwargs(kwargs): @@ -164,7 +165,7 @@ def _uuid_kwargs(kwargs): def _none_of_kwargs(kwargs, vali): delimiter = _string_seq_delimiter(vali, kwargs) #data-parsley-noneof is a custom validator, it can be found in scripts/parsley-noneof.js - kwargs[u'data-parsley-noneof'] = delimiter.join(vali.values) + kwargs[u'data-parsley-noneof'] = u"[" + delimiter.join(vali.values) + u"]" def _trigger_kwargs(kwargs, trigger=u'change'): kwargs[u'data-parsley-trigger'] = trigger @@ -200,8 +201,10 @@ class HiddenInput(_HiddenInput, ParsleyInputMixin): pass -class TextArea(_TextArea, ParsleyInputMixin): - pass +class TextArea(_TextArea): + def __call__(self, field, **kwargs): + kwargs = parsley_kwargs(field, kwargs) + return super(TextArea, self).__call__(field, **kwargs) class CheckboxInput(_CheckboxInput, ParsleyInputMixin): @@ -227,8 +230,10 @@ class ListWidget(_ListWidget): return super(ListWidget, self).__call__(field, **kwargs) -class FileInput(_FileInput, ParsleyInputMixin): - pass +class FileInput(_FileInput): + def __call__(self, field, **kwargs): + kwargs = parsley_kwargs(field, kwargs) + return super(FileInput, self).__call__(field, **kwargs) class StringField(_StringField): From b4073dda5825f7507667b2c33c5fec1c637a070e Mon Sep 17 00:00:00 2001 From: James O'Toole Date: Tue, 17 Feb 2015 12:32:31 +0000 Subject: [PATCH 15/25] Removed inlist validator, because is is no longer in Parsley since 2.x, replaced with a custom valdidator --- scripts/parsley-anyof.js | 5 +++++ wtformsparsleyjs/core.py | 23 +++-------------------- 2 files changed, 8 insertions(+), 20 deletions(-) create mode 100644 scripts/parsley-anyof.js diff --git a/scripts/parsley-anyof.js b/scripts/parsley-anyof.js new file mode 100644 index 0000000..f293c1d --- /dev/null +++ b/scripts/parsley-anyof.js @@ -0,0 +1,5 @@ +$(document).ready(function() { + window.ParsleyValidator.addValidator('anyof', function (value, array) { + return array.indexOf(value); + }, 32).addMessage('en', 'anyof', 'The value you have given is not an listed option.') +}); diff --git a/wtformsparsleyjs/core.py b/wtformsparsleyjs/core.py index 88661de..fec483b 100644 --- a/wtformsparsleyjs/core.py +++ b/wtformsparsleyjs/core.py @@ -136,25 +136,9 @@ def _regexp_kwargs(kwargs, vali): def _url_kwargs(kwargs): kwargs[u'data-parsley-type'] = u'url' -def _string_seq_delimiter(vali, kwargs): - # We normally use a comma as the delimiter - looks clean and it's parsley's default. - # If the strings for which we check contain a comma, we cannot use it as a delimiter. - default_delimiter = u',' - fallback_delimiter = u';;;' - delimiter = default_delimiter - for value in vali.values: - if value.find(',') != -1: - delimiter = fallback_delimiter - break - if delimiter != default_delimiter: - kwargs[u'data-parsley-inlist-delimiter'] = delimiter - return delimiter - - def _anyof_kwargs(kwargs, vali): - delimiter = _string_seq_delimiter(vali, kwargs) - # This is out of date, inlist is a 1.x validator, have yet to find a 2.x alternative, may have to write a custom validator - kwargs[u'data-parsley-inlist'] = delimiter.join(vali.values) + # The inlist validator is no longer available in Parsley 2.x, so a custom anyof validator is used. + kwargs[u'data-parsley-anyof'] = u"[" + u",".join(vali.values) + u"]" def _mac_address_kwargs(kwargs): kwargs[u'data-parsley-pattern'] = '^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$' @@ -163,9 +147,8 @@ def _uuid_kwargs(kwargs): kwargs[u'data-parsley-pattern'] = '^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$' def _none_of_kwargs(kwargs, vali): - delimiter = _string_seq_delimiter(vali, kwargs) #data-parsley-noneof is a custom validator, it can be found in scripts/parsley-noneof.js - kwargs[u'data-parsley-noneof'] = u"[" + delimiter.join(vali.values) + u"]" + kwargs[u'data-parsley-noneof'] = u"[" + u",".join(vali.values) + u"]" def _trigger_kwargs(kwargs, trigger=u'change'): kwargs[u'data-parsley-trigger'] = trigger From 3bc3c9c97137920341692c620bc98ccb9fd359c4 Mon Sep 17 00:00:00 2001 From: James O'Toole Date: Tue, 17 Feb 2015 17:04:22 +0000 Subject: [PATCH 16/25] Using json.dumps as suggested by fuhrysteve --- wtformsparsleyjs/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wtformsparsleyjs/core.py b/wtformsparsleyjs/core.py index fec483b..3dfe12e 100644 --- a/wtformsparsleyjs/core.py +++ b/wtformsparsleyjs/core.py @@ -138,7 +138,7 @@ def _url_kwargs(kwargs): def _anyof_kwargs(kwargs, vali): # The inlist validator is no longer available in Parsley 2.x, so a custom anyof validator is used. - kwargs[u'data-parsley-anyof'] = u"[" + u",".join(vali.values) + u"]" + kwargs[u'data-parsley-anyof'] = json.dumps(vali.values) def _mac_address_kwargs(kwargs): kwargs[u'data-parsley-pattern'] = '^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$' @@ -148,7 +148,7 @@ def _uuid_kwargs(kwargs): def _none_of_kwargs(kwargs, vali): #data-parsley-noneof is a custom validator, it can be found in scripts/parsley-noneof.js - kwargs[u'data-parsley-noneof'] = u"[" + u",".join(vali.values) + u"]" + kwargs[u'data-parsley-noneof'] = json.dumps(vali.values) def _trigger_kwargs(kwargs, trigger=u'change'): kwargs[u'data-parsley-trigger'] = trigger From c54a36ce7aa87f01435a55746f2999843dc56c4a Mon Sep 17 00:00:00 2001 From: James O'Toole Date: Tue, 17 Feb 2015 17:05:07 +0000 Subject: [PATCH 17/25] Forgot to import json --- wtformsparsleyjs/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/wtformsparsleyjs/core.py b/wtformsparsleyjs/core.py index 3dfe12e..e73fa08 100644 --- a/wtformsparsleyjs/core.py +++ b/wtformsparsleyjs/core.py @@ -2,6 +2,7 @@ __author__ = 'Johannes Gehrs (jgehrs@gmail.com)' import re import copy +import json from wtforms.validators import Length, NumberRange, Email, EqualTo, IPAddress, \ Regexp, URL, AnyOf, Optional, InputRequired, MacAddress, UUID, NoneOf From 71f8c99d5b7241a91ce86f28ef5909ffa80733b8 Mon Sep 17 00:00:00 2001 From: James O'Toole Date: Tue, 17 Feb 2015 17:08:23 +0000 Subject: [PATCH 18/25] Grammar correction, pointed out by fuhrysteve --- scripts/parsley-anyof.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/parsley-anyof.js b/scripts/parsley-anyof.js index f293c1d..273744e 100644 --- a/scripts/parsley-anyof.js +++ b/scripts/parsley-anyof.js @@ -1,5 +1,5 @@ $(document).ready(function() { window.ParsleyValidator.addValidator('anyof', function (value, array) { return array.indexOf(value); - }, 32).addMessage('en', 'anyof', 'The value you have given is not an listed option.') + }, 32).addMessage('en', 'anyof', 'The value you have given is not a listed option.') }); From 5063f1001539e79656a99bea917f226fd0aecdf3 Mon Sep 17 00:00:00 2001 From: James O'Toole Date: Wed, 25 Feb 2015 15:59:22 +0000 Subject: [PATCH 19/25] Made multiple selects actually render as multiple selects --- wtformsparsleyjs/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wtformsparsleyjs/core.py b/wtformsparsleyjs/core.py index e73fa08..057555a 100644 --- a/wtformsparsleyjs/core.py +++ b/wtformsparsleyjs/core.py @@ -289,5 +289,5 @@ class FileField(_FileField): class SelectMultipleField(_SelectMultipleField): def __init__(self, *args, **kwargs): - super(SelectMultipleField, self).__init__(widget=Select(), *args, **kwargs) + super(SelectMultipleField, self).__init__(widget=Select(multiple=True), *args, **kwargs) From 04c2ef7f8851e1939547a440ef32624a63158569 Mon Sep 17 00:00:00 2001 From: James O'Toole Date: Thu, 26 Feb 2015 01:13:51 +0000 Subject: [PATCH 20/25] remove console logging --- scripts/parsley-datefield.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/parsley-datefield.js b/scripts/parsley-datefield.js index 87f0e9f..de29aff 100644 --- a/scripts/parsley-datefield.js +++ b/scripts/parsley-datefield.js @@ -27,7 +27,6 @@ $(document).ready(function() { if (!d) throw Error("Unknown format descripter: " + fd); return '(' + d + ')'; }), 'i'); - console.log(re) return re.test(str); }, 32).addMessage('en', 'datefield', 'The input needs to be in the correct date format.') }); From 2eb992921633db9efc9947e510a25672eb8c6259 Mon Sep 17 00:00:00 2001 From: James O'Toole Date: Thu, 26 Feb 2015 01:24:26 +0000 Subject: [PATCH 21/25] Fixed checking is in array incorrectly --- scripts/parsley-anyof.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/parsley-anyof.js b/scripts/parsley-anyof.js index 273744e..5fc738c 100644 --- a/scripts/parsley-anyof.js +++ b/scripts/parsley-anyof.js @@ -1,5 +1,5 @@ $(document).ready(function() { window.ParsleyValidator.addValidator('anyof', function (value, array) { - return array.indexOf(value); + return array.indexOf(value) >= 0; }, 32).addMessage('en', 'anyof', 'The value you have given is not a listed option.') }); From 6e9bbafbe10c77c2dfff0d41c96d30e62937f72b Mon Sep 17 00:00:00 2001 From: James O'Toole Date: Thu, 26 Feb 2015 01:46:55 +0000 Subject: [PATCH 22/25] Updated sample --- wtformsparsleyjs/sample/sample.py | 324 ++- .../sample/static/scripts/parsley-anyof.js | 5 + .../static/scripts/parsley-datefield.js | 32 + .../sample/static/scripts/parsley-noneof.js | 5 + .../sample/static/scripts/parsley.js | 2083 +++++++++++++++++ wtformsparsleyjs/sample/templates/base.html | 6 +- .../templates/wtforms_parsley_sample.html | 17 +- 7 files changed, 2414 insertions(+), 58 deletions(-) create mode 100644 wtformsparsleyjs/sample/static/scripts/parsley-anyof.js create mode 100644 wtformsparsleyjs/sample/static/scripts/parsley-datefield.js create mode 100644 wtformsparsleyjs/sample/static/scripts/parsley-noneof.js create mode 100644 wtformsparsleyjs/sample/static/scripts/parsley.js diff --git a/wtformsparsleyjs/sample/sample.py b/wtformsparsleyjs/sample/sample.py index 1ebad6b..ef05131 100644 --- a/wtformsparsleyjs/sample/sample.py +++ b/wtformsparsleyjs/sample/sample.py @@ -2,7 +2,8 @@ __author__ = 'Johannes Gehrs (jgehrs@gmail.com)' from flask import Flask, render_template, request from wtforms import Form, validators -from wtformsparsleyjs import IntegerField, BooleanField, SelectField, StringField +import wtformsparsleyjs +import datetime app = Flask(__name__) @@ -15,46 +16,283 @@ def parsley_testform(): class ParsleyTestForm(Form): - email = StringField('E-Mail Address', [ - validators.Email('Sorrry, not a valid email address.') - ], default='test@example.com') - first_value = StringField('Some Value', default='Some value') - second_value = StringField('Should be identical', [ - validators.EqualTo(message='Sorry, values do not match.', - fieldname='first_value') - ], default='Some value') - ip_address = StringField('IP4 Address', [ - validators.IPAddress(message='Sorry, not a valid IP4 Address.') - ], default='127.0.0.1') - string_length = StringField('Length of String (5 to 10)', [ - validators.Length(message='Length should be between 5 and 10 characters.', - min=5, max=10) - ], default='Hello!') - number_range = IntegerField('Number Range (5 to 10)', [ - validators.NumberRange(message='Range should be between 5 and 10.', - min=5, max=10) - ], default=7) + email = wtformsparsleyjs.StringField( + label = 'E-Mail Address', + validators = [ + validators.Email( + message = 'Sorrry, not a valid email address.' + ) + ], + default='test@example.com' + ) - required_text = StringField('Required Field', [ - validators.DataRequired(message='Sorry, this is a required field.') - ], default='Mandatory text') - required_select = SelectField('Required Select', [ - validators.DataRequired( - message='Sorry, you have to make a choice.') - ], choices=[('', 'Please select an option'), ('cpp', 'C++'), ('py', 'Python'), - ('text', 'Plain Text') - ], default='py') - required_checkbox = BooleanField('Required Checkbox', [ - validators.DataRequired(message='Sorry, you need to accept this.') - ], default=True) - regexp = StringField('Regex-Matched Hex Color-Code', [ - validators.Regexp(message='Not a proper color code, sorry.', - regex=r'^#[A-Fa-f0-9]{6}$') - ], default='#7D384F') - url = StringField('URL Field', [ - validators.URL(message='Sorry, this is not a valid URL,') - ], default='http://example.com/parsley') - anyof = StringField('Car, Bike or Plane?', [ - validators.AnyOf(message='Sorry, you can only choose from car, bike and plane', - values=['car', 'bike', 'plane']) - ], default='car') + ip_address = wtformsparsleyjs.StringField( + label = 'IP4 Address', + validators = [ + validators.IPAddress( + message = 'Sorry, not a valid IP4 Address.' + ) + ], + default='127.0.0.1' + ) + + uuid = wtformsparsleyjs.StringField( + label = 'UUID', + validators = [ + validators.UUID( + message = 'Sorry, not a valid UUID.' + ) + ], + default='863b5570-ee85-4099-ba1d-33018282cd00' + ) + + mac_address = wtformsparsleyjs.StringField( + label = 'Mac Address', + validators = [ + validators.MacAddress( + message = 'Sorry, not a valid mac address.' + ) + ], + default = '10:B0:46:8C:80:48' + ) + + string_length = wtformsparsleyjs.StringField( + label = 'Length of String (5 to 10)', + validators = [ + validators.Length( + message = 'Length should be between 5 and 10 characters.', + min = 5, + max = 10 + ) + ], + default = 'Hello!' + ) + + number_range = wtformsparsleyjs.IntegerField( + label = 'Number Range (5 to 10)', + validators = [ + validators.NumberRange( + message = 'Range should be between 5 and 10.', + min = 5, + max = 10 + ) + ], + default = 7 + ) + + required_text = wtformsparsleyjs.StringField( + label = 'Required Field', + validators = [ + validators.DataRequired( + message = 'Sorry, this is a required field.' + ) + ], + default = 'Mandatory text' + ) + + required_select = wtformsparsleyjs.SelectField( + label = 'Required Select', + validators = [ + validators.DataRequired( + message = 'Sorry, you have to make a choice.' + ) + ], + choices=[ + ('', 'Please select an option'), + ('cpp', 'C++'), + ('py', 'Python'), + ('text', 'Plain Text') + ], + default = 'py' + ) + + required_checkbox = wtformsparsleyjs.BooleanField( + label = 'Required Checkbox', + validators = [ + validators.DataRequired( + message = 'Sorry, you need to accept this.' + ) + ], + default = True + ) + + regexp = wtformsparsleyjs.StringField( + label = 'Regex-Matched Hex Color-Code', + validators = [ + validators.Regexp( + message = 'Not a proper color code, sorry.', + regex = r'^#[A-Fa-f0-9]{6}$' + ) + ], + default = '#7D384F' + ) + + url = wtformsparsleyjs.StringField( + label = 'URL Field', + validators = [ + validators.URL( + message = 'Sorry, this is not a valid URL,' + ) + ], + default = 'http://example.com/parsley' + ) + + anyof = wtformsparsleyjs.StringField( + 'Car, Bike or Plane?', + validators = [ + validators.AnyOf( + message = 'Sorry, you can only choose from car, bike and plane', + values = ['car', 'bike', 'plane'] + ) + ], + default = 'car' + ) + + date_of_birth = wtformsparsleyjs.DateField( + label = "Date of birth in the format DD-MM-YYYY", + format = "%d-%m-%Y", + validators = [ + validators.InputRequired( + message = "Sorry this input is required." + ) + ], + default = datetime.datetime.today().date() + ) + + date_time = wtformsparsleyjs.DateTimeField( + label = "Date and time in the format DD/MM/YYYY HH:MM", + format = "%d/%m/%Y %H:%M", + validators = [ + validators.InputRequired( + message = "Sorry this input is required." + ) + ], + default = datetime.datetime.today() + ) + + length = wtformsparsleyjs.DecimalField( + label = "Exact length, as a decimal", + validators = [ + validators.InputRequired( + message = "Sorry this input is required." + ) + ], + default = 4.20 + ) + + txt_file = wtformsparsleyjs.FileField( + label = "Optional file field, of .txt format", + validators = [ + validators.Regexp( + r"^.+\.txt$", + message="Must be a *.txt file" + ), + validators.Optional() + ] + ) + + float_field = wtformsparsleyjs.FloatField( + label = "A float value", + validators = [ + validators.InputRequired( + message = "Sorry this input is required." + ) + ], + default = 4.20 + ) + + best_thing_ever = wtformsparsleyjs.RadioField( + label = "Is this the best thing ever?", + choices = [ + ("y", "Yes"), + ("n", "No") + ], + validators = [ + validators.InputRequired( + message = "We need and answer please." + ) + ], + default = "y" + ) + + colour = wtformsparsleyjs.SelectField( + label = "Select your favourite colour.", + choices = [ + ("red", "Red"), + ("blue", "Blue"), + ("green", "Green") + ], + validators = [ + validators.InputRequired( + message = "Sorry this input is required." + ) + ] + ) + + hobbies = wtformsparsleyjs.SelectMultipleField( + label = "Select your hobbies: ", + choices=[ + ("cooking", "Cooking"), + ("coding", "Coding"), + ("reading", "Reading"), + ("fishing", "Fishing"), + ("sewing", "Sewing") + ], + validators = [ + validators.Optional() + ] + ) + + name = wtformsparsleyjs.StringField( + label = "Whom would you have me welease?", + validators = [ + validators.NoneOf( + ["Roger", "Roderick", "Woger", "Woderick"], + message = "Ah. We have no Woger and no Woderick" + ) + ], + default = "Brian" + ) + + hidden = wtformsparsleyjs.HiddenField( + label = "Hidden value", + validators = [ + validators.NumberRange( + min = 5, + message = "Must be greater than 5" + ) + ], + default = 6 + ) + + # No default values for passwords, becuase the aren't rendered as a safety + # feature. + secret = wtformsparsleyjs.PasswordField( + label = "Enter your secret:", + validators = [ + validators.InputRequired( + message = "Sorry this input is required." + ) + ], + ) + + same_secret = wtformsparsleyjs.PasswordField( + label = "Enter your secret again: ", + validators = [ + validators.EqualTo( + fieldname = "secret", + message = "Secrets do not match." + ) + ], + ) + + life_story = wtformsparsleyjs.TextAreaField( + label = "Tell us your life story...", + validators = [ + validators.Length( + min = 50, + message = "C'mon that's not long enough to be a life story" + ) + ], + default = "This is my life story, it has to be at least 50 characters." + ) diff --git a/wtformsparsleyjs/sample/static/scripts/parsley-anyof.js b/wtformsparsleyjs/sample/static/scripts/parsley-anyof.js new file mode 100644 index 0000000..5fc738c --- /dev/null +++ b/wtformsparsleyjs/sample/static/scripts/parsley-anyof.js @@ -0,0 +1,5 @@ +$(document).ready(function() { + window.ParsleyValidator.addValidator('anyof', function (value, array) { + return array.indexOf(value) >= 0; + }, 32).addMessage('en', 'anyof', 'The value you have given is not a listed option.') +}); diff --git a/wtformsparsleyjs/sample/static/scripts/parsley-datefield.js b/wtformsparsleyjs/sample/static/scripts/parsley-datefield.js new file mode 100644 index 0000000..de29aff --- /dev/null +++ b/wtformsparsleyjs/sample/static/scripts/parsley-datefield.js @@ -0,0 +1,32 @@ +$(document).ready(function() { + window.ParsleyValidator.addValidator('datefield', function (str, format) { + /** + * Modified version of micro-strptime.js. + * https://github.com/cho45/micro-strptime.js + */ + if (!format) throw Error("Missing format"); + fds = { + '%': '%', + 'A': '[a-z]+', + 'B': '[a-z]+', + 'Y': '[0-9]{4}', + 'm': '[0-9]{1,2}', + 'd': '[0-9]{1,2}', + 'H': '[0-9]{1,2}', + 'M': '[0-9]{1,2}', + 'S': '[0-9]{1,2}', + 's': '[0-9]+', + 'Z': 'UTC|Z|[+-][0-9][0-9]:?[0-9][0-9]', + 'I': '[0-9]{1,2}', + 'p': 'AM|PM' + }; + // Create a regular expression from the format string, that matches a string of that format. + var re = new RegExp(format.replace(/%(?:([a-zA-Z%])|('[^']+')|("[^"]+"))/g, function (_, a, b, c) { + var fd = a || b || c; + var d = fds[fd]; + if (!d) throw Error("Unknown format descripter: " + fd); + return '(' + d + ')'; + }), 'i'); + return re.test(str); + }, 32).addMessage('en', 'datefield', 'The input needs to be in the correct date format.') +}); diff --git a/wtformsparsleyjs/sample/static/scripts/parsley-noneof.js b/wtformsparsleyjs/sample/static/scripts/parsley-noneof.js new file mode 100644 index 0000000..0b7bf7e --- /dev/null +++ b/wtformsparsleyjs/sample/static/scripts/parsley-noneof.js @@ -0,0 +1,5 @@ +$(document).ready(function() { + window.ParsleyValidator.addValidator('noneof', function (value, array) { + return array.indexOf(value) === -1; + }, 32).addMessage('en', 'noneof', 'You have entered a value which is not allowed.') +}); diff --git a/wtformsparsleyjs/sample/static/scripts/parsley.js b/wtformsparsleyjs/sample/static/scripts/parsley.js new file mode 100644 index 0000000..6345e1e --- /dev/null +++ b/wtformsparsleyjs/sample/static/scripts/parsley.js @@ -0,0 +1,2083 @@ +/*! +* Parsleyjs +* Guillaume Potier - +* Version 2.0.7 - built Sat Jan 24 2015 14:50:11 +* MIT Licensed +* +*/ +!(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module depending on jQuery. + define(['jquery'], factory); + } else { + // No AMD. Register plugin with global jQuery object. + factory(jQuery); + } +}(function ($) { + // small hack for requirejs if jquery is loaded through map and not path + // see http://requirejs.org/docs/jquery.html + if ('undefined' === typeof $ && 'undefined' !== typeof window.jQuery) + $ = window.jQuery; + var ParsleyUtils = { + // Parsley DOM-API + // returns object from dom attributes and values + // if attr is given, returns bool if attr present in DOM or not + attr: function ($element, namespace, checkAttr) { + var + attribute, + obj = {}, + msie = this.msieversion(), + regex = new RegExp('^' + namespace, 'i'); + if ('undefined' === typeof $element || 'undefined' === typeof $element[0]) + return {}; + for (var i in $element[0].attributes) { + attribute = $element[0].attributes[i]; + if ('undefined' !== typeof attribute && null !== attribute && (!msie || msie >= 8 || attribute.specified) && regex.test(attribute.name)) { + if ('undefined' !== typeof checkAttr && new RegExp(checkAttr + '$', 'i').test(attribute.name)) + return true; + obj[this.camelize(attribute.name.replace(namespace, ''))] = this.deserializeValue(attribute.value); + } + } + return 'undefined' === typeof checkAttr ? obj : false; + }, + setAttr: function ($element, namespace, attr, value) { + $element[0].setAttribute(this.dasherize(namespace + attr), String(value)); + }, + // Recursive object / array getter + get: function (obj, path) { + var + i = 0, + paths = (path || '').split('.'); + while (this.isObject(obj) || this.isArray(obj)) { + obj = obj[paths[i++]]; + if (i === paths.length) + return obj; + } + return undefined; + }, + hash: function (length) { + return String(Math.random()).substring(2, length ? length + 2 : 9); + }, + /** Third party functions **/ + // Underscore isArray + isArray: function (mixed) { + return Object.prototype.toString.call(mixed) === '[object Array]'; + }, + // Underscore isObject + isObject: function (mixed) { + return mixed === Object(mixed); + }, + // Zepto deserialize function + deserializeValue: function (value) { + var num; + try { + return value ? + value == "true" || + (value == "false" ? false : + value == "null" ? null : + !isNaN(num = Number(value)) ? num : + /^[\[\{]/.test(value) ? $.parseJSON(value) : + value) + : value; + } catch (e) { return value; } + }, + // Zepto camelize function + camelize: function (str) { + return str.replace(/-+(.)?/g, function(match, chr) { + return chr ? chr.toUpperCase() : ''; + }); + }, + // Zepto dasherize function + dasherize: function (str) { + return str.replace(/::/g, '/') + .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') + .replace(/([a-z\d])([A-Z])/g, '$1_$2') + .replace(/_/g, '-') + .toLowerCase(); + }, + // http://support.microsoft.com/kb/167820 + // http://stackoverflow.com/questions/19999388/jquery-check-if-user-is-using-ie + msieversion: function () { + var + ua = window.navigator.userAgent, + msie = ua.indexOf('MSIE '); + if (msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./)) + return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10); + return 0; + } + }; +// All these options could be overriden and specified directly in DOM using +// `data-parsley-` default DOM-API +// eg: `inputs` can be set in DOM using `data-parsley-inputs="input, textarea"` +// eg: `data-parsley-stop-on-first-failing-constraint="false"` + var ParsleyDefaults = { + // ### General + // Default data-namespace for DOM API + namespace: 'data-parsley-', + // Supported inputs by default + inputs: 'input, textarea, select', + // Excluded inputs by default + excluded: 'input[type=button], input[type=submit], input[type=reset], input[type=hidden]', + // Stop validating field on highest priority failing constraint + priorityEnabled: true, + // ### UI + // Enable\Disable error messages + uiEnabled: true, + // Key events threshold before validation + validationThreshold: 3, + // Focused field on form validation error. 'fist'|'last'|'none' + focus: 'first', + // `$.Event()` that will trigger validation. eg: `keyup`, `change`... + trigger: false, + // Class that would be added on every failing validation Parsley field + errorClass: 'parsley-error', + // Same for success validation + successClass: 'parsley-success', + // Return the `$element` that will receive these above success or error classes + // Could also be (and given directly from DOM) a valid selector like `'#div'` + classHandler: function (ParsleyField) {}, + // Return the `$element` where errors will be appended + // Could also be (and given directly from DOM) a valid selector like `'#div'` + errorsContainer: function (ParsleyField) {}, + // ul elem that would receive errors' list + errorsWrapper: '
    ', + // li elem that would receive error message + errorTemplate: '
  • ' + }; + + var ParsleyAbstract = function() {}; + ParsleyAbstract.prototype = { + asyncSupport: false, + actualizeOptions: function () { + this.options = this.OptionsFactory.get(this); + return this; + }, + // ParsleyValidator validate proxy function . Could be replaced by third party scripts + validateThroughValidator: function (value, constraints, priority) { + return window.ParsleyValidator.validate(value, constraints, priority); + }, + // Subscribe an event and a handler for a specific field or a specific form + // If on a ParsleyForm instance, it will be attached to form instance and also + // To every field instance for this form + subscribe: function (name, fn) { + $.listenTo(this, name.toLowerCase(), fn); + return this; + }, + // Same as subscribe above. Unsubscribe an event for field, or form + its fields + unsubscribe: function (name) { + $.unsubscribeTo(this, name.toLowerCase()); + return this; + }, + // Reset UI + reset: function () { + // Field case: just emit a reset event for UI + if ('ParsleyForm' !== this.__class__) + return $.emit('parsley:field:reset', this); + // Form case: emit a reset event for each field + for (var i = 0; i < this.fields.length; i++) + $.emit('parsley:field:reset', this.fields[i]); + $.emit('parsley:form:reset', this); + }, + // Destroy Parsley instance (+ UI) + destroy: function () { + // Field case: emit destroy event to clean UI and then destroy stored instance + if ('ParsleyForm' !== this.__class__) { + this.$element.removeData('Parsley'); + this.$element.removeData('ParsleyFieldMultiple'); + $.emit('parsley:field:destroy', this); + return; + } + // Form case: destroy all its fields and then destroy stored instance + for (var i = 0; i < this.fields.length; i++) + this.fields[i].destroy(); + this.$element.removeData('Parsley'); + $.emit('parsley:form:destroy', this); + } + }; +/*! +* validator.js +* Guillaume Potier - +* Version 1.0.0 - built Sun Aug 03 2014 17:42:31 +* MIT Licensed +* +*/ +var Validator = ( function ( ) { + var exports = {}; + /** + * Validator + */ + var Validator = function ( options ) { + this.__class__ = 'Validator'; + this.__version__ = '1.0.0'; + this.options = options || {}; + this.bindingKey = this.options.bindingKey || '_validatorjsConstraint'; + }; + Validator.prototype = { + constructor: Validator, + /* + * Validate string: validate( string, Assert, string ) || validate( string, [ Assert, Assert ], [ string, string ] ) + * Validate object: validate( object, Constraint, string ) || validate( object, Constraint, [ string, string ] ) + * Validate binded object: validate( object, string ) || validate( object, [ string, string ] ) + */ + validate: function ( objectOrString, AssertsOrConstraintOrGroup, group ) { + if ( 'string' !== typeof objectOrString && 'object' !== typeof objectOrString ) + throw new Error( 'You must validate an object or a string' ); + // string / array validation + if ( 'string' === typeof objectOrString || _isArray(objectOrString) ) + return this._validateString( objectOrString, AssertsOrConstraintOrGroup, group ); + // binded object validation + if ( this.isBinded( objectOrString ) ) + return this._validateBindedObject( objectOrString, AssertsOrConstraintOrGroup ); + // regular object validation + return this._validateObject( objectOrString, AssertsOrConstraintOrGroup, group ); + }, + bind: function ( object, constraint ) { + if ( 'object' !== typeof object ) + throw new Error( 'Must bind a Constraint to an object' ); + object[ this.bindingKey ] = new Constraint( constraint ); + return this; + }, + unbind: function ( object ) { + if ( 'undefined' === typeof object._validatorjsConstraint ) + return this; + delete object[ this.bindingKey ]; + return this; + }, + isBinded: function ( object ) { + return 'undefined' !== typeof object[ this.bindingKey ]; + }, + getBinded: function ( object ) { + return this.isBinded( object ) ? object[ this.bindingKey ] : null; + }, + _validateString: function ( string, assert, group ) { + var result, failures = []; + if ( !_isArray( assert ) ) + assert = [ assert ]; + for ( var i = 0; i < assert.length; i++ ) { + if ( ! ( assert[ i ] instanceof Assert) ) + throw new Error( 'You must give an Assert or an Asserts array to validate a string' ); + result = assert[ i ].check( string, group ); + if ( result instanceof Violation ) + failures.push( result ); + } + return failures.length ? failures : true; + }, + _validateObject: function ( object, constraint, group ) { + if ( 'object' !== typeof constraint ) + throw new Error( 'You must give a constraint to validate an object' ); + if ( constraint instanceof Constraint ) + return constraint.check( object, group ); + return new Constraint( constraint ).check( object, group ); + }, + _validateBindedObject: function ( object, group ) { + return object[ this.bindingKey ].check( object, group ); + } + }; + Validator.errorCode = { + must_be_a_string: 'must_be_a_string', + must_be_an_array: 'must_be_an_array', + must_be_a_number: 'must_be_a_number', + must_be_a_string_or_array: 'must_be_a_string_or_array' + }; + /** + * Constraint + */ + var Constraint = function ( data, options ) { + this.__class__ = 'Constraint'; + this.options = options || {}; + this.nodes = {}; + if ( data ) { + try { + this._bootstrap( data ); + } catch ( err ) { + throw new Error( 'Should give a valid mapping object to Constraint', err, data ); + } + } + }; + Constraint.prototype = { + constructor: Constraint, + check: function ( object, group ) { + var result, failures = {}; + // check all constraint nodes. + for ( var property in this.nodes ) { + var isRequired = false; + var constraint = this.get(property); + var constraints = _isArray( constraint ) ? constraint : [constraint]; + for (var i = constraints.length - 1; i >= 0; i--) { + if ( 'Required' === constraints[i].__class__ ) { + isRequired = constraints[i].requiresValidation( group ); + continue; + } + } + if ( ! this.has( property, object ) && ! this.options.strict && ! isRequired ) { + continue; + } + try { + if (! this.has( property, this.options.strict || isRequired ? object : undefined ) ) { + // we trigger here a HaveProperty Assert violation to have uniform Violation object in the end + new Assert().HaveProperty( property ).validate( object ); + } + result = this._check( property, object[ property ], group ); + // check returned an array of Violations or an object mapping Violations + if ( ( _isArray( result ) && result.length > 0 ) || ( !_isArray( result ) && !_isEmptyObject( result ) ) ) { + failures[ property ] = result; + } + } catch ( violation ) { + failures[ property ] = violation; + } + } + return _isEmptyObject(failures) ? true : failures; + }, + add: function ( node, object ) { + if ( object instanceof Assert || ( _isArray( object ) && object[ 0 ] instanceof Assert ) ) { + this.nodes[ node ] = object; + return this; + } + if ( 'object' === typeof object && !_isArray( object ) ) { + this.nodes[ node ] = object instanceof Constraint ? object : new Constraint( object ); + return this; + } + throw new Error( 'Should give an Assert, an Asserts array, a Constraint', object ); + }, + has: function ( node, nodes ) { + nodes = 'undefined' !== typeof nodes ? nodes : this.nodes; + return 'undefined' !== typeof nodes[ node ]; + }, + get: function ( node, placeholder ) { + return this.has( node ) ? this.nodes[ node ] : placeholder || null; + }, + remove: function ( node ) { + var _nodes = []; + for ( var i in this.nodes ) + if ( i !== node ) + _nodes[ i ] = this.nodes[ i ]; + this.nodes = _nodes; + return this; + }, + _bootstrap: function ( data ) { + if ( data instanceof Constraint ) + return this.nodes = data.nodes; + for ( var node in data ) + this.add( node, data[ node ] ); + }, + _check: function ( node, value, group ) { + // Assert + if ( this.nodes[ node ] instanceof Assert ) + return this._checkAsserts( value, [ this.nodes[ node ] ], group ); + // Asserts + if ( _isArray( this.nodes[ node ] ) ) + return this._checkAsserts( value, this.nodes[ node ], group ); + // Constraint -> check api + if ( this.nodes[ node ] instanceof Constraint ) + return this.nodes[ node ].check( value, group ); + throw new Error( 'Invalid node', this.nodes[ node ] ); + }, + _checkAsserts: function ( value, asserts, group ) { + var result, failures = []; + for ( var i = 0; i < asserts.length; i++ ) { + result = asserts[ i ].check( value, group ); + if ( 'undefined' !== typeof result && true !== result ) + failures.push( result ); + // Some asserts (Collection for example) could return an object + // if ( result && ! ( result instanceof Violation ) ) + // return result; + // + // // Vast assert majority return Violation + // if ( result instanceof Violation ) + // failures.push( result ); + } + return failures; + } + }; + /** + * Violation + */ + var Violation = function ( assert, value, violation ) { + this.__class__ = 'Violation'; + if ( ! ( assert instanceof Assert ) ) + throw new Error( 'Should give an assertion implementing the Assert interface' ); + this.assert = assert; + this.value = value; + if ( 'undefined' !== typeof violation ) + this.violation = violation; + }; + Violation.prototype = { + show: function () { + var show = { + assert: this.assert.__class__, + value: this.value + }; + if ( this.violation ) + show.violation = this.violation; + return show; + }, + __toString: function () { + if ( 'undefined' !== typeof this.violation ) + this.violation = '", ' + this.getViolation().constraint + ' expected was ' + this.getViolation().expected; + return this.assert.__class__ + ' assert failed for "' + this.value + this.violation || ''; + }, + getViolation: function () { + var constraint, expected; + for ( constraint in this.violation ) + expected = this.violation[ constraint ]; + return { constraint: constraint, expected: expected }; + } + }; + /** + * Assert + */ + var Assert = function ( group ) { + this.__class__ = 'Assert'; + this.__parentClass__ = this.__class__; + this.groups = []; + if ( 'undefined' !== typeof group ) + this.addGroup( group ); + }; + Assert.prototype = { + construct: Assert, + requiresValidation: function ( group ) { + if ( group && !this.hasGroup( group ) ) + return false; + if ( !group && this.hasGroups() ) + return false; + return true; + }, + check: function ( value, group ) { + if ( !this.requiresValidation( group ) ) + return; + try { + return this.validate( value, group ); + } catch ( violation ) { + return violation; + } + }, + hasGroup: function ( group ) { + if ( _isArray( group ) ) + return this.hasOneOf( group ); + // All Asserts respond to "Any" group + if ( 'Any' === group ) + return true; + // Asserts with no group also respond to "Default" group. Else return false + if ( !this.hasGroups() ) + return 'Default' === group; + return -1 !== this.groups.indexOf( group ); + }, + hasOneOf: function ( groups ) { + for ( var i = 0; i < groups.length; i++ ) + if ( this.hasGroup( groups[ i ] ) ) + return true; + return false; + }, + hasGroups: function () { + return this.groups.length > 0; + }, + addGroup: function ( group ) { + if ( _isArray( group ) ) + return this.addGroups( group ); + if ( !this.hasGroup( group ) ) + this.groups.push( group ); + return this; + }, + removeGroup: function ( group ) { + var _groups = []; + for ( var i = 0; i < this.groups.length; i++ ) + if ( group !== this.groups[ i ] ) + _groups.push( this.groups[ i ] ); + this.groups = _groups; + return this; + }, + addGroups: function ( groups ) { + for ( var i = 0; i < groups.length; i++ ) + this.addGroup( groups[ i ] ); + return this; + }, + /** + * Asserts definitions + */ + HaveProperty: function ( node ) { + this.__class__ = 'HaveProperty'; + this.node = node; + this.validate = function ( object ) { + if ( 'undefined' === typeof object[ this.node ] ) + throw new Violation( this, object, { value: this.node } ); + return true; + }; + return this; + }, + Blank: function () { + this.__class__ = 'Blank'; + this.validate = function ( value ) { + if ( 'string' !== typeof value ) + throw new Violation( this, value, { value: Validator.errorCode.must_be_a_string } ); + if ( '' !== value.replace( /^\s+/g, '' ).replace( /\s+$/g, '' ) ) + throw new Violation( this, value ); + return true; + }; + return this; + }, + Callback: function ( fn ) { + this.__class__ = 'Callback'; + this.arguments = Array.prototype.slice.call( arguments ); + if ( 1 === this.arguments.length ) + this.arguments = []; + else + this.arguments.splice( 0, 1 ); + if ( 'function' !== typeof fn ) + throw new Error( 'Callback must be instanciated with a function' ); + this.fn = fn; + this.validate = function ( value ) { + var result = this.fn.apply( this, [ value ].concat( this.arguments ) ); + if ( true !== result ) + throw new Violation( this, value, { result: result } ); + return true; + }; + return this; + }, + Choice: function ( list ) { + this.__class__ = 'Choice'; + if ( !_isArray( list ) && 'function' !== typeof list ) + throw new Error( 'Choice must be instanciated with an array or a function' ); + this.list = list; + this.validate = function ( value ) { + var list = 'function' === typeof this.list ? this.list() : this.list; + for ( var i = 0; i < list.length; i++ ) + if ( value === list[ i ] ) + return true; + throw new Violation( this, value, { choices: list } ); + }; + return this; + }, + Collection: function ( assertOrConstraint ) { + this.__class__ = 'Collection'; + this.constraint = 'undefined' !== typeof assertOrConstraint ? (assertOrConstraint instanceof Assert ? assertOrConstraint : new Constraint( assertOrConstraint )) : false; + this.validate = function ( collection, group ) { + var result, validator = new Validator(), count = 0, failures = {}, groups = this.groups.length ? this.groups : group; + if ( !_isArray( collection ) ) + throw new Violation( this, array, { value: Validator.errorCode.must_be_an_array } ); + for ( var i = 0; i < collection.length; i++ ) { + result = this.constraint ? + validator.validate( collection[ i ], this.constraint, groups ) : + validator.validate( collection[ i ], groups ); + if ( !_isEmptyObject( result ) ) + failures[ count ] = result; + count++; + } + return !_isEmptyObject( failures ) ? failures : true; + }; + return this; + }, + Count: function ( count ) { + this.__class__ = 'Count'; + this.count = count; + this.validate = function ( array ) { + if ( !_isArray( array ) ) + throw new Violation( this, array, { value: Validator.errorCode.must_be_an_array } ); + var count = 'function' === typeof this.count ? this.count( array ) : this.count; + if ( isNaN( Number( count ) ) ) + throw new Error( 'Count must be a valid interger', count ); + if ( count !== array.length ) + throw new Violation( this, array, { count: count } ); + return true; + }; + return this; + }, + Email: function () { + this.__class__ = 'Email'; + this.validate = function ( value ) { + var regExp = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i; + if ( 'string' !== typeof value ) + throw new Violation( this, value, { value: Validator.errorCode.must_be_a_string } ); + if ( !regExp.test( value ) ) + throw new Violation( this, value ); + return true; + }; + return this; + }, + EqualTo: function ( reference ) { + this.__class__ = 'EqualTo'; + if ( 'undefined' === typeof reference ) + throw new Error( 'EqualTo must be instanciated with a value or a function' ); + this.reference = reference; + this.validate = function ( value ) { + var reference = 'function' === typeof this.reference ? this.reference( value ) : this.reference; + if ( reference !== value ) + throw new Violation( this, value, { value: reference } ); + return true; + }; + return this; + }, + GreaterThan: function ( threshold ) { + this.__class__ = 'GreaterThan'; + if ( 'undefined' === typeof threshold ) + throw new Error( 'Should give a threshold value' ); + this.threshold = threshold; + this.validate = function ( value ) { + if ( '' === value || isNaN( Number( value ) ) ) + throw new Violation( this, value, { value: Validator.errorCode.must_be_a_number } ); + if ( this.threshold >= value ) + throw new Violation( this, value, { threshold: this.threshold } ); + return true; + }; + return this; + }, + GreaterThanOrEqual: function ( threshold ) { + this.__class__ = 'GreaterThanOrEqual'; + if ( 'undefined' === typeof threshold ) + throw new Error( 'Should give a threshold value' ); + this.threshold = threshold; + this.validate = function ( value ) { + if ( '' === value || isNaN( Number( value ) ) ) + throw new Violation( this, value, { value: Validator.errorCode.must_be_a_number } ); + if ( this.threshold > value ) + throw new Violation( this, value, { threshold: this.threshold } ); + return true; + }; + return this; + }, + InstanceOf: function ( classRef ) { + this.__class__ = 'InstanceOf'; + if ( 'undefined' === typeof classRef ) + throw new Error( 'InstanceOf must be instanciated with a value' ); + this.classRef = classRef; + this.validate = function ( value ) { + if ( true !== (value instanceof this.classRef) ) + throw new Violation( this, value, { classRef: this.classRef } ); + return true; + }; + return this; + }, + Length: function ( boundaries ) { + this.__class__ = 'Length'; + if ( !boundaries.min && !boundaries.max ) + throw new Error( 'Lenth assert must be instanciated with a { min: x, max: y } object' ); + this.min = boundaries.min; + this.max = boundaries.max; + this.validate = function ( value ) { + if ( 'string' !== typeof value && !_isArray( value ) ) + throw new Violation( this, value, { value: Validator.errorCode.must_be_a_string_or_array } ); + if ( 'undefined' !== typeof this.min && this.min === this.max && value.length !== this.min ) + throw new Violation( this, value, { min: this.min, max: this.max } ); + if ( 'undefined' !== typeof this.max && value.length > this.max ) + throw new Violation( this, value, { max: this.max } ); + if ( 'undefined' !== typeof this.min && value.length < this.min ) + throw new Violation( this, value, { min: this.min } ); + return true; + }; + return this; + }, + LessThan: function ( threshold ) { + this.__class__ = 'LessThan'; + if ( 'undefined' === typeof threshold ) + throw new Error( 'Should give a threshold value' ); + this.threshold = threshold; + this.validate = function ( value ) { + if ( '' === value || isNaN( Number( value ) ) ) + throw new Violation( this, value, { value: Validator.errorCode.must_be_a_number } ); + if ( this.threshold <= value ) + throw new Violation( this, value, { threshold: this.threshold } ); + return true; + }; + return this; + }, + LessThanOrEqual: function ( threshold ) { + this.__class__ = 'LessThanOrEqual'; + if ( 'undefined' === typeof threshold ) + throw new Error( 'Should give a threshold value' ); + this.threshold = threshold; + this.validate = function ( value ) { + if ( '' === value || isNaN( Number( value ) ) ) + throw new Violation( this, value, { value: Validator.errorCode.must_be_a_number } ); + if ( this.threshold < value ) + throw new Violation( this, value, { threshold: this.threshold } ); + return true; + }; + return this; + }, + NotNull: function () { + this.__class__ = 'NotNull'; + this.validate = function ( value ) { + if ( null === value || 'undefined' === typeof value ) + throw new Violation( this, value ); + return true; + }; + return this; + }, + NotBlank: function () { + this.__class__ = 'NotBlank'; + this.validate = function ( value ) { + if ( 'string' !== typeof value ) + throw new Violation( this, value, { value: Validator.errorCode.must_be_a_string } ); + if ( '' === value.replace( /^\s+/g, '' ).replace( /\s+$/g, '' ) ) + throw new Violation( this, value ); + return true; + }; + return this; + }, + Null: function () { + this.__class__ = 'Null'; + this.validate = function ( value ) { + if ( null !== value ) + throw new Violation( this, value ); + return true; + }; + return this; + }, + Range: function ( min, max ) { + this.__class__ = 'Range'; + if ( 'undefined' === typeof min || 'undefined' === typeof max ) + throw new Error( 'Range assert expects min and max values' ); + this.min = min; + this.max = max; + this.validate = function ( value ) { + try { + // validate strings and objects with their Length + if ( ( 'string' === typeof value && isNaN( Number( value ) ) ) || _isArray( value ) ) + new Assert().Length( { min: this.min, max: this.max } ).validate( value ); + // validate numbers with their value + else + new Assert().GreaterThanOrEqual( this.min ).validate( value ) && new Assert().LessThanOrEqual( this.max ).validate( value ); + return true; + } catch ( violation ) { + throw new Violation( this, value, violation.violation ); + } + return true; + }; + return this; + }, + Regexp: function ( regexp, flag ) { + this.__class__ = 'Regexp'; + if ( 'undefined' === typeof regexp ) + throw new Error( 'You must give a regexp' ); + this.regexp = regexp; + this.flag = flag || ''; + this.validate = function ( value ) { + if ( 'string' !== typeof value ) + throw new Violation( this, value, { value: Validator.errorCode.must_be_a_string } ); + if ( !new RegExp( this.regexp, this.flag ).test( value ) ) + throw new Violation( this, value, { regexp: this.regexp, flag: this.flag } ); + return true; + }; + return this; + }, + Required: function () { + this.__class__ = 'Required'; + this.validate = function ( value ) { + if ( 'undefined' === typeof value ) + throw new Violation( this, value ); + try { + if ( 'string' === typeof value ) + new Assert().NotNull().validate( value ) && new Assert().NotBlank().validate( value ); + else if ( true === _isArray( value ) ) + new Assert().Length( { min: 1 } ).validate( value ); + } catch ( violation ) { + throw new Violation( this, value ); + } + return true; + }; + return this; + }, + // Unique() or Unique ( { key: foo } ) + Unique: function ( object ) { + this.__class__ = 'Unique'; + if ( 'object' === typeof object ) + this.key = object.key; + this.validate = function ( array ) { + var value, store = []; + if ( !_isArray( array ) ) + throw new Violation( this, array, { value: Validator.errorCode.must_be_an_array } ); + for ( var i = 0; i < array.length; i++ ) { + value = 'object' === typeof array[ i ] ? array[ i ][ this.key ] : array[ i ]; + if ( 'undefined' === typeof value ) + continue; + if ( -1 !== store.indexOf( value ) ) + throw new Violation( this, array, { value: value } ); + store.push( value ); + } + return true; + }; + return this; + } + }; + // expose to the world these awesome classes + exports.Assert = Assert; + exports.Validator = Validator; + exports.Violation = Violation; + exports.Constraint = Constraint; + /** + * Some useful object prototypes / functions here + */ + // IE8<= compatibility + // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf + if (!Array.prototype.indexOf) + Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { + + if (this === null) { + throw new TypeError(); + } + var t = Object(this); + var len = t.length >>> 0; + if (len === 0) { + return -1; + } + var n = 0; + if (arguments.length > 1) { + n = Number(arguments[1]); + if (n != n) { // shortcut for verifying if it's NaN + n = 0; + } else if (n !== 0 && n != Infinity && n != -Infinity) { + n = (n > 0 || -1) * Math.floor(Math.abs(n)); + } + } + if (n >= len) { + return -1; + } + var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); + for (; k < len; k++) { + if (k in t && t[k] === searchElement) { + return k; + } + } + return -1; + }; + // Test if object is empty, useful for Constraint violations check + var _isEmptyObject = function ( obj ) { + for ( var property in obj ) + return false; + return true; + }; + var _isArray = function ( obj ) { + return Object.prototype.toString.call( obj ) === '[object Array]'; + }; + // AMD export + if ( typeof define === 'function' && define.amd ) { + define( 'vendors/validator.js/dist/validator',[],function() { + return exports; + } ); + // commonjs export + } else if ( typeof module !== 'undefined' && module.exports ) { + module.exports = exports; + // browser + } else { + window[ 'undefined' !== typeof validatorjs_ns ? validatorjs_ns : 'Validator' ] = exports; + } + + return exports; +} )( ); + + // This is needed for Browserify usage that requires Validator.js through module.exports + Validator = 'undefined' !== typeof Validator ? Validator : ('undefined' !== typeof module ? module.exports : null); + var ParsleyValidator = function (validators, catalog) { + this.__class__ = 'ParsleyValidator'; + this.Validator = Validator; + // Default Parsley locale is en + this.locale = 'en'; + this.init(validators || {}, catalog || {}); + }; + ParsleyValidator.prototype = { + init: function (validators, catalog) { + this.catalog = catalog; + for (var name in validators) + this.addValidator(name, validators[name].fn, validators[name].priority, validators[name].requirementsTransformer); + $.emit('parsley:validator:init'); + }, + // Set new messages locale if we have dictionary loaded in ParsleyConfig.i18n + setLocale: function (locale) { + if ('undefined' === typeof this.catalog[locale]) + throw new Error(locale + ' is not available in the catalog'); + this.locale = locale; + return this; + }, + // Add a new messages catalog for a given locale. Set locale for this catalog if set === `true` + addCatalog: function (locale, messages, set) { + if ('object' === typeof messages) + this.catalog[locale] = messages; + if (true === set) + return this.setLocale(locale); + return this; + }, + // Add a specific message for a given constraint in a given locale + addMessage: function (locale, name, message) { + if ('undefined' === typeof this.catalog[locale]) + this.catalog[locale] = {}; + this.catalog[locale][name.toLowerCase()] = message; + return this; + }, + validate: function (value, constraints, priority) { + return new this.Validator.Validator().validate.apply(new Validator.Validator(), arguments); + }, + // Add a new validator + addValidator: function (name, fn, priority, requirementsTransformer) { + this.validators[name.toLowerCase()] = function (requirements) { + return $.extend(new Validator.Assert().Callback(fn, requirements), { + priority: priority, + requirementsTransformer: requirementsTransformer + }); + }; + return this; + }, + updateValidator: function (name, fn, priority, requirementsTransformer) { + return this.addValidator(name, fn, priority, requirementsTransformer); + }, + removeValidator: function (name) { + delete this.validators[name]; + return this; + }, + getErrorMessage: function (constraint) { + var message; + // Type constraints are a bit different, we have to match their requirements too to find right error message + if ('type' === constraint.name) + message = this.catalog[this.locale][constraint.name][constraint.requirements]; + else + message = this.formatMessage(this.catalog[this.locale][constraint.name], constraint.requirements); + return '' !== message ? message : this.catalog[this.locale].defaultMessage; + }, + // Kind of light `sprintf()` implementation + formatMessage: function (string, parameters) { + if ('object' === typeof parameters) { + for (var i in parameters) + string = this.formatMessage(string, parameters[i]); + return string; + } + return 'string' === typeof string ? string.replace(new RegExp('%s', 'i'), parameters) : ''; + }, + // Here is the Parsley default validators list. + // This is basically Validatorjs validators, with different API for some of them + // and a Parsley priority set + validators: { + notblank: function () { + return $.extend(new Validator.Assert().NotBlank(), { priority: 2 }); + }, + required: function () { + return $.extend(new Validator.Assert().Required(), { priority: 512 }); + }, + type: function (type) { + var assert; + switch (type) { + case 'email': + assert = new Validator.Assert().Email(); + break; + // range type just ensure we have a number here + case 'range': + case 'number': + assert = new Validator.Assert().Regexp('^-?(?:\\d+|\\d{1,3}(?:,\\d{3})+)?(?:\\.\\d+)?$'); + break; + case 'integer': + assert = new Validator.Assert().Regexp('^-?\\d+$'); + break; + case 'digits': + assert = new Validator.Assert().Regexp('^\\d+$'); + break; + case 'alphanum': + assert = new Validator.Assert().Regexp('^\\w+$', 'i'); + break; + case 'url': + assert = new Validator.Assert().Regexp('(https?:\\/\\/)?(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,24}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)', 'i'); + break; + default: + throw new Error('validator type `' + type + '` is not supported'); + } + return $.extend(assert, { priority: 256 }); + }, + pattern: function (regexp) { + var flags = ''; + // Test if RegExp is literal, if not, nothing to be done, otherwise, we need to isolate flags and pattern + if (!!(/^\/.*\/(?:[gimy]*)$/.test(regexp))) { + // Replace the regexp literal string with the first match group: ([gimy]*) + // If no flag is present, this will be a blank string + flags = regexp.replace(/.*\/([gimy]*)$/, '$1'); + // Again, replace the regexp literal string with the first match group: + // everything excluding the opening and closing slashes and the flags + regexp = regexp.replace(new RegExp('^/(.*?)/' + flags + '$'), '$1'); + } + return $.extend(new Validator.Assert().Regexp(regexp, flags), { priority: 64 }); + }, + minlength: function (value) { + return $.extend(new Validator.Assert().Length({ min: value }), { + priority: 30, + requirementsTransformer: function () { + return 'string' === typeof value && !isNaN(value) ? parseInt(value, 10) : value; + } + }); + }, + maxlength: function (value) { + return $.extend(new Validator.Assert().Length({ max: value }), { + priority: 30, + requirementsTransformer: function () { + return 'string' === typeof value && !isNaN(value) ? parseInt(value, 10) : value; + } + }); + }, + length: function (array) { + return $.extend(new Validator.Assert().Length({ min: array[0], max: array[1] }), { priority: 32 }); + }, + mincheck: function (length) { + return this.minlength(length); + }, + maxcheck: function (length) { + return this.maxlength(length); + }, + check: function (array) { + return this.length(array); + }, + min: function (value) { + return $.extend(new Validator.Assert().GreaterThanOrEqual(value), { + priority: 30, + requirementsTransformer: function () { + return 'string' === typeof value && !isNaN(value) ? parseInt(value, 10) : value; + } + }); + }, + max: function (value) { + return $.extend(new Validator.Assert().LessThanOrEqual(value), { + priority: 30, + requirementsTransformer: function () { + return 'string' === typeof value && !isNaN(value) ? parseInt(value, 10) : value; + } + }); + }, + range: function (array) { + return $.extend(new Validator.Assert().Range(array[0], array[1]), { + priority: 32, + requirementsTransformer: function () { + for (var i = 0; i < array.length; i++) + array[i] = 'string' === typeof array[i] && !isNaN(array[i]) ? parseInt(array[i], 10) : array[i]; + return array; + } + }); + }, + equalto: function (value) { + return $.extend(new Validator.Assert().EqualTo(value), { + priority: 256, + requirementsTransformer: function () { + return $(value).length ? $(value).val() : value; + } + }); + } + } + }; + + var ParsleyUI = function (options) { + this.__class__ = 'ParsleyUI'; + }; + ParsleyUI.prototype = { + listen: function () { + $.listen('parsley:form:init', this, this.setupForm); + $.listen('parsley:field:init', this, this.setupField); + $.listen('parsley:field:validated', this, this.reflow); + $.listen('parsley:form:validated', this, this.focus); + $.listen('parsley:field:reset', this, this.reset); + $.listen('parsley:form:destroy', this, this.destroy); + $.listen('parsley:field:destroy', this, this.destroy); + return this; + }, + reflow: function (fieldInstance) { + // If this field has not an active UI (case for multiples) don't bother doing something + if ('undefined' === typeof fieldInstance._ui || false === fieldInstance._ui.active) + return; + // Diff between two validation results + var diff = this._diff(fieldInstance.validationResult, fieldInstance._ui.lastValidationResult); + // Then store current validation result for next reflow + fieldInstance._ui.lastValidationResult = fieldInstance.validationResult; + // Field have been validated at least once if here. Useful for binded key events... + fieldInstance._ui.validatedOnce = true; + // Handle valid / invalid / none field class + this.manageStatusClass(fieldInstance); + // Add, remove, updated errors messages + this.manageErrorsMessages(fieldInstance, diff); + // Triggers impl + this.actualizeTriggers(fieldInstance); + // If field is not valid for the first time, bind keyup trigger to ease UX and quickly inform user + if ((diff.kept.length || diff.added.length) && 'undefined' === typeof fieldInstance._ui.failedOnce) + this.manageFailingFieldTrigger(fieldInstance); + }, + // Returns an array of field's error message(s) + getErrorsMessages: function (fieldInstance) { + // No error message, field is valid + if (true === fieldInstance.validationResult) + return []; + var messages = []; + for (var i = 0; i < fieldInstance.validationResult.length; i++) + messages.push(this._getErrorMessage(fieldInstance, fieldInstance.validationResult[i].assert)); + return messages; + }, + manageStatusClass: function (fieldInstance) { + if (true === fieldInstance.validationResult) + this._successClass(fieldInstance); + else if (fieldInstance.validationResult.length > 0) + this._errorClass(fieldInstance); + else + this._resetClass(fieldInstance); + }, + manageErrorsMessages: function (fieldInstance, diff) { + if ('undefined' !== typeof fieldInstance.options.errorsMessagesDisabled) + return; + // Case where we have errorMessage option that configure an unique field error message, regardless failing validators + if ('undefined' !== typeof fieldInstance.options.errorMessage) { + if ((diff.added.length || diff.kept.length)) { + if (0 === fieldInstance._ui.$errorsWrapper.find('.parsley-custom-error-message').length) + fieldInstance._ui.$errorsWrapper + .append( + $(fieldInstance.options.errorTemplate) + .addClass('parsley-custom-error-message') + ); + return fieldInstance._ui.$errorsWrapper + .addClass('filled') + .find('.parsley-custom-error-message') + .html(fieldInstance.options.errorMessage); + } + return fieldInstance._ui.$errorsWrapper + .removeClass('filled') + .find('.parsley-custom-error-message') + .remove(); + } + // Show, hide, update failing constraints messages + for (var i = 0; i < diff.removed.length; i++) + this.removeError(fieldInstance, diff.removed[i].assert.name, true); + for (i = 0; i < diff.added.length; i++) + this.addError(fieldInstance, diff.added[i].assert.name, undefined, diff.added[i].assert, true); + for (i = 0; i < diff.kept.length; i++) + this.updateError(fieldInstance, diff.kept[i].assert.name, undefined, diff.kept[i].assert, true); + }, + // TODO: strange API here, intuitive for manual usage with addError(pslyInstance, 'foo', 'bar') + // but a little bit complex for above internal usage, with forced undefined parameter... + addError: function (fieldInstance, name, message, assert, doNotUpdateClass) { + fieldInstance._ui.$errorsWrapper + .addClass('filled') + .append( + $(fieldInstance.options.errorTemplate) + .addClass('parsley-' + name) + .html(message || this._getErrorMessage(fieldInstance, assert)) + ); + if (true !== doNotUpdateClass) + this._errorClass(fieldInstance); + }, + // Same as above + updateError: function (fieldInstance, name, message, assert, doNotUpdateClass) { + fieldInstance._ui.$errorsWrapper + .addClass('filled') + .find('.parsley-' + name) + .html(message || this._getErrorMessage(fieldInstance, assert)); + if (true !== doNotUpdateClass) + this._errorClass(fieldInstance); + }, + // Same as above twice + removeError: function (fieldInstance, name, doNotUpdateClass) { + fieldInstance._ui.$errorsWrapper + .removeClass('filled') + .find('.parsley-' + name) + .remove(); + // edge case possible here: remove a standard Parsley error that is still failing in fieldInstance.validationResult + // but highly improbable cuz' manually removing a well Parsley handled error makes no sense. + if (true !== doNotUpdateClass) + this.manageStatusClass(fieldInstance); + }, + focus: function (formInstance) { + if (true === formInstance.validationResult || 'none' === formInstance.options.focus) + return formInstance._focusedField = null; + formInstance._focusedField = null; + for (var i = 0; i < formInstance.fields.length; i++) + if (true !== formInstance.fields[i].validationResult && formInstance.fields[i].validationResult.length > 0 && 'undefined' === typeof formInstance.fields[i].options.noFocus) { + if ('first' === formInstance.options.focus) { + formInstance._focusedField = formInstance.fields[i].$element; + return formInstance._focusedField.focus(); + } + formInstance._focusedField = formInstance.fields[i].$element; + } + if (null === formInstance._focusedField) + return null; + return formInstance._focusedField.focus(); + }, + _getErrorMessage: function (fieldInstance, constraint) { + var customConstraintErrorMessage = constraint.name + 'Message'; + if ('undefined' !== typeof fieldInstance.options[customConstraintErrorMessage]) + return window.ParsleyValidator.formatMessage(fieldInstance.options[customConstraintErrorMessage], constraint.requirements); + return window.ParsleyValidator.getErrorMessage(constraint); + }, + _diff: function (newResult, oldResult, deep) { + var + added = [], + kept = []; + for (var i = 0; i < newResult.length; i++) { + var found = false; + for (var j = 0; j < oldResult.length; j++) + if (newResult[i].assert.name === oldResult[j].assert.name) { + found = true; + break; + } + if (found) + kept.push(newResult[i]); + else + added.push(newResult[i]); + } + return { + kept: kept, + added: added, + removed: !deep ? this._diff(oldResult, newResult, true).added : [] + }; + }, + setupForm: function (formInstance) { + formInstance.$element.on('submit.Parsley', false, $.proxy(formInstance.onSubmitValidate, formInstance)); + // UI could be disabled + if (false === formInstance.options.uiEnabled) + return; + formInstance.$element.attr('novalidate', ''); + }, + setupField: function (fieldInstance) { + var _ui = { active: false }; + // UI could be disabled + if (false === fieldInstance.options.uiEnabled) + return; + _ui.active = true; + // Give field its Parsley id in DOM + fieldInstance.$element.attr(fieldInstance.options.namespace + 'id', fieldInstance.__id__); + /** Generate important UI elements and store them in fieldInstance **/ + // $errorClassHandler is the $element that woul have parsley-error and parsley-success classes + _ui.$errorClassHandler = this._manageClassHandler(fieldInstance); + // $errorsWrapper is a div that would contain the various field errors, it will be appended into $errorsContainer + _ui.errorsWrapperId = 'parsley-id-' + ('undefined' !== typeof fieldInstance.options.multiple ? 'multiple-' + fieldInstance.options.multiple : fieldInstance.__id__); + _ui.$errorsWrapper = $(fieldInstance.options.errorsWrapper).attr('id', _ui.errorsWrapperId); + // ValidationResult UI storage to detect what have changed bwt two validations, and update DOM accordingly + _ui.lastValidationResult = []; + _ui.validatedOnce = false; + _ui.validationInformationVisible = false; + // Store it in fieldInstance for later + fieldInstance._ui = _ui; + // Stops excluded inputs from getting errorContainer added + if( !fieldInstance.$element.is(fieldInstance.options.excluded) ) { + /** Mess with DOM now **/ + this._insertErrorWrapper(fieldInstance); + } + // Bind triggers first time + this.actualizeTriggers(fieldInstance); + }, + // Determine which element will have `parsley-error` and `parsley-success` classes + _manageClassHandler: function (fieldInstance) { + // An element selector could be passed through DOM with `data-parsley-class-handler=#foo` + if ('string' === typeof fieldInstance.options.classHandler && $(fieldInstance.options.classHandler).length) + return $(fieldInstance.options.classHandler); + // Class handled could also be determined by function given in Parsley options + var $handler = fieldInstance.options.classHandler(fieldInstance); + // If this function returned a valid existing DOM element, go for it + if ('undefined' !== typeof $handler && $handler.length) + return $handler; + // Otherwise, if simple element (input, texatrea, select...) it will perfectly host the classes + if ('undefined' === typeof fieldInstance.options.multiple || fieldInstance.$element.is('select')) + return fieldInstance.$element; + // But if multiple element (radio, checkbox), that would be their parent + return fieldInstance.$element.parent(); + }, + _insertErrorWrapper: function (fieldInstance) { + var $errorsContainer; + if ('string' === typeof fieldInstance.options.errorsContainer) { + if ($(fieldInstance.options.errorsContainer).length) + return $(fieldInstance.options.errorsContainer).append(fieldInstance._ui.$errorsWrapper); + else if (window.console && window.console.warn) + window.console.warn('The errors container `' + fieldInstance.options.errorsContainer + '` does not exist in DOM'); + } + else if ('function' === typeof fieldInstance.options.errorsContainer) + $errorsContainer = fieldInstance.options.errorsContainer(fieldInstance); + if ('undefined' !== typeof $errorsContainer && $errorsContainer.length) + return $errorsContainer.append(fieldInstance._ui.$errorsWrapper); + return 'undefined' === typeof fieldInstance.options.multiple ? fieldInstance.$element.after(fieldInstance._ui.$errorsWrapper) : fieldInstance.$element.parent().after(fieldInstance._ui.$errorsWrapper); + }, + actualizeTriggers: function (fieldInstance) { + var $toBind = fieldInstance.$element; + if (fieldInstance.options.multiple) + $toBind = $('[' + fieldInstance.options.namespace + 'multiple="' + fieldInstance.options.multiple + '"]') + // Remove Parsley events already binded on this field + $toBind.off('.Parsley'); + // If no trigger is set, all good + if (false === fieldInstance.options.trigger) + return; + var triggers = fieldInstance.options.trigger.replace(/^\s+/g , '').replace(/\s+$/g , ''); + if ('' === triggers) + return; + // Bind fieldInstance.eventValidate if exists (for parsley.ajax for example), ParsleyUI.eventValidate otherwise + $toBind.on( + triggers.split(' ').join('.Parsley ') + '.Parsley', + $.proxy('function' === typeof fieldInstance.eventValidate ? fieldInstance.eventValidate : this.eventValidate, fieldInstance)); + }, + // Called through $.proxy with fieldInstance. `this` context is ParsleyField + eventValidate: function(event) { + // For keyup, keypress, keydown... events that could be a little bit obstrusive + // do not validate if val length < min threshold on first validation. Once field have been validated once and info + // about success or failure have been displayed, always validate with this trigger to reflect every yalidation change. + if (new RegExp('key').test(event.type)) + if (!this._ui.validationInformationVisible && this.getValue().length <= this.options.validationThreshold) + return; + this._ui.validatedOnce = true; + this.validate(); + }, + manageFailingFieldTrigger: function (fieldInstance) { + fieldInstance._ui.failedOnce = true; + // Radio and checkboxes fields must bind every field multiple + if (fieldInstance.options.multiple) + $('[' + fieldInstance.options.namespace + 'multiple="' + fieldInstance.options.multiple + '"]').each(function () { + if (!new RegExp('change', 'i').test($(this).parsley().options.trigger || '')) + return $(this).on('change.ParsleyFailedOnce', false, $.proxy(fieldInstance.validate, fieldInstance)); + }); + // Select case + if (fieldInstance.$element.is('select')) + if (!new RegExp('change', 'i').test(fieldInstance.options.trigger || '')) + return fieldInstance.$element.on('change.ParsleyFailedOnce', false, $.proxy(fieldInstance.validate, fieldInstance)); + // All other inputs fields + if (!new RegExp('keyup', 'i').test(fieldInstance.options.trigger || '')) + return fieldInstance.$element.on('keyup.ParsleyFailedOnce', false, $.proxy(fieldInstance.validate, fieldInstance)); + }, + reset: function (parsleyInstance) { + // Reset all event listeners + parsleyInstance.$element.off('.Parsley'); + parsleyInstance.$element.off('.ParsleyFailedOnce'); + // Nothing to do if UI never initialized for this field + if ('undefined' === typeof parsleyInstance._ui) + return; + if ('ParsleyForm' === parsleyInstance.__class__) + return; + // Reset all errors' li + parsleyInstance._ui.$errorsWrapper + .removeClass('filled') + .children() + .remove(); + // Reset validation class + this._resetClass(parsleyInstance); + // Reset validation flags and last validation result + parsleyInstance._ui.validatedOnce = false; + parsleyInstance._ui.lastValidationResult = []; + parsleyInstance._ui.validationInformationVisible = false; + }, + destroy: function (parsleyInstance) { + this.reset(parsleyInstance); + if ('ParsleyForm' === parsleyInstance.__class__) + return; + if ('undefined' !== typeof parsleyInstance._ui) + parsleyInstance._ui.$errorsWrapper.remove(); + delete parsleyInstance._ui; + }, + _successClass: function (fieldInstance) { + fieldInstance._ui.validationInformationVisible = true; + fieldInstance._ui.$errorClassHandler.removeClass(fieldInstance.options.errorClass).addClass(fieldInstance.options.successClass); + }, + _errorClass: function (fieldInstance) { + fieldInstance._ui.validationInformationVisible = true; + fieldInstance._ui.$errorClassHandler.removeClass(fieldInstance.options.successClass).addClass(fieldInstance.options.errorClass); + }, + _resetClass: function (fieldInstance) { + fieldInstance._ui.$errorClassHandler.removeClass(fieldInstance.options.successClass).removeClass(fieldInstance.options.errorClass); + } + }; + + var ParsleyOptionsFactory = function (defaultOptions, globalOptions, userOptions, namespace) { + this.__class__ = 'OptionsFactory'; + this.__id__ = ParsleyUtils.hash(4); + this.formOptions = null; + this.fieldOptions = null; + this.staticOptions = $.extend(true, {}, defaultOptions, globalOptions, userOptions, { namespace: namespace }); + }; + ParsleyOptionsFactory.prototype = { + get: function (parsleyInstance) { + if ('undefined' === typeof parsleyInstance.__class__) + throw new Error('Parsley Instance expected'); + switch (parsleyInstance.__class__) { + case 'Parsley': + return this.staticOptions; + case 'ParsleyForm': + return this.getFormOptions(parsleyInstance); + case 'ParsleyField': + case 'ParsleyFieldMultiple': + return this.getFieldOptions(parsleyInstance); + default: + throw new Error('Instance ' + parsleyInstance.__class__ + ' is not supported'); + } + }, + getFormOptions: function (formInstance) { + this.formOptions = ParsleyUtils.attr(formInstance.$element, this.staticOptions.namespace); + // not deep extend, since formOptions is a 1 level deep object + return $.extend({}, this.staticOptions, this.formOptions); + }, + getFieldOptions: function (fieldInstance) { + this.fieldOptions = ParsleyUtils.attr(fieldInstance.$element, this.staticOptions.namespace); + if (null === this.formOptions && 'undefined' !== typeof fieldInstance.parent) + this.formOptions = this.getFormOptions(fieldInstance.parent); + // not deep extend, since formOptions and fieldOptions is a 1 level deep object + return $.extend({}, this.staticOptions, this.formOptions, this.fieldOptions); + } + }; + + var ParsleyForm = function (element, OptionsFactory) { + this.__class__ = 'ParsleyForm'; + this.__id__ = ParsleyUtils.hash(4); + if ('OptionsFactory' !== ParsleyUtils.get(OptionsFactory, '__class__')) + throw new Error('You must give an OptionsFactory instance'); + this.OptionsFactory = OptionsFactory; + this.$element = $(element); + this.validationResult = null; + this.options = this.OptionsFactory.get(this); + }; + ParsleyForm.prototype = { + onSubmitValidate: function (event) { + this.validate(undefined, undefined, event); + // prevent form submission if validation fails + if (false === this.validationResult && event instanceof $.Event) { + event.stopImmediatePropagation(); + event.preventDefault(); + } + return this; + }, + // @returns boolean + validate: function (group, force, event) { + this.submitEvent = event; + this.validationResult = true; + var fieldValidationResult = []; + $.emit('parsley:form:validate', this); + // Refresh form DOM options and form's fields that could have changed + this._refreshFields(); + // loop through fields to validate them one by one + for (var i = 0; i < this.fields.length; i++) { + // do not validate a field if not the same as given validation group + if (group && !this._isFieldInGroup(this.fields[i], group)) + continue; + fieldValidationResult = this.fields[i].validate(force); + if (true !== fieldValidationResult && fieldValidationResult.length > 0 && this.validationResult) + this.validationResult = false; + } + $.emit('parsley:form:' + (this.validationResult ? 'success' : 'error'), this); + $.emit('parsley:form:validated', this); + return this.validationResult; + }, + // Iterate over refreshed fields, and stop on first failure + isValid: function (group, force) { + this._refreshFields(); + for (var i = 0; i < this.fields.length; i++) { + // do not validate a field if not the same as given validation group + if (group && !this._isFieldInGroup(this.fields[i], group)) + continue; + if (false === this.fields[i].isValid(force)) + return false; + } + return true; + }, + _isFieldInGroup: function (field, group) { + if(ParsleyUtils.isArray(field.options.group)) + return -1 !== $.inArray(group, field.options.group); + return field.options.group === group; + }, + _refreshFields: function () { + return this.actualizeOptions()._bindFields(); + }, + _bindFields: function () { + var self = this; + this.fields = []; + this.fieldsMappedById = {}; + this.$element.find(this.options.inputs).each(function () { + var fieldInstance = new window.Parsley(this, {}, self); + // Only add valid and not excluded `ParsleyField` and `ParsleyFieldMultiple` children + if (('ParsleyField' === fieldInstance.__class__ || 'ParsleyFieldMultiple' === fieldInstance.__class__) && !fieldInstance.$element.is(fieldInstance.options.excluded)) + if ('undefined' === typeof self.fieldsMappedById[fieldInstance.__class__ + '-' + fieldInstance.__id__]) { + self.fieldsMappedById[fieldInstance.__class__ + '-' + fieldInstance.__id__] = fieldInstance; + self.fields.push(fieldInstance); + } + }); + return this; + } + }; + + var ConstraintFactory = function (parsleyField, name, requirements, priority, isDomConstraint) { + var assert = {}; + if (!new RegExp('ParsleyField').test(ParsleyUtils.get(parsleyField, '__class__'))) + throw new Error('ParsleyField or ParsleyFieldMultiple instance expected'); + if ('function' === typeof window.ParsleyValidator.validators[name]) + assert = window.ParsleyValidator.validators[name](requirements); + if ('Assert' !== assert.__parentClass__) + throw new Error('Valid validator expected'); + var getPriority = function () { + if ('undefined' !== typeof parsleyField.options[name + 'Priority']) + return parsleyField.options[name + 'Priority']; + return ParsleyUtils.get(assert, 'priority') || 2; + }; + priority = priority || getPriority(); + // If validator have a requirementsTransformer, execute it + if ('function' === typeof assert.requirementsTransformer) { + requirements = assert.requirementsTransformer(); + // rebuild assert with new requirements + assert = window.ParsleyValidator.validators[name](requirements); + } + return $.extend(assert, { + name: name, + requirements: requirements, + priority: priority, + groups: [priority], + isDomConstraint: isDomConstraint || ParsleyUtils.attr(parsleyField.$element, parsleyField.options.namespace, name) + }); + }; + + var ParsleyField = function (field, OptionsFactory, parsleyFormInstance) { + this.__class__ = 'ParsleyField'; + this.__id__ = ParsleyUtils.hash(4); + this.$element = $(field); + // If we have a parent `ParsleyForm` instance given, use its `OptionsFactory`, and save parent + if ('undefined' !== typeof parsleyFormInstance) { + this.parent = parsleyFormInstance; + this.OptionsFactory = this.parent.OptionsFactory; + this.options = this.OptionsFactory.get(this); + // Else, take the `Parsley` one + } else { + this.OptionsFactory = OptionsFactory; + this.options = this.OptionsFactory.get(this); + } + // Initialize some properties + this.constraints = []; + this.constraintsByName = {}; + this.validationResult = []; + // Bind constraints + this._bindConstraints(); + }; + ParsleyField.prototype = { + // # Public API + // Validate field and $.emit some events for mainly `ParsleyUI` + // @returns validationResult: + // - `true` if all constraints pass + // - `[]` if not required field and empty (not validated) + // - `[Violation, [Violation...]]` if there were validation errors + validate: function (force) { + this.value = this.getValue(); + // Field Validate event. `this.value` could be altered for custom needs + $.emit('parsley:field:validate', this); + $.emit('parsley:field:' + (this.isValid(force, this.value) ? 'success' : 'error'), this); + // Field validated event. `this.validationResult` could be altered for custom needs too + $.emit('parsley:field:validated', this); + return this.validationResult; + }, + // Just validate field. Do not trigger any event + // Same @return as `validate()` + isValid: function (force, value) { + // Recompute options and rebind constraints to have latest changes + this.refreshConstraints(); + // Sort priorities to validate more important first + var priorities = this._getConstraintsSortedPriorities(); + if (0 === priorities.length) + return this.validationResult = []; + // Value could be passed as argument, needed to add more power to 'parsley:field:validate' + if ('undefined' === typeof value || null === value) + value = this.getValue(); + // If a field is empty and not required, leave it alone, it's just fine + // Except if `data-parsley-validate-if-empty` explicitely added, useful for some custom validators + if (!value.length && !this._isRequired() && 'undefined' === typeof this.options.validateIfEmpty && true !== force) + return this.validationResult = []; + // If we want to validate field against all constraints, just call Validator and let it do the job + if (false === this.options.priorityEnabled) + return true === (this.validationResult = this.validateThroughValidator(value, this.constraints, 'Any')); + // Else, iterate over priorities one by one, and validate related asserts one by one + for (var i = 0; i < priorities.length; i++) + if (true !== (this.validationResult = this.validateThroughValidator(value, this.constraints, priorities[i]))) + return false; + return true; + }, + // @returns Parsley field computed value that could be overrided or configured in DOM + getValue: function () { + var value; + // Value could be overriden in DOM + if ('undefined' !== typeof this.options.value) + value = this.options.value; + else + value = this.$element.val(); + // Handle wrong DOM or configurations + if ('undefined' === typeof value || null === value) + return ''; + // Use `data-parsley-trim-value="true"` to auto trim inputs entry + if (true === this.options.trimValue) + return value.replace(/^\s+|\s+$/g, ''); + return value; + }, + // Actualize options that could have change since previous validation + // Re-bind accordingly constraints (could be some new, removed or updated) + refreshConstraints: function () { + return this.actualizeOptions()._bindConstraints(); + }, + /** + * Add a new constraint to a field + * + * @method addConstraint + * @param {String} name + * @param {Mixed} requirements optional + * @param {Number} priority optional + * @param {Boolean} isDomConstraint optional + */ + addConstraint: function (name, requirements, priority, isDomConstraint) { + name = name.toLowerCase(); + if ('function' === typeof window.ParsleyValidator.validators[name]) { + var constraint = new ConstraintFactory(this, name, requirements, priority, isDomConstraint); + // if constraint already exist, delete it and push new version + if ('undefined' !== this.constraintsByName[constraint.name]) + this.removeConstraint(constraint.name); + this.constraints.push(constraint); + this.constraintsByName[constraint.name] = constraint; + } + return this; + }, + // Remove a constraint + removeConstraint: function (name) { + for (var i = 0; i < this.constraints.length; i++) + if (name === this.constraints[i].name) { + this.constraints.splice(i, 1); + break; + } + delete this.constraintsByName[name]; + return this; + }, + // Update a constraint (Remove + re-add) + updateConstraint: function (name, parameters, priority) { + return this.removeConstraint(name) + .addConstraint(name, parameters, priority); + }, + // # Internals + // Internal only. + // Bind constraints from config + options + DOM + _bindConstraints: function () { + var constraints = [], constraintsByName = {}; + // clean all existing DOM constraints to only keep javascript user constraints + for (var i = 0; i < this.constraints.length; i++) + if (false === this.constraints[i].isDomConstraint) { + constraints.push(this.constraints[i]); + constraintsByName[this.constraints[i].name] = this.constraints[i]; + } + this.constraints = constraints; + this.constraintsByName = constraintsByName; + // then re-add Parsley DOM-API constraints + for (var name in this.options) + this.addConstraint(name, this.options[name]); + // finally, bind special HTML5 constraints + return this._bindHtml5Constraints(); + }, + // Internal only. + // Bind specific HTML5 constraints to be HTML5 compliant + _bindHtml5Constraints: function () { + // html5 required + if (this.$element.hasClass('required') || this.$element.attr('required')) + this.addConstraint('required', true, undefined, true); + // html5 pattern + if ('string' === typeof this.$element.attr('pattern')) + this.addConstraint('pattern', this.$element.attr('pattern'), undefined, true); + // range + if ('undefined' !== typeof this.$element.attr('min') && 'undefined' !== typeof this.$element.attr('max')) + this.addConstraint('range', [this.$element.attr('min'), this.$element.attr('max')], undefined, true); + // HTML5 min + else if ('undefined' !== typeof this.$element.attr('min')) + this.addConstraint('min', this.$element.attr('min'), undefined, true); + // HTML5 max + else if ('undefined' !== typeof this.$element.attr('max')) + this.addConstraint('max', this.$element.attr('max'), undefined, true); + + // length + if ('undefined' !== typeof this.$element.attr('minlength') && 'undefined' !== typeof this.$element.attr('maxlength')) + this.addConstraint('length', [this.$element.attr('minlength'), this.$element.attr('maxlength')], undefined, true); + // HTML5 minlength + else if ('undefined' !== typeof this.$element.attr('minlength')) + this.addConstraint('minlength', this.$element.attr('minlength'), undefined, true); + // HTML5 maxlength + else if ('undefined' !== typeof this.$element.attr('maxlength')) + this.addConstraint('maxlength', this.$element.attr('maxlength'), undefined, true); + + // html5 types + var type = this.$element.attr('type'); + if ('undefined' === typeof type) + return this; + // Small special case here for HTML5 number: integer validator if step attribute is undefined or an integer value, number otherwise + if ('number' === type) { + if (('undefined' === typeof this.$element.attr('step')) || (0 === parseFloat(this.$element.attr('step')) % 1)) { + return this.addConstraint('type', 'integer', undefined, true); + } else { + return this.addConstraint('type', 'number', undefined, true); + } + // Regular other HTML5 supported types + } else if (new RegExp(type, 'i').test('email url range')) { + return this.addConstraint('type', type, undefined, true); + } + return this; + }, + // Internal only. + // Field is required if have required constraint without `false` value + _isRequired: function () { + if ('undefined' === typeof this.constraintsByName.required) + return false; + return false !== this.constraintsByName.required.requirements; + }, + // Internal only. + // Sort constraints by priority DESC + _getConstraintsSortedPriorities: function () { + var priorities = []; + // Create array unique of priorities + for (var i = 0; i < this.constraints.length; i++) + if (-1 === priorities.indexOf(this.constraints[i].priority)) + priorities.push(this.constraints[i].priority); + // Sort them by priority DESC + priorities.sort(function (a, b) { return b - a; }); + return priorities; + } + }; + + var ParsleyMultiple = function () { + this.__class__ = 'ParsleyFieldMultiple'; + }; + ParsleyMultiple.prototype = { + // Add new `$element` sibling for multiple field + addElement: function ($element) { + this.$elements.push($element); + return this; + }, + // See `ParsleyField.refreshConstraints()` + refreshConstraints: function () { + var fieldConstraints; + this.constraints = []; + // Select multiple special treatment + if (this.$element.is('select')) { + this.actualizeOptions()._bindConstraints(); + return this; + } + // Gather all constraints for each input in the multiple group + for (var i = 0; i < this.$elements.length; i++) { + // Check if element have not been dynamically removed since last binding + if (!$('html').has(this.$elements[i]).length) { + this.$elements.splice(i, 1); + continue; + } + fieldConstraints = this.$elements[i].data('ParsleyFieldMultiple').refreshConstraints().constraints; + for (var j = 0; j < fieldConstraints.length; j++) + this.addConstraint(fieldConstraints[j].name, fieldConstraints[j].requirements, fieldConstraints[j].priority, fieldConstraints[j].isDomConstraint); + } + return this; + }, + // See `ParsleyField.getValue()` + getValue: function () { + // Value could be overriden in DOM + if ('undefined' !== typeof this.options.value) + return this.options.value; + // Radio input case + if (this.$element.is('input[type=radio]')) + return $('[' + this.options.namespace + 'multiple="' + this.options.multiple + '"]:checked').val() || ''; + // checkbox input case + if (this.$element.is('input[type=checkbox]')) { + var values = []; + $('[' + this.options.namespace + 'multiple="' + this.options.multiple + '"]:checked').each(function () { + values.push($(this).val()); + }); + return values.length ? values : []; + } + // Select multiple case + if (this.$element.is('select') && null === this.$element.val()) + return []; + // Default case that should never happen + return this.$element.val(); + }, + _init: function (multiple) { + this.$elements = [this.$element]; + this.options.multiple = multiple; + return this; + } + }; + + var + o = $({}), + subscribed = {}; + // $.listen(name, callback); + // $.listen(name, context, callback); + $.listen = function (name) { + if ('undefined' === typeof subscribed[name]) + subscribed[name] = []; + if ('function' === typeof arguments[1]) + return subscribed[name].push({ fn: arguments[1] }); + if ('object' === typeof arguments[1] && 'function' === typeof arguments[2]) + return subscribed[name].push({ fn: arguments[2], ctxt: arguments[1] }); + throw new Error('Wrong parameters'); + }; + $.listenTo = function (instance, name, fn) { + if ('undefined' === typeof subscribed[name]) + subscribed[name] = []; + if (!(instance instanceof ParsleyField) && !(instance instanceof ParsleyForm)) + throw new Error('Must give Parsley instance'); + if ('string' !== typeof name || 'function' !== typeof fn) + throw new Error('Wrong parameters'); + subscribed[name].push({ instance: instance, fn: fn }); + }; + $.unsubscribe = function (name, fn) { + if ('undefined' === typeof subscribed[name]) + return; + if ('string' !== typeof name || 'function' !== typeof fn) + throw new Error('Wrong arguments'); + for (var i = 0; i < subscribed[name].length; i++) + if (subscribed[name][i].fn === fn) + return subscribed[name].splice(i, 1); + }; + $.unsubscribeTo = function (instance, name) { + if ('undefined' === typeof subscribed[name]) + return; + if (!(instance instanceof ParsleyField) && !(instance instanceof ParsleyForm)) + throw new Error('Must give Parsley instance'); + for (var i = 0; i < subscribed[name].length; i++) + if ('undefined' !== typeof subscribed[name][i].instance && subscribed[name][i].instance.__id__ === instance.__id__) + return subscribed[name].splice(i, 1); + }; + $.unsubscribeAll = function (name) { + if ('undefined' === typeof subscribed[name]) + return; + delete subscribed[name]; + }; + // $.emit(name [, arguments...]); + // $.emit(name, instance [, arguments...]); + $.emit = function (name, instance) { + if ('undefined' === typeof subscribed[name]) + return; + // loop through registered callbacks for this event + for (var i = 0; i < subscribed[name].length; i++) { + // if instance is not registered, simple emit + if ('undefined' === typeof subscribed[name][i].instance) { + subscribed[name][i].fn.apply('undefined' !== typeof subscribed[name][i].ctxt ? subscribed[name][i].ctxt : o, Array.prototype.slice.call(arguments, 1)); + continue; + } + // if instance registered but no instance given for the emit, continue + if (!(instance instanceof ParsleyField) && !(instance instanceof ParsleyForm)) + continue; + // if instance is registered and same id, emit + if (subscribed[name][i].instance.__id__ === instance.__id__) { + subscribed[name][i].fn.apply(o, Array.prototype.slice.call(arguments, 1)); + continue; + } + // if registered instance is a Form and fired one is a Field, loop over all its fields and emit if field found + if (subscribed[name][i].instance instanceof ParsleyForm && instance instanceof ParsleyField) + for (var j = 0; j < subscribed[name][i].instance.fields.length; j++) + if (subscribed[name][i].instance.fields[j].__id__ === instance.__id__) { + subscribed[name][i].fn.apply(o, Array.prototype.slice.call(arguments, 1)); + continue; + } + } + }; + $.subscribed = function () { return subscribed; }; + +// ParsleyConfig definition if not already set +window.ParsleyConfig = window.ParsleyConfig || {}; +window.ParsleyConfig.i18n = window.ParsleyConfig.i18n || {}; +// Define then the messages +window.ParsleyConfig.i18n.en = $.extend(window.ParsleyConfig.i18n.en || {}, { + defaultMessage: "This value seems to be invalid.", + type: { + email: "This value should be a valid email.", + url: "This value should be a valid url.", + number: "This value should be a valid number.", + integer: "This value should be a valid integer.", + digits: "This value should be digits.", + alphanum: "This value should be alphanumeric." + }, + notblank: "This value should not be blank.", + required: "This value is required.", + pattern: "This value seems to be invalid.", + min: "This value should be greater than or equal to %s.", + max: "This value should be lower than or equal to %s.", + range: "This value should be between %s and %s.", + minlength: "This value is too short. It should have %s characters or more.", + maxlength: "This value is too long. It should have %s characters or fewer.", + length: "This value length is invalid. It should be between %s and %s characters long.", + mincheck: "You must select at least %s choices.", + maxcheck: "You must select %s choices or fewer.", + check: "You must select between %s and %s choices.", + equalto: "This value should be the same." +}); +// If file is loaded after Parsley main file, auto-load locale +if ('undefined' !== typeof window.ParsleyValidator) + window.ParsleyValidator.addCatalog('en', window.ParsleyConfig.i18n.en, true); + +// Parsley.js 2.0.7 +// http://parsleyjs.org +// (c) 20012-2014 Guillaume Potier, Wisembly +// Parsley may be freely distributed under the MIT license. + + // ### Parsley factory + var Parsley = function (element, options, parsleyFormInstance) { + this.__class__ = 'Parsley'; + this.__version__ = '2.0.7'; + this.__id__ = ParsleyUtils.hash(4); + // Parsley must be instantiated with a DOM element or jQuery $element + if ('undefined' === typeof element) + throw new Error('You must give an element'); + if ('undefined' !== typeof parsleyFormInstance && 'ParsleyForm' !== parsleyFormInstance.__class__) + throw new Error('Parent instance must be a ParsleyForm instance'); + return this.init($(element), options, parsleyFormInstance); + }; + Parsley.prototype = { + init: function ($element, options, parsleyFormInstance) { + if (!$element.length) + throw new Error('You must bind Parsley on an existing element.'); + this.$element = $element; + // If element have already been binded, returns its saved Parsley instance + if (this.$element.data('Parsley')) { + var savedparsleyFormInstance = this.$element.data('Parsley'); + // If saved instance have been binded without a ParsleyForm parent and there is one given in this call, add it + if ('undefined' !== typeof parsleyFormInstance) + savedparsleyFormInstance.parent = parsleyFormInstance; + return savedparsleyFormInstance; + } + // Handle 'static' options + this.OptionsFactory = new ParsleyOptionsFactory(ParsleyDefaults, ParsleyUtils.get(window, 'ParsleyConfig') || {}, options, this.getNamespace(options)); + this.options = this.OptionsFactory.get(this); + // A ParsleyForm instance is obviously a `
    ` elem but also every node that is not an input and have `data-parsley-validate` attribute + if (this.$element.is('form') || (ParsleyUtils.attr(this.$element, this.options.namespace, 'validate') && !this.$element.is(this.options.inputs))) + return this.bind('parsleyForm'); + // Every other supported element and not excluded element is binded as a `ParsleyField` or `ParsleyFieldMultiple` + else if (this.$element.is(this.options.inputs) && !this.$element.is(this.options.excluded)) + return this.isMultiple() ? this.handleMultiple(parsleyFormInstance) : this.bind('parsleyField', parsleyFormInstance); + return this; + }, + isMultiple: function () { + return (this.$element.is('input[type=radio], input[type=checkbox]') && 'undefined' === typeof this.options.multiple) || (this.$element.is('select') && 'undefined' !== typeof this.$element.attr('multiple')); + }, + // Multiples fields are a real nightmare :( + // Maybe some refacto would be appreciated here... + handleMultiple: function (parsleyFormInstance) { + var + that = this, + name, + multiple, + parsleyMultipleInstance; + // Get parsleyFormInstance options if exist, mixed with element attributes + this.options = $.extend(this.options, parsleyFormInstance ? parsleyFormInstance.OptionsFactory.get(parsleyFormInstance) : {}, ParsleyUtils.attr(this.$element, this.options.namespace)); + // Handle multiple name + if (this.options.multiple) + multiple = this.options.multiple; + else if ('undefined' !== typeof this.$element.attr('name') && this.$element.attr('name').length) + multiple = name = this.$element.attr('name'); + else if ('undefined' !== typeof this.$element.attr('id') && this.$element.attr('id').length) + multiple = this.$element.attr('id'); + // Special select multiple input + if (this.$element.is('select') && 'undefined' !== typeof this.$element.attr('multiple')) { + return this.bind('parsleyFieldMultiple', parsleyFormInstance, multiple || this.__id__); + // Else for radio / checkboxes, we need a `name` or `data-parsley-multiple` to properly bind it + } else if ('undefined' === typeof multiple) { + if (window.console && window.console.warn) + window.console.warn('To be binded by Parsley, a radio, a checkbox and a multiple select input must have either a name or a multiple option.', this.$element); + return this; + } + // Remove special chars + multiple = multiple.replace(/(:|\.|\[|\]|\{|\}|\$)/g, ''); + // Add proper `data-parsley-multiple` to siblings if we have a valid multiple name + if ('undefined' !== typeof name) { + $('input[name="' + name + '"]').each(function () { + if ($(this).is('input[type=radio], input[type=checkbox]')) + $(this).attr(that.options.namespace + 'multiple', multiple); + }); + } + // Check here if we don't already have a related multiple instance saved + if ($('[' + this.options.namespace + 'multiple=' + multiple +']').length) { + for (var i = 0; i < $('[' + this.options.namespace + 'multiple=' + multiple +']').length; i++) { + if ('undefined' !== typeof $($('[' + this.options.namespace + 'multiple=' + multiple +']').get(i)).data('Parsley')) { + parsleyMultipleInstance = $($('[' + this.options.namespace + 'multiple=' + multiple +']').get(i)).data('Parsley'); + if (!this.$element.data('ParsleyFieldMultiple')) { + parsleyMultipleInstance.addElement(this.$element); + this.$element.attr(this.options.namespace + 'id', parsleyMultipleInstance.__id__); + } + break; + } + } + } + // Create a secret ParsleyField instance for every multiple field. It would be stored in `data('ParsleyFieldMultiple')` + // And would be useful later to access classic `ParsleyField` stuff while being in a `ParsleyFieldMultiple` instance + this.bind('parsleyField', parsleyFormInstance, multiple, true); + return parsleyMultipleInstance || this.bind('parsleyFieldMultiple', parsleyFormInstance, multiple); + }, + // Retrieve namespace used for DOM-API + getNamespace: function (options) { + // `data-parsley-namespace=` + if ('undefined' !== typeof this.$element.data('parsleyNamespace')) + return this.$element.data('parsleyNamespace'); + if ('undefined' !== typeof ParsleyUtils.get(options, 'namespace')) + return options.namespace; + if ('undefined' !== typeof ParsleyUtils.get(window, 'ParsleyConfig.namespace')) + return window.ParsleyConfig.namespace; + return ParsleyDefaults.namespace; + }, + // Return proper `ParsleyForm`, `ParsleyField` or `ParsleyFieldMultiple` + bind: function (type, parentParsleyFormInstance, multiple, doNotStore) { + var parsleyInstance; + switch (type) { + case 'parsleyForm': + parsleyInstance = $.extend( + new ParsleyForm(this.$element, this.OptionsFactory), + new ParsleyAbstract(), + window.ParsleyExtend + )._bindFields(); + break; + case 'parsleyField': + parsleyInstance = $.extend( + new ParsleyField(this.$element, this.OptionsFactory, parentParsleyFormInstance), + new ParsleyAbstract(), + window.ParsleyExtend + ); + break; + case 'parsleyFieldMultiple': + parsleyInstance = $.extend( + new ParsleyField(this.$element, this.OptionsFactory, parentParsleyFormInstance), + new ParsleyAbstract(), + new ParsleyMultiple(), + window.ParsleyExtend + )._init(multiple); + break; + default: + throw new Error(type + 'is not a supported Parsley type'); + } + if ('undefined' !== typeof multiple) + ParsleyUtils.setAttr(this.$element, this.options.namespace, 'multiple', multiple); + if ('undefined' !== typeof doNotStore) { + this.$element.data('ParsleyFieldMultiple', parsleyInstance); + return parsleyInstance; + } + // Store instance if `ParsleyForm`, `ParsleyField` or `ParsleyFieldMultiple` + if (new RegExp('ParsleyF', 'i').test(parsleyInstance.__class__)) { + // Store for later access the freshly binded instance in DOM element itself using jQuery `data()` + this.$element.data('Parsley', parsleyInstance); + // Tell the world we got a new ParsleyForm or ParsleyField instance! + $.emit('parsley:' + ('parsleyForm' === type ? 'form' : 'field') + ':init', parsleyInstance); + } + return parsleyInstance; + } + }; + // ### jQuery API + // `$('.elem').parsley(options)` or `$('.elem').psly(options)` + $.fn.parsley = $.fn.psly = function (options) { + if (this.length > 1) { + var instances = []; + this.each(function () { + instances.push($(this).parsley(options)); + }); + return instances; + } + // Return undefined if applied to non existing DOM element + if (!$(this).length) { + if (window.console && window.console.warn) + window.console.warn('You must bind Parsley on an existing element.'); + return; + } + return new Parsley(this, options); + }; + // ### ParsleyUI + // UI is a class apart that only listen to some events and them modify DOM accordingly + // Could be overriden by defining a `window.ParsleyConfig.ParsleyUI` appropriate class (with `listen()` method basically) + window.ParsleyUI = 'function' === typeof ParsleyUtils.get(window, 'ParsleyConfig.ParsleyUI') ? + new window.ParsleyConfig.ParsleyUI().listen() : new ParsleyUI().listen(); + // ### ParsleyField and ParsleyForm extension + // Ensure that defined if not already the case + if ('undefined' === typeof window.ParsleyExtend) + window.ParsleyExtend = {}; + // ### ParsleyConfig + // Ensure that defined if not already the case + if ('undefined' === typeof window.ParsleyConfig) + window.ParsleyConfig = {}; + // ### Globals + window.Parsley = window.psly = Parsley; + window.ParsleyUtils = ParsleyUtils; + window.ParsleyValidator = new ParsleyValidator(window.ParsleyConfig.validators, window.ParsleyConfig.i18n); + // ### PARSLEY auto-binding + // Prevent it by setting `ParsleyConfig.autoBind` to `false` + if (false !== ParsleyUtils.get(window, 'ParsleyConfig.autoBind')) + $(function () { + // Works only on `data-parsley-validate`. + if ($('[data-parsley-validate]').length) + $('[data-parsley-validate]').parsley(); + }); +})); diff --git a/wtformsparsleyjs/sample/templates/base.html b/wtformsparsleyjs/sample/templates/base.html index 889f604..6bb9d9d 100644 --- a/wtformsparsleyjs/sample/templates/base.html +++ b/wtformsparsleyjs/sample/templates/base.html @@ -45,8 +45,10 @@ {% block scripts %} - - + + + +