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