diff --git a/JqueryValidationUiGrailsPlugin.groovy b/JqueryValidationUiGrailsPlugin.groovy index 7d5267f..16f3d08 100644 --- a/JqueryValidationUiGrailsPlugin.groovy +++ b/JqueryValidationUiGrailsPlugin.groovy @@ -21,7 +21,7 @@ */ class JqueryValidationUiGrailsPlugin { // the plugin version - def version = "1.4.9" + def version = "1.5.6" // the version or versions of Grails the plugin is designed for def grailsVersion = "1.2.2 > *" // the other plugins this plugin depends on diff --git a/application.properties b/application.properties index 05433dc..e864976 100644 --- a/application.properties +++ b/application.properties @@ -1,5 +1,5 @@ #Grails Metadata file -#Mon Nov 04 21:33:22 SGT 2013 -app.grails.version=2.3.1 +#Tue Jun 10 11:33:49 CDT 2014 +app.grails.version=2.5.0 app.name=jquery-validation-ui app.servlet.version=2.5 diff --git a/web-app/js/jquery-validation-ui/grails-validation-methods.js b/grails-app/assets/javascripts/grails-validation-methods.js similarity index 100% rename from web-app/js/jquery-validation-ui/grails-validation-methods.js rename to grails-app/assets/javascripts/grails-validation-methods.js diff --git a/grails-app/assets/javascripts/jquery-validation/additional-methods.js b/grails-app/assets/javascripts/jquery-validation/additional-methods.js new file mode 100644 index 0000000..b0534b5 --- /dev/null +++ b/grails-app/assets/javascripts/jquery-validation/additional-methods.js @@ -0,0 +1,617 @@ +/*! + * jQuery Validation Plugin 1.11.1 + * + * http://bassistance.de/jquery-plugins/jquery-plugin-validation/ + * http://docs.jquery.com/Plugins/Validation + * + * Copyright 2013 Jörn Zaefferer + * Released under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + */ + +(function() { + + function stripHtml(value) { + // remove html tags and space chars + return value.replace(/<.[^<>]*?>/g, ' ').replace(/ | /gi, ' ') + // remove punctuation + .replace(/[.(),;:!?%#$'"_+=\/\-]*/g,''); + } + jQuery.validator.addMethod("maxWords", function(value, element, params) { + return this.optional(element) || stripHtml(value).match(/\b\w+\b/g).length <= params; + }, jQuery.validator.format("Please enter {0} words or less.")); + + jQuery.validator.addMethod("minWords", function(value, element, params) { + return this.optional(element) || stripHtml(value).match(/\b\w+\b/g).length >= params; + }, jQuery.validator.format("Please enter at least {0} words.")); + + jQuery.validator.addMethod("rangeWords", function(value, element, params) { + var valueStripped = stripHtml(value); + var regex = /\b\w+\b/g; + return this.optional(element) || valueStripped.match(regex).length >= params[0] && valueStripped.match(regex).length <= params[1]; + }, jQuery.validator.format("Please enter between {0} and {1} words.")); + +}()); + +jQuery.validator.addMethod("letterswithbasicpunc", function(value, element) { + return this.optional(element) || /^[a-z\-.,()'"\s]+$/i.test(value); +}, "Letters or punctuation only please"); + +jQuery.validator.addMethod("alphanumeric", function(value, element) { + return this.optional(element) || /^\w+$/i.test(value); +}, "Letters, numbers, and underscores only please"); + +jQuery.validator.addMethod("lettersonly", function(value, element) { + return this.optional(element) || /^[a-z]+$/i.test(value); +}, "Letters only please"); + +jQuery.validator.addMethod("nowhitespace", function(value, element) { + return this.optional(element) || /^\S+$/i.test(value); +}, "No white space please"); + +jQuery.validator.addMethod("ziprange", function(value, element) { + return this.optional(element) || /^90[2-5]\d\{2\}-\d{4}$/.test(value); +}, "Your ZIP-code must be in the range 902xx-xxxx to 905-xx-xxxx"); + +jQuery.validator.addMethod("zipcodeUS", function(value, element) { + return this.optional(element) || /\d{5}-\d{4}$|^\d{5}$/.test(value); +}, "The specified US ZIP Code is invalid"); + +jQuery.validator.addMethod("integer", function(value, element) { + return this.optional(element) || /^-?\d+$/.test(value); +}, "A positive or negative non-decimal number please"); + +/** + * Return true, if the value is a valid vehicle identification number (VIN). + * + * Works with all kind of text inputs. + * + * @example + * @desc Declares a required input element whose value must be a valid vehicle identification number. + * + * @name jQuery.validator.methods.vinUS + * @type Boolean + * @cat Plugins/Validate/Methods + */ +jQuery.validator.addMethod("vinUS", function(v) { + if (v.length !== 17) { + return false; + } + var i, n, d, f, cd, cdv; + var LL = ["A","B","C","D","E","F","G","H","J","K","L","M","N","P","R","S","T","U","V","W","X","Y","Z"]; + var VL = [1,2,3,4,5,6,7,8,1,2,3,4,5,7,9,2,3,4,5,6,7,8,9]; + var FL = [8,7,6,5,4,3,2,10,0,9,8,7,6,5,4,3,2]; + var rs = 0; + for(i = 0; i < 17; i++){ + f = FL[i]; + d = v.slice(i,i+1); + if (i === 8) { + cdv = d; + } + if (!isNaN(d)) { + d *= f; + } else { + for (n = 0; n < LL.length; n++) { + if (d.toUpperCase() === LL[n]) { + d = VL[n]; + d *= f; + if (isNaN(cdv) && n === 8) { + cdv = LL[n]; + } + break; + } + } + } + rs += d; + } + cd = rs % 11; + if (cd === 10) { + cd = "X"; + } + if (cd === cdv) { + return true; + } + return false; +}, "The specified vehicle identification number (VIN) is invalid."); + +/** + * Return true, if the value is a valid date, also making this formal check dd/mm/yyyy. + * + * @example jQuery.validator.methods.date("01/01/1900") + * @result true + * + * @example jQuery.validator.methods.date("01/13/1990") + * @result false + * + * @example jQuery.validator.methods.date("01.01.1900") + * @result false + * + * @example + * @desc Declares an optional input element whose value must be a valid date. + * + * @name jQuery.validator.methods.dateITA + * @type Boolean + * @cat Plugins/Validate/Methods + */ +jQuery.validator.addMethod("dateITA", function(value, element) { + var check = false; + var re = /^\d{1,2}\/\d{1,2}\/\d{4}$/; + if( re.test(value)) { + var adata = value.split('/'); + var gg = parseInt(adata[0],10); + var mm = parseInt(adata[1],10); + var aaaa = parseInt(adata[2],10); + var xdata = new Date(aaaa,mm-1,gg); + if ( ( xdata.getFullYear() === aaaa ) && ( xdata.getMonth() === mm - 1 ) && ( xdata.getDate() === gg ) ){ + check = true; + } else { + check = false; + } + } else { + check = false; + } + return this.optional(element) || check; +}, "Please enter a correct date"); + +/** + * IBAN is the international bank account number. + * It has a country - specific format, that is checked here too + */ +jQuery.validator.addMethod("iban", function(value, element) { + // some quick simple tests to prevent needless work + if (this.optional(element)) { + return true; + } + if (!(/^([a-zA-Z0-9]{4} ){2,8}[a-zA-Z0-9]{1,4}|[a-zA-Z0-9]{12,34}$/.test(value))) { + return false; + } + + // check the country code and find the country specific format + var iban = value.replace(/ /g,'').toUpperCase(); // remove spaces and to upper case + var countrycode = iban.substring(0,2); + var bbancountrypatterns = { + 'AL': "\\d{8}[\\dA-Z]{16}", + 'AD': "\\d{8}[\\dA-Z]{12}", + 'AT': "\\d{16}", + 'AZ': "[\\dA-Z]{4}\\d{20}", + 'BE': "\\d{12}", + 'BH': "[A-Z]{4}[\\dA-Z]{14}", + 'BA': "\\d{16}", + 'BR': "\\d{23}[A-Z][\\dA-Z]", + 'BG': "[A-Z]{4}\\d{6}[\\dA-Z]{8}", + 'CR': "\\d{17}", + 'HR': "\\d{17}", + 'CY': "\\d{8}[\\dA-Z]{16}", + 'CZ': "\\d{20}", + 'DK': "\\d{14}", + 'DO': "[A-Z]{4}\\d{20}", + 'EE': "\\d{16}", + 'FO': "\\d{14}", + 'FI': "\\d{14}", + 'FR': "\\d{10}[\\dA-Z]{11}\\d{2}", + 'GE': "[\\dA-Z]{2}\\d{16}", + 'DE': "\\d{18}", + 'GI': "[A-Z]{4}[\\dA-Z]{15}", + 'GR': "\\d{7}[\\dA-Z]{16}", + 'GL': "\\d{14}", + 'GT': "[\\dA-Z]{4}[\\dA-Z]{20}", + 'HU': "\\d{24}", + 'IS': "\\d{22}", + 'IE': "[\\dA-Z]{4}\\d{14}", + 'IL': "\\d{19}", + 'IT': "[A-Z]\\d{10}[\\dA-Z]{12}", + 'KZ': "\\d{3}[\\dA-Z]{13}", + 'KW': "[A-Z]{4}[\\dA-Z]{22}", + 'LV': "[A-Z]{4}[\\dA-Z]{13}", + 'LB': "\\d{4}[\\dA-Z]{20}", + 'LI': "\\d{5}[\\dA-Z]{12}", + 'LT': "\\d{16}", + 'LU': "\\d{3}[\\dA-Z]{13}", + 'MK': "\\d{3}[\\dA-Z]{10}\\d{2}", + 'MT': "[A-Z]{4}\\d{5}[\\dA-Z]{18}", + 'MR': "\\d{23}", + 'MU': "[A-Z]{4}\\d{19}[A-Z]{3}", + 'MC': "\\d{10}[\\dA-Z]{11}\\d{2}", + 'MD': "[\\dA-Z]{2}\\d{18}", + 'ME': "\\d{18}", + 'NL': "[A-Z]{4}\\d{10}", + 'NO': "\\d{11}", + 'PK': "[\\dA-Z]{4}\\d{16}", + 'PS': "[\\dA-Z]{4}\\d{21}", + 'PL': "\\d{24}", + 'PT': "\\d{21}", + 'RO': "[A-Z]{4}[\\dA-Z]{16}", + 'SM': "[A-Z]\\d{10}[\\dA-Z]{12}", + 'SA': "\\d{2}[\\dA-Z]{18}", + 'RS': "\\d{18}", + 'SK': "\\d{20}", + 'SI': "\\d{15}", + 'ES': "\\d{20}", + 'SE': "\\d{20}", + 'CH': "\\d{5}[\\dA-Z]{12}", + 'TN': "\\d{20}", + 'TR': "\\d{5}[\\dA-Z]{17}", + 'AE': "\\d{3}\\d{16}", + 'GB': "[A-Z]{4}\\d{14}", + 'VG': "[\\dA-Z]{4}\\d{16}" + }; + var bbanpattern = bbancountrypatterns[countrycode]; + // As new countries will start using IBAN in the + // future, we only check if the countrycode is known. + // This prevents false negatives, while almost all + // false positives introduced by this, will be caught + // by the checksum validation below anyway. + // Strict checking should return FALSE for unknown + // countries. + if (typeof bbanpattern !== 'undefined') { + var ibanregexp = new RegExp("^[A-Z]{2}\\d{2}" + bbanpattern + "$", ""); + if (!(ibanregexp.test(iban))) { + return false; // invalid country specific format + } + } + + // now check the checksum, first convert to digits + var ibancheck = iban.substring(4,iban.length) + iban.substring(0,4); + var ibancheckdigits = ""; + var leadingZeroes = true; + var charAt; + for (var i =0; i 9 && + phone_number.match(/^(\+?1-?)?(\([2-9]\d{2}\)|[2-9]\d{2})-?[2-9]\d{2}-?\d{4}$/); +}, "Please specify a valid phone number"); + +jQuery.validator.addMethod('phoneUK', function(phone_number, element) { + phone_number = phone_number.replace(/\(|\)|\s+|-/g,''); + return this.optional(element) || phone_number.length > 9 && + phone_number.match(/^(?:(?:(?:00\s?|\+)44\s?)|(?:\(?0))(?:\d{2}\)?\s?\d{4}\s?\d{4}|\d{3}\)?\s?\d{3}\s?\d{3,4}|\d{4}\)?\s?(?:\d{5}|\d{3}\s?\d{3})|\d{5}\)?\s?\d{4,5})$/); +}, 'Please specify a valid phone number'); + +jQuery.validator.addMethod('mobileUK', function(phone_number, element) { + phone_number = phone_number.replace(/\(|\)|\s+|-/g,''); + return this.optional(element) || phone_number.length > 9 && + phone_number.match(/^(?:(?:(?:00\s?|\+)44\s?|0)7(?:[45789]\d{2}|624)\s?\d{3}\s?\d{3})$/); +}, 'Please specify a valid mobile number'); + +//Matches UK landline + mobile, accepting only 01-3 for landline or 07 for mobile to exclude many premium numbers +jQuery.validator.addMethod('phonesUK', function(phone_number, element) { + phone_number = phone_number.replace(/\(|\)|\s+|-/g,''); + return this.optional(element) || phone_number.length > 9 && + phone_number.match(/^(?:(?:(?:00\s?|\+)44\s?|0)(?:1\d{8,9}|[23]\d{9}|7(?:[45789]\d{8}|624\d{6})))$/); +}, 'Please specify a valid uk phone number'); +// On the above three UK functions, do the following server side processing: +// Compare original input with this RegEx pattern: +// ^\(?(?:(?:00\)?[\s\-]?\(?|\+)(44)\)?[\s\-]?\(?(?:0\)?[\s\-]?\(?)?|0)([1-9]\d{1,4}\)?[\s\d\-]+)$ +// Extract $1 and set $prefix to '+44' if $1 is '44', otherwise set $prefix to '0' +// Extract $2 and remove hyphens, spaces and parentheses. Phone number is combined $prefix and $2. +// A number of very detailed GB telephone number RegEx patterns can also be found at: +// http://www.aa-asterisk.org.uk/index.php/Regular_Expressions_for_Validating_and_Formatting_GB_Telephone_Numbers + +// Matches UK postcode. Does not match to UK Channel Islands that have their own postcodes (non standard UK) +jQuery.validator.addMethod('postcodeUK', function(value, element) { + return this.optional(element) || /^((([A-PR-UWYZ][0-9])|([A-PR-UWYZ][0-9][0-9])|([A-PR-UWYZ][A-HK-Y][0-9])|([A-PR-UWYZ][A-HK-Y][0-9][0-9])|([A-PR-UWYZ][0-9][A-HJKSTUW])|([A-PR-UWYZ][A-HK-Y][0-9][ABEHMNPRVWXY]))\s?([0-9][ABD-HJLNP-UW-Z]{2})|(GIR)\s?(0AA))$/i.test(value); +}, 'Please specify a valid UK postcode'); + +// TODO check if value starts with <, otherwise don't try stripping anything +jQuery.validator.addMethod("strippedminlength", function(value, element, param) { + return jQuery(value).text().length >= param; +}, jQuery.validator.format("Please enter at least {0} characters")); + +// same as email, but TLD is optional +jQuery.validator.addMethod("email2", function(value, element, param) { + return this.optional(element) || /^((([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.test(value); +}, jQuery.validator.messages.email); + +// same as url, but TLD is optional +jQuery.validator.addMethod("url2", function(value, element, param) { + return this.optional(element) || /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)*(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value); +}, jQuery.validator.messages.url); + +// NOTICE: Modified version of Castle.Components.Validator.CreditCardValidator +// Redistributed under the the Apache License 2.0 at http://www.apache.org/licenses/LICENSE-2.0 +// Valid Types: mastercard, visa, amex, dinersclub, enroute, discover, jcb, unknown, all (overrides all other settings) +jQuery.validator.addMethod("creditcardtypes", function(value, element, param) { + if (/[^0-9\-]+/.test(value)) { + return false; + } + + value = value.replace(/\D/g, ""); + + var validTypes = 0x0000; + + if (param.mastercard) { + validTypes |= 0x0001; + } + if (param.visa) { + validTypes |= 0x0002; + } + if (param.amex) { + validTypes |= 0x0004; + } + if (param.dinersclub) { + validTypes |= 0x0008; + } + if (param.enroute) { + validTypes |= 0x0010; + } + if (param.discover) { + validTypes |= 0x0020; + } + if (param.jcb) { + validTypes |= 0x0040; + } + if (param.unknown) { + validTypes |= 0x0080; + } + if (param.all) { + validTypes = 0x0001 | 0x0002 | 0x0004 | 0x0008 | 0x0010 | 0x0020 | 0x0040 | 0x0080; + } + if (validTypes & 0x0001 && /^(5[12345])/.test(value)) { //mastercard + return value.length === 16; + } + if (validTypes & 0x0002 && /^(4)/.test(value)) { //visa + return value.length === 16; + } + if (validTypes & 0x0004 && /^(3[47])/.test(value)) { //amex + return value.length === 15; + } + if (validTypes & 0x0008 && /^(3(0[012345]|[68]))/.test(value)) { //dinersclub + return value.length === 14; + } + if (validTypes & 0x0010 && /^(2(014|149))/.test(value)) { //enroute + return value.length === 15; + } + if (validTypes & 0x0020 && /^(6011)/.test(value)) { //discover + return value.length === 16; + } + if (validTypes & 0x0040 && /^(3)/.test(value)) { //jcb + return value.length === 16; + } + if (validTypes & 0x0040 && /^(2131|1800)/.test(value)) { //jcb + return value.length === 15; + } + if (validTypes & 0x0080) { //unknown + return true; + } + return false; +}, "Please enter a valid credit card number."); + +jQuery.validator.addMethod("ipv4", function(value, element, param) { + return this.optional(element) || /^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/i.test(value); +}, "Please enter a valid IP v4 address."); + +jQuery.validator.addMethod("ipv6", function(value, element, param) { + return this.optional(element) || /^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/i.test(value); +}, "Please enter a valid IP v6 address."); + +/** +* Return true if the field value matches the given format RegExp +* +* @example jQuery.validator.methods.pattern("AR1004",element,/^AR\d{4}$/) +* @result true +* +* @example jQuery.validator.methods.pattern("BR1004",element,/^AR\d{4}$/) +* @result false +* +* @name jQuery.validator.methods.pattern +* @type Boolean +* @cat Plugins/Validate/Methods +*/ +jQuery.validator.addMethod("pattern", function(value, element, param) { + if (this.optional(element)) { + return true; + } + if (typeof param === 'string') { + param = new RegExp('^(?:' + param + ')$'); + } + return param.test(value); +}, "Invalid format."); + + +/* + * Lets you say "at least X inputs that match selector Y must be filled." + * + * The end result is that neither of these inputs: + * + * + * + * + * ...will validate unless at least one of them is filled. + * + * partnumber: {require_from_group: [1,".productinfo"]}, + * description: {require_from_group: [1,".productinfo"]} + * + */ +jQuery.validator.addMethod("require_from_group", function(value, element, options) { + var validator = this; + var selector = options[1]; + var validOrNot = $(selector, element.form).filter(function() { + return validator.elementValue(this); + }).length >= options[0]; + + if(!$(element).data('being_validated')) { + var fields = $(selector, element.form); + fields.data('being_validated', true); + fields.valid(); + fields.data('being_validated', false); + } + return validOrNot; +}, jQuery.format("Please fill at least {0} of these fields.")); + +/* + * Lets you say "either at least X inputs that match selector Y must be filled, + * OR they must all be skipped (left blank)." + * + * The end result, is that none of these inputs: + * + * + * + * + * + * ...will validate unless either at least two of them are filled, + * OR none of them are. + * + * partnumber: {skip_or_fill_minimum: [2,".productinfo"]}, + * description: {skip_or_fill_minimum: [2,".productinfo"]}, + * color: {skip_or_fill_minimum: [2,".productinfo"]} + * + */ +jQuery.validator.addMethod("skip_or_fill_minimum", function(value, element, options) { + var validator = this, + numberRequired = options[0], + selector = options[1]; + var numberFilled = $(selector, element.form).filter(function() { + return validator.elementValue(this); + }).length; + var valid = numberFilled >= numberRequired || numberFilled === 0; + + if(!$(element).data('being_validated')) { + var fields = $(selector, element.form); + fields.data('being_validated', true); + fields.valid(); + fields.data('being_validated', false); + } + return valid; +}, jQuery.format("Please either skip these fields or fill at least {0} of them.")); + +// Accept a value from a file input based on a required mimetype +jQuery.validator.addMethod("accept", function(value, element, param) { + // Split mime on commas in case we have multiple types we can accept + var typeParam = typeof param === "string" ? param.replace(/\s/g, '').replace(/,/g, '|') : "image/*", + optionalValue = this.optional(element), + i, file; + + // Element is optional + if (optionalValue) { + return optionalValue; + } + + if ($(element).attr("type") === "file") { + // If we are using a wildcard, make it regex friendly + typeParam = typeParam.replace(/\*/g, ".*"); + + // Check if the element has a FileList before checking each file + if (element.files && element.files.length) { + for (i = 0; i < element.files.length; i++) { + file = element.files[i]; + + // Grab the mimetype from the loaded file, verify it matches + if (!file.type.match(new RegExp( ".?(" + typeParam + ")$", "i"))) { + return false; + } + } + } + } + + // Either return true because we've validated each file, or because the + // browser does not support element.files and the FileList feature + return true; +}, jQuery.format("Please enter a value with a valid mimetype.")); + +// Older "accept" file extension method. Old docs: http://docs.jquery.com/Plugins/Validation/Methods/accept +jQuery.validator.addMethod("extension", function(value, element, param) { + param = typeof param === "string" ? param.replace(/,/g, '|') : "png|jpe?g|gif"; + return this.optional(element) || value.match(new RegExp(".(" + param + ")$", "i")); +}, jQuery.format("Please enter a value with a valid extension.")); diff --git a/grails-app/assets/javascripts/jquery-validation/additional-methods.min.js b/grails-app/assets/javascripts/jquery-validation/additional-methods.min.js new file mode 100644 index 0000000..5c21711 --- /dev/null +++ b/grails-app/assets/javascripts/jquery-validation/additional-methods.min.js @@ -0,0 +1 @@ +(function(){function e(e){return e.replace(/<.[^<>]*?>/g," ").replace(/ | /gi," ").replace(/[.(),;:!?%#$'"_+=\/\-]*/g,"")}jQuery.validator.addMethod("maxWords",function(t,n,r){return this.optional(n)||e(t).match(/\b\w+\b/g).length<=r},jQuery.validator.format("Please enter {0} words or less."));jQuery.validator.addMethod("minWords",function(t,n,r){return this.optional(n)||e(t).match(/\b\w+\b/g).length>=r},jQuery.validator.format("Please enter at least {0} words."));jQuery.validator.addMethod("rangeWords",function(t,n,r){var i=e(t);var s=/\b\w+\b/g;return this.optional(n)||i.match(s).length>=r[0]&&i.match(s).length<=r[1]},jQuery.validator.format("Please enter between {0} and {1} words."))})();jQuery.validator.addMethod("letterswithbasicpunc",function(e,t){return this.optional(t)||/^[a-z\-.,()'"\s]+$/i.test(e)},"Letters or punctuation only please");jQuery.validator.addMethod("alphanumeric",function(e,t){return this.optional(t)||/^\w+$/i.test(e)},"Letters, numbers, and underscores only please");jQuery.validator.addMethod("lettersonly",function(e,t){return this.optional(t)||/^[a-z]+$/i.test(e)},"Letters only please");jQuery.validator.addMethod("nowhitespace",function(e,t){return this.optional(t)||/^\S+$/i.test(e)},"No white space please");jQuery.validator.addMethod("ziprange",function(e,t){return this.optional(t)||/^90[2-5]\d\{2\}-\d{4}$/.test(e)},"Your ZIP-code must be in the range 902xx-xxxx to 905-xx-xxxx");jQuery.validator.addMethod("zipcodeUS",function(e,t){return this.optional(t)||/\d{5}-\d{4}$|^\d{5}$/.test(e)},"The specified US ZIP Code is invalid");jQuery.validator.addMethod("integer",function(e,t){return this.optional(t)||/^-?\d+$/.test(e)},"A positive or negative non-decimal number please");jQuery.validator.addMethod("vinUS",function(e){if(e.length!==17){return false}var t,n,r,i,s,o;var u=["A","B","C","D","E","F","G","H","J","K","L","M","N","P","R","S","T","U","V","W","X","Y","Z"];var a=[1,2,3,4,5,6,7,8,1,2,3,4,5,7,9,2,3,4,5,6,7,8,9];var f=[8,7,6,5,4,3,2,10,0,9,8,7,6,5,4,3,2];var l=0;for(t=0;t<17;t++){i=f[t];r=e.slice(t,t+1);if(t===8){o=r}if(!isNaN(r)){r*=i}else{for(n=0;n9&&e.match(/^(\+?1-?)?(\([2-9]\d{2}\)|[2-9]\d{2})-?[2-9]\d{2}-?\d{4}$/)},"Please specify a valid phone number");jQuery.validator.addMethod("phoneUK",function(e,t){e=e.replace(/\(|\)|\s+|-/g,"");return this.optional(t)||e.length>9&&e.match(/^(?:(?:(?:00\s?|\+)44\s?)|(?:\(?0))(?:\d{2}\)?\s?\d{4}\s?\d{4}|\d{3}\)?\s?\d{3}\s?\d{3,4}|\d{4}\)?\s?(?:\d{5}|\d{3}\s?\d{3})|\d{5}\)?\s?\d{4,5})$/)},"Please specify a valid phone number");jQuery.validator.addMethod("mobileUK",function(e,t){e=e.replace(/\(|\)|\s+|-/g,"");return this.optional(t)||e.length>9&&e.match(/^(?:(?:(?:00\s?|\+)44\s?|0)7(?:[45789]\d{2}|624)\s?\d{3}\s?\d{3})$/)},"Please specify a valid mobile number");jQuery.validator.addMethod("phonesUK",function(e,t){e=e.replace(/\(|\)|\s+|-/g,"");return this.optional(t)||e.length>9&&e.match(/^(?:(?:(?:00\s?|\+)44\s?|0)(?:1\d{8,9}|[23]\d{9}|7(?:[45789]\d{8}|624\d{6})))$/)},"Please specify a valid uk phone number");jQuery.validator.addMethod("postcodeUK",function(e,t){return this.optional(t)||/^((([A-PR-UWYZ][0-9])|([A-PR-UWYZ][0-9][0-9])|([A-PR-UWYZ][A-HK-Y][0-9])|([A-PR-UWYZ][A-HK-Y][0-9][0-9])|([A-PR-UWYZ][0-9][A-HJKSTUW])|([A-PR-UWYZ][A-HK-Y][0-9][ABEHMNPRVWXY]))\s?([0-9][ABD-HJLNP-UW-Z]{2})|(GIR)\s?(0AA))$/i.test(e)},"Please specify a valid UK postcode");jQuery.validator.addMethod("strippedminlength",function(e,t,n){return jQuery(e).text().length>=n},jQuery.validator.format("Please enter at least {0} characters"));jQuery.validator.addMethod("email2",function(e,t,n){return this.optional(t)||/^((([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.test(e)},jQuery.validator.messages.email);jQuery.validator.addMethod("url2",function(e,t,n){return this.optional(t)||/^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)*(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(e)},jQuery.validator.messages.url);jQuery.validator.addMethod("creditcardtypes",function(e,t,n){if(/[^0-9\-]+/.test(e)){return false}e=e.replace(/\D/g,"");var r=0;if(n.mastercard){r|=1}if(n.visa){r|=2}if(n.amex){r|=4}if(n.dinersclub){r|=8}if(n.enroute){r|=16}if(n.discover){r|=32}if(n.jcb){r|=64}if(n.unknown){r|=128}if(n.all){r=1|2|4|8|16|32|64|128}if(r&1&&/^(5[12345])/.test(e)){return e.length===16}if(r&2&&/^(4)/.test(e)){return e.length===16}if(r&4&&/^(3[47])/.test(e)){return e.length===15}if(r&8&&/^(3(0[012345]|[68]))/.test(e)){return e.length===14}if(r&16&&/^(2(014|149))/.test(e)){return e.length===15}if(r&32&&/^(6011)/.test(e)){return e.length===16}if(r&64&&/^(3)/.test(e)){return e.length===16}if(r&64&&/^(2131|1800)/.test(e)){return e.length===15}if(r&128){return true}return false},"Please enter a valid credit card number.");jQuery.validator.addMethod("ipv4",function(e,t,n){return this.optional(t)||/^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/i.test(e)},"Please enter a valid IP v4 address.");jQuery.validator.addMethod("ipv6",function(e,t,n){return this.optional(t)||/^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/i.test(e)},"Please enter a valid IP v6 address.");jQuery.validator.addMethod("pattern",function(e,t,n){if(this.optional(t)){return true}if(typeof n==="string"){n=new RegExp("^(?:"+n+")$")}return n.test(e)},"Invalid format.");jQuery.validator.addMethod("require_from_group",function(e,t,n){var r=this;var i=n[1];var s=$(i,t.form).filter(function(){return r.elementValue(this)}).length>=n[0];if(!$(t).data("being_validated")){var o=$(i,t.form);o.data("being_validated",true);o.valid();o.data("being_validated",false)}return s},jQuery.format("Please fill at least {0} of these fields."));jQuery.validator.addMethod("skip_or_fill_minimum",function(e,t,n){var r=this,i=n[0],s=n[1];var o=$(s,t.form).filter(function(){return r.elementValue(this)}).length;var u=o>=i||o===0;if(!$(t).data("being_validated")){var a=$(s,t.form);a.data("being_validated",true);a.valid();a.data("being_validated",false)}return u},jQuery.format("Please either skip these fields or fill at least {0} of them."));jQuery.validator.addMethod("accept",function(e,t,n){var r=typeof n==="string"?n.replace(/\s/g,"").replace(/,/g,"|"):"image/*",i=this.optional(t),s,o;if(i){return i}if($(t).attr("type")==="file"){r=r.replace(/\*/g,".*");if(t.files&&t.files.length){for(s=0;s").attr("name", validator.submitButton.name).val( $(validator.submitButton).val() ).appendTo(validator.currentForm); + } + validator.settings.submitHandler.call( validator, validator.currentForm, event ); + if ( validator.submitButton ) { + // and clean up afterwards; thanks to no-block-scope, hidden can be referenced + hidden.remove(); + } + return false; + } + return true; + } + + // prevent submit for invalid forms or custom submit handlers + if ( validator.cancelSubmit ) { + validator.cancelSubmit = false; + return handle(); + } + if ( validator.form() ) { + if ( validator.pendingRequest ) { + validator.formSubmitted = true; + return false; + } + return handle(); + } else { + validator.focusInvalid(); + return false; + } + }); + } + + return validator; + }, + // http://docs.jquery.com/Plugins/Validation/valid + valid: function() { + if ( $(this[0]).is("form")) { + return this.validate().form(); + } else { + var valid = true; + var validator = $(this[0].form).validate(); + this.each(function() { + valid = valid && validator.element(this); + }); + return valid; + } + }, + // attributes: space seperated list of attributes to retrieve and remove + removeAttrs: function( attributes ) { + var result = {}, + $element = this; + $.each(attributes.split(/\s/), function( index, value ) { + result[value] = $element.attr(value); + $element.removeAttr(value); + }); + return result; + }, + // http://docs.jquery.com/Plugins/Validation/rules + rules: function( command, argument ) { + var element = this[0]; + + if ( command ) { + var settings = $.data(element.form, "validator").settings; + var staticRules = settings.rules; + var existingRules = $.validator.staticRules(element); + switch(command) { + case "add": + $.extend(existingRules, $.validator.normalizeRule(argument)); + // remove messages from rules, but allow them to be set separetely + delete existingRules.messages; + staticRules[element.name] = existingRules; + if ( argument.messages ) { + settings.messages[element.name] = $.extend( settings.messages[element.name], argument.messages ); + } + break; + case "remove": + if ( !argument ) { + delete staticRules[element.name]; + return existingRules; + } + var filtered = {}; + $.each(argument.split(/\s/), function( index, method ) { + filtered[method] = existingRules[method]; + delete existingRules[method]; + }); + return filtered; + } + } + + var data = $.validator.normalizeRules( + $.extend( + {}, + $.validator.classRules(element), + $.validator.attributeRules(element), + $.validator.dataRules(element), + $.validator.staticRules(element) + ), element); + + // make sure required is at front + if ( data.required ) { + var param = data.required; + delete data.required; + data = $.extend({required: param}, data); + } + + return data; + } +}); + +// Custom selectors +$.extend($.expr[":"], { + // http://docs.jquery.com/Plugins/Validation/blank + blank: function( a ) { return !$.trim("" + $(a).val()); }, + // http://docs.jquery.com/Plugins/Validation/filled + filled: function( a ) { return !!$.trim("" + $(a).val()); }, + // http://docs.jquery.com/Plugins/Validation/unchecked + unchecked: function( a ) { return !$(a).prop("checked"); } +}); + +// constructor for validator +$.validator = function( options, form ) { + this.settings = $.extend( true, {}, $.validator.defaults, options ); + this.currentForm = form; + this.init(); +}; + +$.validator.format = function( source, params ) { + if ( arguments.length === 1 ) { + return function() { + var args = $.makeArray(arguments); + args.unshift(source); + return $.validator.format.apply( this, args ); + }; + } + if ( arguments.length > 2 && params.constructor !== Array ) { + params = $.makeArray(arguments).slice(1); + } + if ( params.constructor !== Array ) { + params = [ params ]; + } + $.each(params, function( i, n ) { + source = source.replace( new RegExp("\\{" + i + "\\}", "g"), function() { + return n; + }); + }); + return source; +}; + +$.extend($.validator, { + + defaults: { + messages: {}, + groups: {}, + rules: {}, + errorClass: "error", + validClass: "valid", + errorElement: "label", + focusInvalid: true, + errorContainer: $([]), + errorLabelContainer: $([]), + onsubmit: true, + ignore: ":hidden", + ignoreTitle: false, + onfocusin: function( element, event ) { + this.lastActive = element; + + // hide error label and remove error class on focus if enabled + if ( this.settings.focusCleanup && !this.blockFocusCleanup ) { + if ( this.settings.unhighlight ) { + this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass ); + } + this.addWrapper(this.errorsFor(element)).hide(); + } + }, + onfocusout: function( element, event ) { + if ( !this.checkable(element) && (element.name in this.submitted || !this.optional(element)) ) { + this.element(element); + } + }, + onkeyup: function( element, event ) { + if ( event.which === 9 && this.elementValue(element) === "" ) { + return; + } else if ( element.name in this.submitted || element === this.lastElement ) { + this.element(element); + } + }, + onclick: function( element, event ) { + // click on selects, radiobuttons and checkboxes + if ( element.name in this.submitted ) { + this.element(element); + } + // or option elements, check parent select in that case + else if ( element.parentNode.name in this.submitted ) { + this.element(element.parentNode); + } + }, + highlight: function( element, errorClass, validClass ) { + if ( element.type === "radio" ) { + this.findByName(element.name).addClass(errorClass).removeClass(validClass); + } else { + $(element).addClass(errorClass).removeClass(validClass); + } + }, + unhighlight: function( element, errorClass, validClass ) { + if ( element.type === "radio" ) { + this.findByName(element.name).removeClass(errorClass).addClass(validClass); + } else { + $(element).removeClass(errorClass).addClass(validClass); + } + } + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/setDefaults + setDefaults: function( settings ) { + $.extend( $.validator.defaults, settings ); + }, + + messages: { + required: "This field is required.", + remote: "Please fix this field.", + email: "Please enter a valid email address.", + url: "Please enter a valid URL.", + date: "Please enter a valid date.", + dateISO: "Please enter a valid date (ISO).", + number: "Please enter a valid number.", + digits: "Please enter only digits.", + creditcard: "Please enter a valid credit card number.", + equalTo: "Please enter the same value again.", + maxlength: $.validator.format("Please enter no more than {0} characters."), + minlength: $.validator.format("Please enter at least {0} characters."), + rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."), + range: $.validator.format("Please enter a value between {0} and {1}."), + max: $.validator.format("Please enter a value less than or equal to {0}."), + min: $.validator.format("Please enter a value greater than or equal to {0}.") + }, + + autoCreateRanges: false, + + prototype: { + + init: function() { + this.labelContainer = $(this.settings.errorLabelContainer); + this.errorContext = this.labelContainer.length && this.labelContainer || $(this.currentForm); + this.containers = $(this.settings.errorContainer).add( this.settings.errorLabelContainer ); + this.submitted = {}; + this.valueCache = {}; + this.pendingRequest = 0; + this.pending = {}; + this.invalid = {}; + this.reset(); + + var groups = (this.groups = {}); + $.each(this.settings.groups, function( key, value ) { + if ( typeof value === "string" ) { + value = value.split(/\s/); + } + $.each(value, function( index, name ) { + groups[name] = key; + }); + }); + var rules = this.settings.rules; + $.each(rules, function( key, value ) { + rules[key] = $.validator.normalizeRule(value); + }); + + function delegate(event) { + var validator = $.data(this[0].form, "validator"), + eventType = "on" + event.type.replace(/^validate/, ""); + if ( validator.settings[eventType] ) { + validator.settings[eventType].call(validator, this[0], event); + } + } + $(this.currentForm) + .validateDelegate(":text, [type='password'], [type='file'], select, textarea, " + + "[type='number'], [type='search'] ,[type='tel'], [type='url'], " + + "[type='email'], [type='datetime'], [type='date'], [type='month'], " + + "[type='week'], [type='time'], [type='datetime-local'], " + + "[type='range'], [type='color'] ", + "focusin focusout keyup", delegate) + .validateDelegate("[type='radio'], [type='checkbox'], select, option", "click", delegate); + + if ( this.settings.invalidHandler ) { + $(this.currentForm).bind("invalid-form.validate", this.settings.invalidHandler); + } + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/form + form: function() { + this.checkForm(); + $.extend(this.submitted, this.errorMap); + this.invalid = $.extend({}, this.errorMap); + if ( !this.valid() ) { + $(this.currentForm).triggerHandler("invalid-form", [this]); + } + this.showErrors(); + return this.valid(); + }, + + checkForm: function() { + this.prepareForm(); + for ( var i = 0, elements = (this.currentElements = this.elements()); elements[i]; i++ ) { + this.check( elements[i] ); + } + return this.valid(); + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/element + element: function( element ) { + element = this.validationTargetFor( this.clean( element ) ); + this.lastElement = element; + this.prepareElement( element ); + this.currentElements = $(element); + var result = this.check( element ) !== false; + if ( result ) { + delete this.invalid[element.name]; + } else { + this.invalid[element.name] = true; + } + if ( !this.numberOfInvalids() ) { + // Hide error containers on last error + this.toHide = this.toHide.add( this.containers ); + } + this.showErrors(); + return result; + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/showErrors + showErrors: function( errors ) { + if ( errors ) { + // add items to error list and map + $.extend( this.errorMap, errors ); + this.errorList = []; + for ( var name in errors ) { + this.errorList.push({ + message: errors[name], + element: this.findByName(name)[0] + }); + } + // remove items from success list + this.successList = $.grep( this.successList, function( element ) { + return !(element.name in errors); + }); + } + if ( this.settings.showErrors ) { + this.settings.showErrors.call( this, this.errorMap, this.errorList ); + } else { + this.defaultShowErrors(); + } + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/resetForm + resetForm: function() { + if ( $.fn.resetForm ) { + $(this.currentForm).resetForm(); + } + this.submitted = {}; + this.lastElement = null; + this.prepareForm(); + this.hideErrors(); + this.elements().removeClass( this.settings.errorClass ).removeData( "previousValue" ); + }, + + numberOfInvalids: function() { + return this.objectLength(this.invalid); + }, + + objectLength: function( obj ) { + var count = 0; + for ( var i in obj ) { + count++; + } + return count; + }, + + hideErrors: function() { + this.addWrapper( this.toHide ).hide(); + }, + + valid: function() { + return this.size() === 0; + }, + + size: function() { + return this.errorList.length; + }, + + focusInvalid: function() { + if ( this.settings.focusInvalid ) { + try { + $(this.findLastActive() || this.errorList.length && this.errorList[0].element || []) + .filter(":visible") + .focus() + // manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find + .trigger("focusin"); + } catch(e) { + // ignore IE throwing errors when focusing hidden elements + } + } + }, + + findLastActive: function() { + var lastActive = this.lastActive; + return lastActive && $.grep(this.errorList, function( n ) { + return n.element.name === lastActive.name; + }).length === 1 && lastActive; + }, + + elements: function() { + var validator = this, + rulesCache = {}; + + // select all valid inputs inside the form (no submit or reset buttons) + return $(this.currentForm) + .find("input, select, textarea") + .not(":submit, :reset, :image, [disabled]") + .not( this.settings.ignore ) + .filter(function() { + if ( !this.name && validator.settings.debug && window.console ) { + console.error( "%o has no name assigned", this); + } + + // select only the first element for each name, and only those with rules specified + if ( this.name in rulesCache || !validator.objectLength($(this).rules()) ) { + return false; + } + + rulesCache[this.name] = true; + return true; + }); + }, + + clean: function( selector ) { + return $(selector)[0]; + }, + + errors: function() { + var errorClass = this.settings.errorClass.replace(" ", "."); + return $(this.settings.errorElement + "." + errorClass, this.errorContext); + }, + + reset: function() { + this.successList = []; + this.errorList = []; + this.errorMap = {}; + this.toShow = $([]); + this.toHide = $([]); + this.currentElements = $([]); + }, + + prepareForm: function() { + this.reset(); + this.toHide = this.errors().add( this.containers ); + }, + + prepareElement: function( element ) { + this.reset(); + this.toHide = this.errorsFor(element); + }, + + elementValue: function( element ) { + var type = $(element).attr("type"), + val = $(element).val(); + + if ( type === "radio" || type === "checkbox" ) { + return $("input[name='" + $(element).attr("name") + "']:checked").val(); + } + + if ( typeof val === "string" ) { + return val.replace(/\r/g, ""); + } + return val; + }, + + check: function( element ) { + element = this.validationTargetFor( this.clean( element ) ); + + var rules = $(element).rules(); + var dependencyMismatch = false; + var val = this.elementValue(element); + var result; + + for (var method in rules ) { + var rule = { method: method, parameters: rules[method] }; + try { + + result = $.validator.methods[method].call( this, val, element, rule.parameters ); + + // if a method indicates that the field is optional and therefore valid, + // don't mark it as valid when there are no other rules + if ( result === "dependency-mismatch" ) { + dependencyMismatch = true; + continue; + } + dependencyMismatch = false; + + if ( result === "pending" ) { + this.toHide = this.toHide.not( this.errorsFor(element) ); + return; + } + + if ( !result ) { + this.formatAndAdd( element, rule ); + return false; + } + } catch(e) { + if ( this.settings.debug && window.console ) { + console.log( "Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method.", e ); + } + throw e; + } + } + if ( dependencyMismatch ) { + return; + } + if ( this.objectLength(rules) ) { + this.successList.push(element); + } + return true; + }, + + // return the custom message for the given element and validation method + // specified in the element's HTML5 data attribute + customDataMessage: function( element, method ) { + return $(element).data("msg-" + method.toLowerCase()) || (element.attributes && $(element).attr("data-msg-" + method.toLowerCase())); + }, + + // return the custom message for the given element name and validation method + customMessage: function( name, method ) { + var m = this.settings.messages[name]; + return m && (m.constructor === String ? m : m[method]); + }, + + // return the first defined argument, allowing empty strings + findDefined: function() { + for(var i = 0; i < arguments.length; i++) { + if ( arguments[i] !== undefined ) { + return arguments[i]; + } + } + return undefined; + }, + + defaultMessage: function( element, method ) { + return this.findDefined( + this.customMessage( element.name, method ), + this.customDataMessage( element, method ), + // title is never undefined, so handle empty string as undefined + !this.settings.ignoreTitle && element.title || undefined, + $.validator.messages[method], + "Warning: No message defined for " + element.name + "" + ); + }, + + formatAndAdd: function( element, rule ) { + var message = this.defaultMessage( element, rule.method ), + theregex = /\$?\{(\d+)\}/g; + if ( typeof message === "function" ) { + message = message.call(this, rule.parameters, element); + } else if (theregex.test(message)) { + message = $.validator.format(message.replace(theregex, "{$1}"), rule.parameters); + } + this.errorList.push({ + message: message, + element: element + }); + + this.errorMap[element.name] = message; + this.submitted[element.name] = message; + }, + + addWrapper: function( toToggle ) { + if ( this.settings.wrapper ) { + toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) ); + } + return toToggle; + }, + + defaultShowErrors: function() { + var i, elements; + for ( i = 0; this.errorList[i]; i++ ) { + var error = this.errorList[i]; + if ( this.settings.highlight ) { + this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass ); + } + this.showLabel( error.element, error.message ); + } + if ( this.errorList.length ) { + this.toShow = this.toShow.add( this.containers ); + } + if ( this.settings.success ) { + for ( i = 0; this.successList[i]; i++ ) { + this.showLabel( this.successList[i] ); + } + } + if ( this.settings.unhighlight ) { + for ( i = 0, elements = this.validElements(); elements[i]; i++ ) { + this.settings.unhighlight.call( this, elements[i], this.settings.errorClass, this.settings.validClass ); + } + } + this.toHide = this.toHide.not( this.toShow ); + this.hideErrors(); + this.addWrapper( this.toShow ).show(); + }, + + validElements: function() { + return this.currentElements.not(this.invalidElements()); + }, + + invalidElements: function() { + return $(this.errorList).map(function() { + return this.element; + }); + }, + + showLabel: function( element, message ) { + var label = this.errorsFor( element ); + if ( label.length ) { + // refresh error/success class + label.removeClass( this.settings.validClass ).addClass( this.settings.errorClass ); + // replace message on existing label + label.html(message); + } else { + // create label + label = $("<" + this.settings.errorElement + ">") + .attr("for", this.idOrName(element)) + .addClass(this.settings.errorClass) + .html(message || ""); + if ( this.settings.wrapper ) { + // make sure the element is visible, even in IE + // actually showing the wrapped element is handled elsewhere + label = label.hide().show().wrap("<" + this.settings.wrapper + "/>").parent(); + } + if ( !this.labelContainer.append(label).length ) { + if ( this.settings.errorPlacement ) { + this.settings.errorPlacement(label, $(element) ); + } else { + label.insertAfter(element); + } + } + } + if ( !message && this.settings.success ) { + label.text(""); + if ( typeof this.settings.success === "string" ) { + label.addClass( this.settings.success ); + } else { + this.settings.success( label, element ); + } + } + this.toShow = this.toShow.add(label); + }, + + errorsFor: function( element ) { + var name = this.idOrName(element); + return this.errors().filter(function() { + return $(this).attr("for") === name; + }); + }, + + idOrName: function( element ) { + return this.groups[element.name] || (this.checkable(element) ? element.name : element.id || element.name); + }, + + validationTargetFor: function( element ) { + // if radio/checkbox, validate first element in group instead + if ( this.checkable(element) ) { + element = this.findByName( element.name ).not(this.settings.ignore)[0]; + } + return element; + }, + + checkable: function( element ) { + return (/radio|checkbox/i).test(element.type); + }, + + findByName: function( name ) { + return $(this.currentForm).find("[name='" + name + "']"); + }, + + getLength: function( value, element ) { + switch( element.nodeName.toLowerCase() ) { + case "select": + return $("option:selected", element).length; + case "input": + if ( this.checkable( element) ) { + return this.findByName(element.name).filter(":checked").length; + } + } + return value.length; + }, + + depend: function( param, element ) { + return this.dependTypes[typeof param] ? this.dependTypes[typeof param](param, element) : true; + }, + + dependTypes: { + "boolean": function( param, element ) { + return param; + }, + "string": function( param, element ) { + return !!$(param, element.form).length; + }, + "function": function( param, element ) { + return param(element); + } + }, + + optional: function( element ) { + var val = this.elementValue(element); + return !$.validator.methods.required.call(this, val, element) && "dependency-mismatch"; + }, + + startRequest: function( element ) { + if ( !this.pending[element.name] ) { + this.pendingRequest++; + this.pending[element.name] = true; + } + }, + + stopRequest: function( element, valid ) { + this.pendingRequest--; + // sometimes synchronization fails, make sure pendingRequest is never < 0 + if ( this.pendingRequest < 0 ) { + this.pendingRequest = 0; + } + delete this.pending[element.name]; + if ( valid && this.pendingRequest === 0 && this.formSubmitted && this.form() ) { + $(this.currentForm).submit(); + this.formSubmitted = false; + } else if (!valid && this.pendingRequest === 0 && this.formSubmitted) { + $(this.currentForm).triggerHandler("invalid-form", [this]); + this.formSubmitted = false; + } + }, + + previousValue: function( element ) { + return $.data(element, "previousValue") || $.data(element, "previousValue", { + old: null, + valid: true, + message: this.defaultMessage( element, "remote" ) + }); + } + + }, + + classRuleSettings: { + required: {required: true}, + email: {email: true}, + url: {url: true}, + date: {date: true}, + dateISO: {dateISO: true}, + number: {number: true}, + digits: {digits: true}, + creditcard: {creditcard: true} + }, + + addClassRules: function( className, rules ) { + if ( className.constructor === String ) { + this.classRuleSettings[className] = rules; + } else { + $.extend(this.classRuleSettings, className); + } + }, + + classRules: function( element ) { + var rules = {}; + var classes = $(element).attr("class"); + if ( classes ) { + $.each(classes.split(" "), function() { + if ( this in $.validator.classRuleSettings ) { + $.extend(rules, $.validator.classRuleSettings[this]); + } + }); + } + return rules; + }, + + attributeRules: function( element ) { + var rules = {}; + var $element = $(element); + var type = $element[0].getAttribute("type"); + + for (var method in $.validator.methods) { + var value; + + // support for in both html5 and older browsers + if ( method === "required" ) { + value = $element.get(0).getAttribute(method); + // Some browsers return an empty string for the required attribute + // and non-HTML5 browsers might have required="" markup + if ( value === "" ) { + value = true; + } + // force non-HTML5 browsers to return bool + value = !!value; + } else { + value = $element.attr(method); + } + + // convert the value to a number for number inputs, and for text for backwards compability + // allows type="date" and others to be compared as strings + if ( /min|max/.test( method ) && ( type === null || /number|range|text/.test( type ) ) ) { + value = Number(value); + } + + if ( value ) { + rules[method] = value; + } else if ( type === method && type !== 'range' ) { + // exception: the jquery validate 'range' method + // does not test for the html5 'range' type + rules[method] = true; + } + } + + // maxlength may be returned as -1, 2147483647 (IE) and 524288 (safari) for text inputs + if ( rules.maxlength && /-1|2147483647|524288/.test(rules.maxlength) ) { + delete rules.maxlength; + } + + return rules; + }, + + dataRules: function( element ) { + var method, value, + rules = {}, $element = $(element); + for (method in $.validator.methods) { + value = $element.data("rule-" + method.toLowerCase()); + if ( value !== undefined ) { + rules[method] = value; + } + } + return rules; + }, + + staticRules: function( element ) { + var rules = {}; + var validator = $.data(element.form, "validator"); + if ( validator.settings.rules ) { + rules = $.validator.normalizeRule(validator.settings.rules[element.name]) || {}; + } + return rules; + }, + + normalizeRules: function( rules, element ) { + // handle dependency check + $.each(rules, function( prop, val ) { + // ignore rule when param is explicitly false, eg. required:false + if ( val === false ) { + delete rules[prop]; + return; + } + if ( val.param || val.depends ) { + var keepRule = true; + switch (typeof val.depends) { + case "string": + keepRule = !!$(val.depends, element.form).length; + break; + case "function": + keepRule = val.depends.call(element, element); + break; + } + if ( keepRule ) { + rules[prop] = val.param !== undefined ? val.param : true; + } else { + delete rules[prop]; + } + } + }); + + // evaluate parameters + $.each(rules, function( rule, parameter ) { + rules[rule] = $.isFunction(parameter) ? parameter(element) : parameter; + }); + + // clean number parameters + $.each(['minlength', 'maxlength'], function() { + if ( rules[this] ) { + rules[this] = Number(rules[this]); + } + }); + $.each(['rangelength', 'range'], function() { + var parts; + if ( rules[this] ) { + if ( $.isArray(rules[this]) ) { + rules[this] = [Number(rules[this][0]), Number(rules[this][1])]; + } else if ( typeof rules[this] === "string" ) { + parts = rules[this].split(/[\s,]+/); + rules[this] = [Number(parts[0]), Number(parts[1])]; + } + } + }); + + if ( $.validator.autoCreateRanges ) { + // auto-create ranges + if ( rules.min && rules.max ) { + rules.range = [rules.min, rules.max]; + delete rules.min; + delete rules.max; + } + if ( rules.minlength && rules.maxlength ) { + rules.rangelength = [rules.minlength, rules.maxlength]; + delete rules.minlength; + delete rules.maxlength; + } + } + + return rules; + }, + + // Converts a simple string to a {string: true} rule, e.g., "required" to {required:true} + normalizeRule: function( data ) { + if ( typeof data === "string" ) { + var transformed = {}; + $.each(data.split(/\s/), function() { + transformed[this] = true; + }); + data = transformed; + } + return data; + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/addMethod + addMethod: function( name, method, message ) { + $.validator.methods[name] = method; + $.validator.messages[name] = message !== undefined ? message : $.validator.messages[name]; + if ( method.length < 3 ) { + $.validator.addClassRules(name, $.validator.normalizeRule(name)); + } + }, + + methods: { + + // http://docs.jquery.com/Plugins/Validation/Methods/required + required: function( value, element, param ) { + // check if dependency is met + if ( !this.depend(param, element) ) { + return "dependency-mismatch"; + } + if ( element.nodeName.toLowerCase() === "select" ) { + // could be an array for select-multiple or a string, both are fine this way + var val = $(element).val(); + return val && val.length > 0; + } + if ( this.checkable(element) ) { + return this.getLength(value, element) > 0; + } + return $.trim(value).length > 0; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/email + email: function( value, element ) { + // contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/ + return this.optional(element) || /^((([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.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/url + url: function( value, element ) { + // contributed by Scott Gonzalez: http://projects.scottsplayground.com/iri/ + return this.optional(element) || /^(https?|s?ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/date + date: function( value, element ) { + return this.optional(element) || !/Invalid|NaN/.test(new Date(value).toString()); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/dateISO + dateISO: function( value, element ) { + return this.optional(element) || /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/number + number: function( value, element ) { + return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/digits + digits: function( value, element ) { + return this.optional(element) || /^\d+$/.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/creditcard + // based on http://en.wikipedia.org/wiki/Luhn + creditcard: function( value, element ) { + if ( this.optional(element) ) { + return "dependency-mismatch"; + } + // accept only spaces, digits and dashes + if ( /[^0-9 \-]+/.test(value) ) { + return false; + } + var nCheck = 0, + nDigit = 0, + bEven = false; + + value = value.replace(/\D/g, ""); + + for (var n = value.length - 1; n >= 0; n--) { + var cDigit = value.charAt(n); + nDigit = parseInt(cDigit, 10); + if ( bEven ) { + if ( (nDigit *= 2) > 9 ) { + nDigit -= 9; + } + } + nCheck += nDigit; + bEven = !bEven; + } + + return (nCheck % 10) === 0; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/minlength + minlength: function( value, element, param ) { + var length = $.isArray( value ) ? value.length : this.getLength($.trim(value), element); + return this.optional(element) || length >= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/maxlength + maxlength: function( value, element, param ) { + var length = $.isArray( value ) ? value.length : this.getLength($.trim(value), element); + return this.optional(element) || length <= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/rangelength + rangelength: function( value, element, param ) { + var length = $.isArray( value ) ? value.length : this.getLength($.trim(value), element); + return this.optional(element) || ( length >= param[0] && length <= param[1] ); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/min + min: function( value, element, param ) { + return this.optional(element) || value >= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/max + max: function( value, element, param ) { + return this.optional(element) || value <= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/range + range: function( value, element, param ) { + return this.optional(element) || ( value >= param[0] && value <= param[1] ); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/equalTo + equalTo: function( value, element, param ) { + // bind to the blur event of the target in order to revalidate whenever the target field is updated + // TODO find a way to bind the event just once, avoiding the unbind-rebind overhead + var target = $(param); + if ( this.settings.onfocusout ) { + target.unbind(".validate-equalTo").bind("blur.validate-equalTo", function() { + $(element).valid(); + }); + } + return value === target.val(); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/remote + remote: function( value, element, param ) { + if ( this.optional(element) ) { + return "dependency-mismatch"; + } + + var previous = this.previousValue(element); + if (!this.settings.messages[element.name] ) { + this.settings.messages[element.name] = {}; + } + previous.originalMessage = this.settings.messages[element.name].remote; + this.settings.messages[element.name].remote = previous.message; + + param = typeof param === "string" && {url:param} || param; + + if ( previous.old === value ) { + return previous.valid; + } + + previous.old = value; + var validator = this; + this.startRequest(element); + var data = {}; + data[element.name] = value; + $.ajax($.extend(true, { + url: param, + mode: "abort", + port: "validate" + element.name, + dataType: "json", + data: data, + success: function( response ) { + validator.settings.messages[element.name].remote = previous.originalMessage; + var valid = response === true || response === "true"; + if ( valid ) { + var submitted = validator.formSubmitted; + //validator.prepareElement(element); + validator.formSubmitted = submitted; + validator.successList.push(element); + delete validator.invalid[element.name]; + validator.showErrors(); + } else { + var errors = {}; + var message = response || validator.defaultMessage( element, "remote" ); + errors[element.name] = previous.message = $.isFunction(message) ? message(value) : message; + validator.invalid[element.name] = true; + validator.showErrors(errors); + } + previous.valid = valid; + validator.stopRequest(element, valid); + } + }, param)); + return "pending"; + } + + } + +}); + +// deprecated, use $.validator.format instead +$.format = $.validator.format; + +}(jQuery)); + +// ajax mode: abort +// usage: $.ajax({ mode: "abort"[, port: "uniqueport"]}); +// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort() +(function($) { + var pendingRequests = {}; + // Use a prefilter if available (1.5+) + if ( $.ajaxPrefilter ) { + $.ajaxPrefilter(function( settings, _, xhr ) { + var port = settings.port; + if ( settings.mode === "abort" ) { + if ( pendingRequests[port] ) { + pendingRequests[port].abort(); + } + pendingRequests[port] = xhr; + } + }); + } else { + // Proxy ajax + var ajax = $.ajax; + $.ajax = function( settings ) { + var mode = ( "mode" in settings ? settings : $.ajaxSettings ).mode, + port = ( "port" in settings ? settings : $.ajaxSettings ).port; + if ( mode === "abort" ) { + if ( pendingRequests[port] ) { + pendingRequests[port].abort(); + } + pendingRequests[port] = ajax.apply(this, arguments); + return pendingRequests[port]; + } + return ajax.apply(this, arguments); + }; + } +}(jQuery)); + +// provides delegate(type: String, delegate: Selector, handler: Callback) plugin for easier event delegation +// handler is only called when $(event.target).is(delegate), in the scope of the jquery-object for event.target +(function($) { + $.extend($.fn, { + validateDelegate: function( delegate, type, handler ) { + return this.bind(type, function( event ) { + var target = $(event.target); + if ( target.is(delegate) ) { + return handler.apply(target, arguments); + } + }); + } + }); +}(jQuery)); diff --git a/grails-app/assets/javascripts/jquery-validation/jquery.validate.min.js b/grails-app/assets/javascripts/jquery-validation/jquery.validate.min.js new file mode 100644 index 0000000..95e4343 --- /dev/null +++ b/grails-app/assets/javascripts/jquery-validation/jquery.validate.min.js @@ -0,0 +1 @@ +(function(e){e.extend(e.fn,{validate:function(t){if(!this.length){if(t&&t.debug&&window.console){console.warn("Nothing selected, can't validate, returning nothing.")}return}var n=e.data(this[0],"validator");if(n){return n}this.attr("novalidate","novalidate");n=new e.validator(t,this[0]);e.data(this[0],"validator",n);if(n.settings.onsubmit){this.validateDelegate(":submit","click",function(t){if(n.settings.submitHandler){n.submitButton=t.target}if(e(t.target).hasClass("cancel")){n.cancelSubmit=true}if(e(t.target).attr("formnovalidate")!==undefined){n.cancelSubmit=true}});this.submit(function(t){function r(){var r;if(n.settings.submitHandler){if(n.submitButton){r=e("").attr("name",n.submitButton.name).val(e(n.submitButton).val()).appendTo(n.currentForm)}n.settings.submitHandler.call(n,n.currentForm,t);if(n.submitButton){r.remove()}return false}return true}if(n.settings.debug){t.preventDefault()}if(n.cancelSubmit){n.cancelSubmit=false;return r()}if(n.form()){if(n.pendingRequest){n.formSubmitted=true;return false}return r()}else{n.focusInvalid();return false}})}return n},valid:function(){if(e(this[0]).is("form")){return this.validate().form()}else{var t=true;var n=e(this[0].form).validate();this.each(function(){t=t&&n.element(this)});return t}},removeAttrs:function(t){var n={},r=this;e.each(t.split(/\s/),function(e,t){n[t]=r.attr(t);r.removeAttr(t)});return n},rules:function(t,n){var r=this[0];if(t){var i=e.data(r.form,"validator").settings;var s=i.rules;var o=e.validator.staticRules(r);switch(t){case"add":e.extend(o,e.validator.normalizeRule(n));delete o.messages;s[r.name]=o;if(n.messages){i.messages[r.name]=e.extend(i.messages[r.name],n.messages)}break;case"remove":if(!n){delete s[r.name];return o}var u={};e.each(n.split(/\s/),function(e,t){u[t]=o[t];delete o[t]});return u}}var a=e.validator.normalizeRules(e.extend({},e.validator.classRules(r),e.validator.attributeRules(r),e.validator.dataRules(r),e.validator.staticRules(r)),r);if(a.required){var f=a.required;delete a.required;a=e.extend({required:f},a)}return a}});e.extend(e.expr[":"],{blank:function(t){return!e.trim(""+e(t).val())},filled:function(t){return!!e.trim(""+e(t).val())},unchecked:function(t){return!e(t).prop("checked")}});e.validator=function(t,n){this.settings=e.extend(true,{},e.validator.defaults,t);this.currentForm=n;this.init()};e.validator.format=function(t,n){if(arguments.length===1){return function(){var n=e.makeArray(arguments);n.unshift(t);return e.validator.format.apply(this,n)}}if(arguments.length>2&&n.constructor!==Array){n=e.makeArray(arguments).slice(1)}if(n.constructor!==Array){n=[n]}e.each(n,function(e,n){t=t.replace(new RegExp("\\{"+e+"\\}","g"),function(){return n})});return t};e.extend(e.validator,{defaults:{messages:{},groups:{},rules:{},errorClass:"error",validClass:"valid",errorElement:"label",focusInvalid:true,errorContainer:e([]),errorLabelContainer:e([]),onsubmit:true,ignore:":hidden",ignoreTitle:false,onfocusin:function(e,t){this.lastActive=e;if(this.settings.focusCleanup&&!this.blockFocusCleanup){if(this.settings.unhighlight){this.settings.unhighlight.call(this,e,this.settings.errorClass,this.settings.validClass)}this.addWrapper(this.errorsFor(e)).hide()}},onfocusout:function(e,t){if(!this.checkable(e)&&(e.name in this.submitted||!this.optional(e))){this.element(e)}},onkeyup:function(e,t){if(t.which===9&&this.elementValue(e)===""){return}else if(e.name in this.submitted||e===this.lastElement){this.element(e)}},onclick:function(e,t){if(e.name in this.submitted){this.element(e)}else if(e.parentNode.name in this.submitted){this.element(e.parentNode)}},highlight:function(t,n,r){if(t.type==="radio"){this.findByName(t.name).addClass(n).removeClass(r)}else{e(t).addClass(n).removeClass(r)}},unhighlight:function(t,n,r){if(t.type==="radio"){this.findByName(t.name).removeClass(n).addClass(r)}else{e(t).removeClass(n).addClass(r)}}},setDefaults:function(t){e.extend(e.validator.defaults,t)},messages:{required:"This field is required.",remote:"Please fix this field.",email:"Please enter a valid email address.",url:"Please enter a valid URL.",date:"Please enter a valid date.",dateISO:"Please enter a valid date (ISO).",number:"Please enter a valid number.",digits:"Please enter only digits.",creditcard:"Please enter a valid credit card number.",equalTo:"Please enter the same value again.",maxlength:e.validator.format("Please enter no more than {0} characters."),minlength:e.validator.format("Please enter at least {0} characters."),rangelength:e.validator.format("Please enter a value between {0} and {1} characters long."),range:e.validator.format("Please enter a value between {0} and {1}."),max:e.validator.format("Please enter a value less than or equal to {0}."),min:e.validator.format("Please enter a value greater than or equal to {0}.")},autoCreateRanges:false,prototype:{init:function(){function r(t){var n=e.data(this[0].form,"validator"),r="on"+t.type.replace(/^validate/,"");if(n.settings[r]){n.settings[r].call(n,this[0],t)}}this.labelContainer=e(this.settings.errorLabelContainer);this.errorContext=this.labelContainer.length&&this.labelContainer||e(this.currentForm);this.containers=e(this.settings.errorContainer).add(this.settings.errorLabelContainer);this.submitted={};this.valueCache={};this.pendingRequest=0;this.pending={};this.invalid={};this.reset();var t=this.groups={};e.each(this.settings.groups,function(n,r){if(typeof r==="string"){r=r.split(/\s/)}e.each(r,function(e,r){t[r]=n})});var n=this.settings.rules;e.each(n,function(t,r){n[t]=e.validator.normalizeRule(r)});e(this.currentForm).validateDelegate(":text, [type='password'], [type='file'], select, textarea, "+"[type='number'], [type='search'] ,[type='tel'], [type='url'], "+"[type='email'], [type='datetime'], [type='date'], [type='month'], "+"[type='week'], [type='time'], [type='datetime-local'], "+"[type='range'], [type='color'] ","focusin focusout keyup",r).validateDelegate("[type='radio'], [type='checkbox'], select, option","click",r);if(this.settings.invalidHandler){e(this.currentForm).bind("invalid-form.validate",this.settings.invalidHandler)}},form:function(){this.checkForm();e.extend(this.submitted,this.errorMap);this.invalid=e.extend({},this.errorMap);if(!this.valid()){e(this.currentForm).triggerHandler("invalid-form",[this])}this.showErrors();return this.valid()},checkForm:function(){this.prepareForm();for(var e=0,t=this.currentElements=this.elements();t[e];e++){this.check(t[e])}return this.valid()},element:function(t){t=this.validationTargetFor(this.clean(t));this.lastElement=t;this.prepareElement(t);this.currentElements=e(t);var n=this.check(t)!==false;if(n){delete this.invalid[t.name]}else{this.invalid[t.name]=true}if(!this.numberOfInvalids()){this.toHide=this.toHide.add(this.containers)}this.showErrors();return n},showErrors:function(t){if(t){e.extend(this.errorMap,t);this.errorList=[];for(var n in t){this.errorList.push({message:t[n],element:this.findByName(n)[0]})}this.successList=e.grep(this.successList,function(e){return!(e.name in t)})}if(this.settings.showErrors){this.settings.showErrors.call(this,this.errorMap,this.errorList)}else{this.defaultShowErrors()}},resetForm:function(){if(e.fn.resetForm){e(this.currentForm).resetForm()}this.submitted={};this.lastElement=null;this.prepareForm();this.hideErrors();this.elements().removeClass(this.settings.errorClass).removeData("previousValue")},numberOfInvalids:function(){return this.objectLength(this.invalid)},objectLength:function(e){var t=0;for(var n in e){t++}return t},hideErrors:function(){this.addWrapper(this.toHide).hide()},valid:function(){return this.size()===0},size:function(){return this.errorList.length},focusInvalid:function(){if(this.settings.focusInvalid){try{e(this.findLastActive()||this.errorList.length&&this.errorList[0].element||[]).filter(":visible").focus().trigger("focusin")}catch(t){}}},findLastActive:function(){var t=this.lastActive;return t&&e.grep(this.errorList,function(e){return e.element.name===t.name}).length===1&&t},elements:function(){var t=this,n={};return e(this.currentForm).find("input, select, textarea").not(":submit, :reset, :image, [disabled]").not(this.settings.ignore).filter(function(){if(!this.name&&t.settings.debug&&window.console){console.error("%o has no name assigned",this)}if(this.name in n||!t.objectLength(e(this).rules())){return false}n[this.name]=true;return true})},clean:function(t){return e(t)[0]},errors:function(){var t=this.settings.errorClass.replace(" ",".");return e(this.settings.errorElement+"."+t,this.errorContext)},reset:function(){this.successList=[];this.errorList=[];this.errorMap={};this.toShow=e([]);this.toHide=e([]);this.currentElements=e([])},prepareForm:function(){this.reset();this.toHide=this.errors().add(this.containers)},prepareElement:function(e){this.reset();this.toHide=this.errorsFor(e)},elementValue:function(t){var n=e(t).attr("type"),r=e(t).val();if(n==="radio"||n==="checkbox"){return e("input[name='"+e(t).attr("name")+"']:checked").val()}if(typeof r==="string"){return r.replace(/\r/g,"")}return r},check:function(t){t=this.validationTargetFor(this.clean(t));var n=e(t).rules();var r=false;var i=this.elementValue(t);var s;for(var o in n){var u={method:o,parameters:n[o]};try{s=e.validator.methods[o].call(this,i,t,u.parameters);if(s==="dependency-mismatch"){r=true;continue}r=false;if(s==="pending"){this.toHide=this.toHide.not(this.errorsFor(t));return}if(!s){this.formatAndAdd(t,u);return false}}catch(a){if(this.settings.debug&&window.console){console.log("Exception occurred when checking element "+t.id+", check the '"+u.method+"' method.",a)}throw a}}if(r){return}if(this.objectLength(n)){this.successList.push(t)}return true},customDataMessage:function(t,n){return e(t).data("msg-"+n.toLowerCase())||t.attributes&&e(t).attr("data-msg-"+n.toLowerCase())},customMessage:function(e,t){var n=this.settings.messages[e];return n&&(n.constructor===String?n:n[t])},findDefined:function(){for(var e=0;eWarning: No message defined for "+t.name+"")},formatAndAdd:function(t,n){var r=this.defaultMessage(t,n.method),i=/\$?\{(\d+)\}/g;if(typeof r==="function"){r=r.call(this,n.parameters,t)}else if(i.test(r)){r=e.validator.format(r.replace(i,"{$1}"),n.parameters)}this.errorList.push({message:r,element:t});this.errorMap[t.name]=r;this.submitted[t.name]=r},addWrapper:function(e){if(this.settings.wrapper){e=e.add(e.parent(this.settings.wrapper))}return e},defaultShowErrors:function(){var e,t;for(e=0;this.errorList[e];e++){var n=this.errorList[e];if(this.settings.highlight){this.settings.highlight.call(this,n.element,this.settings.errorClass,this.settings.validClass)}this.showLabel(n.element,n.message)}if(this.errorList.length){this.toShow=this.toShow.add(this.containers)}if(this.settings.success){for(e=0;this.successList[e];e++){this.showLabel(this.successList[e])}}if(this.settings.unhighlight){for(e=0,t=this.validElements();t[e];e++){this.settings.unhighlight.call(this,t[e],this.settings.errorClass,this.settings.validClass)}}this.toHide=this.toHide.not(this.toShow);this.hideErrors();this.addWrapper(this.toShow).show()},validElements:function(){return this.currentElements.not(this.invalidElements())},invalidElements:function(){return e(this.errorList).map(function(){return this.element})},showLabel:function(t,n){var r=this.errorsFor(t);if(r.length){r.removeClass(this.settings.validClass).addClass(this.settings.errorClass);r.html(n)}else{r=e("<"+this.settings.errorElement+">").attr("for",this.idOrName(t)).addClass(this.settings.errorClass).html(n||"");if(this.settings.wrapper){r=r.hide().show().wrap("<"+this.settings.wrapper+"/>").parent()}if(!this.labelContainer.append(r).length){if(this.settings.errorPlacement){this.settings.errorPlacement(r,e(t))}else{r.insertAfter(t)}}}if(!n&&this.settings.success){r.text("");if(typeof this.settings.success==="string"){r.addClass(this.settings.success)}else{this.settings.success(r,t)}}this.toShow=this.toShow.add(r)},errorsFor:function(t){var n=this.idOrName(t);return this.errors().filter(function(){return e(this).attr("for")===n})},idOrName:function(e){return this.groups[e.name]||(this.checkable(e)?e.name:e.id||e.name)},validationTargetFor:function(e){if(this.checkable(e)){e=this.findByName(e.name).not(this.settings.ignore)[0]}return e},checkable:function(e){return/radio|checkbox/i.test(e.type)},findByName:function(t){return e(this.currentForm).find("[name='"+t+"']")},getLength:function(t,n){switch(n.nodeName.toLowerCase()){case"select":return e("option:selected",n).length;case"input":if(this.checkable(n)){return this.findByName(n.name).filter(":checked").length}}return t.length},depend:function(e,t){return this.dependTypes[typeof e]?this.dependTypes[typeof e](e,t):true},dependTypes:{"boolean":function(e,t){return e},string:function(t,n){return!!e(t,n.form).length},"function":function(e,t){return e(t)}},optional:function(t){var n=this.elementValue(t);return!e.validator.methods.required.call(this,n,t)&&"dependency-mismatch"},startRequest:function(e){if(!this.pending[e.name]){this.pendingRequest++;this.pending[e.name]=true}},stopRequest:function(t,n){this.pendingRequest--;if(this.pendingRequest<0){this.pendingRequest=0}delete this.pending[t.name];if(n&&this.pendingRequest===0&&this.formSubmitted&&this.form()){e(this.currentForm).submit();this.formSubmitted=false}else if(!n&&this.pendingRequest===0&&this.formSubmitted){e(this.currentForm).triggerHandler("invalid-form",[this]);this.formSubmitted=false}},previousValue:function(t){return e.data(t,"previousValue")||e.data(t,"previousValue",{old:null,valid:true,message:this.defaultMessage(t,"remote")})}},classRuleSettings:{required:{required:true},email:{email:true},url:{url:true},date:{date:true},dateISO:{dateISO:true},number:{number:true},digits:{digits:true},creditcard:{creditcard:true}},addClassRules:function(t,n){if(t.constructor===String){this.classRuleSettings[t]=n}else{e.extend(this.classRuleSettings,t)}},classRules:function(t){var n={};var r=e(t).attr("class");if(r){e.each(r.split(" "),function(){if(this in e.validator.classRuleSettings){e.extend(n,e.validator.classRuleSettings[this])}})}return n},attributeRules:function(t){var n={};var r=e(t);var i=r[0].getAttribute("type");for(var s in e.validator.methods){var o;if(s==="required"){o=r.get(0).getAttribute(s);if(o===""){o=true}o=!!o}else{o=r.attr(s)}if(/min|max/.test(s)&&(i===null||/number|range|text/.test(i))){o=Number(o)}if(o){n[s]=o}else if(i===s&&i!=="range"){n[s]=true}}if(n.maxlength&&/-1|2147483647|524288/.test(n.maxlength)){delete n.maxlength}return n},dataRules:function(t){var n,r,i={},s=e(t);for(n in e.validator.methods){r=s.data("rule-"+n.toLowerCase());if(r!==undefined){i[n]=r}}return i},staticRules:function(t){var n={};var r=e.data(t.form,"validator");if(r.settings.rules){n=e.validator.normalizeRule(r.settings.rules[t.name])||{}}return n},normalizeRules:function(t,n){e.each(t,function(r,i){if(i===false){delete t[r];return}if(i.param||i.depends){var s=true;switch(typeof i.depends){case"string":s=!!e(i.depends,n.form).length;break;case"function":s=i.depends.call(n,n);break}if(s){t[r]=i.param!==undefined?i.param:true}else{delete t[r]}}});e.each(t,function(r,i){t[r]=e.isFunction(i)?i(n):i});e.each(["minlength","maxlength"],function(){if(t[this]){t[this]=Number(t[this])}});e.each(["rangelength","range"],function(){var n;if(t[this]){if(e.isArray(t[this])){t[this]=[Number(t[this][0]),Number(t[this][1])]}else if(typeof t[this]==="string"){n=t[this].split(/[\s,]+/);t[this]=[Number(n[0]),Number(n[1])]}}});if(e.validator.autoCreateRanges){if(t.min&&t.max){t.range=[t.min,t.max];delete t.min;delete t.max}if(t.minlength&&t.maxlength){t.rangelength=[t.minlength,t.maxlength];delete t.minlength;delete t.maxlength}}return t},normalizeRule:function(t){if(typeof t==="string"){var n={};e.each(t.split(/\s/),function(){n[this]=true});t=n}return t},addMethod:function(t,n,r){e.validator.methods[t]=n;e.validator.messages[t]=r!==undefined?r:e.validator.messages[t];if(n.length<3){e.validator.addClassRules(t,e.validator.normalizeRule(t))}},methods:{required:function(t,n,r){if(!this.depend(r,n)){return"dependency-mismatch"}if(n.nodeName.toLowerCase()==="select"){var i=e(n).val();return i&&i.length>0}if(this.checkable(n)){return this.getLength(t,n)>0}return e.trim(t).length>0},email:function(e,t){return this.optional(t)||/^((([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.test(e)},url:function(e,t){return this.optional(t)||/^(https?|s?ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(e)},date:function(e,t){return this.optional(t)||!/Invalid|NaN/.test((new Date(e)).toString())},dateISO:function(e,t){return this.optional(t)||/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/.test(e)},number:function(e,t){return this.optional(t)||/^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(e)},digits:function(e,t){return this.optional(t)||/^\d+$/.test(e)},creditcard:function(e,t){if(this.optional(t)){return"dependency-mismatch"}if(/[^0-9 \-]+/.test(e)){return false}var n=0,r=0,i=false;e=e.replace(/\D/g,"");for(var s=e.length-1;s>=0;s--){var o=e.charAt(s);r=parseInt(o,10);if(i){if((r*=2)>9){r-=9}}n+=r;i=!i}return n%10===0},minlength:function(t,n,r){var i=e.isArray(t)?t.length:this.getLength(e.trim(t),n);return this.optional(n)||i>=r},maxlength:function(t,n,r){var i=e.isArray(t)?t.length:this.getLength(e.trim(t),n);return this.optional(n)||i<=r},rangelength:function(t,n,r){var i=e.isArray(t)?t.length:this.getLength(e.trim(t),n);return this.optional(n)||i>=r[0]&&i<=r[1]},min:function(e,t,n){return this.optional(t)||e>=n},max:function(e,t,n){return this.optional(t)||e<=n},range:function(e,t,n){return this.optional(t)||e>=n[0]&&e<=n[1]},equalTo:function(t,n,r){var i=e(r);if(this.settings.onfocusout){i.unbind(".validate-equalTo").bind("blur.validate-equalTo",function(){e(n).valid()})}return t===i.val()},remote:function(t,n,r){if(this.optional(n)){return"dependency-mismatch"}var i=this.previousValue(n);if(!this.settings.messages[n.name]){this.settings.messages[n.name]={}}i.originalMessage=this.settings.messages[n.name].remote;this.settings.messages[n.name].remote=i.message;r=typeof r==="string"&&{url:r}||r;if(i.old===t){return i.valid}i.old=t;var s=this;this.startRequest(n);var o={};o[n.name]=t;e.ajax(e.extend(true,{url:r,mode:"abort",port:"validate"+n.name,dataType:"json",data:o,success:function(r){s.settings.messages[n.name].remote=i.originalMessage;var o=r===true||r==="true";if(o){var u=s.formSubmitted;s.prepareElement(n);s.formSubmitted=u;s.successList.push(n);delete s.invalid[n.name];s.showErrors()}else{var a={};var f=r||s.defaultMessage(n,"remote");a[n.name]=i.message=e.isFunction(f)?f(t):f;s.invalid[n.name]=true;s.showErrors(a)}i.valid=o;s.stopRequest(n,o)}},r));return"pending"}}});e.format=e.validator.format})(jQuery);(function(e){var t={};if(e.ajaxPrefilter){e.ajaxPrefilter(function(e,n,r){var i=e.port;if(e.mode==="abort"){if(t[i]){t[i].abort()}t[i]=r}})}else{var n=e.ajax;e.ajax=function(r){var i=("mode"in r?r:e.ajaxSettings).mode,s=("port"in r?r:e.ajaxSettings).port;if(i==="abort"){if(t[s]){t[s].abort()}t[s]=n.apply(this,arguments);return t[s]}return n.apply(this,arguments)}}})(jQuery);(function(e){e.extend(e.fn,{validateDelegate:function(t,n,r){return this.bind(n,function(n){var i=e(n.target);if(i.is(t)){return r.apply(i,arguments)}})}})})(jQuery) \ No newline at end of file diff --git a/grails-app/assets/javascripts/jqueryValidationUiPlugin.js b/grails-app/assets/javascripts/jqueryValidationUiPlugin.js new file mode 100644 index 0000000..692652d --- /dev/null +++ b/grails-app/assets/javascripts/jqueryValidationUiPlugin.js @@ -0,0 +1,4 @@ +//= require jquery +//= require jquery-validation/jquery.validate +//= require jquery-validation/additional-methods +//= require grails-validation-methods \ No newline at end of file diff --git a/grails-app/assets/javascripts/jqueryValidationUiPluginQTip.js b/grails-app/assets/javascripts/jqueryValidationUiPluginQTip.js new file mode 100644 index 0000000..5293c60 --- /dev/null +++ b/grails-app/assets/javascripts/jqueryValidationUiPluginQTip.js @@ -0,0 +1,2 @@ +//= require jqueryValidationUiPlugin +//= require qTip/jquery.qtip.pack \ No newline at end of file diff --git a/web-app/js/qTip/jquery.qtip.js b/grails-app/assets/javascripts/qTip/jquery.qtip.js similarity index 100% rename from web-app/js/qTip/jquery.qtip.js rename to grails-app/assets/javascripts/qTip/jquery.qtip.js diff --git a/web-app/css/errors.css b/grails-app/assets/stylesheets/errors.css similarity index 100% rename from web-app/css/errors.css rename to grails-app/assets/stylesheets/errors.css diff --git a/grails-app/assets/stylesheets/jqueryValidationUiPluginQTip.css b/grails-app/assets/stylesheets/jqueryValidationUiPluginQTip.css new file mode 100644 index 0000000..2cb8e9a --- /dev/null +++ b/grails-app/assets/stylesheets/jqueryValidationUiPluginQTip.css @@ -0,0 +1,3 @@ +/* +*= require qTip/jquery.qtip +*/ \ No newline at end of file diff --git a/web-app/css/main.css b/grails-app/assets/stylesheets/main.css similarity index 100% rename from web-app/css/main.css rename to grails-app/assets/stylesheets/main.css diff --git a/web-app/css/mobile.css b/grails-app/assets/stylesheets/mobile.css similarity index 100% rename from web-app/css/mobile.css rename to grails-app/assets/stylesheets/mobile.css diff --git a/web-app/css/qTip/jquery.qtip.css b/grails-app/assets/stylesheets/qTip/jquery.qtip.css similarity index 100% rename from web-app/css/qTip/jquery.qtip.css rename to grails-app/assets/stylesheets/qTip/jquery.qtip.css diff --git a/grails-app/conf/BuildConfig.groovy b/grails-app/conf/BuildConfig.groovy index 69ad4de..fbb42b1 100644 --- a/grails-app/conf/BuildConfig.groovy +++ b/grails-app/conf/BuildConfig.groovy @@ -1,13 +1,27 @@ grails.project.class.dir = "target/classes" grails.project.test.class.dir = "target/test-classes" grails.project.test.reports.dir = "target/test-reports" -//grails.project.war.file = "target/${appName}-${appVersion}.war" // Disable SVN handling with release plugin grails.release.scm.enabled = false // Default repo to release is grailsCentral grails.project.repos.default = "grailsCentral" +grails.project.fork = [ + // configure settings for compilation JVM, note that if you alter the Groovy version forked compilation is required + // compile: [maxMemory: 256, minMemory: 64, debug: false, maxPerm: 256, daemon:true], + + // configure settings for the test-app JVM, uses the daemon by default + test: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, daemon:true], + // configure settings for the run-app JVM + run: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, forkReserve:false], + // configure settings for the run-war JVM + war: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, forkReserve:false], + // configure settings for the Console UI JVM + console: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256] +] + +grails.project.dependency.resolver = "maven" grails.project.dependency.resolution = { // inherit Grails' default dependencies inherits("global") { @@ -35,17 +49,19 @@ grails.project.dependency.resolution = { // runtime 'mysql:mysql-connector-java:5.1.5' if (grailsVersion[0..2].toDouble() >= 2.2) { - test "org.spockframework:spock-grails-support:0.7-groovy-2.0" + test("org.spockframework:spock-grails-support:0.7-groovy-2.0") { + export = false + } } //build "org.springframework:spring-orm:3.2.5.RELEASE" } plugins { - if (grailsVersion[0..2].toDouble() >= 2.3) { - runtime ':hibernate:3.6.10.2' - } - runtime ":jquery:1.7.2" - compile ":constraints:0.6.0" - compile ":jquery-validation:1.9" - build ":release:3.0.1" + runtime(':hibernate:3.6.10.18') { + export = false + } + runtime ":jquery:1.11.1" + compile ":constraints:0.8.0" + compile ":asset-pipeline:2.3.2" + build ":release:3.1.1" } } diff --git a/grails-app/conf/JqueryValidationUiResources.groovy b/grails-app/conf/JqueryValidationUiResources.groovy deleted file mode 100644 index c433188..0000000 --- a/grails-app/conf/JqueryValidationUiResources.groovy +++ /dev/null @@ -1,12 +0,0 @@ -modules = { - 'jquery-validation-ui' { - dependsOn 'jquery, jquery-validate' - resource id:"validation-methods", url:[plugin:'jqueryValidationUi', dir:'js/jquery-validation-ui', file:'grails-validation-methods.js'] - } - - 'jquery-validation-ui-qtip' { - dependsOn 'jquery-validation-ui' - resource id:"qtip", url:[plugin:'jqueryValidationUi', dir:'js/qTip', file:'jquery.qtip.pack.js'] - resource id:"qtip-theme", url:[plugin:'jqueryValidationUi', dir:'css/qTip', file:'jquery.qtip.css'] - } -} diff --git a/grails-app/services/org/grails/jquery/validation/ui/JqueryValidationService.groovy b/grails-app/services/org/grails/jquery/validation/ui/JqueryValidationService.groovy index e2373f4..497d82e 100644 --- a/grails-app/services/org/grails/jquery/validation/ui/JqueryValidationService.groovy +++ b/grails-app/services/org/grails/jquery/validation/ui/JqueryValidationService.groovy @@ -1,522 +1,480 @@ -/* Copyright 2010-2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.grails.jquery.validation.ui - -import org.codehaus.groovy.grails.validation.ConstrainedPropertyBuilder -import org.codehaus.groovy.grails.commons.DefaultGrailsDomainClass -import org.codehaus.groovy.grails.web.pages.FastStringWriter -import grails.util.GrailsNameUtils -import org.springframework.web.context.request.RequestContextHolder - -/** - * - * @author Lim Chee Kin - * - * @since 0.1 - */ -class JqueryValidationService { - - static final String TYPE_MISMATCH_MESSAGE_PREFIX = "typeMismatch." - static final Integer VALIDATION_RULE_LENGTH = 15 - static final Integer VALIDATION_MESSAGE_LENGTH = 30 - static final ERROR_CODE_SUFFIXES = [ - "error", - "invalid" - ] - static final String VALUE_PLACEHOLDER = "[[[JqueryValidationUIValuePlaceholder]]]" - static final DEFAULT_ERROR_MESSAGE_CODES_MAP = [ - matches: "default.doesnt.match.message", - url: "default.invalid.url.message", - creditCard: "default.invalid.creditCard.message", - email: "default.invalid.email.message", - range: "default.invalid.range.message", - size: "default.invalid.size.message", - max: "default.invalid.max.message", - min: "default.invalid.min.message", - maxSize: "default.invalid.max.size.message", - minSize: "default.invalid.min.size.message", - inList: "default.not.inlist.message", - blank: "default.blank.message", - notEqual: "default.not.equal.message", - nullable: "default.null.message", - validator: "default.invalid.validator.message", - unique: "default.not.unique.message" - ] - static transactional = false - def grailsApplication - def messageSource - - Map getConstrainedProperties(Class validatableClass) { - def constrainedProperties - if (!validatableClass.constraints) { - throw new NullPointerException("validatableClass.constraints is null!") - } - if (validatableClass.constraints instanceof Closure) { - def validationClosure = validatableClass.constraints - def constrainedPropertyBuilder = new ConstrainedPropertyBuilder(validatableClass.newInstance()) - validationClosure.setDelegate(constrainedPropertyBuilder) - validationClosure() - constrainedProperties = constrainedPropertyBuilder.constrainedProperties - } else { - constrainedProperties = validatableClass.constraints - } - return constrainedProperties - } - - String createJavaScriptConstraints(List constrainedPropertiesEntries, Locale locale) { - FastStringWriter javaScriptConstraints = new FastStringWriter(VALIDATION_RULE_LENGTH * constrainedPropertiesEntries.size()) - String namespacedPropertyName - def constrainedPropertyValues - - constrainedPropertiesEntries.eachWithIndex { constrainedPropertiesEntry, entryIndex -> - constrainedPropertyValues = constrainedPropertiesEntry.constrainedProperties.values() - constrainedPropertyValues.eachWithIndex { constrainedProperty, propertyIndex -> - namespacedPropertyName = constrainedPropertiesEntry.namespace?"'${constrainedPropertiesEntry.namespace}.${constrainedProperty.propertyName}'":constrainedProperty.propertyName - javaScriptConstraints << "${namespacedPropertyName}: " - javaScriptConstraints << _createJavaScriptConstraints(constrainedProperty, locale, constrainedPropertiesEntry.namespace, false) - if (entryIndex == constrainedPropertiesEntries.size() - 1 && - propertyIndex == constrainedPropertyValues.size() - 1) { - javaScriptConstraints << "\n" - } else { - javaScriptConstraints << ",\n" - } - } - } - return javaScriptConstraints.toString() - } - - String createJavaScriptMessages(List constrainedPropertiesEntries, Locale locale) { - FastStringWriter javaScriptMessages = new FastStringWriter(VALIDATION_MESSAGE_LENGTH * constrainedPropertiesEntries.size()) - String namespacedPropertyName - def constrainedPropertyValues - - constrainedPropertiesEntries.eachWithIndex { constrainedPropertiesEntry, entryIndex -> - constrainedPropertyValues = constrainedPropertiesEntry.constrainedProperties.values() - constrainedPropertyValues.eachWithIndex { constrainedProperty, propertyIndex -> - namespacedPropertyName = constrainedPropertiesEntry.namespace?"'${constrainedPropertiesEntry.namespace}.${constrainedProperty.propertyName}'":constrainedProperty.propertyName - javaScriptMessages << "${namespacedPropertyName}: " - javaScriptMessages << _createJavaScriptMessages(constrainedProperty, locale, constrainedPropertiesEntry.namespace) - if (entryIndex == constrainedPropertiesEntries.size() - 1 && - propertyIndex == constrainedPropertyValues.size() - 1) { - javaScriptMessages << "\n" - } else { - javaScriptMessages << ",\n" - } - } - } - return javaScriptMessages.toString() - } - - String getValidationMetadatas(DefaultGrailsDomainClass domainClass, String[] properties, Locale locale) { - def constrainedProperties = _getConstrainedProperties(domainClass, properties) - String namespace - Integer dotIndex - Integer propertiesSize = properties.size() - FastStringWriter validationMetadatas = new FastStringWriter(propertiesSize * (VALIDATION_RULE_LENGTH + VALIDATION_MESSAGE_LENGTH)) - validationMetadatas << "{ " - properties.eachWithIndex { p, i -> - dotIndex = p.indexOf('.') - namespace = dotIndex > -1 ? p.substring(0, dotIndex) : null - // println "$i) \"$p\": \"${_createJavaScriptConstraints(constrainedProperties[p], locale, namespace, true) }\"" - validationMetadatas << "\"$p\": \"${_createJavaScriptConstraints(constrainedProperties[p], locale, namespace, true) }\"" - if (i < propertiesSize - 1) { - validationMetadatas << ", " - } else { - validationMetadatas << " " - } - } - validationMetadatas << " };" - println "validationMetadatas.toString() = ${validationMetadatas.toString()}" - return validationMetadatas.toString(); - } - - private Map getConstraintsMap(Class propertyType) { - def constraintsMap - if (propertyType == String) { - constraintsMap = grailsApplication.config.jqueryValidationUi.StringConstraintsMap - } else if (propertyType == Date) { - constraintsMap = grailsApplication.config.jqueryValidationUi.DateConstraintsMap - } else if (propertyType.superclass == Number) { - constraintsMap = grailsApplication.config.jqueryValidationUi.NumberConstraintsMap - } else if (propertyType.interfaces.find { it == Collection }) { - constraintsMap = grailsApplication.config.jqueryValidationUi.CollectionConstraintsMap - } else { - constraintsMap = grailsApplication.config.jqueryValidationUi.ObjectConstraintsMap - } - return constraintsMap - } - - private List getConstraintNames(def constrainedProperty) { - def constraintNames = constrainedProperty.appliedConstraints.collect { return it.name } - if (constraintNames.contains("blank") && constraintNames.contains("nullable")) { - constraintNames.remove("nullable") // blank constraint take precedence - } - return constraintNames - } - - private String createRemoteJavaScriptConstraints(String contextPath, String constraintName, String validatableClassName, String propertyName) { - String remoteJavaScriptConstraints = "\t${constraintName.equals('unique') || constraintName.equals('validator')?constraintName:'custom'}: {\n" + - "\turl: '${contextPath}/JQueryRemoteValidator/validate',\n" + - "\ttype: 'post',\n" + - "\tdata: {\n" + - "\t\tvalidatableClass: '${validatableClassName}',\n" + - "\t\tproperty: '${propertyName}'" - - if (!constraintName.equals('unique') && !constraintName.equals('validator')) { - remoteJavaScriptConstraints += ",\n\t\tconstraint: '${constraintName}'" - } - - if (constraintName.equals('unique')) { - def conf = grailsApplication.config.jqueryValidationUi.flatten() - String idSelector = conf.get("${GrailsNameUtils.getPropertyName(validatableClassName)}.${propertyName}.unique.idSelector" as String) - idSelector = idSelector?:conf.get("${propertyName}.unique.idSelector" as String) - if (idSelector) { - remoteJavaScriptConstraints += ",\n\t\tid: function() { return \$('$idSelector').length ? \$('$idSelector').val() : '0'; }" - } else { - remoteJavaScriptConstraints += ",\n\t\tid: function() { return \$('input:hidden#id', myForm).length ? \$('input:hidden#id', myForm).val() : '0'; }" - } - } - - remoteJavaScriptConstraints += "\n\t}\n\t}" - - return remoteJavaScriptConstraints - } - - private String getTypeMismatchMessage(Class validatableClass, Class propertyType, String propertyNamespace, String propertyName, Locale locale) { - def code - def defaultMessage = "Error message for ${code} undefined." - def message - - if (propertyNamespace) { - code = "${TYPE_MISMATCH_MESSAGE_PREFIX}${propertyNamespace}.${propertyName}" - } else { - code = "${TYPE_MISMATCH_MESSAGE_PREFIX}${validatableClass.name}.${propertyName}" - } - message = messageSource.getMessage(code, null, null, locale) - - if (!message) { - code = "${TYPE_MISMATCH_MESSAGE_PREFIX}${propertyName}" - message = messageSource.getMessage(code, null, null, locale) - } - - if (!message) { - code = "${TYPE_MISMATCH_MESSAGE_PREFIX}${propertyType.name}" - message = messageSource.getMessage(code, [propertyName].toArray(), defaultMessage, locale) - } - - return message.encodeAsJavaScript() - } - - private String getMessage(Class validatableClass, String propertyName, def args, String constraintName, Locale locale) { - def code = "${validatableClass.name}.${propertyName}.${constraintName}" - def defaultMessage = "Error message for ${code} undefined." - def message = messageSource.getMessage(code, args == null ? null : args.toArray(), null, locale) - - ERROR_CODE_SUFFIXES.each { errorSuffix -> - message = message?:messageSource.getMessage("${code}.${errorSuffix}", args == null ? null : args.toArray(), null, locale) - } - if (!message) { - code = "${GrailsNameUtils.getPropertyName(validatableClass)}.${propertyName}.${constraintName}" - message = messageSource.getMessage(code, args == null ? null : args.toArray(), null, locale) - } - - ERROR_CODE_SUFFIXES.each { errorSuffix -> - message = message?:messageSource.getMessage("${code}.${errorSuffix}", args == null ? null : args.toArray(), null, locale) - } - if (!message) { - code = DEFAULT_ERROR_MESSAGE_CODES_MAP[constraintName] - message = messageSource.getMessage(code, args == null ? null : args.toArray(), defaultMessage, locale) - } - - return message.encodeAsJavaScript().replace(VALUE_PLACEHOLDER.encodeAsJavaScript(), "' + \$('#${propertyName}').val() + '") - } - - private String _createJavaScriptConstraints(def constrainedProperty, Locale locale, String namespace, boolean forMetadata) { - FastStringWriter javaScriptConstraints = new FastStringWriter(forMetadata ? VALIDATION_RULE_LENGTH : VALIDATION_RULE_LENGTH + VALIDATION_MESSAGE_LENGTH) - def constraintsMap - String javaScriptConstraint - String javaScriptConstraintCode - - javaScriptConstraints << "{ " - constraintsMap = getConstraintsMap(constrainedProperty.propertyType) - def constraintNames = getConstraintNames(constrainedProperty) - - switch (constrainedProperty.propertyType) { - case Date: - javaScriptConstraintCode = "date: true" - break - case Long: - case Integer: - case Short: - case BigInteger: - javaScriptConstraintCode = "digits: true" - break - case Float: - case Double: - case BigDecimal: - javaScriptConstraintCode = "${(locale.country == 'br' || locale.country == 'de')?'numberDE':'number'}: true" - break - } - - if (javaScriptConstraintCode) { - javaScriptConstraints << javaScriptConstraintCode - if (constraintNames.size() > 0) { - javaScriptConstraints << ", " - } else { - javaScriptConstraints << " " - } - } - constraintNames.eachWithIndex { constraintName, i -> - javaScriptConstraint = constraintsMap[constraintName] - javaScriptConstraintCode = null - if (javaScriptConstraint) { - switch (constraintName) { - case "nullable": - if (!constrainedProperty.isNullable()) { - javaScriptConstraintCode = "${javaScriptConstraint}: true" - } - break - case "blank": - if (!constrainedProperty.isBlank()) { - javaScriptConstraintCode = "${javaScriptConstraint}: true" - } - break - case "creditCard": - if (constrainedProperty.isCreditCard()) { - javaScriptConstraintCode = "${javaScriptConstraint}: true" - } - break - case "email": - if (constrainedProperty.isEmail()) { - javaScriptConstraintCode = "${javaScriptConstraint}: true" - } - break - case "url": - if (constrainedProperty.isUrl()) { - javaScriptConstraintCode = "${javaScriptConstraint}: true" - } - break - case "inList": - javaScriptConstraintCode = "${javaScriptConstraint}: [" - if (constrainedProperty.propertyType == Date) { - constrainedProperty.inList.eachWithIndex { val, listIndex -> - javaScriptConstraintCode += "new Date(${val.time})" - javaScriptConstraintCode += listIndex < constrainedProperty.inList.size() - 1 ? ", " : "" - } - } else { - constrainedProperty.inList.eachWithIndex { val, listIndex -> - javaScriptConstraintCode += "'${val}'" - javaScriptConstraintCode += listIndex < constrainedProperty.inList.size() - 1 ? ", " : "" - } - } - javaScriptConstraintCode += "]" - break - case "matches": - javaScriptConstraintCode = "${javaScriptConstraint}: '${constrainedProperty.matches.replaceAll('\\\\', '\\\\\\\\')}'" - break - case "max": - javaScriptConstraintCode = "${javaScriptConstraint}: ${constrainedProperty.propertyType == Date ? "new Date(${constrainedProperty.max.time})" : constrainedProperty.max}" - break - case "maxSize": - javaScriptConstraintCode = "${javaScriptConstraint}: ${constrainedProperty.maxSize}" - break - case "min": - javaScriptConstraintCode = "${javaScriptConstraint}: ${constrainedProperty.propertyType == Date ? "new Date(${constrainedProperty.min.time})" : constrainedProperty.min}" - break - case "minSize": - javaScriptConstraintCode = "${javaScriptConstraint}: ${constrainedProperty.minSize}" - break - case "notEqual": - javaScriptConstraintCode = "${javaScriptConstraint}: ${constrainedProperty.propertyType == Date ? "new Date(${constrainedProperty.notEqual.time})" : "'${constrainedProperty.notEqual}'"}" - break - case "range": - def range = constrainedProperty.range - if (constrainedProperty.propertyType == Date) { - javaScriptConstraintCode = "${javaScriptConstraint}: [new Date(${range.from.time}), new Date(${range.to.time})]" - } else { - javaScriptConstraintCode = "${javaScriptConstraint}: [${range.from}, ${range.to}]" - } - break - case "size": - def size = constrainedProperty.size - javaScriptConstraintCode = "${javaScriptConstraint}: [${size.from}, ${size.to}]" - break - case "unique": - case "validator": - javaScriptConstraintCode = createRemoteJavaScriptConstraints(RequestContextHolder.requestAttributes.contextPath, constraintName, constrainedProperty.owningClass.name, constrainedProperty.propertyName) - break - } -} else { - def customConstraintsMap = grailsApplication.config.jqueryValidationUi.CustomConstraintsMap - if (customConstraintsMap && customConstraintsMap[constraintName]) { - javaScriptConstraintCode = "$constraintName: ${customConstraintsMap[constraintName]}" - } else { - log.info "${constraintName} constraint not found even in the CustomConstraintsMap, use custom constraint and remote validation" - javaScriptConstraintCode = createRemoteJavaScriptConstraints(RequestContextHolder.requestAttributes.contextPath, constraintName, constrainedProperty.owningClass.name, constrainedProperty.propertyName) - } -} -if (javaScriptConstraintCode) { - javaScriptConstraints << javaScriptConstraintCode - if (i < constraintNames.size() - 1) { - javaScriptConstraints << ", " - } else { - javaScriptConstraints << " " - } -} -} - -if (forMetadata) { - javaScriptConstraints << ", messages: ${_createJavaScriptMessages(constrainedProperty, locale, namespace)}" -} - -javaScriptConstraints << "}" - -return javaScriptConstraints.toString() -} - -private String _createJavaScriptMessages(def constrainedProperty, Locale locale, String namespace) { -def args = [] -FastStringWriter javaScriptMessages = new FastStringWriter(VALIDATION_MESSAGE_LENGTH) -String javaScriptConstraint -def constraintNames -String javaScriptMessageCode - - -def constraintsMap = getConstraintsMap(constrainedProperty.propertyType) -javaScriptMessages << "{ " -constraintNames = getConstraintNames(constrainedProperty) -javaScriptMessageCode = null -switch (constrainedProperty.propertyType) { -case Date: - javaScriptMessageCode = "date: '${getTypeMismatchMessage(constrainedProperty.owningClass, constrainedProperty.propertyType, namespace, constrainedProperty.propertyName, locale)}'" - break -case Long: -case Integer: -case Short: -case BigInteger: - javaScriptMessageCode = "digits: '${getTypeMismatchMessage(constrainedProperty.owningClass, constrainedProperty.propertyType, namespace, constrainedProperty.propertyName, locale)}'" - break -case Float: -case Double: -case BigDecimal: - if (locale.country == 'br' || locale.country == 'de') - javaScriptMessageCode = "numberDE: '${getTypeMismatchMessage(constrainedProperty.owningClass, constrainedProperty.propertyType, namespace, constrainedProperty.propertyName, locale)}'" - else - javaScriptMessageCode = "number: '${getTypeMismatchMessage(constrainedProperty.owningClass, constrainedProperty.propertyType, namespace, constrainedProperty.propertyName, locale)}'" - break -} - -if (javaScriptMessageCode) { -javaScriptMessages << javaScriptMessageCode -if (constraintNames.size() > 0) { - javaScriptMessages << ", " -} else { - javaScriptMessages << " " -} -} - -constraintNames.eachWithIndex { constraintName, i -> -javaScriptConstraint = constraintsMap[constraintName] -javaScriptMessageCode = null -args.clear() -args = [constrainedProperty.propertyName, constrainedProperty.owningClass] -if (javaScriptConstraint) { - switch (constraintName) { - case "nullable": - if (!constrainedProperty.isNullable()) { - javaScriptMessageCode = "${javaScriptConstraint}: '${getMessage(constrainedProperty.owningClass, constrainedProperty.propertyName, args, constraintName, locale)}'" - } - case "blank": - if (!constrainedProperty.isBlank()) { - javaScriptMessageCode = "${javaScriptConstraint}: '${getMessage(constrainedProperty.owningClass, constrainedProperty.propertyName, args, constraintName, locale)}'" - } - break - case "creditCard": - case "email": - case "url": - if (constrainedProperty.isCreditCard() || constrainedProperty.isEmail() || constrainedProperty.isUrl()) { - args << VALUE_PLACEHOLDER - javaScriptMessageCode = "${javaScriptConstraint}: function() { return '${getMessage(constrainedProperty.owningClass, constrainedProperty.propertyName, args, constraintName, locale)}'; }" - } - break - case "inList": - case "matches": - case "max": - case "maxSize": - case "min": - case "minSize": - case "notEqual": - args << VALUE_PLACEHOLDER - args << constrainedProperty."${constraintName}" - javaScriptMessageCode = "${javaScriptConstraint}: function() { return '${getMessage(constrainedProperty.owningClass, constrainedProperty.propertyName, args, constraintName, locale)}'; }" - break - - case "range": - case "size": - args << VALUE_PLACEHOLDER - def range = constrainedProperty."${constraintName}" - args << range.from - args << range.to - javaScriptMessageCode = "${javaScriptConstraint}: function() { return '${getMessage(constrainedProperty.owningClass, constrainedProperty.propertyName, args, constraintName, locale)}'; }" - break - - case "unique": - case "validator": - args << VALUE_PLACEHOLDER - javaScriptMessageCode = "${javaScriptConstraint}: function() { return '${getMessage(constrainedProperty.owningClass, constrainedProperty.propertyName, args, constraintName, locale)}'; }" - break - } -} else { - def customConstraintsMap = grailsApplication.config.jqueryValidationUi.CustomConstraintsMap - if (customConstraintsMap && customConstraintsMap[constraintName]) { - args << VALUE_PLACEHOLDER - javaScriptMessageCode = "${constraintName}: function() { return '${getMessage(constrainedProperty.owningClass, constrainedProperty.propertyName, args, constraintName, locale)}'; }" - } // else remote validation, using remote message. -} -if (javaScriptMessageCode) { - javaScriptMessages << javaScriptMessageCode - if (i < constraintNames.size() - 1) { - javaScriptMessages << ", " - } else { - javaScriptMessages << " " - } -} -} -javaScriptMessages << "}" - -return javaScriptMessages.toString() -} - -private _getConstrainedProperties(DefaultGrailsDomainClass domainClass, String[] properties) { - def constrainedProperties = domainClass.constrainedProperties.findAll{ k, v -> properties.contains(k) } - def childConstrainedProperties - def dotProperties = properties.findAll { it.indexOf('.') > -1 } // nested/embedded class - def dotPropertiesValues = dotProperties.collect { it.substring(0, it.indexOf('.')) }.unique() - def dotConstrainedProperties = domainClass.constrainedProperties.findAll{ k, v -> dotPropertiesValues.contains(k) } - dotConstrainedProperties.each { propertyName, constrainedProperty -> - childConstrainedProperties = getConstrainedProperties(constrainedProperty.propertyType).findAll{ k, v -> dotProperties.contains("$propertyName.$k" as String) } // contains() only work after converted to String - childConstrainedProperties.each { k, v -> - constrainedProperties["$propertyName.$k" as String] = v - } - } - - /*constrainedProperties.each { k, v -> - println "* $k = $v" - }*/ - - return constrainedProperties -} - -} +/* Copyright 2010-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.grails.jquery.validation.ui + +import org.codehaus.groovy.grails.validation.ConstrainedPropertyBuilder +import org.codehaus.groovy.grails.commons.DefaultGrailsDomainClass +import org.codehaus.groovy.grails.web.pages.FastStringWriter +import grails.util.GrailsNameUtils +import org.springframework.web.context.request.RequestContextHolder + +/** + * + * @author Lim Chee Kin + * + * @since 0.1 + */ +class JqueryValidationService { + + static final String TYPE_MISMATCH_MESSAGE_PREFIX = "typeMismatch." + static final Integer VALIDATION_RULE_LENGTH = 15 + static final Integer VALIDATION_MESSAGE_LENGTH = 30 + static final ERROR_CODE_SUFFIXES = [ + "error", + "invalid" + ] + static final String VALUE_PLACEHOLDER = "[[[JqueryValidationUIValuePlaceholder]]]" + static final DEFAULT_ERROR_MESSAGE_CODES_MAP = [ + matches: "default.doesnt.match.message", + url: "default.invalid.url.message", + creditCard: "default.invalid.creditCard.message", + email: "default.invalid.email.message", + range: "default.invalid.range.message", + size: "default.invalid.size.message", + max: "default.invalid.max.message", + min: "default.invalid.min.message", + maxSize: "default.invalid.max.size.message", + minSize: "default.invalid.min.size.message", + inList: "default.not.inlist.message", + blank: "default.blank.message", + notEqual: "default.not.equal.message", + nullable: "default.null.message", + validator: "default.invalid.validator.message", + unique: "default.not.unique.message" + ] + static transactional = false + def grailsApplication + def messageSource + + Map getConstrainedProperties(Class validatableClass) { + def constrainedProperties + if (!validatableClass.constraints) { + throw new NullPointerException("validatableClass.constraints is null!") + } + if (validatableClass.constraints instanceof Closure) { + def validationClosure = validatableClass.constraints + def constrainedPropertyBuilder = new ConstrainedPropertyBuilder(validatableClass.newInstance()) + validationClosure.setDelegate(constrainedPropertyBuilder) + validationClosure() + constrainedProperties = constrainedPropertyBuilder.constrainedProperties + } else { + constrainedProperties = validatableClass.constraints + } + return constrainedProperties + } + + String createJavaScriptConstraints(List constrainedPropertiesEntries, Locale locale) { + FastStringWriter javaScriptConstraints = new FastStringWriter(VALIDATION_RULE_LENGTH * constrainedPropertiesEntries.size()) + String namespacedPropertyName + def constrainedPropertyValues + + constrainedPropertiesEntries.eachWithIndex { constrainedPropertiesEntry, entryIndex -> + constrainedPropertyValues = constrainedPropertiesEntry.constrainedProperties.values() + constrainedPropertyValues.eachWithIndex { constrainedProperty, propertyIndex -> + namespacedPropertyName = constrainedPropertiesEntry.namespace?"'${constrainedPropertiesEntry.namespace}.${constrainedProperty.propertyName}'":constrainedProperty.propertyName + javaScriptConstraints << "${namespacedPropertyName}: " + javaScriptConstraints << _createJavaScriptConstraints(constrainedProperty, locale, constrainedPropertiesEntry.namespace, false) + if (entryIndex == constrainedPropertiesEntries.size() - 1 && + propertyIndex == constrainedPropertyValues.size() - 1) { + javaScriptConstraints << "\n" + } else { + javaScriptConstraints << ",\n" + } + } + } + return javaScriptConstraints.toString() + } + + String createJavaScriptMessages(List constrainedPropertiesEntries, String formName, Locale locale) { + FastStringWriter javaScriptMessages = new FastStringWriter(VALIDATION_MESSAGE_LENGTH * constrainedPropertiesEntries.size()) + String namespacedPropertyName + def constrainedPropertyValues + + constrainedPropertiesEntries.eachWithIndex { constrainedPropertiesEntry, entryIndex -> + constrainedPropertyValues = constrainedPropertiesEntry.constrainedProperties.values() + constrainedPropertyValues.eachWithIndex { constrainedProperty, propertyIndex -> + namespacedPropertyName = constrainedPropertiesEntry.namespace?"'${constrainedPropertiesEntry.namespace}.${constrainedProperty.propertyName}'":constrainedProperty.propertyName + javaScriptMessages << "${namespacedPropertyName}: " + javaScriptMessages << _createJavaScriptMessages(constrainedProperty, formName, locale, constrainedPropertiesEntry.namespace) + if (entryIndex == constrainedPropertiesEntries.size() - 1 && + propertyIndex == constrainedPropertyValues.size() - 1) { + javaScriptMessages << "\n" + } else { + javaScriptMessages << ",\n" + } + } + } + return javaScriptMessages.toString() + } + + String getValidationMetadatas(DefaultGrailsDomainClass domainClass, String[] properties, Locale locale) { + def constrainedProperties = _getConstrainedProperties(domainClass, properties) + String namespace + Integer dotIndex + Integer propertiesSize = properties.size() + FastStringWriter validationMetadatas = new FastStringWriter(propertiesSize * (VALIDATION_RULE_LENGTH + VALIDATION_MESSAGE_LENGTH)) + validationMetadatas << "{ " + properties.eachWithIndex { p, i -> + dotIndex = p.indexOf('.') + namespace = dotIndex > -1 ? p.substring(0, dotIndex) : null + // println "$i) \"$p\": \"${_createJavaScriptConstraints(constrainedProperties[p], locale, namespace, true) }\"" + validationMetadatas << "\"$p\": \"${_createJavaScriptConstraints(constrainedProperties[p], locale, namespace, true) }\"" + if (i < propertiesSize - 1) { + validationMetadatas << ", " + } else { + validationMetadatas << " " + } + } + validationMetadatas << " };" + println "validationMetadatas.toString() = ${validationMetadatas.toString()}" + return validationMetadatas.toString(); + } + + private Map getConstraintsMap(Class propertyType) { + def constraintsMap + if (propertyType == String) { + constraintsMap = grailsApplication.config.jqueryValidationUi.StringConstraintsMap + } else if (propertyType == Date) { + constraintsMap = grailsApplication.config.jqueryValidationUi.DateConstraintsMap + } else if (propertyType.superclass == Number) { + constraintsMap = grailsApplication.config.jqueryValidationUi.NumberConstraintsMap + } else if (propertyType.interfaces.find { it == Collection }) { + constraintsMap = grailsApplication.config.jqueryValidationUi.CollectionConstraintsMap + } else { + constraintsMap = grailsApplication.config.jqueryValidationUi.ObjectConstraintsMap + } + return constraintsMap + } + + private List getConstraintNames(def constrainedProperty) { + def constraintNames = constrainedProperty.appliedConstraints.collect { return it.name } + if (constraintNames.contains("blank") && constraintNames.contains("nullable")) { + constraintNames.remove("nullable") // blank constraint take precedence + } + return constraintNames + } + + private String createRemoteJavaScriptConstraints(String contextPath, String constraintName, String validatableClassName, String propertyName) { + String remoteJavaScriptConstraints = "\t${constraintName.equals('unique') || constraintName.equals('validator')?'remote':'custom'}: {\n" + + "\turl: '${contextPath}/JQueryRemoteValidator/validate',\n" + + "\tasync: false,\n" + + "\ttype: 'post',\n" + + "\tdata: {\n" + + "\t\tvalidatableClass: '${validatableClassName}',\n" + + "\t\tproperty: '${propertyName}'" + + //if (!constraintName.equals('unique') && !constraintName.equals('validator')) { + remoteJavaScriptConstraints += ",\n\t\tconstraint: '${constraintName}'" + //} + + if (constraintName.equals('unique')) { + def conf = grailsApplication.config.jqueryValidationUi.flatten() + String idSelector = conf.get("${GrailsNameUtils.getPropertyName(validatableClassName)}.${propertyName}.unique.idSelector" as String) + idSelector = idSelector?:conf.get("${propertyName}.unique.idSelector" as String) + if (idSelector) { + remoteJavaScriptConstraints += ",\n\t\tid: function() { return \$('$idSelector').length ? \$('$idSelector').val() : '0'; }" + } else { + remoteJavaScriptConstraints += ",\n\t\tid: function() { return \$('input:hidden#id', myForm).length ? \$('input:hidden#id', myForm).val() : '0'; }" + } + } + + remoteJavaScriptConstraints += "\n\t}\n\t}" + + return remoteJavaScriptConstraints + } + + private String getTypeMismatchMessage(Class validatableClass, Class propertyType, String propertyNamespace, String propertyName, Locale locale) { + def code + def defaultMessage = "Error message for ${code} undefined." + def message + + if (propertyNamespace) { + code = "${TYPE_MISMATCH_MESSAGE_PREFIX}${propertyNamespace}.${propertyName}" + } else { + code = "${TYPE_MISMATCH_MESSAGE_PREFIX}${validatableClass.name}.${propertyName}" + } + message = messageSource.getMessage(code, null, null, locale) + + if (!message) { + code = "${TYPE_MISMATCH_MESSAGE_PREFIX}${propertyName}" + message = messageSource.getMessage(code, null, null, locale) + } + + if (!message) { + code = "${TYPE_MISMATCH_MESSAGE_PREFIX}${propertyType.name}" + message = messageSource.getMessage(code, [propertyName].toArray(), defaultMessage, locale) + } + + return message.encodeAsJavaScript() + } + + String getMessage(Class validatableClass, String propertyName, String formName, List args, String constraintName, Locale locale) { + def code = "${validatableClass.name}.${propertyName}.${constraintName}" + def defaultMessage = "Error message for ${code} undefined." + def message = messageSource.getMessage(code, args == null ? null : args.toArray(), null, locale) + + ERROR_CODE_SUFFIXES.each { errorSuffix -> + message = message?:messageSource.getMessage("${code}.${errorSuffix}", args == null ? null : args.toArray(), null, locale) + } + if (!message) { + code = "${GrailsNameUtils.getPropertyName(validatableClass)}.${propertyName}.${constraintName}" + message = messageSource.getMessage(code, args == null ? null : args.toArray(), null, locale) + } + + ERROR_CODE_SUFFIXES.each { errorSuffix -> + message = message?:messageSource.getMessage("${code}.${errorSuffix}", args == null ? null : args.toArray(), null, locale) + } + if (!message) { + code = DEFAULT_ERROR_MESSAGE_CODES_MAP[constraintName] + message = messageSource.getMessage(code, args == null ? null : args.toArray(), defaultMessage, locale) + } + + return message.encodeAsJavaScript().replace(VALUE_PLACEHOLDER.encodeAsJavaScript(), "' + \$('form[name=${formName}] [name=${propertyName}]').val() + '") + } + + private String _createJavaScriptConstraints(def constrainedProperty, Locale locale, String namespace, boolean forMetadata) { + def constraintsMap + String javaScriptConstraint + def constraintsCodeList = [] + + constraintsMap = getConstraintsMap(constrainedProperty.propertyType) + def constraintNames = getConstraintNames(constrainedProperty) + + switch (constrainedProperty.propertyType) { + case Date: + constraintsCodeList << "date: true" + break + case Long: + case Integer: + case Short: + case BigInteger: + constraintsCodeList << "digits: true" + break + case Float: + case Double: + case BigDecimal: + constraintsCodeList << "${(locale.country == 'br' || locale.country == 'de')?'numberDE':'number'}: true" + break + } + + constraintNames.eachWithIndex { constraintName, i -> + javaScriptConstraint = constraintsMap[constraintName] + if (javaScriptConstraint) { + switch (constraintName) { + case "nullable": + if (!constrainedProperty.isNullable()) { + constraintsCodeList << "${javaScriptConstraint}: true" + } + break + case "blank": + if (!constrainedProperty.isBlank()) { + constraintsCodeList << "${javaScriptConstraint}: true" + } + break + case "creditCard": + if (constrainedProperty.isCreditCard()) { + constraintsCodeList << "${javaScriptConstraint}: true" + } + break + case "email": + if (constrainedProperty.isEmail()) { + constraintsCodeList << "${javaScriptConstraint}: true" + } + break + case "url": + if (constrainedProperty.isUrl()) { + constraintsCodeList << "${javaScriptConstraint}: true" + } + break + case "inList": + String javaScriptConstraintCode = "${javaScriptConstraint}: [" + if (constrainedProperty.propertyType == Date) { + constrainedProperty.inList.eachWithIndex { val, listIndex -> + javaScriptConstraintCode += "new Date(${val.time})" + javaScriptConstraintCode += listIndex < constrainedProperty.inList.size() - 1 ? ", " : "" + } + } else { + constrainedProperty.inList.eachWithIndex { val, listIndex -> + javaScriptConstraintCode += "'${val}'" + javaScriptConstraintCode += listIndex < constrainedProperty.inList.size() - 1 ? ", " : "" + } + } + javaScriptConstraintCode += "]" + constraintsCodeList << javaScriptConstraintCode + break + case "matches": + constraintsCodeList << "${javaScriptConstraint}: '${constrainedProperty.matches.replaceAll('\\\\', '\\\\\\\\')}'" + break + case "max": + constraintsCodeList << "${javaScriptConstraint}: ${constrainedProperty.propertyType == Date ? "new Date(${constrainedProperty.max.time})" : constrainedProperty.max}" + break + case "maxSize": + constraintsCodeList << "${javaScriptConstraint}: ${constrainedProperty.maxSize}" + break + case "min": + constraintsCodeList << "${javaScriptConstraint}: ${constrainedProperty.propertyType == Date ? "new Date(${constrainedProperty.min.time})" : constrainedProperty.min}" + break + case "minSize": + constraintsCodeList << "${javaScriptConstraint}: ${constrainedProperty.minSize}" + break + case "notEqual": + constraintsCodeList << "${javaScriptConstraint}: ${constrainedProperty.propertyType == Date ? "new Date(${constrainedProperty.notEqual.time})" : "'${constrainedProperty.notEqual}'"}" + break + case "range": + def range = constrainedProperty.range + if (constrainedProperty.propertyType == Date) { + constraintsCodeList << "${javaScriptConstraint}: [new Date(${range.from.time}), new Date(${range.to.time})]" + } else { + constraintsCodeList << "${javaScriptConstraint}: [${range.from}, ${range.to}]" + } + break + case "size": + def size = constrainedProperty.size + constraintsCodeList << "${javaScriptConstraint}: [${size.from}, ${size.to}]" + break + case "unique": + case "validator": + constraintsCodeList << createRemoteJavaScriptConstraints(RequestContextHolder.requestAttributes.contextPath, constraintName, constrainedProperty.owningClass.name, constrainedProperty.propertyName) + break + } + } else { + def customConstraintsMap = grailsApplication.config.jqueryValidationUi.CustomConstraintsMap + if (customConstraintsMap && customConstraintsMap[constraintName]) { + constraintsCodeList << "$constraintName: ${customConstraintsMap[constraintName]}" + } else { + log.info "${constraintName} constraint not found even in the CustomConstraintsMap, use custom constraint and remote validation" + constraintsCodeList << createRemoteJavaScriptConstraints(RequestContextHolder.requestAttributes.contextPath, constraintName, constrainedProperty.owningClass.name, constrainedProperty.propertyName) + } + } + } + + if (forMetadata) { + constraintsCodeList << "messages: ${_createJavaScriptMessages(constrainedProperty, locale, namespace)}" + } + + return "{ ${constraintsCodeList.join(", ")} }" + } + + private String _createJavaScriptMessages(def constrainedProperty, String formName, Locale locale, String namespace) { + def args = [] + def javaScriptMessagesList = [] + String javaScriptConstraint + def constraintNames + + def constraintsMap = getConstraintsMap(constrainedProperty.propertyType) + constraintNames = getConstraintNames(constrainedProperty) + switch (constrainedProperty.propertyType) { + case Date: + javaScriptMessagesList << "date: '${getTypeMismatchMessage(constrainedProperty.owningClass, constrainedProperty.propertyType, namespace, constrainedProperty.propertyName, locale)}'" + break + case Long: + case Integer: + case Short: + case BigInteger: + javaScriptMessagesList << "digits: '${getTypeMismatchMessage(constrainedProperty.owningClass, constrainedProperty.propertyType, namespace, constrainedProperty.propertyName, locale)}'" + break + case Float: + case Double: + case BigDecimal: + if (locale.country == 'br' || locale.country == 'de') + javaScriptMessagesList << "numberDE: '${getTypeMismatchMessage(constrainedProperty.owningClass, constrainedProperty.propertyType, namespace, constrainedProperty.propertyName, locale)}'" + else + javaScriptMessagesList << "number: '${getTypeMismatchMessage(constrainedProperty.owningClass, constrainedProperty.propertyType, namespace, constrainedProperty.propertyName, locale)}'" + break + } + + constraintNames.eachWithIndex { constraintName, i -> + javaScriptConstraint = constraintsMap[constraintName] + + args.clear() + args = [constrainedProperty.propertyName, constrainedProperty.owningClass] + if (javaScriptConstraint) { + switch (constraintName) { + case "nullable": + if (!constrainedProperty.isNullable()) { + javaScriptMessagesList << "${javaScriptConstraint}: '${getMessage(constrainedProperty.owningClass, constrainedProperty.propertyName, formName, args, constraintName, locale)}'" + } + case "blank": + if (!constrainedProperty.isBlank()) { + javaScriptMessagesList << "${javaScriptConstraint}: '${getMessage(constrainedProperty.owningClass, constrainedProperty.propertyName, formName, args, constraintName, locale)}'" + } + break + case "creditCard": + case "email": + case "url": + if (constrainedProperty.isCreditCard() || constrainedProperty.isEmail() || constrainedProperty.isUrl()) { + args << VALUE_PLACEHOLDER + javaScriptMessagesList << "${javaScriptConstraint}: function() { return '${getMessage(constrainedProperty.owningClass, constrainedProperty.propertyName, formName, args, constraintName, locale)}'; }" + } + break + case "inList": + case "matches": + case "max": + case "maxSize": + case "min": + case "minSize": + case "notEqual": + args << VALUE_PLACEHOLDER + args << constrainedProperty."${constraintName}" + javaScriptMessagesList << "${javaScriptConstraint}: function() { return '${getMessage(constrainedProperty.owningClass, constrainedProperty.propertyName, formName, args, constraintName, locale)}'; }" + break + + case "range": + case "size": + args << VALUE_PLACEHOLDER + def range = constrainedProperty."${constraintName}" + args << range.from + args << range.to + javaScriptMessagesList << "${javaScriptConstraint}: function() { return '${getMessage(constrainedProperty.owningClass, constrainedProperty.propertyName, formName, args, constraintName, locale)}'; }" + break + + case "unique": + case "validator": + args << VALUE_PLACEHOLDER + javaScriptMessagesList << "remote: function() { return '${getMessage(constrainedProperty.owningClass, constrainedProperty.propertyName, formName, args, constraintName, locale)}'; }" + break + } + } else { + def customConstraintsMap = grailsApplication.config.jqueryValidationUi.CustomConstraintsMap + if (customConstraintsMap && customConstraintsMap[constraintName]) { + args << VALUE_PLACEHOLDER + javaScriptMessagesList << "${constraintName}: function() { return '${getMessage(constrainedProperty.owningClass, constrainedProperty.propertyName, formName, args, constraintName, locale)}'; }" + } // else remote validation, using remote message. + } + } + return "{ ${javaScriptMessagesList.join(", ")} }" + } + + private _getConstrainedProperties(DefaultGrailsDomainClass domainClass, String[] properties) { + def constrainedProperties = domainClass.constrainedProperties.findAll{ k, v -> properties.contains(k) } + def childConstrainedProperties + def dotProperties = properties.findAll { it.indexOf('.') > -1 } // nested/embedded class + def dotPropertiesValues = dotProperties.collect { it.substring(0, it.indexOf('.')) }.unique() + def dotConstrainedProperties = domainClass.constrainedProperties.findAll{ k, v -> dotPropertiesValues.contains(k) } + dotConstrainedProperties.each { propertyName, constrainedProperty -> + childConstrainedProperties = getConstrainedProperties(constrainedProperty.propertyType).findAll{ k, v -> dotProperties.contains("$propertyName.$k" as String) } // contains() only work after converted to String + childConstrainedProperties.each { k, v -> + constrainedProperties["$propertyName.$k" as String] = v + } + } + + /*constrainedProperties.each { k, v -> + println "* $k = $v" + }*/ + + return constrainedProperties + } + +} diff --git a/grails-app/taglib/org/grails/jquery/validation/ui/JQueryValidationUiTagLib.groovy b/grails-app/taglib/org/grails/jquery/validation/ui/JQueryValidationUiTagLib.groovy index 3818a8f..174280d 100644 --- a/grails-app/taglib/org/grails/jquery/validation/ui/JQueryValidationUiTagLib.groovy +++ b/grails-app/taglib/org/grails/jquery/validation/ui/JQueryValidationUiTagLib.groovy @@ -1,255 +1,257 @@ -/* Copyright 2010-2013 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.grails.jquery.validation.ui - -import org.springframework.web.servlet.support.RequestContextUtils as RCU -import grails.util.GrailsNameUtils -import java.lang.reflect.Field -import org.springframework.util.ReflectionUtils - -/** - * - * @author Lim Chee Kin - * - * @since 0.1 - */ -class JQueryValidationUiTagLib { - static namespace = "jqvalui" - static final String TAG_ERROR_PREFIX = "Tag [jqvalui:renderValidationScript] Error: " - - def jqueryValidationService - - def resources = { attrs, body -> - String type = attrs.remove("type") - def packed - if (!type) { - packed = grailsApplication.config.jqueryValidationUi.qTip.get("packed", true) - renderJavaScript g.resource(plugin:"jqueryValidationUi", dir:"js/qTip", file:"jquery.qtip.${packed?'pack.js':'js'}") - renderJavaScript g.resource(plugin:"jqueryValidationUi", dir:"js/jquery-validation-ui", file:"grails-validation-methods.js") - renderCSS g.resource(plugin:"jqueryValidationUi", dir:"css/qTip", file:"jquery.qtip.css") - } else if (type.equals("js")) { - packed = grailsApplication.config.jqueryValidationUi.qTip.get("packed", true) - renderJavaScript g.resource(plugin:"jqueryValidationUi", dir:"js/qTip", file:"jquery.qtip.${packed?'pack.js':'js'}") - renderJavaScript g.resource(plugin:"jqueryValidationUi", dir:"js/jquery-validation-ui", file:"grails-validation-methods.js") - } else if (type.equals("css")) { - renderCSS g.resource(plugin:"jqueryValidationUi", dir:"css/qTip", file:"jquery.qtip.css") - } - } - - def renderErrors = { attrs, body -> - def renderErrorsOnTop = attrs.render ? Boolean.valueOf(attrs.render) : grailsApplication.config.jqueryValidationUi.get("renderErrorsOnTop", true) - if (renderErrorsOnTop) { - String qTipClasses = grailsApplication.config.jqueryValidationUi.qTip.classes?:"" - String style = attrs.remove("style")?:'' - String bodyText = body() - def writer = getOut() - writer << """ - - - - -""" - if (bodyText) { - writer << bodyText - } else { - writer << '' - } - writer << """ - - - -""" - } - } - - def renderError = { attrs, body -> - String labelFor = attrs.remove("for") - if (!labelFor) { - throwTagError("Tag [jqvalui:renderError] Error: Tag missing required attribute [for]") - } - def renderErrorsOnTop = grailsApplication.config.jqueryValidationUi.get("renderErrorsOnTop", true) - if (!renderErrorsOnTop) { - String style = attrs.remove("style")?:'' - String qTipClasses = grailsApplication.config.jqueryValidationUi.qTip.classes?:"" - def writer = getOut() - writer << """ - - - - - - - - """ - writer << body() - writer << """ - - - -""" - } - } - - private String getConnectorColor() { - def jQueryUiStyle = grailsApplication.config.jqueryValidationUi.qTip.get("jQueryUiStyle", false) - return "rgb(217, 82, 82)" - } - - private renderJavaScript(def url) { - out << '\n' - } - - private renderCSS(def url) { - out << '\n' - } - - def renderValidationScript = { attrs, body -> - String forClass = attrs.remove("for") - def alsoProperties = attrs.remove("also") - def notProperties = attrs.remove("not") - String form = attrs.remove("form") - def config = grailsApplication.config.jqueryValidationUi - def jQueryUiStyle = config.qTip.get("jQueryUiStyle", false) - String qTipClasses = config.qTip.classes?:"" - String errorClass = attrs.errorClass?:config.errorClass?:"error" - String validClass = attrs.validClass?:config.validClass?:"valid" - String errorContainer = attrs.errorContainer?:config.errorContainer?:"#errorContainer" - String errorLabelContainer = attrs.errorLabelContainer?:config.errorLabelContainer?:"div.errors ul" - String errorElement = attrs.errorElement?:config.errorElement?:"label" - String errorWrapper = attrs.errorWrapper?:config.errorWrapper?:"li" - - - def onsubmit = attrs.onsubmit ? Boolean.valueOf(attrs.onsubmit) : config.get("onsubmit", true) - def onkeyup = attrs.onkeyup ?: config.get("onkeyup", false) - def qtip = attrs.qtip ? Boolean.valueOf(attrs.qtip) : config.get("qtip", false) - - def submitHandler = attrs.remove("submitHandler")?:config.submitHandler?:null - def highlightHandler = attrs.remove("highlight")?:config.highlight?:null - def unhighlightHandler = attrs.remove("unhighlight")?:config.unhighlight?:null - - - def renderErrorsOnTop = attrs.renderErrorsOnTop ? Boolean.valueOf(attrs.renderErrorsOnTop) : config.get("renderErrorsOnTop", true) - String renderErrorsOptions = "" - Locale locale = RCU.getLocale(request) - - if (!forClass) { - throwTagError("${TAG_ERROR_PREFIX}Tag missing required attribute [for]") - } - def validatableClass = grailsApplication.classLoader.loadClass(forClass) - if (!validatableClass) { - throwTagError("${TAG_ERROR_PREFIX}Invalid validatableClass defined in attribute [for], $validatableClassName not found!") - } - - if (notProperties) { - notProperties = notProperties.split(',').collect { it.trim() } - } - if (alsoProperties) { - alsoProperties = alsoProperties.split(',').collect { it.trim() } - if (notProperties) { - notProperties.addAll(alsoProperties) - } else { - notProperties = new ArrayList(alsoProperties) - } - } - - if (renderErrorsOnTop) { - renderErrorsOptions = """ -errorContainer: '$errorContainer', -errorLabelContainer: '$errorLabelContainer', -wrapper: '$errorWrapper', -""" - } else if (qtip) { - renderErrorsOptions = """ -success: function(label) -{ - \$('[id="' + label.attr('for') + '"]').qtip('destroy'); -}, -errorPlacement: function(error, element) -{ - if (\$(error).text()) - \$(element).filter(':not(.${validClass})').qtip({ - overwrite: true, - content: error, - position: { my: 'left center', at: 'right center' }, - show: { - event: false, - ready: true - }, - hide: false, - style: { - widget: ${jQueryUiStyle}, - classes: '${qTipClasses}', - tip: true - } - }); -}, -""" - } - - ConstrainedPropertiesEntry rootConstrainedPropertiesEntry = new ConstrainedPropertiesEntry(validatableClass: validatableClass) - rootConstrainedPropertiesEntry.constrainedProperties = jqueryValidationService.getConstrainedProperties(validatableClass) - if (notProperties) { - def rootNotProperties = notProperties.findAll { it.indexOf('.') == -1 } - notProperties.removeAll(rootNotProperties) - rootConstrainedPropertiesEntry.constrainedProperties = rootConstrainedPropertiesEntry.constrainedProperties.findAll{ k, v -> !rootNotProperties.contains(k) } - } - def constrainedPropertiesEntries = [rootConstrainedPropertiesEntry] - ConstrainedPropertiesEntry childConstrainedPropertiesEntry - def childNotProperties - Class childValidatableClass - alsoProperties.each { propertyName -> - childValidatableClass = findField(validatableClass, propertyName).type - childConstrainedPropertiesEntry = new ConstrainedPropertiesEntry(namespace:propertyName, validatableClass: childValidatableClass) - childConstrainedPropertiesEntry.constrainedProperties = jqueryValidationService.getConstrainedProperties(childValidatableClass) - if (notProperties) { - childNotProperties = notProperties.collect {it.indexOf(propertyName) != -1 ? it.substring(propertyName.length() + 1) : null } - notProperties.removeAll(childNotProperties) - childConstrainedPropertiesEntry.constrainedProperties = childConstrainedPropertiesEntry.constrainedProperties.findAll{ k, v -> !childNotProperties.contains(k) } - } - constrainedPropertiesEntries << childConstrainedPropertiesEntry - } - String rules = jqueryValidationService.createJavaScriptConstraints(constrainedPropertiesEntries, locale) - String messages = jqueryValidationService.createJavaScriptMessages(constrainedPropertiesEntries, locale) - out << render(plugin: 'jqueryValidationUi', template: '/taglib/renderValidationScript', - model: [ - form: form, onkeyup: onkeyup, errorClass: errorClass, - errorElement: errorElement, validClass: validClass, onsubmit: onsubmit, - submitHandler: submitHandler, highlightHandler: highlightHandler, - unhighlightHandler: unhighlightHandler, renderErrorsOptions: renderErrorsOptions, - rules: rules, messages: messages - ]) - } - - private Field findField(Class clazz, String name) { - Field field - - if (name.indexOf('.') == -1) { - field = ReflectionUtils.findField(clazz, name) - } else { - Class declaringClass = clazz - def fieldNames = name.split("\\.") - for (fieldName in fieldNames) { - field = ReflectionUtils.findField(declaringClass, fieldName) - if (!field) { - throw new IllegalArgumentException("Property $name invalid!") - } - declaringClass = field.type - } - } - return field - } -} - - +/* Copyright 2010-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.grails.jquery.validation.ui + +import org.springframework.web.servlet.support.RequestContextUtils as RCU +import grails.util.GrailsNameUtils +import java.lang.reflect.Field +import org.springframework.util.ReflectionUtils + +/** + * + * @author Lim Chee Kin + * + * @since 0.1 + */ +class JQueryValidationUiTagLib { + static namespace = "jqvalui" + static final String TAG_ERROR_PREFIX = "Tag [jqvalui:renderValidationScript] Error: " + + def jqueryValidationService + + def renderErrors = { attrs, body -> + def renderErrorsOnTop = attrs.render ? Boolean.valueOf(attrs.render) : grailsApplication.config.jqueryValidationUi.get("renderErrorsOnTop", true) + if (renderErrorsOnTop) { + String qTipClasses = grailsApplication.config.jqueryValidationUi.qTip.classes?:"" + String style = attrs.remove("style")?:'' + String bodyText = body() + def writer = getOut() + writer << """ + + + + +""" + if (bodyText) { + writer << bodyText + } else { + writer << '' + } + writer << """ + + + +""" + } + } + + def renderError = { attrs, body -> + String labelFor = attrs.remove("for") + if (!labelFor) { + throwTagError("Tag [jqvalui:renderError] Error: Tag missing required attribute [for]") + } + def renderErrorsOnTop = grailsApplication.config.jqueryValidationUi.get("renderErrorsOnTop", true) + if (!renderErrorsOnTop) { + String style = attrs.remove("style")?:'' + String qTipClasses = grailsApplication.config.jqueryValidationUi.qTip.classes?:"" + def writer = getOut() + writer << """ + + + + + + + + """ + writer << body() + writer << """ + + + +""" + } + } + + private String getConnectorColor() { + def jQueryUiStyle = grailsApplication.config.jqueryValidationUi.qTip.get("jQueryUiStyle", false) + return "rgb(217, 82, 82)" + } + + def renderValidationScript = { attrs, body -> + String forClass = attrs.remove("for") + def alsoProperties = attrs.remove("also") + def notProperties = attrs.remove("not") + String form = attrs.remove("form") + def config = grailsApplication.config.jqueryValidationUi + def jQueryUiStyle = config.qTip.get("jQueryUiStyle", false) + String qTipClasses = config.qTip.classes?:"" + String errorClass = attrs.errorClass?:config.errorClass?:"error" + String validClass = attrs.validClass?:config.validClass?:"valid" + String errorContainer = attrs.errorContainer?:config.errorContainer?:"#errorContainer" + String errorLabelContainer = attrs.errorLabelContainer?:config.errorLabelContainer?:"div.errors ul" + String errorElement = attrs.errorElement?:config.errorElement?:"label" + String errorWrapper = attrs.errorWrapper?:config.errorWrapper?:"li" + + + def onsubmit = attrs.onsubmit ? Boolean.valueOf(attrs.onsubmit) : config.get("onsubmit", true) + def onkeyup = attrs.onkeyup ?: config.get("onkeyup", false) + def qtip = attrs.qtip ? Boolean.valueOf(attrs.qtip) : config.get("qtip", false) + + def submitHandler = attrs.remove("submitHandler")?:config.submitHandler?:null + def highlightHandler = attrs.remove("highlight")?:config.highlight?:null + def unhighlightHandler = attrs.remove("unhighlight")?:config.unhighlight?:null + + + def renderErrorsOnTop = attrs.renderErrorsOnTop ? Boolean.valueOf(attrs.renderErrorsOnTop) : config.get("renderErrorsOnTop", true) + String renderErrorsOptions = "" + Locale locale = RCU.getLocale(request) + + if (!forClass) { + throwTagError("${TAG_ERROR_PREFIX}Tag missing required attribute [for]") + } + def validatableClass = grailsApplication.classLoader.loadClass(forClass) + if (!validatableClass) { + throwTagError("${TAG_ERROR_PREFIX}Invalid validatableClass defined in attribute [for], $validatableClassName not found!") + } + + if (notProperties) { + notProperties = notProperties.split(',').collect { it.trim() } + } + if (alsoProperties) { + alsoProperties = alsoProperties.split(',').collect { it.trim() } + if (notProperties) { + notProperties.addAll(alsoProperties) + } else { + notProperties = new ArrayList(alsoProperties) + } + } + + if (renderErrorsOnTop) { + renderErrorsOptions = """ +errorContainer: '$errorContainer', +errorLabelContainer: '$errorLabelContainer', +wrapper: '$errorWrapper', +""" + } else if (qtip) { + renderErrorsOptions = """ +success: function(label) +{ + \$('[id="' + label.attr('for') + '"]').qtip('destroy'); +}, +errorPlacement: function(error, element) +{ + if (\$(error).text()) { + var errorTarget = \$('#' + element[0].id + 'Target'); + if(errorTarget[0]) { + \$(element).filter(':not(.${validClass})').qtip({ + overwrite: true, + content: error, + position: { + my: 'left center', + at: 'right center', + target: errorTarget + }, + show: { + event: false, + ready: true + }, + hide: false, + style: { + widget: ${jQueryUiStyle}, + classes: '${qTipClasses}', + tip: true + } + }); + } else { + \$(element).filter(':not(.${validClass})').qtip({ + overwrite: true, + content: error, + position: { + my: 'left center', + at: 'right center' + }, + show: { + event: false, + ready: true + }, + hide: false, + style: { + widget: ${jQueryUiStyle}, + classes: '${qTipClasses}', + tip: true + } + }); + } + } +}, +""" + } + + ConstrainedPropertiesEntry rootConstrainedPropertiesEntry = new ConstrainedPropertiesEntry(validatableClass: validatableClass) + rootConstrainedPropertiesEntry.constrainedProperties = jqueryValidationService.getConstrainedProperties(validatableClass) + if (notProperties) { + def rootNotProperties = notProperties.findAll { it.indexOf('.') == -1 } + notProperties.removeAll(rootNotProperties) + rootConstrainedPropertiesEntry.constrainedProperties = rootConstrainedPropertiesEntry.constrainedProperties.findAll{ k, v -> !rootNotProperties.contains(k) } + } + def constrainedPropertiesEntries = [rootConstrainedPropertiesEntry] + ConstrainedPropertiesEntry childConstrainedPropertiesEntry + def childNotProperties + Class childValidatableClass + alsoProperties.each { propertyName -> + childValidatableClass = findField(validatableClass, propertyName).type + childConstrainedPropertiesEntry = new ConstrainedPropertiesEntry(namespace:propertyName, validatableClass: childValidatableClass) + childConstrainedPropertiesEntry.constrainedProperties = jqueryValidationService.getConstrainedProperties(childValidatableClass) + if (notProperties) { + childNotProperties = notProperties.collect {it.indexOf(propertyName) != -1 ? it.substring(propertyName.length() + 1) : null } + notProperties.removeAll(childNotProperties) + childConstrainedPropertiesEntry.constrainedProperties = childConstrainedPropertiesEntry.constrainedProperties.findAll{ k, v -> !childNotProperties.contains(k) } + } + constrainedPropertiesEntries << childConstrainedPropertiesEntry + } + String rules = jqueryValidationService.createJavaScriptConstraints(constrainedPropertiesEntries, locale) + String messages = jqueryValidationService.createJavaScriptMessages(constrainedPropertiesEntries, form, locale) + out << render(plugin: 'jqueryValidationUi', template: '/taglib/renderValidationScript', + model: [ + form: form, onkeyup: onkeyup, errorClass: errorClass, + errorElement: errorElement, validClass: validClass, onsubmit: onsubmit, + submitHandler: submitHandler, highlightHandler: highlightHandler, + unhighlightHandler: unhighlightHandler, renderErrorsOptions: renderErrorsOptions, + rules: rules, messages: messages + ]) + } + + private Field findField(Class clazz, String name) { + Field field + + if (name.indexOf('.') == -1) { + field = ReflectionUtils.findField(clazz, name) + } else { + Class declaringClass = clazz + def fieldNames = name.split("\\.") + for (fieldName in fieldNames) { + field = ReflectionUtils.findField(declaringClass, fieldName) + if (!field) { + throw new IllegalArgumentException("Property $name invalid!") + } + declaringClass = field.type + } + } + return field + } +} + + diff --git a/grails-app/utils/org/grails/jquery/validation/ui/LettersonlyConstraint.groovy b/grails-app/utils/org/grails/jquery/validation/ui/LettersonlyConstraint.groovy index 6a86c25..c16e4d2 100644 --- a/grails-app/utils/org/grails/jquery/validation/ui/LettersonlyConstraint.groovy +++ b/grails-app/utils/org/grails/jquery/validation/ui/LettersonlyConstraint.groovy @@ -1,32 +1,32 @@ -/* Copyright 2010 the original author or authors. -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -package org.grails.jquery.validation.ui - -/** -* -* @author Lim Chee Kin -* -* @since 1.2 -*/ -class LettersonlyConstraint { - - def supports = { type -> - return type!= null && String.class.isAssignableFrom(type); - } - - def validate = { propertyValue -> - return propertyValue ==~ /^[a-z]+$/ - } -} +/* Copyright 2010 the original author or authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.grails.jquery.validation.ui + +/** +* +* @author Lim Chee Kin +* +* @since 1.2 +*/ +class LettersonlyConstraint { + + def supports = { type -> + return type!= null && String.class.isAssignableFrom(type); + } + + def validate = { propertyValue -> + return propertyValue ==~ /^[a-zA-Z]+$/ + } +} diff --git a/test/unit/org/grails/jquery/validation/ui/JqueryValidationServiceSpec.groovy b/test/unit/org/grails/jquery/validation/ui/JqueryValidationServiceSpec.groovy index a492375..7aad63e 100644 --- a/test/unit/org/grails/jquery/validation/ui/JqueryValidationServiceSpec.groovy +++ b/test/unit/org/grails/jquery/validation/ui/JqueryValidationServiceSpec.groovy @@ -19,7 +19,7 @@ public class JqueryValidationServiceSpec extends Specification { service.messageSource = messageSource when: - def message = service.getMessage(this.class, "prop", null, "max", null) + def message = service.getMessage(this.class, "prop", null, null, "max", null) then: 1 * messageSource.getMessage("${this.class.name}.prop.max", null, null, null) >> "my 'message'" diff --git a/web-app/images/apple-touch-icon-retina.png b/web-app/images/apple-touch-icon-retina.png deleted file mode 100644 index 5cc83ed..0000000 Binary files a/web-app/images/apple-touch-icon-retina.png and /dev/null differ diff --git a/web-app/images/apple-touch-icon.png b/web-app/images/apple-touch-icon.png deleted file mode 100644 index aba337f..0000000 Binary files a/web-app/images/apple-touch-icon.png and /dev/null differ diff --git a/web-app/images/favicon.ico b/web-app/images/favicon.ico deleted file mode 100644 index 3dfcb92..0000000 Binary files a/web-app/images/favicon.ico and /dev/null differ diff --git a/web-app/images/grails_logo.jpg b/web-app/images/grails_logo.jpg deleted file mode 100644 index 8be657c..0000000 Binary files a/web-app/images/grails_logo.jpg and /dev/null differ diff --git a/web-app/images/grails_logo.png b/web-app/images/grails_logo.png deleted file mode 100644 index 9836b93..0000000 Binary files a/web-app/images/grails_logo.png and /dev/null differ diff --git a/web-app/images/leftnav_btm.png b/web-app/images/leftnav_btm.png deleted file mode 100644 index 582e1eb..0000000 Binary files a/web-app/images/leftnav_btm.png and /dev/null differ diff --git a/web-app/images/leftnav_midstretch.png b/web-app/images/leftnav_midstretch.png deleted file mode 100644 index 3cb8a51..0000000 Binary files a/web-app/images/leftnav_midstretch.png and /dev/null differ diff --git a/web-app/images/leftnav_top.png b/web-app/images/leftnav_top.png deleted file mode 100644 index 6afec7d..0000000 Binary files a/web-app/images/leftnav_top.png and /dev/null differ diff --git a/web-app/images/skin/database_add.png b/web-app/images/skin/database_add.png deleted file mode 100644 index 802bd6c..0000000 Binary files a/web-app/images/skin/database_add.png and /dev/null differ diff --git a/web-app/images/skin/database_delete.png b/web-app/images/skin/database_delete.png deleted file mode 100644 index cce652e..0000000 Binary files a/web-app/images/skin/database_delete.png and /dev/null differ diff --git a/web-app/images/skin/database_edit.png b/web-app/images/skin/database_edit.png deleted file mode 100644 index e501b66..0000000 Binary files a/web-app/images/skin/database_edit.png and /dev/null differ diff --git a/web-app/images/skin/database_save.png b/web-app/images/skin/database_save.png deleted file mode 100644 index 44c06dd..0000000 Binary files a/web-app/images/skin/database_save.png and /dev/null differ diff --git a/web-app/images/skin/database_table.png b/web-app/images/skin/database_table.png deleted file mode 100644 index 693709c..0000000 Binary files a/web-app/images/skin/database_table.png and /dev/null differ diff --git a/web-app/images/skin/exclamation.png b/web-app/images/skin/exclamation.png deleted file mode 100644 index c37bd06..0000000 Binary files a/web-app/images/skin/exclamation.png and /dev/null differ diff --git a/web-app/images/skin/house.png b/web-app/images/skin/house.png deleted file mode 100644 index fed6221..0000000 Binary files a/web-app/images/skin/house.png and /dev/null differ diff --git a/web-app/images/skin/information.png b/web-app/images/skin/information.png deleted file mode 100644 index 12cd1ae..0000000 Binary files a/web-app/images/skin/information.png and /dev/null differ diff --git a/web-app/images/skin/shadow.jpg b/web-app/images/skin/shadow.jpg deleted file mode 100644 index b7ed44f..0000000 Binary files a/web-app/images/skin/shadow.jpg and /dev/null differ diff --git a/web-app/images/skin/sorted_asc.gif b/web-app/images/skin/sorted_asc.gif deleted file mode 100644 index 6b179c1..0000000 Binary files a/web-app/images/skin/sorted_asc.gif and /dev/null differ diff --git a/web-app/images/skin/sorted_desc.gif b/web-app/images/skin/sorted_desc.gif deleted file mode 100644 index 38b3a01..0000000 Binary files a/web-app/images/skin/sorted_desc.gif and /dev/null differ diff --git a/web-app/images/spinner.gif b/web-app/images/spinner.gif deleted file mode 100644 index 1ed786f..0000000 Binary files a/web-app/images/spinner.gif and /dev/null differ diff --git a/web-app/images/springsource.png b/web-app/images/springsource.png deleted file mode 100644 index e806d00..0000000 Binary files a/web-app/images/springsource.png and /dev/null differ diff --git a/web-app/js/application.js b/web-app/js/application.js deleted file mode 100644 index b2adb96..0000000 --- a/web-app/js/application.js +++ /dev/null @@ -1,9 +0,0 @@ -if (typeof jQuery !== 'undefined') { - (function($) { - $('#spinner').ajaxStart(function() { - $(this).fadeIn(); - }).ajaxStop(function() { - $(this).fadeOut(); - }); - })(jQuery); -} diff --git a/web-app/js/qTip/jquery.qtip.pack.js b/web-app/js/qTip/jquery.qtip.pack.js deleted file mode 100644 index 80c61d2..0000000 --- a/web-app/js/qTip/jquery.qtip.pack.js +++ /dev/null @@ -1,19 +0,0 @@ -/* -* qTip - The jQuery tooltip plugin -* http://craigsworks.com/projects/qtip/ -* -* Version: 2.0.0pre -* Copyright 2009 Craig Michael Thompson - http://craigsworks.com -* -* Dual licensed under MIT or GPL Version 2 licenses -* http://en.wikipedia.org/wiki/MIT_License -* http://en.wikipedia.org/wiki/GNU_General_Public_License -* -* Date: fatal: Not a git repository (or any of the parent directories): .git -*/ - -"use strict"; // Enable ECMAScript "strict" operation for this function. See more: http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/ -/*jslint browser: true, onevar: true, undef: true, nomen: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: true */ -/*global window: false, jQuery: false */ - -eval(function(p,a,c,k,e,d){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('"5o 6e";Y 19=4V,V=4r,25=5p;(Q(b,H,J){Q K(f,e){Y m,k=b();7(!f)U V;4E{7("1R"2b f&&"1D"!==1g f.1R)f.1R={1Y:f.1R};7("14"2b f){7("1D"!==1g f.14||f.14.2k)f.14={1t:f.14};m=f.14.1t||V;7(!b.2F(m)&&(!m&&!m.1o||m.1n<1||"1D"===1g m&&!m.2k))m=f.14.1t=V;7("1e"2b f.14&&"1D"!==1g f.14.1e)f.14.1e={1t:f.14.1e}}7("18"2b f){7("1D"!==1g f.18)f.18={1T:f.18,2h:f.18};7("1D"!==1g f.18.1u)f.18.1u={};7("5q"!==1g f.18.1u.2y)f.18.1u.2y=!!f.18.1u.2y}7("R"2b f){7("1D"!==1g f.R)f.R={1l:f.R};7("1D"!==1g f.R)f.R=f.R.2k?{17:f.R}:{1l:f.R}}7("W"2b f)7("1D"!==1g f.W)f.W=f.W.2k?{17:f.W}:{1l:f.W};7("1d"2b f&&"1D"!==1g f.1d)f.1d={2X:f.1d}}4n(p){}7(b.2F(m)){f.14.1t=[];e.1p(Q(){Y q=m.2c(T);7(q){f.14.1t.4P(q);k=k.1X(b(T))}})}15 k=e;b.1p(b.16.X.1m,Q(){T.3I&&T.3I(f)});U e?k:f}Q N(f,e,m){Q k(a){Y d,i=a.2G("."),j=e[i[0]];38(a=1;a",{2K:d}):b("",{"1G":"1a-1H-3k",1t:"4G S",1e:"4G S",13:{"1t-5t":"-67"}}).5u(b("<5v />",{"1G":"1a-4f 1a-4f-4t"}));a.1O.3W(a.2s).1o("3V","1O").3f(h+"-"+(d===19?"4t":"1O")).4y(Q(i){b(T).1W("1a-1H-4y",i.1Y==="3P")}).3Z(Q(){a.S.2o("1a-1H-1J")||c.W();U V}).1q("35 60 44 5w 3B",Q(i){b(T).1W("1a-1H-5x 1a-1H-2r",/5y$/i.1A(i.1Y))});c.3c()}Q B(){Y a=c.1c;a.2s&&D();a.2s=b("<1U />",{"1G":h+"-2s "+(e.1d.1K?"1a-1K-4c":"")}).36(a.1e=b("<1U />",{1P:h+"-"+m+"-1e","1G":h+"-1e",2K:e.14.1e.1t})).3W(a.2N);7(e.14.1e.1O)z();15 c.1k===19&&c.3c()}Q w(a){Y d=c.1c;7(!c.1k||!a)U V;7(b.2F(a))a=a.2c(f);a.2k&&a.1n>0?d.14.5z().36(a.13({3L:"3t"})):d.14.2K(a);d.S.3g("4j",Q(i){Q j(o){g=g.3v(o);7(g.1n===0){c.3c();c.1k===19&&c.22(c.1F.1l);i()}}Y g=b("3b:3v([1h]):3v([1f])",c.1c.14);g.1p(Q(o,u){Y t=["5G","5A","5E","5B",""].5C(".X-5D ");b(T).1q(t,Q(){21(c.1w.3b[o]);j(T)});(Q s(){7(u.1h)U j(u);c.1w.3b[o]=31(s,20)})();U 19});g.1n===0&&j(g)});U c}Q C(a,d,i,j){Q g(y){7(n.S.2o("1a-1H-1J"))U V;n.R.2t("X-"+m+"-1M");21(c.1w.R);21(c.1w.W);Y r=Q(){c.R(y)};7(e.R.27>0)c.1w.R=31(r,e.R.27);15 r()}Q o(y){7(n.S.2o("1a-1H-1J"))U V;Y r=b(y.4p||y.17).3X(l)[0]==n.S[0];21(c.1w.R);21(c.1w.W);7(e.W.2f&&(e.18.17==="1S"&&r||/1S(49|4o|3x)/.1A(y.1Y)&&r)){y.5I();y.5F();U V}n.S.4q(1,1);7(e.W.27>0)c.1w.W=31(Q(){c.W(y)},e.W.27);15 c.W(y)}Q u(y){7(n.S.2o("1a-1H-1J"))U V;21(c.1w.1M);c.1w.1M=31(Q(){c.W(y)},e.W.1M)}Q t(y){c.1c.S.1Q(":2g")&&c.22(y)}Y s=".X-"+m,n={R:e.R.17,W:e.W.17,S:c.1c.S},v={R:2j(e.R.1l).2G(" "),W:2j(e.W.1l).2G(" ")},F=b.2z.2Z&&/^6\\.[0-9]/.1A(b.2z.4D);f.1q("29.X",Q(){c.2D()});7(i&&e.W.2f){n.W=n.W.1X(n.S);n.S.1q("3A"+s,Q(){n.S.2o("1a-1H-1J")||21(c.1w.W)})}7(d){7("2E"===1g e.W.1M){n.R.1q("X-"+m+"-1M",u);b.1p(b.16.X.3s,Q(y,r){n.W.1X(c.1c.S).1q(r+s+"-1M",u)})}b.1p(v.W,Q(y,r){Y x=b.5H(r,v.R);7(x>-1&&b(n.W).1X(n.R).1n===b(n.W).1n||r==="4h"){n.R.1q(r+s,Q(E){n.S.1Q(":2g")?o(E):g(E)});1L v.R[x]}15 n.W.1q(r+s,o)})}7(a){b.1p(v.R,Q(y,r){n.R.1q(r+s,g)});n.S.1q("3A"+s,Q(){c.2r()})}7(j){7(e.18.1u.2O||e.18.1u.2y)b(H).1q("2O"+s,t);7(e.18.1u.2y||F&&n.S.13("18")==="2f")b(1V).1q("4s"+s,t);/4h/i.1A(e.W.1l)&&b(1V).1q("35"+s,Q(y){Y r=c.1c.S;b(y.17).3X(l).1n===0&&b(y.17).1X(f).1n>1&&r.1Q(":2g")&&!r.2o("1a-1H-1J")&&c.W()});e.18.17==="1S"&&b(1V).1q("2Y"+s,Q(y){7(e.18.1u.1S&&!n.S.2o("1a-1H-1J")&&n.S.1Q(":2g"))c.22(y||b.16.X.1S)})}}Q A(a,d,i,j){j=2w(j,10)!==0;Y g=".X-"+m,o={R:a?e.R.17:b("<1U/>"),W:d?e.W.17:b("<1U/>"),S:i?c.1c.S:b("<1U/>")};d={R:2j(e.R.1l).2G(" "),W:2j(e.W.1l).2G(" ")};7(c.1k){b.1p(d.R,Q(u,t){o.R.1y(t+g)});o.R.1y("2Y"+g).1y("3B"+g).1y("X-"+m+"-1M");b.1p(d.W,Q(u,t){o.W.1X(o.S).1y(t+g)});b.1p(b.16.X.3s,Q(u,t){o.W.1X(i?c.1c.14:25).1y(t+g+"-1M")});o.W.1y("3B"+g);o.S.1y("3A"+g);7(j){b(H).1y("2O"+g);b(1V).1y("35"+g+" 2Y"+g)}}15 a&&o.R.1y(d.R+g+"-2W")}Y c=T,h="1a-S",l=".X."+h;c.1P=m;c.1k=V;c.1c={17:f};c.1F={1l:{},17:25,1J:V};c.1w={3b:[]};c.2p=e;c.1m={};b.1B(c,{2a:Q(a){Y d=c.1c,i=b.2U("5J");7(c.1k)U V;c.1k=a?-2:-1;d.S=b("<1U/>").1o({1P:h+"-"+m,3V:"S","1G":h+" X 1a-S-2M 1a-4k-4l "+e.1d.2X}).13("z-3i",b.16.X.3u+b(l).1n).1W("1a-1K",e.1d.1K).1W("1a-1H-1J",c.1F.1J).1Z("X",c).33(e.18.28);d.2N=b("<1U />",{"1G":h+"-2N"}).33(d.S);d.14=b("<1U />",{"1G":h+"-14 "+(e.1d.1K?"1a-1K-14":""),1P:h+"-"+m+"-14"}).33(d.2N);e.14.1e.1t&&B();w(e.14.1t);b.1p(b.16.X.1m,Q(){T.2P==="2a"&&T(c)});c.1k=19;C(1,1,1,1);b.1p(e.3w,Q(j,g){d.S.1q("S"+j,g)});d.S.3g("4j",Q(j){7(e.R.30||a){d.S.W();c.R(c.1F.1l)}d.S.3l("1a-S-2M");i.2Q=b.1B({},c.1F.1l);d.S.2t(i,[c.26()]);j()});U c},2H:Q(a){2V(a.2q()){1I"2m":a=q("18");1E;1I"3h":a=q("3h");1E;3k:a=k(a.2q());a=a[0].1r?a[0].1x():a[0].2k?a[0]:a[0][a[1]];1E}U a},3N:Q(a,d){a=a.2q();Y i=k(a),j=c.1c,g=j.S,o,u,t,s={5K:{1P:Q(){Y n=d===19?b.16.X.3r:d,v=h+"-"+n;7(n!==V&&n.1n>0&&!b("#1a-S-"+n).1n){g[0].1P=v;j.14[0].1P=v+"-14";j.1e[0].1P=v+"-1e"}},"^14.1t":Q(){w(d)},"^14.1e.1t":Q(){7(c.1k)7(!c.1c.1e&&d){B();c.22()}15 d?c.1c.1e.2K(d):D()},"^14.1e.1O":Q(){Y n=c.1c.1O,v=c.1c.1e;7(c.1k)7(d){v||B();z()}15 n.29()},"^18.(1T|2h)$":Q(){Y n=/1T$/i.1A(a)?"1T":"2h";7("1x"===1g d)e.18[n]=2l b.16.X.1m.2v(d)},"^18.(1T|2h|1u|17)":Q(){c.1k&&c.22()},"^18.28$":Q(){7(c.1k===19){g.33(d);c.22()}},"^(R|W).(1l|17|2f|27|1M)":Q(n,v,F,y){Y r=a.3z(/2f/i)>-1?[0,[0,1,1,1]]:[a.3j(0,3),a.3a(0)==="s"?[1,0,0,0]:[0,1,0,0]];7(r[0])n[v]=y;A.32(c,r[1]);7(r[0])n[v]=F;C.32(c,r[1])},"^R.30$":Q(){c.1k===V&&c.R()},"^1d.2X$":Q(){c.1c.S.1o("1G",h+" X 1a-4k-4l "+d)},"^1d.1K$":Q(){g.1W("1a-1K",!!d);j.2s.1W("1a-1K-4c",!!d);j.14.1W("1a-1K-14",!!d)},"^3w.(2a|R|3x|W|2r|4e)":Q(n,v,F,y){b.2F(d)?j.S.1q("S"+v,F):j.S.1y("S"+v,y)}}};b.1p(c.1m,Q(n){7("1D"===1g T.3T)s[n]=T.3T});o=i[0][i[1]];i[0][i[1]]=d.5e?b(d):d;K(e,f);38(u 2b s)38(t 2b s[u])5N(t,"i").1A(a)&&s[u][t].2c(c,i[0],i[1],d,o);U c},3n:Q(a,d){Q i(){Y s=b(T),n=a?"1o":"2R",v=/^1|0$/.1A(s.13("3K"));c.1c.1e&&f[n]("2S-4m",h+"-"+m+"-1e");f[n]("2S-4I",h+"-"+m+"-14");7(a){7(b.2z.2Z&&T.1d&&v){t=T.1d;t.3G("3O");t.3G("3K")}}15 v&&s.W()}7(c.1k===V)U V;Y j=a?"R":"W",g=c.1c.S,o=e[j],u=g.1Q(":2g"),t;7((1g a).3z("3S|2E"))a=!g.1Q(":2g");7(!u&&!a||g.1Q(":5O"))U c;7(d){7(c.1F.1l&&/5Q|5R/.1A(d.1Y)&&/49|4o/.1A(c.1F.1l.1Y)&&b(d.17).1X(e.R.17).1n<2&&b(d.4p).3X(l).1n>0)U c;c.1F.1l=b.1B({},d)}u=b.2U("S"+j);u.2Q=b.1B({},d);g.2t(u,[c.26(),3R]);7(u.3J())U c;7(a){c.2r();c.22(d);o.4S&&b(l).X("W")}15 21(c.1w.R);g.1o("2S-5T",5U(!a));g.4q(1,1);7(b.2F(o.2i)){o.2i.2c(g,c.26());g.3g(Q(){i.2c(T);b(T).4B()})}15 7(o.2i===V){g[j]();i.2c(g)}15 g.5V(3R,a?1:0,i);a&&o.17.2t("X-"+m+"-1M");U c},R:Q(a){c.3n(19,a)},W:Q(a){c.3n(V,a)},2r:Q(a){7(c.1k===4r)U V;Y d=c.1c.S,i=b(l),j=2w(d.13("z-3i"),10),g=b.16.X.3u+i.1n,o=h+"-2r",u=b.1B({},a);7(!d.2o(o)&&j!==g){i.13("z-3i",Q(t,s){U s-1});b(l+"."+o).1p(Q(){Y t=b(T),s=t.X(),n;7(!s||s.1k===V)U 19;t.3l(o);n=b.2U("6n");n.2Q=u;t.2t(n,[s,g])});a=b.2U("5W");a.2Q=u;d.2t(a,[c.26(),g]);a.3J()||d.13({5Y:g}).3f(o)}U c},22:Q(a){7(c.1k===V)U V;Y d=e.18.17,i=c.1c.S,j=e.18,g=j.1T,o=j.2h,u=c.1c.S.1f(),t=c.1c.S.1h(),s=b(j.28)[0],n=0,v=0,F=b.2U("4T"),y=i.13("18")==="2f",r=b(j.1u.28&&s!==1V.3p?s:H),x={11:0,12:0};s={11:Q(E){Y I=r.3e,G=o.x==="11"?n:o.x==="1C"?-n:-n/2,L=E+u-r.1f-I;G=(g.x==="11"?u:g.x==="1C"?-u:-u/2)- -2*j.1u.x-(g.1r==="x"?G:0);7(I-E>0)x.11-=G;15 7(L>0)x.11-=(g.x==="1s"?-1:1)*G;U x.11-E},12:Q(E){Y I=r.3d,G=o.y==="12"?v:o.y==="1z"?-v:-v/2,L=E+t-r.1h-I;G=(g.y==="12"?t:g.y==="1z"?-t:-t/2)- -2*j.1u.y-(g.1r==="y"?G:0);7(I-E>0)x.12-=G;15 7(L>0)x.12-=(g.y==="1s"?-1:1)*G;U x.12-E}};r={4z:r,1h:r[(r[0]===H?"h":"61")+"62"](),1f:r[(r[0]===H?"w":"63")+"64"](),3e:r.3e(),3d:r.3d()};7(d==="1S"){o={x:"11",y:"12"};a=j.1u.1S||!a||!a.34?b.1B({},b.16.X.1S):a;x={12:a.3M,11:a.34}}15{7(d==="1l")d=a&&a.17&&a.1Y!=="4s"&&a.1Y!=="2O"?c.1F.17=b(a.17):c.1F.17;d=b(d).65(0);7(d.1n===0)U c;15 7(d[0]===1V||d[0]===H){n=d.1f();v=d.1h();7(d[0]===H)x={12:y?0:r.3d,11:y?0:r.3e}}15 7(d.1Q("66")&&b.16.X.1m.4u){x=b.16.X.1m.4u(d,o);n=x.1f;v=x.1h;x=x.2m}15{n=d.4v();v=d.4w();x=p(d)}x.11+=o.x==="1C"?n:o.x==="1s"?n/2:0;x.12+=o.y==="1z"?v:o.y==="1s"?v/2:0}x.11+=j.1u.x+(g.x==="1C"?-u:g.x==="1s"?-u/2:0);x.12+=j.1u.y+(g.y==="1z"?-t:g.y==="1s"?-t/2:0);x.3Q=j.1u.2y&&d[0]!==H&&d[0]!==1V.3p?{11:s.11(x.11),12:s.12(x.12)}:{11:0,12:0};i.1o("1G",Q(){U b(T).1o("1G").2T(/1a-S-4x-\\w+/i,"")}).3f(h+"-4x-"+g.4A());F.2Q=b.1B({},a);i.2t(F,[c.26(),x,r.4z]);7(F.3J())U c;1L x.3Q;7(i.1Q(":2g")&&b.2F(j.2i)){j.2i.2c(i,c.26(),x);i.3g(Q(){Y E=b(T);E.13({3K:"",1h:""});b.2z.2Z&&T.1d&&T.1d.3G("3O");E.4B()})}15 68(x.11,x.12)||i.13(x);U c},3c:Q(){7(!c.1k||!(b.2z.2Z&&2w(b.2z.4D.3a(0),10)<9))U V;Y a=c.1c.S;a.1o("1d");Y d;a.13({1f:"4F",1h:"4F"});d=q("3h");b.1p(["1f","1h"],Q(i,j){Y g=2w(a.13("3m-"+j),10)||0,o=2w(a.13("4H-"+j),10)||0;d[j]=g+o?1v.4H(1v.3m(d[j],o),g):d[j]});a.13(d)},3H:Q(a){Y d=c.1c.S;7(c.1k)d.1W("1a-1H-1J",a);15 c.1F.1J=!!a;U c},2D:Q(){Y a=c.1c,d=a.17.1Z("3C");c.1k&&b.1p(c.1m,Q(){T.2P==="2a"&&T.2D()});A(1,1,1,1);f.4K("X");c.1k&&a.S.29();d&&f.1o("1e",d);f.2R("2S-4I");U f},26:Q(){Y a=b.1B({},c);1L a.1F;1L a.1w;1L a.2p;1L a.1m;1L a.2a;1L a.26;U a}})}Q O(f,e){Y m,k=b(T);m=b(1V.3p);Y p=k.1R?k.1R(e.1R):{};p=b.1B(19,{},e,K(b.1B(19,{},(p&&e.1R.1Y==="6b"?p[e.1R.6c]:{})||p)));Y q=p.18,D=T===1V?m:k;k.4K("1R");7("3S"===1g p.14.1t)7(p.14.1o!==V&&k.1o(p.14.1o))p.14.1t=k.1o(p.14.1o);15 U V;7(q.28===V)q.28=m;7(q.17===V)q.17=D;7(p.R.17===V)p.R.17=D;7(p.W.17===V)p.W.17=D;q.2h=2l b.16.X.1m.2v(q.2h);q.1T=2l b.16.X.1m.2v(q.1T);7(k.1Z("X"))7(p.3D)k.X("2D");15 7(p.3D===V)U V;m=2l N(k,p,f);k.1Z("X",m);U m}Q M(f,e,m){Y k=1v.1N(e/2),p=1v.1N(m/2);e={4L:[[0,0],[e,m],[e,0]],4M:[[0,0],[e,0],[0,m]],4N:[[0,m],[e,0],[e,m]],4O:[[0,0],[0,m],[e,m]],6f:[[0,m],[k,0],[e,m]],6g:[[0,0],[e,0],[k,m]],6h:[[0,0],[e,p],[0,m]],6i:[[e,0],[e,m],[0,p]]};e.6j=e.4L;e.6k=e.4M;e.6m=e.4N;e.6o=e.4O;U e[f]}Q P(f){Q e(h){Y l=q.1b,a=["11","1C"],d=p.2m,i,j;7(p.1j===V||!l)U V;h=h||k.1j;i=h.1r;j=i==="y"?"x":"y";a[i==="y"?"4P":"6p"]("12","1z");d=1v.3m(h[j]==="1s"?d:0,d);l.13({12:"",1z:"",11:"",1C:"",4Q:""});2V(h[i==="y"?"x":"y"]){1I"1s":l.13(a[0],"50%").13("4Q-"+a[0],-1v.1N(w[i==="y"?"1f":"1h"]/2)+d);1E;1I a[0]:l.13(a[0],d);1E;1I a[1]:l.13(a[1],d);1E}d=w[i==="x"?"1f":"1h"];7(A){D.1W("1a-S-2M",!D.1Q(":2g"));d-=2w(z.13("1i-"+h[i]+"-1f"),10)||0;D.3l("1a-S-2M")}7(c==="23"&&/1z|1C/.1A(h[h.1r]))d+=A?1:-1;l.13(h[i],-d)}Q m(h,l,a){7(q.1b){h=b.1B({},k.1j);l=h.1r==="y"?["y","12","11","1h","x"]:["x","11","12","1f","y"];Y d=a.3Q,i=[0,0];7(k.1j.2f!==19){7(d.11)h.x=h.x==="1s"?d.11>0?"11":"1C":h.x==="11"?"1C":"11";7(d.12)h.y=h.y==="1s"?d.12>0?"12":"1z":h.y==="12"?"1z":"12";7(h.1x()!==B.1j.1x()&&(B.12!==d.12||B.11!==d.11))k.2L(h)}i[0]=A?2w(z.13("1i-"+h[l[0]]+"-1f"),10)||0:c==="23"?1:0;i[1]=1v.3m(h[l[4]]==="1s"?p.2m:0,p.2m);a[l[1]]+=(h[l[0]]===l[1]?1:-1)*(w[l[3]]-i[0]);a[l[2]]-=(h[l[4]]===l[2]||h[l[4]]==="1s"?1:-1)*i[1];B.11=d.11;B.12=d.12;B.1j=h}}Y k=T,p=f.2p.1d.1b,q=f.1c,D=q.S,z=q.2N,B={12:0,11:0,1j:{1x:Q(){}}},w={1f:p.1f,1h:p.1h},C={},A=p.1i||0,c=p.2u||V;k.1j=25;k.2I=25;k.3T={"^18.1T|1d.1b.(1j|2I|2u|1i)":Q(){A=p.1i;7(k.3F())T.2H("18.17")!=="1S"&&T.22();15 k.2D()},"^1d.1b.(1h|1f)":Q(){w={1f:p.1f,1h:p.1h};k.2W();k.2L();f.22()},"^1d.2X$":Q(){k.3q();k.2L()}};b.1B(k,{3F:Q(){Y h=b.2z.2Z,l=k.4U(),a=k[k.2I?"2I":"1j"].1x().6r("1s")>-1;7(l){7(c===19)c=b("<2e />")[0].37?"2e":h&&(a||w.1h!==w.1f)?"23":"2J";15 7(c==="2e")c=h?"23":!b("<2e />")[0].37?"2J":"2e";15 7(c==="2J")c=h&&a?"23":c;k.2W();k.3q();k.2L();D.1y(".X-1b").1q("4T.X-1b",m)}U l},4U:Q(){Y h=p.1j,l=f.2p.18.2h,a=f.2p.18.1T;7(a.1x)a=a.1x();7(h===V||a===V&&l===V)U V;15 7(h===19)k.1j=2l b.16.X.1m.2v(a);15 7(!h.1x){k.1j=2l b.16.X.1m.2v(h);k.1j.2f=19}U k.1j.1x()!=="6s"},3q:Q(){Y h=q.1b,l=k.1j[k.1j.1r],a="1i-"+l+"-2C";C.2n=h.13("3o-2C","").13("1i","").13("3o-2C")||"2x";C.1i=h.2H(0).1d?h.2H(0).1d["1i"+l.3a(0)+l.3j(1)+"6t"]:h.13(a)||"2x";7(/4W?\\(0, 0, 0(, 0)?\\)|2x/i.1A(C.2n))C.2n=z.13(A?"3o-2C":a);7(!C.1i||/4W?\\(0, 0, 0(, 0)?\\)|2x/i.1A(C.1i))C.1i=z.13(a)||C.2n;b("*",h).1X(h).13("3o-2C","2x").13("1i",0)},2W:Q(){Y h=w.1f,l=w.1h;q.1b&&q.1b.29();q.1b=b(\'<1U 1G="1a-S-1b" />\').1W("1a-1K-14",f.2p.1d.1K).13(w).3W(D);2V(c){1I"2e":b(\'<2e 1h="\'+l+\'" 1f="\'+h+\'" />\').33(q.1b)[0].37("2d").4a();1E;1I"23":q.1b.2K(\'<23:45 4X="0 0" 4Y="\'+h+" "+l+\'" 4Z="\'+!!A+\'" 1d="40:41(#3k#42); 3L:43-3t; 51:19; 18: 4J; 12:0; 11:0; 1f:\'+h+"24; 1h:"+l+"24; 52-53:"+k.1j.y+\';"><23:54 55="\'+(A-2)+\'24" 56="57" 58="10" 1d="40:41(#3k#42); 3L:43-3t;" />23:45>\');1E;1I"2J":q.1b.36(\'<1U 1G="1a-S-1b-5a" />\').36(A?\'<1U 1G="1a-S-1b-1i" />\':"");1E}U k},2L:Q(h){Y l=q.1b,a=w.1f,d=w.1h,i=A>0?0:1,j=1v.46(A/2+0.5),g=p.2I,o,u;7(!h)h=k.1j;7(g===V)g=h;15{g=2l b.16.X.1m.2v(g);g.1r=h.1r;7(g.x==="39")g.x=h.x;15 7(g.y==="39")g.y=h.y;15 7(g.x===g.y)g[h.1r]=h[h.1r]}u=1v[/b|r/.1A(g[g.1r==="y"?"x":"y"])?"46":"1N"];l=l.4d();2V(c){1I"2e":l=l.2H(0).37("2d");l.47&&l.47();l.5c(0,0,48,48);38(o=M(g.1x(),a,d);i<2;i++){7(i){l.4a();l.5d(u((g.x==="11"?1:g.x==="1C"?-1:0)*(A+1)*(g.1r==="y"?0.5:1)),u((g.y==="12"?1:g.y==="1z"?-1:0)*(A+1)*(g.1r==="x"?0.5:1)))}l.5f();l.5g(o[0][0],o[0][1]);l.4b(o[1][0],o[1][1]);l.4b(o[2][0],o[2][1]);l.5i();l.5k=C[i?"2n":"1i"];l.2n()}1E;1I"23":o=M(g.1x(),a,d);i="m"+o[0][0]+","+o[0][1]+" l"+o[1][0]+","+o[1][1]+" "+o[2][0]+","+o[2][1]+" 5l";l.1o({5m:i,5n:C.2n});7(A){l.4d().1o("2C",C.1i);7(g.1r==="y"){l.13("12",(g.y==="12"?1:-1)*(A-2));l.13("11",g.x==="11"?1:-2)}15{l.13("11",(g.x==="11"?1:-1)*(A-2));l.13("12",g.y==="12"?1:-2)}}1E;1I"2J":7(g.1r==="y"){i=a>d?1.5:ad?5:2.2;j=[1v.1N(i*j*(g.x==="1C"?-1:1)*(g.y==="1s"?0.9:1)),g.y==="12"?j:g.y==="1z"?-j:0]}l.2R("1d").1p(Q(t){Y s={x:g.1r==="x"?g.x==="11"?"1C":"11":g.x,y:g.1r==="y"?g.y==="12"?"1z":"12":g.y},n=g.x==="1s"?["11","1C",s.y,d,a]:["12","1z",s.x,a,d],v=C[!t&&A?"1i":"2n"];t&&b(T).13({18:"4J","z-3i":1,11:j[0],12:j[1]});g.x==="1s"||g.y==="1s"?b(T).13("1i-"+n[2],n[3]+"24 3E "+v).13("1i-"+n[0],1v.1N(n[4]/2)+"24 4g 2x").13("1i-"+n[1],1v.1N(n[4]/2)+"24 4g 2x"):b(T).13("1i-1f",1v.1N(d/2)+"24 "+1v.1N(a/2)+"24").13("1i-"+s.x,1v.1N(a/2)+"24 3E "+v).13("1i-"+s.y,1v.1N(d/2)+"24 3E "+v)});1E}e(h);U k},2D:Q(){q.1b&&q.1b.29();D.1y(".X-1b")}})}b.16.X=Q(f,e,m){Y k=2j(f).2q(),p=25,q=k==="3H"?[19]:b.5L(2A).5M(1,10),D=q[q.1n-1],z=b.1B(19,{},f),B;7(!2A.1n&&T.1Z("X")||k==="5P")U(z=T.1Z("X"))?z.26():J;15 7("1x"===1g f){T.1p(Q(){Y w=b(T).1Z("X");7(!w)U 19;7(/5S|3N/.1A(k)&&e)7(m!==J)w.3N(e,m);15 p=w.2H(e);15{7(!w.1k&&(k==="R"||k==="3n")){7(D&&D.5X)w.1F.1l=D;w.2a(1)}15 7(k==="5Z"){k="3H";q=[V]}w[k]&&w[k].32(w[k],q)}});U p!==25?p:T}15 7("1D"===1g f||!2A.1n){B=K(z,T);z=b.1B(19,{},b.16.X.3U,z);U b.16.X.1q.2c(B,z,D)}};b.16.X.1q=Q(f,e){U T.1p(Q(m){Q k(c){Q h(){z.2a(1g c==="1D"||B.R.30);w.R.1y(C.R);w.W.1y(C.W)}7(z.1F.1J)U V;z.1F.1l=b.1B({},c);7(B.R.27>0){21(z.1w.R);z.1w.R=31(h,B.R.27);C.R!==C.W&&w.W.1q(C.W,Q(){21(z.1w.R)})}15 h()}Y p=b(T),q=f.1P,D=f.14.1t,z,B,w,C,A;f.1P=q=q===V||q.1n<1||b("#1a-S-"+q).1n?b.16.X.3r++:q;A=".X-"+q+"-2W";z=O.2c(T,q,f);7(z===V)U 19;B=z.2p;7(b.69(D))B.14.1t=D[m];p.1o("1e")&&p.1Z("3C",p.1o("1e")).2R("1e");b.1p(b.16.X.1m,Q(){T.2P==="2P"&&T(z)});w={R:B.R.17,W:B.W.17};C={R:2j(B.R.1l).2T(" ",A+" ")+A,W:2j(B.W.1l).2T(" ",A+" ")+A};w.R.1q(C.R,k);7(f.R.30||f.4i)k(e)})};b.1p({1o:Q(f){Y e=b(T),m=e.1Z("X");U 2A.1n===1&&f==="1e"&&m&&m.1k===19?e.1Z("3C"):25},29:b.1a?25:Q(f,e){b(T).1p(Q(){7(!e)7(!f||b.3O(f,[T]).1n)b("*",T).1X(T).1p(Q(){b(T).6q("29")})})}},Q(f,e){7(!e)U 19;b.16["4R"+f]=b.16[f];b.16[f]=Q(){U e.32(T,2A)||b.16["4R"+f].32(T,2A)}});b(1V.3p).1o("3V",Q(f,e){U!e?"6u":e});b(1V).1q("2Y.X",Q(f){b.16.X.1S={34:f.34,3M:f.3M}});b.16.X.3r=0;b.16.X.3s="3Z 59 35 44 2Y 3Y 3P".2G(" ");b.16.X.3u=5b;b.16.X.1m={2v:Q(f){f=2j(f).2T(/([A-Z])/," $1").2T(/5h/5j,"1s").2q();T.x=(f.3y(/11|1C/i)||f.3y(/1s/)||["39"])[0].2q();T.y=(f.3y(/12|1z|1s/i)||["39"])[0].2q();T.1r=f.3a(0).3z(/^(t|b)/)>-1?"y":"x";T.1x=Q(){U T.1r==="y"?T.y+T.x:T.x+T.y};T.4A=Q(){Y e=T.x.3j(0,1),m=T.y.3j(0,1);U e===m?e:e==="c"||e!=="c"&&m!=="c"?m+e:e+m}}};b.16.X.3U={4i:V,1P:V,3D:19,1R:{1Y:"1G"},14:{1t:19,1o:"1e",1e:{1t:V,1O:V}},18:{1T:"12 11",2h:"1z 1C",17:V,28:V,1u:{x:0,y:0,1S:19,2y:V,2O:19,28:V},2i:19},R:{17:V,1l:"3P",2i:19,27:3R,4S:V,30:V},W:{17:V,1l:"3Y",2i:19,27:0,2f:V,1M:V},1d:{2X:"",1K:V},3w:{2a:b.2B,3x:b.2B,R:b.2B,W:b.2B,2r:b.2B,4e:b.2B}};b.16.X.1m.1b=Q(f){Y e=f.1m.1b,m=f.2p.1d.1b;7(m&&m.1j)7(e)U e;15{f.1m.1b=2l P(f);f.1m.1b.3F();U f.1m.1b}};b.16.X.1m.1b.2P="2a";b.16.X.1m.1b.3I=Q(f){4E{Y e=f.1d.1b;7(1g e!=="1D")f.1d.1b={1j:e};7(!/1x|3S/i.1A(1g e.1j))e.1j=4V;7(1g e.2u!=="1x")e.2u=19;7(!/2e|2J/i.1A(e.2u))e.2u=19;1g e.1f!=="2E"&&1L e.1f;1g e.1h!=="2E"&&1L e.1h;1g e.1i!=="2E"&&1L e.1i;1g e.2m!=="2E"&&1L e.2m}4n(m){}};b.1B(19,b.16.X.3U,{1d:{1b:{1j:19,2I:V,2u:19,1f:9,1h:9,1i:0,2m:0}}})})(6v,6d);',62,404,'|||||||if|||||||||||||||||||||||||||||||||||||||||||||function|show|tooltip|this|return|FALSE|hide|qtip|var|||left|top|css|content|else|fn|target|position|TRUE|ui|tip|elements|style|title|width|typeof|height|border|corner|rendered|event|plugins|length|attr|each|bind|precedance|center|text|adjust|Math|timers|string|unbind|bottom|test|extend|right|object|break|cache|class|state|case|disabled|widget|delete|inactive|floor|button|id|is|metadata|mouse|my|div|document|toggleClass|add|type|data||clearTimeout|reposition|vml|px|NULL|hash|delay|container|remove|render|in|call||canvas|fixed|visible|at|effect|String|jquery|new|offset|fill|hasClass|options|toLowerCase|focus|titlebar|trigger|method|Corner|parseInt|transparent|screen|browser|arguments|noop|color|destroy|number|isFunction|split|get|mimic|polygon|html|update|accessible|wrapper|resize|initialize|originalEvent|removeAttr|aria|replace|Event|switch|create|classes|mousemove|msie|ready|setTimeout|apply|appendTo|pageX|mousedown|append|getContext|for|inherit|charAt|img|redraw|scrollTop|scrollLeft|addClass|queue|dimensions|index|substr|default|removeClass|max|toggle|background|body|detectColours|nextid|inactiveEvents|block|zindex|not|events|move|match|search|mouseover|mouseout|oldtitle|overwrite|solid|init|removeAttribute|disable|sanitize|isDefaultPrevented|opacity|display|pageY|set|filter|mouseenter|adjusted|90|boolean|checks|defaults|role|prependTo|parents|mouseleave|click|behavior|url|VML|inline|mouseup|shape|ceil|restore|3E3|out|save|lineTo|header|children|blur|icon|dashed|unfocus|prerender|fx|helper|reset|labelledby|catch|leave|relatedTarget|stop|false|scroll|close|imagemap|outerWidth|outerHeight|pos|hover|elem|abbreviation|dequeue|offsetParent|version|try|auto|Close|min|describedby|absolute|removeData|bottomright|bottomleft|topright|topleft|push|margin|Old|solo|tooltipmove|detectCorner|true|rgba|coordorigin|coordsize|stroked||antialias|vertical|align|stroke|weight|joinstyle|miter|miterlimit|dblclick|inner|15E3|clearRect|translate|nodeType|beginPath|moveTo|middle|closePath|gi|fillStyle|xe|path|fillcolor|use|null|undefined|do|while|indent|prepend|span|keyup|active|down|empty|error|unload|join|image|load|preventDefault|abort|inArray|stopPropagation|tooltiprender|builtin|makeArray|slice|RegExp|animated|api|over|enter|option|hidden|Boolean|fadeTo|tooltipfocus|timeStamp|zIndex|enable|keydown|outerH|eight|outerW|idth|eq|area|10000em|isNaN|isArray|offsetTop|html5|name|window|strict|topcenter|bottomcenter|rightcenter|leftcenter|lefttop|righttop|offsetLeft|leftbottom|tooltipblur|rightbottom|unshift|triggerHandler|indexOf|centercenter|Color|application|jQuery'.split('|'),0,{}))