From b7759c1956d97b062d16074381adc9da0955c1a6 Mon Sep 17 00:00:00 2001 From: gollum23 Date: Wed, 11 Jun 2014 21:38:19 -0500 Subject: [PATCH 01/21] Update README.rst Update tag into form --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index c8c38ff..c5c56a1 100644 --- a/README.rst +++ b/README.rst @@ -96,7 +96,7 @@ Put this form inside a .. code-block:: html -
+ {{ form.as_p }}
From 03fbb0e116a47bb0b10edf560ad587d71951bbb7 Mon Sep 17 00:00:00 2001 From: Shabda Raaj Date: Tue, 8 Jul 2014 14:51:09 +0530 Subject: [PATCH 02/21] Added link to demo --- README.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.rst b/README.rst index c5c56a1..20c049c 100644 --- a/README.rst +++ b/README.rst @@ -28,6 +28,11 @@ the form field attributes. Django-parsley adds these validations to client side, Parsley plays well with ``crispy-forms`` et all. +Demo +---- +`Demo`_ at https://agiliq.com/demo/parsley/ + + Installation ------------ @@ -156,3 +161,4 @@ For bug reports open a github ticket. Patches gratefully accepted. Need help? `C .. _parsleyjs: http://parsleyjs.org/ .. _contact us here: http://agiliq.com/contactus +.. _demo: http://agiliq.com/demo/parsley/ From cb4e25b342816e919d7c6a5680d2bc284f814af9 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 17 Jan 2015 15:00:01 +0100 Subject: [PATCH 03/21] setup.py: allow to install for Django<=1.8 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index f8a384b..26e4ca3 100644 --- a/setup.py +++ b/setup.py @@ -123,7 +123,7 @@ def find_package_data( "Framework :: Django", ], zip_safe=False, - install_requires=["Django>=1.3,<=1.7"], - tests_require=["Django>=1.3,<=1.7", "six"], + install_requires=["Django>=1.3,<=1.8"], + tests_require=["Django>=1.3,<=1.8", "six"], test_suite='runtests.runtests', ) From 27f5a06e7c8a97657064ce08139c07886086b3f2 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 17 Jan 2015 15:00:18 +0100 Subject: [PATCH 04/21] travis: simplify and extend test matrix --- .travis.yml | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index fb206dd..38f5840 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,16 @@ language: python python: - - 2.6 - 2.7 - - 3.2 - - 3.3 + - 3.4 env: - - DJANGO=Django==1.3.7 - - DJANGO=Django==1.4.2 - - DJANGO=Django==1.5.5 - - DJANGO=https://github.com/django/django/tarball/stable/1.6.x + - DJANGO='Django>=1.3,<1.4' + - DJANGO='Django>=1.4,<1.5' + - DJANGO='Django>=1.5,<1.6' + - DJANGO='Django>=1.6,<1.7' + - DJANGO='Django>=1.7,<1.8' + - DJANGO='https://github.com/django/django/tarball/stable/1.8.x' install: - pip install --use-mirrors $DJANGO @@ -22,11 +22,7 @@ after_success: coveralls matrix: exclude: - - python: 3.2 - env: DJANGO=Django==1.3.7 - - python: 3.3 - env: DJANGO=Django==1.3.7 - - python: 3.2 - env: DJANGO=Django==1.4.2 - - python: 3.3 - env: DJANGO=Django==1.4.2 + - python: 3.4 + env: DJANGO='Django>=1.3,<1.4' + - python: 3.4 + env: DJANGO='Django>=1.4,<1.5' From e1c1b5ad71c8b6c9d68f4c74b6a5fd8f42994da0 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 17 Jan 2015 21:09:52 +0100 Subject: [PATCH 05/21] runtests: call django.setup for Django 1.7+ --- runtests.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/runtests.py b/runtests.py index 8905868..3fdb1be 100755 --- a/runtests.py +++ b/runtests.py @@ -22,6 +22,11 @@ STATIC_URL = "/static/", ) + # Setup Django 1.7+ (AppRegistryNotReady). + import django + if hasattr(django, 'setup'): + django.setup() + from django.test.simple import DjangoTestSuiteRunner From 5ea2a5c8c7b1030f651ee24a1f387dabc8d8fb01 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 7 Feb 2015 00:35:05 +0100 Subject: [PATCH 06/21] Remove unused example/core/static/core/js/parsley.js --- example/core/static/core/js/parsley.js | 1335 ------------------------ 1 file changed, 1335 deletions(-) delete mode 100644 example/core/static/core/js/parsley.js diff --git a/example/core/static/core/js/parsley.js b/example/core/static/core/js/parsley.js deleted file mode 100644 index 98ffb26..0000000 --- a/example/core/static/core/js/parsley.js +++ /dev/null @@ -1,1335 +0,0 @@ -/* - * Parsley.js allows you to verify your form inputs frontend side, without writing a line of javascript. Or so.. - * - * Author: Guillaume Potier - @guillaumepotier -*/ - -!function ($) { - - 'use strict'; - - /** - * Validator class stores all constraints functions and associated messages. - * Provides public interface to add, remove or modify them - * - * @class Validator - * @constructor - */ - var Validator = function ( options ) { - /** - * Error messages - * - * @property messages - * @type {Object} - */ - this.messages = { - defaultMessage: "This value seems to be invalid." - , type: { - email: "This value should be a valid email." - , url: "This value should be a valid url." - , urlstrict: "This value should be a valid url." - , number: "This value should be a valid number." - , digits: "This value should be digits." - , dateIso: "This value should be a valid date (YYYY-MM-DD)." - , alphanum: "This value should be alphanumeric." - , phone: "This value should be a valid phone number." - } - , notnull: "This value should not be null." - , notblank: "This value should not be blank." - , required: "This value is required." - , regexp: "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 less." - , rangelength: "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 less." - , rangecheck: "You must select between %s and %s choices." - , equalto: "This value should be the same." - }, - - this.init( options ); - }; - - Validator.prototype = { - - constructor: Validator - - /** - * Validator list. Built-in validators functions - * - * @property validators - * @type {Object} - */ - , validators: { - notnull: function ( val ) { - return val.length > 0; - } - - , notblank: function ( val ) { - return 'string' === typeof val && '' !== val.replace( /^\s+/g, '' ).replace( /\s+$/g, '' ); - } - - // Works on all inputs. val is object for checkboxes - , required: function ( val ) { - - // for checkboxes and select multiples. Check there is at least one required value - if ( 'object' === typeof val ) { - for ( var i in val ) { - if ( this.required( val[ i ] ) ) { - return true; - } - } - - return false; - } - - return this.notnull( val ) && this.notblank( val ); - } - - , type: function ( val, type ) { - var regExp; - - switch ( type ) { - case 'number': - regExp = /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/; - break; - case 'digits': - regExp = /^\d+$/; - break; - case 'alphanum': - regExp = /^\w+$/; - break; - case 'email': - regExp = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))){2,6}$/i; - break; - case 'url': - val = new RegExp( '(https?|s?ftp|git)', 'i' ).test( val ) ? val : 'http://' + val; - /* falls through */ - case 'urlstrict': - regExp = /^(https?|s?ftp|git):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i; - break; - case 'dateIso': - regExp = /^(\d{4})\D?(0[1-9]|1[0-2])\D?([12]\d|0[1-9]|3[01])$/; - break; - case 'phone': - regExp = /^((\+\d{1,3}(-| )?\(?\d\)?(-| )?\d{1,5})|(\(?\d{2,6}\)?))(-| )?(\d{3,4})(-| )?(\d{4})(( x| ext)\d{1,5}){0,1}$/; - break; - default: - return false; - } - - // test regExp if not null - return '' !== val ? regExp.test( val ) : false; - } - - , regexp: function ( val, regExp, self ) { - return new RegExp( regExp, self.options.regexpFlag || '' ).test( val ); - } - - , minlength: function ( val, min ) { - return val.length >= min; - } - - , maxlength: function ( val, max ) { - return val.length <= max; - } - - , rangelength: function ( val, arrayRange ) { - return this.minlength( val, arrayRange[ 0 ] ) && this.maxlength( val, arrayRange[ 1 ] ); - } - - , min: function ( val, min ) { - return Number( val ) >= min; - } - - , max: function ( val, max ) { - return Number( val ) <= max; - } - - , range: function ( val, arrayRange ) { - return val >= arrayRange[ 0 ] && val <= arrayRange[ 1 ]; - } - - , equalto: function ( val, elem, self ) { - self.options.validateIfUnchanged = true; - - return val === $( elem ).val(); - } - - , remote: function ( val, url, self ) { - var result = null - , data = {} - , dataType = {}; - - data[ self.$element.attr( 'name' ) ] = val; - - if ( 'undefined' !== typeof self.options.remoteDatatype ) { - dataType = { dataType: self.options.remoteDatatype }; - } - - var manage = function ( isConstraintValid, message ) { - // remove error message if we got a server message, different from previous message - if ( 'undefined' !== typeof message && 'undefined' !== typeof self.Validator.messages.remote && message !== self.Validator.messages.remote ) { - $( self.ulError + ' .remote' ).remove(); - } - - self.updtConstraint( { name: 'remote', valid: isConstraintValid }, message ); - self.manageValidationResult(); - }; - - // transform string response into object - var handleResponse = function ( response ) { - if ( 'object' === typeof response ) { - return response; - } - - try { - response = $.parseJSON( response ); - } catch ( err ) {} - - return response; - } - - var manageErrorMessage = function ( response ) { - return 'object' === typeof response && null !== response ? ( 'undefined' !== typeof response.error ? response.error : ( 'undefined' !== typeof response.message ? response.message : null ) ) : null; - } - - $.ajax( $.extend( {}, { - url: url - , data: data - , type: self.options.remoteMethod || 'GET' - , success: function ( response ) { - response = handleResponse( response ); - manage( 1 === response || true === response || ( 'object' === typeof response && null !== response && 'undefined' !== typeof response.success ), manageErrorMessage( response ) - ); - } - , error: function ( response ) { - response = handleResponse( response ); - manage( false, manageErrorMessage( response ) ); - } - }, dataType ) ); - - return result; - } - - /** - * Aliases for checkboxes constraints - */ - , mincheck: function ( obj, val ) { - return this.minlength( obj, val ); - } - - , maxcheck: function ( obj, val ) { - return this.maxlength( obj, val); - } - - , rangecheck: function ( obj, arrayRange ) { - return this.rangelength( obj, arrayRange ); - } - } - - /* - * Register custom validators and messages - */ - , init: function ( options ) { - var customValidators = options.validators - , customMessages = options.messages; - - var key; - for ( key in customValidators ) { - this.addValidator(key, customValidators[ key ]); - } - - for ( key in customMessages ) { - this.addMessage(key, customMessages[ key ]); - } - } - - /** - * Replace %s placeholders by values - * - * @method formatMesssage - * @param {String} message Message key - * @param {Mixed} args Args passed by validators functions. Could be string, number or object - * @return {String} Formatted string - */ - , formatMesssage: function ( message, args ) { - - if ( 'object' === typeof args ) { - for ( var i in args ) { - message = this.formatMesssage( message, args[ i ] ); - } - - return message; - } - - return 'string' === typeof message ? message.replace( new RegExp( '%s', 'i' ), args ) : ''; - } - - /** - * Add / override a validator in validators list - * - * @method addValidator - * @param {String} name Validator name. Will automatically bindable through data-name='' - * @param {Function} fn Validator function. Must return {Boolean} - */ - , addValidator: function ( name, fn ) { - this.validators[ name ] = fn; - } - - /** - * Add / override error message - * - * @method addMessage - * @param {String} name Message name. Will automatically be binded to validator with same name - * @param {String} message Message - */ - , addMessage: function ( key, message, type ) { - - if ( 'undefined' !== typeof type && true === type ) { - this.messages.type[ key ] = message; - return; - } - - // custom types messages are a bit tricky cuz' nested ;) - if ( 'type' === key ) { - for ( var i in message ) { - this.messages.type[ i ] = message[ i ]; - } - - return; - } - - this.messages[ key ] = message; - } - }; - - /** - * ParsleyField class manage each form field inside a validated Parsley form. - * Returns if field valid or not depending on its value and constraints - * Manage field error display and behavior, event triggers and more - * - * @class ParsleyField - * @constructor - */ - var ParsleyField = function ( element, options, type ) { - this.options = options; - this.Validator = new Validator( options ); - - // if type is ParsleyFieldMultiple, just return this. used for clone - if ( type === 'ParsleyFieldMultiple' ) { - return this; - } - - this.init( element, type || 'ParsleyField' ); - }; - - ParsleyField.prototype = { - - constructor: ParsleyField - - /** - * Set some properties, bind constraint validators and validation events - * - * @method init - * @param {Object} element - * @param {Object} options - */ - , init: function ( element, type ) { - this.type = type; - this.valid = true; - this.element = element; - this.validatedOnce = false; - this.$element = $( element ); - this.val = this.$element.val(); - this.isRequired = false; - this.constraints = {}; - - // overriden by ParsleyItemMultiple if radio or checkbox input - if ( 'undefined' === typeof this.isRadioOrCheckbox ) { - this.isRadioOrCheckbox = false; - this.hash = this.generateHash(); - this.errorClassHandler = this.options.errors.classHandler( element, this.isRadioOrCheckbox ) || this.$element; - } - - // error ul dom management done only once at init - this.ulErrorManagement(); - - // bind some html5 properties - this.bindHtml5Constraints(); - - // bind validators to field - this.addConstraints(); - - // bind parsley events if validators have been registered - if ( this.hasConstraints() ) { - this.bindValidationEvents(); - } - } - - , setParent: function ( elem ) { - this.$parent = $( elem ); - } - - , getParent: function () { - return this.$parent; - } - - /** - * Bind some extra html5 types / validators - * - * @private - * @method bindHtml5Constraints - */ - , bindHtml5Constraints: function () { - // add html5 required support + class required support - if ( this.$element.hasClass( 'required' ) || this.$element.prop( 'required' ) ) { - this.options.required = true; - } - - // add html5 supported types & options - if ( 'undefined' !== typeof this.$element.attr( 'type' ) && new RegExp( this.$element.attr( 'type' ), 'i' ).test( 'email url number range' ) ) { - this.options.type = this.$element.attr( 'type' ); - - // number and range types could have min and/or max values - if ( new RegExp( this.options.type, 'i' ).test( 'number range' ) ) { - this.options.type = 'number'; - - // double condition to support jQuery and Zepto.. :( - if ( 'undefined' !== typeof this.$element.attr( 'min' ) && this.$element.attr( 'min' ).length ) { - this.options.min = this.$element.attr( 'min' ); - } - - if ( 'undefined' !== typeof this.$element.attr( 'max' ) && this.$element.attr( 'max' ).length ) { - this.options.max = this.$element.attr( 'max' ); - } - } - } - - if ( 'string' === typeof this.$element.attr( 'pattern' ) && this.$element.attr( 'pattern' ).length ) { - this.options.regexp = this.$element.attr( 'pattern' ); - } - - } - - /** - * Attach field validators functions passed through data-api - * - * @private - * @method addConstraints - */ - , addConstraints: function () { - for ( var constraint in this.options ) { - var addConstraint = {}; - addConstraint[ constraint ] = this.options[ constraint ]; - this.addConstraint( addConstraint, true ); - } - } - - /** - * Dynamically add a new constraint to a field - * - * @method addConstraint - * @param {Object} constraint { name: requirements } - */ - , addConstraint: function ( constraint, doNotUpdateValidationEvents ) { - for ( var name in constraint ) { - name = name.toLowerCase(); - - if ( 'function' === typeof this.Validator.validators[ name ] ) { - this.constraints[ name ] = { - name: name - , requirements: constraint[ name ] - , valid: null - } - - if ( name === 'required' ) { - this.isRequired = true; - } - - this.addCustomConstraintMessage( name ); - } - } - - // force field validation next check and reset validation events - if ( 'undefined' === typeof doNotUpdateValidationEvents ) { - this.bindValidationEvents(); - } - } - - /** - * Dynamically update an existing constraint to a field. - * Simple API: { name: requirements } - * - * @method updtConstraint - * @param {Object} constraint - */ - , updateConstraint: function ( constraint, message ) { - for ( var name in constraint ) { - this.updtConstraint( { name: name, requirements: constraint[ name ], valid: null }, message ); - } - } - - /** - * Dynamically update an existing constraint to a field. - * Complex API: { name: name, requirements: requirements, valid: boolean } - * - * @method updtConstraint - * @param {Object} constraint - */ - , updtConstraint: function ( constraint, message ) { - this.constraints[ constraint.name ] = $.extend( true, this.constraints[ constraint.name ], constraint ); - - if ( 'string' === typeof message ) { - this.Validator.messages[ constraint.name ] = message ; - } - - // force field validation next check and reset validation events - this.bindValidationEvents(); - } - - /** - * Dynamically remove an existing constraint to a field. - * - * @method removeConstraint - * @param {String} constraintName - */ - , removeConstraint: function ( constraintName ) { - var constraintName = constraintName.toLowerCase(); - - delete this.constraints[ constraintName ]; - - if ( constraintName === 'required' ) { - this.isRequired = false; - } - - // if there are no more constraint, destroy parsley instance for this field - if ( !this.hasConstraints() ) { - // in a form context, remove item from parent - if ( 'ParsleyForm' === typeof this.getParent() ) { - this.getParent().removeItem( this.$element ); - return; - } - - this.destroy(); - return; - } - - this.bindValidationEvents(); - } - - /** - * Add custom constraint message, passed through data-API - * - * @private - * @method addCustomConstraintMessage - * @param constraint - */ - , addCustomConstraintMessage: function ( constraint ) { - // custom message type data-type-email-message -> typeEmailMessage | data-minlength-error => minlengthMessage - var customMessage = constraint - + ( 'type' === constraint && 'undefined' !== typeof this.options[ constraint ] ? this.options[ constraint ].charAt( 0 ).toUpperCase() + this.options[ constraint ].substr( 1 ) : '' ) - + 'Message'; - - if ( 'undefined' !== typeof this.options[ customMessage ] ) { - this.Validator.addMessage( 'type' === constraint ? this.options[ constraint ] : constraint, this.options[ customMessage ], 'type' === constraint ); - } - } - - /** - * Bind validation events on a field - * - * @private - * @method bindValidationEvents - */ - , bindValidationEvents: function () { - // this field has validation events, that means it has to be validated - this.valid = null; - this.$element.addClass( 'parsley-validated' ); - - // remove eventually already binded events - this.$element.off( '.' + this.type ); - - // force add 'change' event if async remote validator here to have result before form submitting - if ( this.options.remote && !new RegExp( 'change', 'i' ).test( this.options.trigger ) ) { - this.options.trigger = !this.options.trigger ? 'change' : ' change'; - } - - // alaways bind keyup event, for better UX when a field is invalid - var triggers = ( !this.options.trigger ? '' : this.options.trigger ) - + ( new RegExp( 'key', 'i' ).test( this.options.trigger ) ? '' : ' keyup' ); - - // alaways bind change event, for better UX when a select is invalid - if ( this.$element.is( 'select' ) ) { - triggers += new RegExp( 'change', 'i' ).test( triggers ) ? '' : ' change'; - } - - // trim triggers to bind them correctly with .on() - triggers = triggers.replace( /^\s+/g , '' ).replace( /\s+$/g , '' ); - - this.$element.on( ( triggers + ' ' ).split( ' ' ).join( '.' + this.type + ' ' ), false, $.proxy( this.eventValidation, this ) ); - } - - /** - * Hash management. Used for ul error - * - * @method generateHash - * @returns {String} 5 letters unique hash - */ - , generateHash: function () { - return 'parsley-' + ( Math.random() + '' ).substring( 2 ); - } - - /** - * Public getHash accessor - * - * @method getHash - * @returns {String} hash - */ - , getHash: function () { - return this.hash; - } - - /** - * Returns field val needed for validation - * Special treatment for radio & checkboxes - * - * @method getVal - * @returns {String} val - */ - , getVal: function () { - return this.$element.data('value') || this.$element.val(); - } - - /** - * Called when validation is triggered by an event - * Do nothing if val.length < this.options.validationMinlength - * - * @method eventValidation - * @param {Object} event jQuery event - */ - , eventValidation: function ( event ) { - var val = this.getVal(); - - // do nothing on keypress event if not explicitely passed as data-trigger and if field has not already been validated once - if ( event.type === 'keyup' && !/keyup/i.test( this.options.trigger ) && !this.validatedOnce ) { - return true; - } - - // do nothing on change event if not explicitely passed as data-trigger and if field has not already been validated once - if ( event.type === 'change' && !/change/i.test( this.options.trigger ) && !this.validatedOnce ) { - return true; - } - - // start validation process only if field has enough chars and validation never started - if ( !this.isRadioOrCheckbox && this.getLength(val) < this.options.validationMinlength && !this.validatedOnce ) { - return true; - } - - this.validate(); - } - - /** - * Get the length of a given value - * - * @method getLength - * @return {int} The length of the value - */ - , getLength: function(val) { - if (!val || !val.hasOwnProperty('length')) return 0; - return val.length; - } - - /** - * Return if field verify its constraints - * - * @method isValid - * @return {Boolean} Is field valid or not - */ - , isValid: function () { - return this.validate( false ); - } - - /** - * Return if field has constraints - * - * @method hasConstraints - * @return {Boolean} Is field has constraints or not - */ - , hasConstraints: function () { - for ( var constraint in this.constraints ) { - return true; - } - - return false; - } - - /** - * Validate a field & display errors - * - * @method validate - * @param {Boolean} errorBubbling set to false if you just want valid boolean without error bubbling next to fields - * @return {Boolean} Is field valid or not - */ - , validate: function ( errorBubbling ) { - var val = this.getVal() - , valid = null; - - // do not even bother trying validating a field w/o constraints - if ( !this.hasConstraints() ) { - return null; - } - - // reset Parsley validation if onFieldValidate returns true, or if field is empty and not required - if ( this.options.listeners.onFieldValidate( this.element, this ) || ( '' === val && !this.isRequired ) ) { - this.reset(); - return null; - } - - // do not validate a field already validated and unchanged ! - if ( !this.needsValidation( val ) ) { - return this.valid; - } - - valid = this.applyValidators(); - - if ( 'undefined' !== typeof errorBubbling ? errorBubbling : this.options.showErrors ) { - this.manageValidationResult(); - } - - return valid; - } - - /** - * Check if value has changed since previous validation - * - * @method needsValidation - * @param value - * @return {Boolean} - */ - , needsValidation: function ( val ) { - if ( !this.options.validateIfUnchanged && this.valid !== null && this.val === val && this.validatedOnce ) { - return false; - } - - this.val = val; - return this.validatedOnce = true; - } - - /** - * Loop through every fields validators - * Adds errors after unvalid fields - * - * @method applyValidators - * @return {Mixed} {Boolean} If field valid or not, null if not validated - */ - , applyValidators: function () { - var valid = null; - - for ( var constraint in this.constraints ) { - var result = this.Validator.validators[ this.constraints[ constraint ].name ]( this.val, this.constraints[ constraint ].requirements, this ); - - if ( false === result ) { - valid = false; - this.constraints[ constraint ].valid = valid; - this.options.listeners.onFieldError( this.element, this.constraints, this ); - } else if ( true === result ) { - this.constraints[ constraint ].valid = true; - valid = false !== valid; - this.options.listeners.onFieldSuccess( this.element, this.constraints, this ); - } - } - - return valid; - } - - /** - * Fired when all validators have be executed - * Returns true or false if field is valid or not - * Display errors messages below failed fields - * Adds parsley-success or parsley-error class on fields - * - * @method manageValidationResult - * @return {Boolean} Is field valid or not - */ - , manageValidationResult: function () { - var valid = null; - - for ( var constraint in this.constraints ) { - if ( false === this.constraints[ constraint ].valid ) { - this.manageError( this.constraints[ constraint ] ); - valid = false; - } else if ( true === this.constraints[ constraint ].valid ) { - this.removeError( this.constraints[ constraint ].name ); - valid = false !== valid; - } - } - - this.valid = valid; - - if ( true === this.valid ) { - this.removeErrors(); - this.errorClassHandler.removeClass( this.options.errorClass ).addClass( this.options.successClass ); - return true; - } else if ( false === this.valid ) { - this.errorClassHandler.removeClass( this.options.successClass ).addClass( this.options.errorClass ); - return false; - } - - // remove li error, and ul error if no more li inside - if ( this.ulError && $( this.ulError ).children().length === 0 ) { - this.removeErrors(); - } - - return valid; - } - - /** - * Manage ul error Container - * - * @private - * @method ulErrorManagement - */ - , ulErrorManagement: function () { - this.ulError = '#' + this.hash; - this.ulTemplate = $( this.options.errors.errorsWrapper ).attr( 'id', this.hash ).addClass( 'parsley-error-list' ); - } - - /** - * Remove li / ul error - * - * @method removeError - * @param {String} constraintName Method Name - */ - , removeError: function ( constraintName ) { - var liError = this.ulError + ' .' + constraintName - , that = this; - - this.options.animate ? $( liError ).fadeOut( this.options.animateDuration, function () { - $( this ).remove(); - - if ( that.ulError && $( that.ulError ).children().length === 0 ) { - that.removeErrors(); - } } ) : $( liError ).remove(); - } - - /** - * Add li error - * - * @method addError - * @param {Object} { minlength: "error message for minlength constraint" } - */ - , addError: function ( error ) { - for ( var constraint in error ) { - var liTemplate = $( this.options.errors.errorElem ).addClass( constraint ); - - $( this.ulError ).append( this.options.animate ? $( liTemplate ).html( error[ constraint ] ).hide().fadeIn( this.options.animateDuration ) : $( liTemplate ).html( error[ constraint ] ) ); - } - } - - /** - * Remove all ul / li errors - * - * @method removeErrors - */ - , removeErrors: function () { - this.options.animate ? $( this.ulError ).fadeOut( this.options.animateDuration, function () { $( this ).remove(); } ) : $( this.ulError ).remove(); - } - - /** - * Remove ul errors and parsley error or success classes - * - * @method reset - */ - , reset: function () { - this.valid = null; - this.removeErrors(); - this.validatedOnce = false; - this.errorClassHandler.removeClass( this.options.successClass ).removeClass( this.options.errorClass ); - - for ( var constraint in this.constraints ) { - this.constraints[ constraint ].valid = null; - } - - return this; - } - - /** - * Add li / ul errors messages - * - * @method manageError - * @param {Object} constraint - */ - , manageError: function ( constraint ) { - // display ulError container if it has been removed previously (or never shown) - if ( !$( this.ulError ).length ) { - this.manageErrorContainer(); - } - - // TODO: refacto properly - // if required constraint but field is not null, do not display - if ( 'required' === constraint.name && null !== this.getVal() && this.getVal().length > 0 ) { - return; - // if empty required field and non required constraint fails, do not display - } else if ( this.isRequired && 'required' !== constraint.name && ( null === this.getVal() || 0 === this.getVal().length ) ) { - return; - } - - // TODO: refacto error name w/ proper & readable function - var constraintName = constraint.name - , liClass = false !== this.options.errorMessage ? 'custom-error-message' : constraintName - , liError = {} - , message = false !== this.options.errorMessage ? this.options.errorMessage : ( constraint.name === 'type' ? - this.Validator.messages[ constraintName ][ constraint.requirements ] : ( 'undefined' === typeof this.Validator.messages[ constraintName ] ? - this.Validator.messages.defaultMessage : this.Validator.formatMesssage( this.Validator.messages[ constraintName ], constraint.requirements ) ) ); - - // add liError if not shown. Do not add more than once custom errorMessage if exist - if ( !$( this.ulError + ' .' + liClass ).length ) { - liError[ liClass ] = message; - this.addError( liError ); - } - } - - /** - * Create ul error container - * - * @method manageErrorContainer - */ - , manageErrorContainer: function () { - var errorContainer = this.options.errorContainer || this.options.errors.container( this.element, this.isRadioOrCheckbox ) - , ulTemplate = this.options.animate ? this.ulTemplate.show() : this.ulTemplate; - - if ( 'undefined' !== typeof errorContainer ) { - $( errorContainer ).append( ulTemplate ); - return; - } - - !this.isRadioOrCheckbox ? this.$element.after( ulTemplate ) : this.$element.parent().after( ulTemplate ); - } - - /** - * Add custom listeners - * - * @param {Object} { listener: function () {} }, eg { onFormSubmit: function ( valid, event, focus ) { ... } } - */ - , addListener: function ( object ) { - for ( var listener in object ) { - this.options.listeners[ listener ] = object[ listener ]; - } - } - - /** - * Destroy parsley field instance - * - * @private - * @method destroy - */ - , destroy: function () { - this.$element.removeClass( 'parsley-validated' ); - this.reset().$element.off( '.' + this.type ).removeData( this.type ); - } - }; - - /** - * ParsleyFieldMultiple override ParsleyField for checkbox and radio inputs - * Pseudo-heritance to manage divergent behavior from ParsleyItem in dedicated methods - * - * @class ParsleyFieldMultiple - * @constructor - */ - var ParsleyFieldMultiple = function ( element, options, type ) { - this.initMultiple( element, options ); - this.inherit( element, options ); - this.Validator = new Validator( options ); - - // call ParsleyField constructor - this.init( element, type || 'ParsleyFieldMultiple' ); - }; - - ParsleyFieldMultiple.prototype = { - - constructor: ParsleyFieldMultiple - - /** - * Set some specific properties, call some extra methods to manage radio / checkbox - * - * @method init - * @param {Object} element - * @param {Object} options - */ - , initMultiple: function ( element, options ) { - this.element = element; - this.$element = $( element ); - this.group = options.group || false; - this.hash = this.getName(); - this.siblings = this.group ? '[data-group="' + this.group + '"]' : 'input[name="' + this.$element.attr( 'name' ) + '"]'; - this.isRadioOrCheckbox = true; - this.isRadio = this.$element.is( 'input[type=radio]' ); - this.isCheckbox = this.$element.is( 'input[type=checkbox]' ); - this.errorClassHandler = options.errors.classHandler( element, this.isRadioOrCheckbox ) || this.$element.parent(); - } - - /** - * Set specific constraints messages, do pseudo-heritance - * - * @private - * @method inherit - * @param {Object} element - * @param {Object} options - */ - , inherit: function ( element, options ) { - var clone = new ParsleyField( element, options, 'ParsleyFieldMultiple' ); - - for ( var property in clone ) { - if ( 'undefined' === typeof this[ property ] ) { - this[ property ] = clone [ property ]; - } - } - } - - /** - * Set specific constraints messages, do pseudo-heritance - * - * @method getName - * @returns {String} radio / checkbox hash is cleaned 'name' or data-group property - */ - , getName: function () { - if ( this.group ) { - return 'parsley-' + this.group; - } - - if ( 'undefined' === typeof this.$element.attr( 'name' ) ) { - throw "A radio / checkbox input must have a data-group attribute or a name to be Parsley validated !"; - } - - return 'parsley-' + this.$element.attr( 'name' ).replace( /(:|\.|\[|\])/g, '' ); - } - - /** - * Special treatment for radio & checkboxes - * Returns checked radio or checkboxes values - * - * @method getVal - * @returns {String} val - */ - , getVal: function () { - if ( this.isRadio ) { - return $( this.siblings + ':checked' ).val() || ''; - } - - if ( this.isCheckbox ) { - var values = []; - - $( this.siblings + ':checked' ).each( function () { - values.push( $( this ).val() ); - } ); - - return values; - } - } - - /** - * Bind validation events on a field - * - * @private - * @method bindValidationEvents - */ - , bindValidationEvents: function () { - // this field has validation events, that means it has to be validated - this.valid = null; - this.$element.addClass( 'parsley-validated' ); - - // remove eventually already binded events - this.$element.off( '.' + this.type ); - - // alaways bind keyup event, for better UX when a field is invalid - var self = this - , triggers = ( !this.options.trigger ? '' : this.options.trigger ) - + ( new RegExp( 'change', 'i' ).test( this.options.trigger ) ? '' : ' change' ); - - // trim triggers to bind them correctly with .on() - triggers = triggers.replace( /^\s+/g , '' ).replace( /\s+$/g ,'' ); - - // bind trigger event on every siblings - $( this.siblings ).each(function () { - $( this ).on( triggers.split( ' ' ).join( '.' + self.type + ' ' ) , false, $.proxy( self.eventValidation, self ) ); - } ) - } - }; - - /** - * ParsleyForm class manage Parsley validated form. - * Manage its fields and global validation - * - * @class ParsleyForm - * @constructor - */ - var ParsleyForm = function ( element, options, type ) { - this.init( element, options, type || 'parsleyForm' ); - }; - - ParsleyForm.prototype = { - - constructor: ParsleyForm - - /* init data, bind jQuery on() actions */ - , init: function ( element, options, type ) { - this.type = type; - this.items = []; - this.$element = $( element ); - this.options = options; - var self = this; - - this.$element.find( options.inputs ).each( function () { - self.addItem( this ); - }); - - this.$element.on( 'submit.' + this.type , false, $.proxy( this.validate, this ) ); - } - - /** - * Add custom listeners - * - * @param {Object} { listener: function () {} }, eg { onFormSubmit: function ( valid, event, focus ) { ... } } - */ - , addListener: function ( object ) { - for ( var listener in object ) { - if ( new RegExp( 'Field' ).test( listener ) ) { - for ( var item = 0; item < this.items.length; item++ ) { - this.items[ item ].addListener( object ); - } - } else { - this.options.listeners[ listener ] = object[ listener ]; - } - } - } - - /** - * Adds a new parsleyItem child to ParsleyForm - * - * @method addItem - * @param elem - */ - , addItem: function ( elem ) { - if ( $( elem ).is( this.options.excluded ) ) { - return false; - } - - var ParsleyField = $( elem ).parsley( this.options ); - ParsleyField.setParent( this ); - - this.items.push( ParsleyField ); - } - - /** - * Removes a parsleyItem child from ParsleyForm - * - * @method removeItem - * @param elem - * @return {Boolean} - */ - , removeItem: function ( elem ) { - var parsleyItem = $( elem ).parsley(); - - // identify & remove item if same Parsley hash - for ( var i = 0; i < this.items.length; i++ ) { - if ( this.items[ i ].hash === parsleyItem.hash ) { - this.items[ i ].destroy(); - this.items.splice( i, 1 ); - return true; - } - } - - return false; - } - - /** - * Process each form field validation - * Display errors, call custom onFormSubmit() function - * - * @method validate - * @param {Object} event jQuery Event - * @return {Boolean} Is form valid or not - */ - , validate: function ( event ) { - var valid = true; - this.focusedField = false; - - for ( var item = 0; item < this.items.length; item++ ) { - if ( 'undefined' !== typeof this.items[ item ] && false === this.items[ item ].validate() ) { - valid = false; - - if ( !this.focusedField && 'first' === this.options.focus || 'last' === this.options.focus ) { - this.focusedField = this.items[ item ].$element; - } - } - } - - // form is invalid, focus an error field depending on focus policy - if ( this.focusedField && !valid ) { - this.focusedField.focus(); - } - - this.options.listeners.onFormSubmit( valid, event, this ); - - return valid; - } - - , isValid: function () { - for ( var item = 0; item < this.items.length; item++ ) { - if ( false === this.items[ item ].isValid() ) { - return false; - } - } - - return true; - } - - /** - * Remove all errors ul under invalid fields - * - * @method removeErrors - */ - , removeErrors: function () { - for ( var item = 0; item < this.items.length; item++ ) { - this.items[ item ].parsley( 'reset' ); - } - } - - /** - * destroy Parsley binded on the form and its fields - * - * @method destroy - */ - , destroy: function () { - for ( var item = 0; item < this.items.length; item++ ) { - this.items[ item ].destroy(); - } - - this.$element.off( '.' + this.type ).removeData( this.type ); - } - - /** - * reset Parsley binded on the form and its fields - * - * @method reset - */ - , reset: function () { - for ( var item = 0; item < this.items.length; item++ ) { - this.items[ item ].reset(); - } - } - }; - - /** - * Parsley plugin definition - * Provides an interface to access public Validator, ParsleyForm and ParsleyField functions - * - * @class Parsley - * @constructor - * @param {Mixed} Options. {Object} to configure Parsley or {String} method name to call a public class method - * @param {Function} Callback function - * @return {Mixed} public class method return - */ - $.fn.parsley = function ( option, fn ) { - var options = $.extend( true, {}, $.fn.parsley.defaults, 'undefined' !== typeof window.ParsleyConfig ? window.ParsleyConfig : {}, option, this.data() ) - , newInstance = null; - - function bind ( self, type ) { - var parsleyInstance = $( self ).data( type ); - - // if data never binded or we want to clone a build (for radio & checkboxes), bind it right now! - if ( !parsleyInstance ) { - switch ( type ) { - case 'parsleyForm': - parsleyInstance = new ParsleyForm( self, options, 'parsleyForm' ); - break; - case 'parsleyField': - parsleyInstance = new ParsleyField( self, options, 'parsleyField' ); - break; - case 'parsleyFieldMultiple': - parsleyInstance = new ParsleyFieldMultiple( self, options, 'parsleyFieldMultiple' ); - break; - default: - return; - } - - $( self ).data( type, parsleyInstance ); - } - - // here is our parsley public function accessor - if ( 'string' === typeof option && 'function' === typeof parsleyInstance[ option ] ) { - var response = parsleyInstance[ option ]( fn ); - - return 'undefined' !== typeof response ? response : $( self ); - } - - return parsleyInstance; - } - - // if a form elem is given, bind all its input children - if ( $( this ).is( 'form' ) || true === $( this ).data( 'bind' ) ) { - newInstance = bind ( $( this ), 'parsleyForm' ); - - // if it is a Parsley supported single element, bind it too, except inputs type hidden - // add here a return instance, cuz' we could call public methods on single elems with data[ option ]() above - } else if ( $( this ).is( options.inputs ) && !$( this ).is( options.excluded ) ) { - newInstance = bind( $( this ), !$( this ).is( 'input[type=radio], input[type=checkbox]' ) ? 'parsleyField' : 'parsleyFieldMultiple' ); - } - - return 'function' === typeof fn ? fn() : newInstance; - }; - - $.fn.parsley.Constructor = ParsleyForm; - - /** - * Parsley plugin configuration - * - * @property $.fn.parsley.defaults - * @type {Object} - */ - $.fn.parsley.defaults = { - // basic data-api overridable properties here.. - inputs: 'input, textarea, select' // Default supported inputs. - , excluded: 'input[type=hidden], input[type=file], :disabled' // Do not validate input[type=hidden] & :disabled. - , trigger: false // $.Event() that will trigger validation. eg: keyup, change.. - , animate: true // fade in / fade out error messages - , animateDuration: 300 // fadein/fadout ms time - , focus: 'first' // 'fist'|'last'|'none' which error field would have focus first on form validation - , validationMinlength: 3 // If trigger validation specified, only if value.length > validationMinlength - , successClass: 'parsley-success' // Class name on each valid input - , errorClass: 'parsley-error' // Class name on each invalid input - , errorMessage: false // Customize an unique error message showed if one constraint fails - , validators: {} // Add your custom validators functions - , showErrors: true // Set to false if you don't want Parsley to display error messages - , messages: {} // Add your own error messages here - - //some quite advanced configuration here.. - , validateIfUnchanged: false // false: validate once by field value change - , errors: { - classHandler: function ( elem, isRadioOrCheckbox ) {} // specify where parsley error-success classes are set - , container: function ( elem, isRadioOrCheckbox ) {} // specify an elem where errors will be **apened** - , errorsWrapper: '
    ' // do not set an id for this elem, it would have an auto-generated id - , errorElem: '
  • ' // each field constraint fail in an li - } - , listeners: { - onFieldValidate: function ( elem, ParsleyForm ) { return false; } // Executed on validation. Return true to ignore field validation - , onFormSubmit: function ( isFormValid, event, ParsleyForm ) {} // Executed once on form validation - , onFieldError: function ( elem, constraints, ParsleyField ) {} // Executed when a field is detected as invalid - , onFieldSuccess: function ( elem, constraints, ParsleyField ) {} // Executed when a field passes validation - } - }; - - /* PARSLEY auto-bind DATA-API + Global config retrieving - * =================================================== */ - $( window ).on( 'load', function () { - $( '[data-validate="parsley"]' ).each( function () { - $( this ).parsley(); - } ); - } ); - -// This plugin works with jQuery or Zepto (with data extension built for Zepto.) -}(window.jQuery || window.Zepto); From 8700d140d4b4e400b6b276c6e80ed20c62aebe9b Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 7 Feb 2015 00:38:02 +0100 Subject: [PATCH 07/21] Update Parsleyjs to v2.0.7 --- parsley/static/parsley/js/parsley.js | 965 +++++++++++++---------- parsley/static/parsley/js/parsley.min.js | 6 +- 2 files changed, 554 insertions(+), 417 deletions(-) diff --git a/parsley/static/parsley/js/parsley.js b/parsley/static/parsley/js/parsley.js index 0074c72..6345e1e 100644 --- a/parsley/static/parsley/js/parsley.js +++ b/parsley/static/parsley/js/parsley.js @@ -1,26 +1,40 @@ /*! * Parsleyjs * Guillaume Potier - -* Version 2.0.0-rc4 - built Sat Mar 15 2014 13:45:08 +* Version 2.0.7 - built Sat Jan 24 2015 14:50:11 * MIT Licensed * */ -!(function($) { +!(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, + 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 && attribute.specified && regex.test(attribute.name)) { - if ('undefined' !== typeof checkAttr && new RegExp(checkAttr, 'i').test(attribute.name)) - return true; + 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); } } @@ -30,15 +44,16 @@ $element[0].setAttribute(this.dasherize(namespace + attr), String(value)); }, // Recursive object / array getter - get: function (obj, path, placeholder) { - var i = 0, - paths = (path || '').split('.'); + 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 || placeholder; + return obj; } - return placeholder; + return undefined; }, hash: function (length) { return String(Math.random()).substring(2, length ? length + 2 : 9); @@ -79,7 +94,17 @@ .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 @@ -92,17 +117,17 @@ // Supported inputs by default inputs: 'input, textarea, select', // Excluded inputs by default - excluded: 'input[type=button], input[type=submit], input[type=reset]', + 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 + // 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`.. + // `$.Event()` that will trigger validation. eg: `keyup`, `change`... trigger: false, // Class that would be added on every failing validation Parsley field errorClass: 'parsley-error', @@ -124,12 +149,12 @@ ParsleyAbstract.prototype = { asyncSupport: false, actualizeOptions: function () { - this.options = this.parsleyInstance.OptionsFactory.get(this); + 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.apply(window.ParsleyValidator, arguments); + 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 @@ -157,34 +182,35 @@ destroy: function () { // Field case: emit destroy event to clean UI and then destroy stored instance if ('ParsleyForm' !== this.__class__) { - $.emit('parsley:field:destroy', this); 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(); - $.emit('parsley:form:destroy', this); this.$element.removeData('Parsley'); + $.emit('parsley:form:destroy', this); } }; /*! * validator.js * Guillaume Potier - -* Version 0.5.7 - built Wed Mar 12 2014 19:16:34 +* Version 1.0.0 - built Sun Aug 03 2014 17:42:31 * MIT Licensed * */ -( function ( exports ) { +var Validator = ( function ( ) { + var exports = {}; /** * Validator */ var Validator = function ( options ) { this.__class__ = 'Validator'; - this.__version__ = '0.5.7'; + this.__version__ = '1.0.0'; this.options = options || {}; this.bindingKey = this.options.bindingKey || '_validatorjsConstraint'; - return this; }; Validator.prototype = { constructor: Validator, @@ -267,27 +293,37 @@ throw new Error( 'Should give a valid mapping object to Constraint', err, data ); } } - return this; }; Constraint.prototype = { constructor: Constraint, check: function ( object, group ) { var result, failures = {}; - // check all constraint nodes if strict validation enabled. Else, only object nodes that have a constraint - for ( var property in this.options.strict ? this.nodes : object ) { - if ( this.options.strict ? this.has( property, object ) : this.has( property ) ) { + // 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 ) ) ) + if ( ( _isArray( result ) && result.length > 0 ) || ( !_isArray( result ) && !_isEmptyObject( result ) ) ) { failures[ property ] = result; - // in strict mode, get a violation for each constraint node not in object - } else if ( this.options.strict ) { - try { - // we trigger here a HaveProperty Assert violation to have uniform Violation object in the end - new Assert().HaveProperty( property ).validate( object ); - } catch ( violation ) { - failures[ property ] = violation; } + } catch ( violation ) { + failures[ property ] = violation; } } return _isEmptyObject(failures) ? true : failures; @@ -304,7 +340,7 @@ throw new Error( 'Should give an Assert, an Asserts array, a Constraint', object ); }, has: function ( node, nodes ) { - var nodes = 'undefined' !== typeof nodes ? nodes : this.nodes; + nodes = 'undefined' !== typeof nodes ? nodes : this.nodes; return 'undefined' !== typeof nodes[ node ]; }, get: function ( node, placeholder ) { @@ -377,8 +413,8 @@ }, __toString: function () { if ( 'undefined' !== typeof this.violation ) - var violation = '", ' + this.getViolation().constraint + ' expected was ' + this.getViolation().expected; - return this.assert.__class__ + ' assert failed for "' + this.value + 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; @@ -396,14 +432,18 @@ this.groups = []; if ( 'undefined' !== typeof group ) this.addGroup( group ); - return this; }; Assert.prototype = { construct: Assert, - check: function ( value, group ) { + requiresValidation: function ( group ) { if ( group && !this.hasGroup( group ) ) - return; + 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 ); @@ -479,15 +519,14 @@ this.__class__ = 'Callback'; this.arguments = Array.prototype.slice.call( arguments ); if ( 1 === this.arguments.length ) - this.arguments = [] + 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 arguments = [ value ].concat( this.arguments) ; - var result = this.fn.apply( this, arguments ); + var result = this.fn.apply( this, [ value ].concat( this.arguments ) ); if ( true !== result ) throw new Violation( this, value, { result: result } ); return true; @@ -508,9 +547,9 @@ }; return this; }, - Collection: function ( constraint ) { + Collection: function ( assertOrConstraint ) { this.__class__ = 'Collection'; - this.constraint = 'undefined' !== typeof constraint ? new Constraint( constraint ) : false; + 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 ) ) @@ -554,19 +593,6 @@ }; return this; }, - Eql: function ( eql ) { - this.__class__ = 'Eql'; - if ( 'undefined' === typeof eql ) - throw new Error( 'Equal must be instanciated with an Array or an Object' ); - this.eql = eql; - this.validate = function ( value ) { - var eql = 'function' === typeof this.eql ? this.eql( value ) : this.eql; - if ( !expect.eql( eql, value ) ) - throw new Violation( this, value, { eql: eql } ); - return true; - }; - return this; - }, EqualTo: function ( reference ) { this.__class__ = 'EqualTo'; if ( 'undefined' === typeof reference ) @@ -620,18 +646,6 @@ }; return this; }, - IPv4: function () { - this.__class__ = 'IPv4'; - this.validate = function ( value ) { - var regExp = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; - 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; - }, Length: function ( boundaries ) { this.__class__ = 'Length'; if ( !boundaries.min && !boundaries.max ) @@ -679,18 +693,6 @@ }; return this; }, - Mac: function () { - this.__class__ = 'Mac'; - this.validate = function ( value ) { - var regExp = /^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$/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; - }, NotNull: function () { this.__class__ = 'NotNull'; this.validate = function ( value ) { @@ -762,12 +764,14 @@ this.validate = function ( value ) { if ( 'undefined' === typeof value ) throw new Violation( this, value ); - if ( 'string' === typeof value ) - try { + try { + if ( 'string' === typeof value ) new Assert().NotNull().validate( value ) && new Assert().NotBlank().validate( value ); - } catch ( violation ) { - throw new Violation( this, value ); - } + else if ( true === _isArray( value ) ) + new Assert().Length( { min: 1 } ).validate( value ); + } catch ( violation ) { + throw new Violation( this, value ); + } return true; }; return this; @@ -807,7 +811,7 @@ if (!Array.prototype.indexOf) Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { - if (this == null) { + if (this === null) { throw new TypeError(); } var t = Object(this); @@ -820,7 +824,7 @@ n = Number(arguments[1]); if (n != n) { // shortcut for verifying if it's NaN n = 0; - } else if (n != 0 && n != Infinity && n != -Infinity) { + } else if (n !== 0 && n != Infinity && n != -Infinity) { n = (n > 0 || -1) * Math.floor(Math.abs(n)); } } @@ -843,78 +847,25 @@ }; var _isArray = function ( obj ) { return Object.prototype.toString.call( obj ) === '[object Array]'; - } - // https://github.com/LearnBoost/expect.js/blob/master/expect.js - var expect = { - eql: function ( actual, expected ) { - if ( actual === expected ) { - return true; - } else if ( 'undefined' !== typeof Buffer - && Buffer.isBuffer( actual ) && Buffer.isBuffer( expected ) ) { - if ( actual.length !== expected.length ) return false; - for ( var i = 0; i < actual.length; i++ ) - if ( actual[i] !== expected[i] ) return false; - return true; - } else if ( actual instanceof Date && expected instanceof Date ) { - return actual.getTime() === expected.getTime(); - } else if ( typeof actual !== 'object' && typeof expected !== 'object' ) { - // loosy == - return actual == expected; - } else { - return this.objEquiv(actual, expected); - } - }, - isUndefinedOrNull: function ( value ) { - return value === null || typeof value === 'undefined'; - }, - isArguments: function ( object ) { - return Object.prototype.toString.call(object) == '[object Arguments]'; - }, - keys: function ( obj ) { - if ( Object.keys ) - return Object.keys( obj ); - var keys = []; - for ( var i in obj ) - if ( Object.prototype.hasOwnProperty.call( obj, i ) ) - keys.push(i); - return keys; - }, - objEquiv: function ( a, b ) { - if ( this.isUndefinedOrNull( a ) || this.isUndefinedOrNull( b ) ) - return false; - if ( a.prototype !== b.prototype ) return false; - if ( this.isArguments( a ) ) { - if ( !this.isArguments( b ) ) - return false; - return eql( pSlice.call( a ) , pSlice.call( b ) ); - } - try { - var ka = this.keys( a ), kb = this.keys( b ), key, i; - } catch ( e ) { - return false; - } - if ( ka.length !== kb.length ) - return false; - ka.sort(); - kb.sort(); - for ( i = ka.length - 1; i >= 0; i-- ) - if ( ka[ i ] != kb[ i ] ) - return false; - for ( i = ka.length - 1; i >= 0; i-- ) { - key = ka[i]; - if ( !this.eql( a[ key ], b[ key ] ) ) - return false; - } - return true; - } }; - // AMD Compliance - if ( "function" === typeof define && define.amd ) { - define( 'validator', [],function() { return exports; } ); + // 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; } -} )( 'undefined' === typeof exports ? this[ '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; @@ -926,7 +877,7 @@ init: function (validators, catalog) { this.catalog = catalog; for (var name in validators) - this.addValidator(name, validators[name].fn, validators[name].priority); + 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 @@ -946,22 +897,26 @@ }, // Add a specific message for a given constraint in a given locale addMessage: function (locale, name, message) { - if (undefined === typeof this.catalog[locale]) + if ('undefined' === typeof this.catalog[locale]) this.catalog[locale] = {}; - this.catalog[locale][name] = message; + 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) { - this.validators[name] = function (requirements) { - return $.extend(new Validator.Assert().Callback(fn, requirements), { priority: priority }); + 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) { - return addValidator(name, fn, priority); + updateValidator: function (name, fn, priority, requirementsTransformer) { + return this.addValidator(name, fn, priority, requirementsTransformer); }, removeValidator: function (name) { delete this.validators[name]; @@ -971,16 +926,16 @@ 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 = window.ParsleyConfig.i18n[this.locale][constraint.name][constraint.requirements]; + message = this.catalog[this.locale][constraint.name][constraint.requirements]; else - message = this.formatMesssage(window.ParsleyConfig.i18n[this.locale][constraint.name], constraint.requirements); - return '' !== message ? message : window.ParsleyConfig.i18n[this.locale].defaultMessage; + message = this.formatMessage(this.catalog[this.locale][constraint.name], constraint.requirements); + return '' !== message ? message : this.catalog[this.locale].defaultMessage; }, // Kind of light `sprintf()` implementation - formatMesssage: function (string, parameters) { + formatMessage: function (string, parameters) { if ('object' === typeof parameters) { for (var i in parameters) - string = this.formatMesssage(string, parameters[i]); + string = this.formatMessage(string, parameters[i]); return string; } return 'string' === typeof string ? string.replace(new RegExp('%s', 'i'), parameters) : ''; @@ -1001,6 +956,8 @@ 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; @@ -1014,7 +971,7 @@ 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,4}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)', 'i'); + 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'); @@ -1022,13 +979,33 @@ return $.extend(assert, { priority: 256 }); }, pattern: function (regexp) { - return $.extend(new Validator.Assert().Regexp(regexp), { priority: 64 }); + 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 (length) { - return $.extend(new Validator.Assert().Length({ min: length }), { priority: 30 }); + 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 (length) { - return $.extend(new Validator.Assert().Length({ max: length }), { priority: 30 }); + 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 }); @@ -1101,7 +1078,7 @@ 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.. + // 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); @@ -1113,6 +1090,16 @@ 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); @@ -1129,19 +1116,19 @@ 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')); - 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); - } else { - fieldInstance._ui.$errorsWrapper - .removeClass('filled') - .find('.parsley-custom-error-message') - .remove(); } - return; + 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++) @@ -1152,13 +1139,15 @@ 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 parametter.. + // 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))); + .append( + $(fieldInstance.options.errorTemplate) + .addClass('parsley-' + name) + .html(message || this._getErrorMessage(fieldInstance, assert)) + ); if (true !== doNotUpdateClass) this._errorClass(fieldInstance); }, @@ -1201,11 +1190,12 @@ _getErrorMessage: function (fieldInstance, constraint) { var customConstraintErrorMessage = constraint.name + 'Message'; if ('undefined' !== typeof fieldInstance.options[customConstraintErrorMessage]) - return fieldInstance.options[customConstraintErrorMessage]; + return window.ParsleyValidator.formatMessage(fieldInstance.options[customConstraintErrorMessage], constraint.requirements); return window.ParsleyValidator.getErrorMessage(constraint); }, _diff: function (newResult, oldResult, deep) { - var added = [], + var + added = [], kept = []; for (var i = 0; i < newResult.length; i++) { var found = false; @@ -1252,36 +1242,50 @@ _ui.validationInformationVisible = false; // Store it in fieldInstance for later fieldInstance._ui = _ui; - /** Mess with DOM now **/ - this._insertErrorWrapper(fieldInstance); + // 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; - return 'undefined' === typeof fieldInstance.options.multiple ? fieldInstance.$element : fieldInstance.$element.parent(); + // 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) + 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'); - - if ('function' === typeof fieldInstance.options.errorsContainer) + } + 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 - fieldInstance.$element.off('.Parsley'); + $toBind.off('.Parsley'); // If no trigger is set, all good if (false === fieldInstance.options.trigger) return; @@ -1289,15 +1293,13 @@ if ('' === triggers) return; // Bind fieldInstance.eventValidate if exists (for parsley.ajax for example), ParsleyUI.eventValidate otherwise - fieldInstance.$element - .on( - triggers.split(' ').join('.Parsley ') + '.Parsley', - false, - $.proxy('function' === typeof fieldInstance.eventValidate ? fieldInstance.eventValidate : this.eventValidate, fieldInstance)); + $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 + // 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)) @@ -1308,29 +1310,34 @@ }, manageFailingFieldTrigger: function (fieldInstance) { fieldInstance._ui.failedOnce = true; - // Radio and checkboxes fields + // 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).parsley().$element.on('change.ParsleyFailedOnce', false, $.proxy(fieldInstance.validate, fieldInstance)); + 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) { - // Nothing to do if UI never initialized for this field - if ('undefined' === typeof parsleyInstance._ui) - return; // 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.children().each(function () { - $(this).remove(); - }); + parsleyInstance._ui.$errorsWrapper + .removeClass('filled') + .children() + .remove(); // Reset validation class this._resetClass(parsleyInstance); // Reset validation flags and last validation result @@ -1339,13 +1346,11 @@ parsleyInstance._ui.validationInformationVisible = false; }, destroy: function (parsleyInstance) { - // Nothing to do if UI never initialized for this field - if ('undefined' === typeof parsleyInstance._ui) - return; this.reset(parsleyInstance); if ('ParsleyForm' === parsleyInstance.__class__) return; - parsleyInstance._ui.$errorsWrapper.remove(); + if ('undefined' !== typeof parsleyInstance._ui) + parsleyInstance._ui.$errorsWrapper.remove(); delete parsleyInstance._ui; }, _successClass: function (fieldInstance) { @@ -1378,6 +1383,7 @@ case 'ParsleyForm': return this.getFormOptions(parsleyInstance); case 'ParsleyField': + case 'ParsleyFieldMultiple': return this.getFieldOptions(parsleyInstance); default: throw new Error('Instance ' + parsleyInstance.__class__ + ' is not supported'); @@ -1390,33 +1396,31 @@ }, getFieldOptions: function (fieldInstance) { this.fieldOptions = ParsleyUtils.attr(fieldInstance.$element, this.staticOptions.namespace); - if (null === this.formOptions && 'ParsleyForm' === fieldInstance.parsleyInstance.__proxy__) - this.formOptions = getFormOptions(fieldInstance.parsleyInstance); + 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, parsleyInstance) { + var ParsleyForm = function (element, OptionsFactory) { this.__class__ = 'ParsleyForm'; this.__id__ = ParsleyUtils.hash(4); - if ('Parsley' !== ParsleyUtils.get(parsleyInstance, '__class__')) - throw new Error('You must give a Parsley instance'); - this.parsleyInstance = parsleyInstance; + 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 = { - init: function () { - this.validationResult = null; - this.options = this.parsleyInstance.OptionsFactory.get(this); - this._bindFields(); - return this; - }, onSubmitValidate: function (event) { this.validate(undefined, undefined, event); // prevent form submission if validation fails - if (false === this.validationResult && event instanceof $.Event) + if (false === this.validationResult && event instanceof $.Event) { + event.stopImmediatePropagation(); event.preventDefault(); + } return this; }, // @returns boolean @@ -1424,18 +1428,19 @@ 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(); - $.emit('parsley:form:validate', this); // 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 && group !== this.fields[i].options.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; }, @@ -1444,44 +1449,59 @@ 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 && group !== this.fields[i].options.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.parsleyInstance); - // Only add valid and not excluded ParsleyField children - if ('ParsleyField' === fieldInstance.__class__ && !fieldInstance.$element.is(fieldInstance.options.excluded)) - self.fields.push(fieldInstance); + 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) { - if ('ParsleyField' !== ParsleyUtils.get(parsleyField, '__class__')) - throw new Error('ParsleyField instance expected'); - if ('function' !== typeof window.ParsleyValidator.validators[name] && - 'Assert' !== window.ParsleyValidator.validators[name](requirements).__parentClass__) + 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 (parsleyField, name) { + var getPriority = function () { if ('undefined' !== typeof parsleyField.options[name + 'Priority']) return parsleyField.options[name + 'Priority']; - return ParsleyUtils.get(window.ParsleyValidator.validators[name](requirements), 'priority', 2); + return ParsleyUtils.get(assert, 'priority') || 2; }; - priority = priority || getPriority(parsleyField, name); - if ('function' === typeof window.ParsleyValidator.validators[name](requirements).requirementsTransformer) - requirements = window.ParsleyValidator.validators[name](requirements).requirementsTransformer(); - return $.extend(window.ParsleyValidator.validators[name](requirements), { + 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, @@ -1490,40 +1510,34 @@ }); }; - var ParsleyField = function(field, parsleyInstance) { + var ParsleyField = function (field, OptionsFactory, parsleyFormInstance) { this.__class__ = 'ParsleyField'; this.__id__ = ParsleyUtils.hash(4); - if ('Parsley' !== ParsleyUtils.get(parsleyInstance, '__class__')) - throw new Error('You must give a Parsley instance'); - this.parsleyInstance = parsleyInstance; this.$element = $(field); - this.options = this.parsleyInstance.OptionsFactory.get(this); + // 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 = { - init: function () { - this.constraints = []; - this.validationResult = []; - // Select / radio / checkbox multiple inputs hack - if ((this.$element.is('input[type=radio], input[type=checkbox]') && 'undefined' === typeof this.options.multiple) || (this.$element.is('select') && 'undefined' !== typeof this.$element.attr('multiple'))) { - if ('undefined' !== typeof this.$element.attr('name') && this.$element.attr('name').length) - this.options.multiple = this.$element.attr('name'); - else if ('undefined' !== typeof this.$element.attr('id') && this.$element.attr('id').length) - this.options.multiple = this.$element.attr('id'); - if ('undefined' === typeof this.options.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, and id or a multiple option.', this.$element); - return this.parsleyInstance; - } - this.options.multiple = this.options.multiple.replace(/(:|\.|\[|\]|\$)/g, ''); - ParsleyUtils.setAttr(this.$element, this.options.namespace, 'multiple', this.options.multiple); - } - this.bindConstraints(); - return this; - }, - // Returns validationResult. For field, it could be: - // - `true` if all green - // - `[]` if non required field and empty - // - `[Violation, [Violation..]]` if errors + // # 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 @@ -1533,28 +1547,21 @@ $.emit('parsley:field:validated', this); return this.validationResult; }, - 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; - }, + // Just validate field. Do not trigger any event // Same @return as `validate()` isValid: function (force, value) { - // Sort priorities to validate more important first - var priorities = this.getConstraintsSortedPriorities(); - // Value could be passed as argument, needed to add more power to 'parsley:field:validate' - value = value || this.getValue(); // 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 - // And if multiple field - if ('' === value && !this.isRequired() && 'undefined' === typeof this.options.validateIfEmpty && 'undefined' === typeof this.options.multiple && 'undefined' === typeof force) + 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) @@ -1565,55 +1572,85 @@ return false; return true; }, - // Field is required if have required constraint without `false` value - isRequired: function () { - var constraintIndex = this._constraintIndex('required'); - return !(-1 === constraintIndex || (-1 !== constraintIndex && false === this.constraints[constraintIndex].requirements)); - }, + // @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) - return this.options.value; - // Regular input, textarea and simple select - if ('undefined' === typeof this.options.multiple) { - var value = this.$element.val(); - // Use `data-parsley-trim-value="true"` to auto trim inputs entry - if (true === this.options.trimValue) - return value.replace(/^\s+|\s+$/g, ''); - return 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 : ''; + 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; } - // Select multiple case - if (this.$element.is('select')) - return null === this.$element.val() ? '' : this.$element.val(); + return this; }, - refreshConstraints: function () { - return this.actualizeOptions().bindConstraints(); + // 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); }, - bindConstraints: function () { - var constraints = []; + // # 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) + 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(); + return this._bindHtml5Constraints(); }, - bindHtml5Constraints: function () { + // 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); @@ -1629,69 +1666,118 @@ // 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, that is in fact an integer validator - if ('number' === type) - return this.addConstraint('type', 'integer', undefined, true); + // 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')) + } else if (new RegExp(type, 'i').test('email url range')) { return this.addConstraint('type', type, undefined, true); - }, - /** - * 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 (-1 !== this._constraintIndex(constraint.name)) - this.removeConstraint(constraint.name); - this.constraints.push(constraint); } return this; }, - removeConstraint: function (name) { - for (var i = 0; i < this.constraints.length; i++) - if (name === this.constraints[i].name) { - this.constraints.splice(i, 1); - break; - } - return this; - }, - updateConstraint: function (name, parameters, priority) { - return this.removeConstraint(name) - .addConstraint(name, parameters, priority); + // 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; }, - _constraintIndex: function (name) { + // 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 (name === this.constraints[i].name) - return i; - return -1; + 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() { + var ParsleyMultiple = function () { this.__class__ = 'ParsleyFieldMultiple'; }; ParsleyMultiple.prototype = { - init: function (multiple) { + // 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; - ParsleyUtils.setAttr(this.$element, this.options.namespace, 'multiple', this.options.multiple); return this; } }; - var o = $({}), subscribed = {}; + var + o = $({}), + subscribed = {}; // $.listen(name, callback); // $.listen(name, context, callback); $.listen = function (name) { @@ -1736,7 +1822,7 @@ delete subscribed[name]; }; // $.emit(name [, arguments...]); - // $.emit(name, instance [, arguments..]); + // $.emit(name, instance [, arguments...]); $.emit = function (name, instance) { if ('undefined' === typeof subscribed[name]) return; @@ -1787,10 +1873,10 @@ window.ParsleyConfig.i18n.en = $.extend(window.ParsleyConfig.i18n.en || {}, { 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 less.", + 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 less.", + maxcheck: "You must select %s choices or fewer.", check: "You must select between %s and %s choices.", equalto: "This value should be the same." }); @@ -1798,58 +1884,102 @@ window.ParsleyConfig.i18n.en = $.extend(window.ParsleyConfig.i18n.en || {}, { if ('undefined' !== typeof window.ParsleyValidator) window.ParsleyValidator.addCatalog('en', window.ParsleyConfig.i18n.en, true); -// Parsley.js 2.0.0-rc4 +// 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, parsleyInstance) { + var Parsley = function (element, options, parsleyFormInstance) { this.__class__ = 'Parsley'; - this.__version__ = '2.0.0-rc4'; + this.__version__ = '2.0.7'; this.__id__ = ParsleyUtils.hash(4); - // Parsley must be instanciated with a DOM element or jQuery $element + // Parsley must be instantiated with a DOM element or jQuery $element if ('undefined' === typeof element) throw new Error('You must give an element'); - return this.init($(element), options, parsleyInstance); + 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, parsleyInstance) { + 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 savedParsleyInstance = 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 parsleyInstance && 'ParsleyField' === savedParsleyInstance.parsleyInstance.__proxy__) - savedParsleyInstance.parsleyInstance = parsleyInstance; - return savedParsleyInstance; + 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)); - options = this.OptionsFactory.get(this); + 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, options.namespace, 'validate') && !this.$element.is(options.inputs))) { - return this.bind('parsleyForm', parsleyInstance); - // Else every other element that is supported is binded as a `ParsleyField` - } else if (this.$element.is(options.inputs)) { - if ((this.$element.is('input[type=radio], input[type=checkbox]') && 'undefined' === typeof options.multiple) || (this.$element.is('select') && 'undefined' !== typeof this.$element.attr('multiple'))) { - if ('undefined' !== typeof this.$element.attr('name') && this.$element.attr('name').length) - options.multiple = this.$element.attr('name'); - else if ('undefined' !== typeof this.$element.attr('id') && this.$element.attr('id').length) - options.multiple = this.$element.attr('id'); - if ('undefined' === typeof options.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, and id or a multiple option.', this.$element); - return this; + 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; } - options.multiple = options.multiple.replace(/(:|\.|\[|\]|\$)/g, ''); - return this.bind('parsleyFieldMultiple', parsleyInstance, options.multiple); } - return this.bind('parsleyField', parsleyInstance); } - return this; + // 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) { @@ -1863,38 +1993,44 @@ if ('undefined' !== typeof window.ParsleyValidator) return ParsleyDefaults.namespace; }, // Return proper `ParsleyForm`, `ParsleyField` or `ParsleyFieldMultiple` - bind: function (type, parentParsleyInstance, multiple) { + bind: function (type, parentParsleyFormInstance, multiple, doNotStore) { var parsleyInstance; switch (type) { case 'parsleyForm': parsleyInstance = $.extend( - new ParsleyForm(this.$element, parentParsleyInstance || this), + new ParsleyForm(this.$element, this.OptionsFactory), new ParsleyAbstract(), window.ParsleyExtend - ).init(); + )._bindFields(); break; case 'parsleyField': parsleyInstance = $.extend( - new ParsleyField(this.$element, parentParsleyInstance || this), + new ParsleyField(this.$element, this.OptionsFactory, parentParsleyFormInstance), new ParsleyAbstract(), window.ParsleyExtend - ).init(); + ); break; case 'parsleyFieldMultiple': parsleyInstance = $.extend( - new ParsleyField(this.$element, parentParsleyInstance || this), + new ParsleyField(this.$element, this.OptionsFactory, parentParsleyFormInstance), new ParsleyAbstract(), new ParsleyMultiple(), window.ParsleyExtend - ).init(multiple); + )._init(multiple); break; default: throw new Error(type + 'is not a supported Parsley type'); } - if ('ParsleyForm' === parsleyInstance.__class__ || 'ParsleyField' === parsleyInstance.__class__) { + 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); - this.__proxy__ = parsleyInstance.__class__; // Tell the world we got a new ParsleyForm or ParsleyField instance! $.emit('parsley:' + ('parsleyForm' === type ? 'form' : 'field') + ':init', parsleyInstance); } @@ -1922,7 +2058,7 @@ if ('undefined' !== typeof window.ParsleyValidator) // ### 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') ? + 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 @@ -1939,8 +2075,9 @@ if ('undefined' !== typeof window.ParsleyValidator) // ### PARSLEY auto-binding // Prevent it by setting `ParsleyConfig.autoBind` to `false` if (false !== ParsleyUtils.get(window, 'ParsleyConfig.autoBind')) - $(document).ready(function () { + $(function () { // Works only on `data-parsley-validate`. - $('[data-parsley-validate]').parsley(); + if ($('[data-parsley-validate]').length) + $('[data-parsley-validate]').parsley(); }); -})(window.jQuery); +})); diff --git a/parsley/static/parsley/js/parsley.min.js b/parsley/static/parsley/js/parsley.min.js index c24c1de..2cdbebe 100644 --- a/parsley/static/parsley/js/parsley.min.js +++ b/parsley/static/parsley/js/parsley.min.js @@ -1,9 +1,9 @@ /*! * Parsleyjs * Guillaume Potier - -* Version 2.0.0-rc4 - built Sat Mar 15 2014 13:45:08 +* Version 2.0.7 - built Sat Jan 24 2015 14:50:11 * MIT Licensed * */ -!function(a){var b={attr:function(a,b,c){var d,e={},f=new RegExp("^"+b,"i");if("undefined"==typeof a||"undefined"==typeof a[0])return{};for(var g in a[0].attributes)if(d=a[0].attributes[g],"undefined"!=typeof d&&null!==d&&d.specified&&f.test(d.name)){if("undefined"!=typeof c&&new RegExp(c,"i").test(d.name))return!0;e[this.camelize(d.name.replace(b,""))]=this.deserializeValue(d.value)}return"undefined"==typeof c?e:!1},setAttr:function(a,b,c,d){a[0].setAttribute(this.dasherize(b+c),String(d))},get:function(a,b,c){for(var d=0,e=(b||"").split(".");this.isObject(a)||this.isArray(a);)if(a=a[e[d++]],d===e.length)return a||c;return c},hash:function(a){return String(Math.random()).substring(2,a?a+2:9)},isArray:function(a){return"[object Array]"===Object.prototype.toString.call(a)},isObject:function(a){return a===Object(a)},deserializeValue:function(b){var c;try{return b?"true"==b||("false"==b?!1:"null"==b?null:isNaN(c=Number(b))?/^[\[\{]/.test(b)?a.parseJSON(b):b:c):b}catch(d){return b}},camelize:function(a){return a.replace(/-+(.)?/g,function(a,b){return b?b.toUpperCase():""})},dasherize:function(a){return a.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}},c={namespace:"data-parsley-",inputs:"input, textarea, select",excluded:"input[type=button], input[type=submit], input[type=reset]",priorityEnabled:!0,uiEnabled:!0,validationThreshold:3,focus:"first",trigger:!1,errorClass:"parsley-error",successClass:"parsley-success",classHandler:function(){},errorsContainer:function(){},errorsWrapper:'
      ',errorTemplate:"
    • "},d=function(){};d.prototype={asyncSupport:!1,actualizeOptions:function(){return this.options=this.parsleyInstance.OptionsFactory.get(this),this},validateThroughValidator:function(){return window.ParsleyValidator.validate.apply(window.ParsleyValidator,arguments)},subscribe:function(b,c){return a.listenTo(this,b.toLowerCase(),c),this},unsubscribe:function(b){return a.unsubscribeTo(this,b.toLowerCase()),this},reset:function(){if("ParsleyForm"!==this.__class__)return a.emit("parsley:field:reset",this);for(var b=0;b0||!g(c)&&!f(c))&&(d[h]=c);else if(this.options.strict)try{(new e).HaveProperty(h).validate(a)}catch(i){d[h]=i}return f(d)?!0:d},add:function(a,b){if(b instanceof e||g(b)&&b[0]instanceof e)return this.nodes[a]=b,this;if("object"==typeof b&&!g(b))return this.nodes[a]=b instanceof c?b:new c(b),this;throw new Error("Should give an Assert, an Asserts array, a Constraint",b)},has:function(a,b){var b="undefined"!=typeof b?b:this.nodes;return"undefined"!=typeof b[a]},get:function(a,b){return this.has(a)?this.nodes[a]:b||null},remove:function(a){var b=[];for(var c in this.nodes)c!==a&&(b[c]=this.nodes[c]);return this.nodes=b,this},_bootstrap:function(a){if(a instanceof c)return this.nodes=a.nodes;for(var b in a)this.add(b,a[b])},_check:function(a,b,d){if(this.nodes[a]instanceof e)return this._checkAsserts(b,[this.nodes[a]],d);if(g(this.nodes[a]))return this._checkAsserts(b,this.nodes[a],d);if(this.nodes[a]instanceof c)return this.nodes[a].check(b,d);throw new Error("Invalid node",this.nodes[a])},_checkAsserts:function(a,b,c){for(var d,e=[],f=0;f0},addGroup:function(a){return g(a)?this.addGroups(a):(this.hasGroup(a)||this.groups.push(a),this)},removeGroup:function(a){for(var b=[],c=0;c=a)throw new d(this,a,{threshold:this.threshold});return!0},this},GreaterThanOrEqual:function(a){if(this.__class__="GreaterThanOrEqual","undefined"==typeof a)throw new Error("Should give a threshold value");return this.threshold=a,this.validate=function(a){if(""===a||isNaN(Number(a)))throw new d(this,a,{value:b.errorCode.must_be_a_number});if(this.threshold>a)throw new d(this,a,{threshold:this.threshold});return!0},this},InstanceOf:function(a){if(this.__class__="InstanceOf","undefined"==typeof a)throw new Error("InstanceOf must be instanciated with a value");return this.classRef=a,this.validate=function(a){if(!0!=a instanceof this.classRef)throw new d(this,a,{classRef:this.classRef});return!0},this},IPv4:function(){return this.__class__="IPv4",this.validate=function(a){var c=/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;if("string"!=typeof a)throw new d(this,a,{value:b.errorCode.must_be_a_string});if(!c.test(a))throw new d(this,a);return!0},this},Length:function(a){if(this.__class__="Length",!a.min&&!a.max)throw new Error("Lenth assert must be instanciated with a { min: x, max: y } object");return this.min=a.min,this.max=a.max,this.validate=function(a){if("string"!=typeof a&&!g(a))throw new d(this,a,{value:b.errorCode.must_be_a_string_or_array});if("undefined"!=typeof this.min&&this.min===this.max&&a.length!==this.min)throw new d(this,a,{min:this.min,max:this.max});if("undefined"!=typeof this.max&&a.length>this.max)throw new d(this,a,{max:this.max});if("undefined"!=typeof this.min&&a.length>>0;if(0===c)return-1;var d=0;if(arguments.length>1&&(d=Number(arguments[1]),d!=d?d=0:0!=d&&1/0!=d&&d!=-1/0&&(d=(d>0||-1)*Math.floor(Math.abs(d)))),d>=c)return-1;for(var e=d>=0?d:Math.max(c-Math.abs(d),0);c>e;e++)if(e in b&&b[e]===a)return e;return-1});var f=function(a){for(var b in a)return!1;return!0},g=function(a){return"[object Array]"===Object.prototype.toString.call(a)},h={eql:function(a,b){if(a===b)return!0;if("undefined"!=typeof Buffer&&Buffer.isBuffer(a)&&Buffer.isBuffer(b)){if(a.length!==b.length)return!1;for(var c=0;c=0;d--)if(e[d]!=f[d])return!1;for(d=e.length-1;d>=0;d--)if(c=e[d],!this.eql(a[c],b[c]))return!1;return!0}};"function"==typeof define&&define.amd&&define("validator",[],function(){return a})}("undefined"==typeof exports?this["undefined"!=typeof validatorjs_ns?validatorjs_ns:"Validator"]={}:exports);var e=function(a,b){this.__class__="ParsleyValidator",this.Validator=Validator,this.locale="en",this.init(a||{},b||{})};e.prototype={init:function(b,c){this.catalog=c;for(var d in b)this.addValidator(d,b[d].fn,b[d].priority);a.emit("parsley:validator:init")},setLocale:function(a){if("undefined"==typeof this.catalog[a])throw new Error(a+" is not available in the catalog");return this.locale=a,this},addCatalog:function(a,b,c){return"object"==typeof b&&(this.catalog[a]=b),!0===c?this.setLocale(a):this},addMessage:function(a,b,c){void 0===typeof this.catalog[a]&&(this.catalog[a]={}),this.catalog[a][b]=c},validate:function(){return(new this.Validator.Validator).validate.apply(new Validator.Validator,arguments)},addValidator:function(b,c,d){return this.validators[b]=function(b){return a.extend((new Validator.Assert).Callback(c,b),{priority:d})},this},updateValidator:function(a,b,c){return addValidator(a,b,c)},removeValidator:function(a){return delete this.validators[a],this},getErrorMessage:function(a){var b;return b="type"===a.name?window.ParsleyConfig.i18n[this.locale][a.name][a.requirements]:this.formatMesssage(window.ParsleyConfig.i18n[this.locale][a.name],a.requirements),""!==b?b:window.ParsleyConfig.i18n[this.locale].defaultMessage},formatMesssage:function(a,b){if("object"==typeof b){for(var c in b)a=this.formatMesssage(a,b[c]);return a}return"string"==typeof a?a.replace(new RegExp("%s","i"),b):""},validators:{notblank:function(){return a.extend((new Validator.Assert).NotBlank(),{priority:2})},required:function(){return a.extend((new Validator.Assert).Required(),{priority:512})},type:function(b){var c;switch(b){case"email":c=(new Validator.Assert).Email();break;case"number":c=(new Validator.Assert).Regexp("^-?(?:\\d+|\\d{1,3}(?:,\\d{3})+)?(?:\\.\\d+)?$");break;case"integer":c=(new Validator.Assert).Regexp("^-?\\d+$");break;case"digits":c=(new Validator.Assert).Regexp("^\\d+$");break;case"alphanum":c=(new Validator.Assert).Regexp("^\\w+$","i");break;case"url":c=(new Validator.Assert).Regexp("(https?:\\/\\/)?(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,4}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)","i");break;default:throw new Error("validator type `"+b+"` is not supported")}return a.extend(c,{priority:256})},pattern:function(b){return a.extend((new Validator.Assert).Regexp(b),{priority:64})},minlength:function(b){return a.extend((new Validator.Assert).Length({min:b}),{priority:30})},maxlength:function(b){return a.extend((new Validator.Assert).Length({max:b}),{priority:30})},length:function(b){return a.extend((new Validator.Assert).Length({min:b[0],max:b[1]}),{priority:32})},mincheck:function(a){return this.minlength(a)},maxcheck:function(a){return this.maxlength(a)},check:function(a){return this.length(a)},min:function(b){return a.extend((new Validator.Assert).GreaterThanOrEqual(b),{priority:30,requirementsTransformer:function(){return"string"!=typeof b||isNaN(b)?b:parseInt(b,10)}})},max:function(b){return a.extend((new Validator.Assert).LessThanOrEqual(b),{priority:30,requirementsTransformer:function(){return"string"!=typeof b||isNaN(b)?b:parseInt(b,10)}})},range:function(b){return a.extend((new Validator.Assert).Range(b[0],b[1]),{priority:32,requirementsTransformer:function(){for(var a=0;a0?this._errorClass(a):this._resetClass(a)},manageErrorsMessages:function(b,c){if("undefined"==typeof b.options.errorsMessagesDisabled){if("undefined"!=typeof b.options.errorMessage)return void(c.added.length||c.kept.length?(0===b._ui.$errorsWrapper.find(".parsley-custom-error-message").length&&b._ui.$errorsWrapper.append(a(b.options.errorTemplate).addClass("parsley-custom-error-message")),b._ui.$errorsWrapper.addClass("filled").find(".parsley-custom-error-message").html(b.options.errorMessage)):b._ui.$errorsWrapper.removeClass("filled").find(".parsley-custom-error-message").remove());for(var d=0;d0&&"undefined"==typeof a.fields[b].options.noFocus){if("first"===a.options.focus)return a._focusedField=a.fields[b].$element,a._focusedField.focus();a._focusedField=a.fields[b].$element}return null===a._focusedField?null:a._focusedField.focus()},_getErrorMessage:function(a,b){var c=b.name+"Message";return"undefined"!=typeof a.options[c]?a.options[c]:window.ParsleyValidator.getErrorMessage(b)},_diff:function(a,b,c){for(var d=[],e=[],f=0;f0&&this.validationResult&&(this.validationResult=!1));return a.emit("parsley:form:validated",this),this.validationResult},isValid:function(a,b){this._refreshFields();for(var c=0;c1){var c=[];return this.each(function(){c.push(a(this).parsley(b))}),c}return a(this).length?new n(this,b):void(window.console&&window.console.warn&&window.console.warn("You must bind Parsley on an existing element."))},window.ParsleyUI="function"==typeof b.get(window.ParsleyConfig,"ParsleyUI")?(new window.ParsleyConfig.ParsleyUI).listen():(new f).listen(),"undefined"==typeof window.ParsleyExtend&&(window.ParsleyExtend={}),"undefined"==typeof window.ParsleyConfig&&(window.ParsleyConfig={}),window.Parsley=window.psly=n,window.ParsleyUtils=b,window.ParsleyValidator=new e(window.ParsleyConfig.validators,window.ParsleyConfig.i18n),!1!==b.get(window,"ParsleyConfig.autoBind")&&a(document).ready(function(){a("[data-parsley-validate]").parsley()})}(window.jQuery); +!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a(jQuery)}(function(a){"undefined"==typeof a&&"undefined"!=typeof window.jQuery&&(a=window.jQuery);var b={attr:function(a,b,c){var d,e={},f=this.msieversion(),g=new RegExp("^"+b,"i");if("undefined"==typeof a||"undefined"==typeof a[0])return{};for(var h in a[0].attributes)if(d=a[0].attributes[h],"undefined"!=typeof d&&null!==d&&(!f||f>=8||d.specified)&&g.test(d.name)){if("undefined"!=typeof c&&new RegExp(c+"$","i").test(d.name))return!0;e[this.camelize(d.name.replace(b,""))]=this.deserializeValue(d.value)}return"undefined"==typeof c?e:!1},setAttr:function(a,b,c,d){a[0].setAttribute(this.dasherize(b+c),String(d))},get:function(a,b){for(var c=0,d=(b||"").split(".");this.isObject(a)||this.isArray(a);)if(a=a[d[c++]],c===d.length)return a;return void 0},hash:function(a){return String(Math.random()).substring(2,a?a+2:9)},isArray:function(a){return"[object Array]"===Object.prototype.toString.call(a)},isObject:function(a){return a===Object(a)},deserializeValue:function(b){var c;try{return b?"true"==b||("false"==b?!1:"null"==b?null:isNaN(c=Number(b))?/^[\[\{]/.test(b)?a.parseJSON(b):b:c):b}catch(d){return b}},camelize:function(a){return a.replace(/-+(.)?/g,function(a,b){return b?b.toUpperCase():""})},dasherize:function(a){return a.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()},msieversion:function(){var a=window.navigator.userAgent,b=a.indexOf("MSIE ");return b>0||navigator.userAgent.match(/Trident.*rv\:11\./)?parseInt(a.substring(b+5,a.indexOf(".",b)),10):0}},c={namespace:"data-parsley-",inputs:"input, textarea, select",excluded:"input[type=button], input[type=submit], input[type=reset], input[type=hidden]",priorityEnabled:!0,uiEnabled:!0,validationThreshold:3,focus:"first",trigger:!1,errorClass:"parsley-error",successClass:"parsley-success",classHandler:function(){},errorsContainer:function(){},errorsWrapper:'
        ',errorTemplate:"
      • "},d=function(){};d.prototype={asyncSupport:!1,actualizeOptions:function(){return this.options=this.OptionsFactory.get(this),this},validateThroughValidator:function(a,b,c){return window.ParsleyValidator.validate(a,b,c)},subscribe:function(b,c){return a.listenTo(this,b.toLowerCase(),c),this},unsubscribe:function(b){return a.unsubscribeTo(this,b.toLowerCase()),this},reset:function(){if("ParsleyForm"!==this.__class__)return a.emit("parsley:field:reset",this);for(var b=0;b=0;l--)"Required"!==k[l].__class__||(i=k[l].requiresValidation(b));if(this.has(h,a)||this.options.strict||i)try{this.has(h,this.options.strict||i?a:void 0)||(new e).HaveProperty(h).validate(a),c=this._check(h,a[h],b),(g(c)&&c.length>0||!g(c)&&!f(c))&&(d[h]=c)}catch(m){d[h]=m}}return f(d)?!0:d},add:function(a,b){if(b instanceof e||g(b)&&b[0]instanceof e)return this.nodes[a]=b,this;if("object"==typeof b&&!g(b))return this.nodes[a]=b instanceof c?b:new c(b),this;throw new Error("Should give an Assert, an Asserts array, a Constraint",b)},has:function(a,b){return b="undefined"!=typeof b?b:this.nodes,"undefined"!=typeof b[a]},get:function(a,b){return this.has(a)?this.nodes[a]:b||null},remove:function(a){var b=[];for(var c in this.nodes)c!==a&&(b[c]=this.nodes[c]);return this.nodes=b,this},_bootstrap:function(a){if(a instanceof c)return this.nodes=a.nodes;for(var b in a)this.add(b,a[b])},_check:function(a,b,d){if(this.nodes[a]instanceof e)return this._checkAsserts(b,[this.nodes[a]],d);if(g(this.nodes[a]))return this._checkAsserts(b,this.nodes[a],d);if(this.nodes[a]instanceof c)return this.nodes[a].check(b,d);throw new Error("Invalid node",this.nodes[a])},_checkAsserts:function(a,b,c){for(var d,e=[],f=0;f0},addGroup:function(a){return g(a)?this.addGroups(a):(this.hasGroup(a)||this.groups.push(a),this)},removeGroup:function(a){for(var b=[],c=0;c=a)throw new d(this,a,{threshold:this.threshold});return!0},this},GreaterThanOrEqual:function(a){if(this.__class__="GreaterThanOrEqual","undefined"==typeof a)throw new Error("Should give a threshold value");return this.threshold=a,this.validate=function(a){if(""===a||isNaN(Number(a)))throw new d(this,a,{value:b.errorCode.must_be_a_number});if(this.threshold>a)throw new d(this,a,{threshold:this.threshold});return!0},this},InstanceOf:function(a){if(this.__class__="InstanceOf","undefined"==typeof a)throw new Error("InstanceOf must be instanciated with a value");return this.classRef=a,this.validate=function(a){if(!0!=a instanceof this.classRef)throw new d(this,a,{classRef:this.classRef});return!0},this},Length:function(a){if(this.__class__="Length",!a.min&&!a.max)throw new Error("Lenth assert must be instanciated with a { min: x, max: y } object");return this.min=a.min,this.max=a.max,this.validate=function(a){if("string"!=typeof a&&!g(a))throw new d(this,a,{value:b.errorCode.must_be_a_string_or_array});if("undefined"!=typeof this.min&&this.min===this.max&&a.length!==this.min)throw new d(this,a,{min:this.min,max:this.max});if("undefined"!=typeof this.max&&a.length>this.max)throw new d(this,a,{max:this.max});if("undefined"!=typeof this.min&&a.length>>0;if(0===c)return-1;var d=0;if(arguments.length>1&&(d=Number(arguments[1]),d!=d?d=0:0!==d&&1/0!=d&&d!=-1/0&&(d=(d>0||-1)*Math.floor(Math.abs(d)))),d>=c)return-1;for(var e=d>=0?d:Math.max(c-Math.abs(d),0);c>e;e++)if(e in b&&b[e]===a)return e;return-1});var f=function(a){for(var b in a)return!1;return!0},g=function(a){return"[object Array]"===Object.prototype.toString.call(a)};return"function"==typeof define&&define.amd?define("vendors/validator.js/dist/validator",[],function(){return a}):"undefined"!=typeof module&&module.exports?module.exports=a:window["undefined"!=typeof validatorjs_ns?validatorjs_ns:"Validator"]=a,a}();e="undefined"!=typeof e?e:"undefined"!=typeof module?module.exports:null;var f=function(a,b){this.__class__="ParsleyValidator",this.Validator=e,this.locale="en",this.init(a||{},b||{})};f.prototype={init:function(b,c){this.catalog=c;for(var d in b)this.addValidator(d,b[d].fn,b[d].priority,b[d].requirementsTransformer);a.emit("parsley:validator:init")},setLocale:function(a){if("undefined"==typeof this.catalog[a])throw new Error(a+" is not available in the catalog");return this.locale=a,this},addCatalog:function(a,b,c){return"object"==typeof b&&(this.catalog[a]=b),!0===c?this.setLocale(a):this},addMessage:function(a,b,c){return"undefined"==typeof this.catalog[a]&&(this.catalog[a]={}),this.catalog[a][b.toLowerCase()]=c,this},validate:function(){return(new this.Validator.Validator).validate.apply(new e.Validator,arguments)},addValidator:function(b,c,d,f){return this.validators[b.toLowerCase()]=function(b){return a.extend((new e.Assert).Callback(c,b),{priority:d,requirementsTransformer:f})},this},updateValidator:function(a,b,c,d){return this.addValidator(a,b,c,d)},removeValidator:function(a){return delete this.validators[a],this},getErrorMessage:function(a){var b;return b="type"===a.name?this.catalog[this.locale][a.name][a.requirements]:this.formatMessage(this.catalog[this.locale][a.name],a.requirements),""!==b?b:this.catalog[this.locale].defaultMessage},formatMessage:function(a,b){if("object"==typeof b){for(var c in b)a=this.formatMessage(a,b[c]);return a}return"string"==typeof a?a.replace(new RegExp("%s","i"),b):""},validators:{notblank:function(){return a.extend((new e.Assert).NotBlank(),{priority:2})},required:function(){return a.extend((new e.Assert).Required(),{priority:512})},type:function(b){var c;switch(b){case"email":c=(new e.Assert).Email();break;case"range":case"number":c=(new e.Assert).Regexp("^-?(?:\\d+|\\d{1,3}(?:,\\d{3})+)?(?:\\.\\d+)?$");break;case"integer":c=(new e.Assert).Regexp("^-?\\d+$");break;case"digits":c=(new e.Assert).Regexp("^\\d+$");break;case"alphanum":c=(new e.Assert).Regexp("^\\w+$","i");break;case"url":c=(new e.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 `"+b+"` is not supported")}return a.extend(c,{priority:256})},pattern:function(b){var c="";return/^\/.*\/(?:[gimy]*)$/.test(b)&&(c=b.replace(/.*\/([gimy]*)$/,"$1"),b=b.replace(new RegExp("^/(.*?)/"+c+"$"),"$1")),a.extend((new e.Assert).Regexp(b,c),{priority:64})},minlength:function(b){return a.extend((new e.Assert).Length({min:b}),{priority:30,requirementsTransformer:function(){return"string"!=typeof b||isNaN(b)?b:parseInt(b,10)}})},maxlength:function(b){return a.extend((new e.Assert).Length({max:b}),{priority:30,requirementsTransformer:function(){return"string"!=typeof b||isNaN(b)?b:parseInt(b,10)}})},length:function(b){return a.extend((new e.Assert).Length({min:b[0],max:b[1]}),{priority:32})},mincheck:function(a){return this.minlength(a)},maxcheck:function(a){return this.maxlength(a)},check:function(a){return this.length(a)},min:function(b){return a.extend((new e.Assert).GreaterThanOrEqual(b),{priority:30,requirementsTransformer:function(){return"string"!=typeof b||isNaN(b)?b:parseInt(b,10)}})},max:function(b){return a.extend((new e.Assert).LessThanOrEqual(b),{priority:30,requirementsTransformer:function(){return"string"!=typeof b||isNaN(b)?b:parseInt(b,10)}})},range:function(b){return a.extend((new e.Assert).Range(b[0],b[1]),{priority:32,requirementsTransformer:function(){for(var a=0;a0?this._errorClass(a):this._resetClass(a)},manageErrorsMessages:function(b,c){if("undefined"==typeof b.options.errorsMessagesDisabled){if("undefined"!=typeof b.options.errorMessage)return c.added.length||c.kept.length?(0===b._ui.$errorsWrapper.find(".parsley-custom-error-message").length&&b._ui.$errorsWrapper.append(a(b.options.errorTemplate).addClass("parsley-custom-error-message")),b._ui.$errorsWrapper.addClass("filled").find(".parsley-custom-error-message").html(b.options.errorMessage)):b._ui.$errorsWrapper.removeClass("filled").find(".parsley-custom-error-message").remove();for(var d=0;d0&&"undefined"==typeof a.fields[b].options.noFocus){if("first"===a.options.focus)return a._focusedField=a.fields[b].$element,a._focusedField.focus();a._focusedField=a.fields[b].$element}return null===a._focusedField?null:a._focusedField.focus()},_getErrorMessage:function(a,b){var c=b.name+"Message";return"undefined"!=typeof a.options[c]?window.ParsleyValidator.formatMessage(a.options[c],b.requirements):window.ParsleyValidator.getErrorMessage(b)},_diff:function(a,b,c){for(var d=[],e=[],f=0;f0&&this.validationResult&&(this.validationResult=!1));return a.emit("parsley:form:"+(this.validationResult?"success":"error"),this),a.emit("parsley:form:validated",this),this.validationResult},isValid:function(a,b){this._refreshFields();for(var c=0;c1){var c=[];return this.each(function(){c.push(a(this).parsley(b))}),c}return a(this).length?new o(this,b):void(window.console&&window.console.warn&&window.console.warn("You must bind Parsley on an existing element."))},window.ParsleyUI="function"==typeof b.get(window,"ParsleyConfig.ParsleyUI")?(new window.ParsleyConfig.ParsleyUI).listen():(new g).listen(),"undefined"==typeof window.ParsleyExtend&&(window.ParsleyExtend={}),"undefined"==typeof window.ParsleyConfig&&(window.ParsleyConfig={}),window.Parsley=window.psly=o,window.ParsleyUtils=b,window.ParsleyValidator=new f(window.ParsleyConfig.validators,window.ParsleyConfig.i18n),!1!==b.get(window,"ParsleyConfig.autoBind")&&a(function(){a("[data-parsley-validate]").length&&a("[data-parsley-validate]").parsley()})}); \ No newline at end of file From 006119eb3e3532fa1f8eb40f7650e2bbab4c09a2 Mon Sep 17 00:00:00 2001 From: Mauricio de Abreu Antunes Date: Sun, 8 Feb 2015 22:21:08 -0200 Subject: [PATCH 08/21] Fixed badge links No python package links should be linked to crate.io. --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 20c049c..1132ea0 100644 --- a/README.rst +++ b/README.rst @@ -2,11 +2,11 @@ django-parsley ============== .. image:: https://pypip.in/d/django-parsley/badge.png - :target: https://crate.io/packages/django-parsley + :target: https://pypi.python.org/pypi/django-parsley :alt: Downloads .. image:: https://pypip.in/v/django-parsley/badge.png - :target: https://crate.io/packages/django-parsley + :target: https://pypi.python.org/pypi/django-parsley :alt: Latest Release .. image:: https://travis-ci.org/agiliq/Django-parsley.png?branch=master From 863e3f40518801c63b77defa293132cff46b226a Mon Sep 17 00:00:00 2001 From: Nicole Harris Date: Tue, 10 Feb 2015 07:33:36 +1100 Subject: [PATCH 09/21] Change field_type prefix pattern to match parsleys validation pattern --- parsley/decorators.py | 2 +- parsley/tests/tests.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/parsley/decorators.py b/parsley/decorators.py index 7f49d46..b38f4ba 100644 --- a/parsley/decorators.py +++ b/parsley/decorators.py @@ -64,7 +64,7 @@ class ParsleyChoiceFieldRenderer(ParsleyChoiceFieldRendererMixin, field.widget.r error_message = field.error_messages.get('invalid', None) if error_message: - attrs["{prefix}-type-{0}-message".format(field_type, prefix=prefix)] = error_message + attrs["{prefix}-type-message".format(field_type, prefix=prefix)] = error_message def parsleyfy(klass): diff --git a/parsley/tests/tests.py b/parsley/tests/tests.py index 369c987..95bc113 100644 --- a/parsley/tests/tests.py +++ b/parsley/tests/tests.py @@ -261,7 +261,7 @@ def test_field_type_message(self): attrs = form.fields['email'].widget.attrs self.assertEqual(attrs, { "data-parsley-type": "email", - "data-parsley-type-email-message": "Invalid email" + "data-parsley-type-message": "Invalid email" }) def test_override_default_message(self): From c11296dafcea475a6a6ec85b016b7a59b56c2470 Mon Sep 17 00:00:00 2001 From: shiva Date: Fri, 24 Apr 2015 15:29:20 +0530 Subject: [PATCH 10/21] Removed 1.8 dependency for now --- .travis.yml | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 38f5840..c821800 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,8 +9,8 @@ env: - DJANGO='Django>=1.4,<1.5' - DJANGO='Django>=1.5,<1.6' - DJANGO='Django>=1.6,<1.7' - - DJANGO='Django>=1.7,<1.8' - - DJANGO='https://github.com/django/django/tarball/stable/1.8.x' +# - DJANGO='Django>=1.7,<1.8' +# - DJANGO='https://github.com/django/django/tarball/stable/1.8.x' install: - pip install --use-mirrors $DJANGO diff --git a/setup.py b/setup.py index 26e4ca3..373f8f5 100644 --- a/setup.py +++ b/setup.py @@ -124,6 +124,6 @@ def find_package_data( ], zip_safe=False, install_requires=["Django>=1.3,<=1.8"], - tests_require=["Django>=1.3,<=1.8", "six"], + tests_require=["Django>=1.3,<=1.7", "six"], test_suite='runtests.runtests', ) From 513fa9164a905458e49fc8347c29688d8ce7b16b Mon Sep 17 00:00:00 2001 From: Akshar Raaj Date: Tue, 1 Sep 2015 12:44:18 +0530 Subject: [PATCH 11/21] Removed unused imports --- parsley/tests/tests.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/parsley/tests/tests.py b/parsley/tests/tests.py index 369c987..ce58105 100644 --- a/parsley/tests/tests.py +++ b/parsley/tests/tests.py @@ -2,7 +2,6 @@ import six from django import forms -from django.contrib import admin from django.test import TestCase from django.utils.translation import ugettext_lazy as _ @@ -11,10 +10,8 @@ from .forms import (TextForm, TextForm2, FieldTypeForm, ExtraDataForm, ExtraDataMissingFieldForm, FormWithWidgets, StudentModelForm, FormWithCleanField, FormWithCustomInit, FormWithCustomChoices, - FormWithMedia, FormWithoutMedia, MultiWidgetForm, CustomErrorMessageForm, + MultiWidgetForm, CustomErrorMessageForm, CustomPrefixForm, FormWithRadioSelect, FormWithRadioSelectNotRequired) -from .models import Student -from .admin import StudentAdmin class ParsleyTestCase(TestCase): From 84d3322c6d3be84349e76e0e6a6aae5a9943d35b Mon Sep 17 00:00:00 2001 From: Akshar Raaj Date: Tue, 1 Sep 2015 20:36:37 +0530 Subject: [PATCH 12/21] Fixes https://github.com/agiliq/Django-parsley/issues/46 Regex parsley attributes were changed in parsley.js 2.x. Modified code to reflect those changes in Django-parsley. --- parsley/decorators.py | 4 ++-- parsley/tests/tests.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/parsley/decorators.py b/parsley/decorators.py index 7f49d46..7ad4b84 100644 --- a/parsley/decorators.py +++ b/parsley/decorators.py @@ -35,11 +35,11 @@ class ParsleyChoiceFieldRenderer(ParsleyChoiceFieldRendererMixin, field.widget.r attrs["{prefix}-required-message".format(prefix=prefix)] = error_message if isinstance(field, forms.RegexField): - attrs.update({"{prefix}-regexp".format(prefix=prefix): field.regex.pattern}) + attrs.update({"{prefix}-pattern".format(prefix=prefix): field.regex.pattern}) error_message = field.error_messages.get('invalid', None) if error_message: - attrs["{prefix}-regexp-message".format(prefix=prefix)] = error_message + attrs["{prefix}-pattern-message".format(prefix=prefix)] = error_message if field.regex.flags & re.IGNORECASE: attrs.update({"{prefix}-regexp-flag".format(prefix=prefix): "i"}) diff --git a/parsley/tests/tests.py b/parsley/tests/tests.py index ce58105..1ccf63f 100644 --- a/parsley/tests/tests.py +++ b/parsley/tests/tests.py @@ -78,9 +78,9 @@ def test_data_types(self): self.assertEqual(fields["age"].widget.attrs["data-parsley-type"], "digits") self.assertEqual(fields["income"].widget.attrs["data-parsley-type"], "number") self.assertEqual(fields["income2"].widget.attrs["data-parsley-type"], "number") - self.assertEqual(fields["topnav"].widget.attrs["data-parsley-regexp"], "#[A-Fa-f0-9]{6}") + self.assertEqual(fields["topnav"].widget.attrs["data-parsley-pattern"], "#[A-Fa-f0-9]{6}") self.assertNotIn("data-parsley-regexp-flag", fields["topnav"].widget.attrs) - self.assertEqual(fields["topnav2"].widget.attrs["data-parsley-regexp"], "#[a-z]+") + self.assertEqual(fields["topnav2"].widget.attrs["data-parsley-pattern"], "#[a-z]+") self.assertEqual(fields["topnav2"].widget.attrs["data-parsley-regexp-flag"], "i") @@ -226,19 +226,19 @@ def test_parsley_attributes(self): "data-parsley-minlength": 3, "data-parsley-maxlength": 3, "maxlength": "3", - "data-parsley-regexp": r'^(\d)+$', + "data-parsley-pattern": r'^(\d)+$', }) self.assertAttrsEqual(fields[1].widget.attrs, { "data-parsley-minlength": 3, "data-parsley-maxlength": 3, "maxlength": "3", - "data-parsley-regexp": r'^(\d)+$', + "data-parsley-pattern": r'^(\d)+$', }) self.assertAttrsEqual(fields[2].widget.attrs, { "data-parsley-minlength": 4, "data-parsley-maxlength": 4, "maxlength": "4", - "data-parsley-regexp": r'^(\d)+$', + "data-parsley-pattern": r'^(\d)+$', }) From 4151015c7e186da8eca605269de4fc949dad4155 Mon Sep 17 00:00:00 2001 From: Akshar Raaj Date: Wed, 2 Sep 2015 12:24:17 +0530 Subject: [PATCH 13/21] Fixes https://github.com/agiliq/Django-parsley/issues/63 required=True on RadioSelect wasn't being validated by parsley.js. data-parsley-mincheck has a different purpose from data-parsley-required. In our scenario we want data-parsley-required. data-parsley-mincheck should be used when we want to validate that a minimum number of radioboxes are checked. But data-parsley-mincheck can't enforce required=True. --- parsley/tests/tests.py | 4 ++-- parsley/widgets.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/parsley/tests/tests.py b/parsley/tests/tests.py index 95bc113..571fb1a 100644 --- a/parsley/tests/tests.py +++ b/parsley/tests/tests.py @@ -163,14 +163,14 @@ def test_radio_select(self): self.assertEqual(form.fields['state'].choices, [("NY", "NY"), ("OH", "OH")]) radio_select_html = form.fields['state'].widget.render("state", "NY") - self.assertEqual(1, len(re.findall('data-parsley-mincheck', radio_select_html))) + self.assertEqual(1, len(re.findall('data-parsley-required', radio_select_html))) def test_radio_select_not_required(self): form = FormWithRadioSelectNotRequired() self.assertEqual(form.fields['state'].choices, [("NY", "NY"), ("OH", "OH")]) radio_select_html = form.fields['state'].widget.render("state", "NY") - self.assertEqual(0, len(re.findall('data-parsley-mincheck', radio_select_html))) + self.assertEqual(0, len(re.findall('data-parsley-required', radio_select_html))) class TestCleanFields(ParsleyTestCase): diff --git a/parsley/widgets.py b/parsley/widgets.py index 0a7c376..d45a7ee 100644 --- a/parsley/widgets.py +++ b/parsley/widgets.py @@ -6,5 +6,5 @@ def __iter__(self): def __getitem__(self, idx): choice = self.choices[idx] if idx == len(self.choices) - 1: - self.attrs["{prefix}-mincheck".format(prefix=self.parsley_namespace)] = "1" + self.attrs["{prefix}-required".format(prefix=self.parsley_namespace)] = "true" return super(ParsleyChoiceFieldRendererMixin, self).__getitem__(idx) From 06e48388f2a71fe7280256742ba931a1428743c4 Mon Sep 17 00:00:00 2001 From: Akshar Raaj Date: Wed, 2 Sep 2015 13:51:13 +0530 Subject: [PATCH 14/21] pypip.in has been down for several days. So using shields.io badges. --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 1132ea0..2a54e4c 100644 --- a/README.rst +++ b/README.rst @@ -1,11 +1,11 @@ django-parsley ============== -.. image:: https://pypip.in/d/django-parsley/badge.png +.. image:: https://img.shields.io/pypi/dm/django-parsley.svg :target: https://pypi.python.org/pypi/django-parsley :alt: Downloads -.. image:: https://pypip.in/v/django-parsley/badge.png +.. image:: https://img.shields.io/pypi/v/django-parsley.svg :target: https://pypi.python.org/pypi/django-parsley :alt: Latest Release From 62e58c9c4b6bd62388976e3140c0023552990284 Mon Sep 17 00:00:00 2001 From: Akshar Raaj Date: Wed, 2 Sep 2015 17:09:48 +0530 Subject: [PATCH 15/21] There is no need to add "errors" and "listeners" in django-admin.js Probably these things while calling $(this).parsley() was required for old version of parsley.js, but isn't required with current version of parsley.js. --- parsley/static/parsley/js/parsley.django-admin.js | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/parsley/static/parsley/js/parsley.django-admin.js b/parsley/static/parsley/js/parsley.django-admin.js index 6085de4..f3154c9 100644 --- a/parsley/static/parsley/js/parsley.django-admin.js +++ b/parsley/static/parsley/js/parsley.django-admin.js @@ -8,21 +8,6 @@ $( window ).on( 'load', function () { $( 'form' ).each( function () { $( this ).parsley({ - animate: false, - errors: { - errorsWrapper: '
          ', - container: function (element, isRadioOrCheckbox) { - return $("
          ").prependTo(element.closest(".form-row")); - } - }, - listeners: { - onFieldError: function (element) { - var container = element.closest(".form-row"); - if (container.not(".errors")) { - container.addClass("errors"); - } - } - } }); } ); } ); From 8009c3effb24b387755051cff4355f79699330b8 Mon Sep 17 00:00:00 2001 From: Akshar Raaj Date: Fri, 4 Sep 2015 15:43:51 +0530 Subject: [PATCH 16/21] Added changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..52be551 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# Django-parsley changelog + +## 0.6 + +- Added checkbox and radio button validation support. +- Updated parsley.js, included with this library, to 2.0.7 +- Changed error message attributes to confirm with parsley.js 2.x. More details here. +- Changed RegexField attributes to confirm with parsley.js 2.x. More details here. \ No newline at end of file From e4173dd911286452ca91c458b00ea6a95b5f8d99 Mon Sep 17 00:00:00 2001 From: Akshar Raaj Date: Fri, 4 Sep 2015 15:54:54 +0530 Subject: [PATCH 17/21] version bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 373f8f5..e3d0a4a 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -VERSION = '0.5' +VERSION = '0.6' import os import sys From 11f55260570b20ce15a5fa4b57ab8d8ee5d75781 Mon Sep 17 00:00:00 2001 From: Ali Lozano Date: Thu, 17 Mar 2016 09:54:30 -0500 Subject: [PATCH 18/21] added assignment tag to parsley --- parsley/templatetags/parsley.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 parsley/templatetags/parsley.py diff --git a/parsley/templatetags/parsley.py b/parsley/templatetags/parsley.py new file mode 100644 index 0000000..0a467fc --- /dev/null +++ b/parsley/templatetags/parsley.py @@ -0,0 +1,9 @@ +from django import template +from .decorators import parsley_form + +register = template.Library() + + +@register.assignment_tag() +def parsleyfy(form): + return parsley_form(form) From 90d187855891b5328be97553731c25ef3a45b7bd Mon Sep 17 00:00:00 2001 From: Ali Lozano Date: Thu, 17 Mar 2016 09:56:04 -0500 Subject: [PATCH 19/21] added assignment tag to parsley --- parsley/decorators.py | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/parsley/decorators.py b/parsley/decorators.py index 51bd8a3..b074027 100644 --- a/parsley/decorators.py +++ b/parsley/decorators.py @@ -66,6 +66,23 @@ class ParsleyChoiceFieldRenderer(ParsleyChoiceFieldRendererMixin, field.widget.r if error_message: attrs["{prefix}-type-message".format(field_type, prefix=prefix)] = error_message +def parsley_form(form): + prefix = getattr(getattr(form, 'Meta', None), 'parsley_namespace', 'data-parsley') + for _, field in form.fields.items(): + update_widget_attrs(field, prefix) + extras = getattr(getattr(form, 'Meta', None), 'parsley_extras', {}) + for field_name, data in extras.items(): + for key, value in data.items(): + if field_name not in form.fields: + continue + attrs = form.fields[field_name].widget.attrs + if key == 'equalto': + # Use HTML id for {prefix}-equalto + value = '#' + form[value].id_for_label + if isinstance(value, bool): + value = "true" if value else "false" + attrs["{prefix}-%s".format(prefix=prefix) % key] = value + return form def parsleyfy(klass): "A decorator to add {prefix}-* attributes to your form.fields" @@ -73,21 +90,8 @@ def parsleyfy(klass): def new_init(self, *args, **kwargs): old_init(self, *args, **kwargs) - prefix = getattr(getattr(self, 'Meta', None), 'parsley_namespace', 'data-parsley') - for _, field in self.fields.items(): - update_widget_attrs(field, prefix) - extras = getattr(getattr(self, 'Meta', None), 'parsley_extras', {}) - for field_name, data in extras.items(): - for key, value in data.items(): - if field_name not in self.fields: - continue - attrs = self.fields[field_name].widget.attrs - if key == 'equalto': - # Use HTML id for {prefix}-equalto - value = '#' + self[value].id_for_label - if isinstance(value, bool): - value = "true" if value else "false" - attrs["{prefix}-%s".format(prefix=prefix) % key] = value + parsley_form(self) + klass.__init__ = new_init return klass From bdc103a6f4856227641abaa2180a8b71975f9f5a Mon Sep 17 00:00:00 2001 From: Ali Lozano Date: Thu, 17 Mar 2016 10:17:55 -0500 Subject: [PATCH 20/21] added assignment tag to parsley --- parsley/templatetags/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 parsley/templatetags/__init__.py diff --git a/parsley/templatetags/__init__.py b/parsley/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 From 2ffd36536105f92a997a96f5c19e6a3f9f4c029c Mon Sep 17 00:00:00 2001 From: Ali Lozano Date: Thu, 17 Mar 2016 10:21:20 -0500 Subject: [PATCH 21/21] added assignment tag to parsley --- parsley/templatetags/parsley.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parsley/templatetags/parsley.py b/parsley/templatetags/parsley.py index 0a467fc..12428c6 100644 --- a/parsley/templatetags/parsley.py +++ b/parsley/templatetags/parsley.py @@ -1,5 +1,5 @@ from django import template -from .decorators import parsley_form +from parsley.decorators import parsley_form register = template.Library()