From 8107fd204403a7929d4489c920871fb91052de14 Mon Sep 17 00:00:00 2001 From: bwilson Date: Wed, 12 Feb 2014 12:47:39 -0600 Subject: [PATCH 1/9] Make letters constraint case-insensitive. --- .../ui/LettersonlyConstraint.groovy | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) 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]+$/ + } +} From 57cd6423aa13da7bc57495f498de1e19e850a41c Mon Sep 17 00:00:00 2001 From: bwilson Date: Wed, 12 Feb 2014 12:50:40 -0600 Subject: [PATCH 2/9] Fixed extraneous commas that tripped up IE. Eliminated Melody incompatibility. Made remote validation call synchronous. --- .../ui/JqueryValidationService.groovy | 1002 ++++++++--------- 1 file changed, 480 insertions(+), 522 deletions(-) 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..8d03b34 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, 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')?'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, 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(), "' + \$('#${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, 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, args, constraintName, locale)}'" + } + case "blank": + if (!constrainedProperty.isBlank()) { + javaScriptMessagesList << "${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 + javaScriptMessagesList << "${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}" + javaScriptMessagesList << "${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 + javaScriptMessagesList << "${javaScriptConstraint}: function() { return '${getMessage(constrainedProperty.owningClass, constrainedProperty.propertyName, args, constraintName, locale)}'; }" + break + + case "unique": + case "validator": + args << VALUE_PLACEHOLDER + javaScriptMessagesList << "remote: 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 + javaScriptMessagesList << "${constraintName}: function() { return '${getMessage(constrainedProperty.owningClass, constrainedProperty.propertyName, 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 + } + +} From bcecb77849ace0ee74bf9bc91d45f73634a01b2b Mon Sep 17 00:00:00 2001 From: bwilson Date: Wed, 12 Feb 2014 12:53:29 -0600 Subject: [PATCH 3/9] Added ability to specify an alternate element to target a qtip message to. The alternate element must have an id that is [form element id] + "Target". --- .../ui/JQueryValidationUiTagLib.groovy | 537 +++++++++--------- 1 file changed, 282 insertions(+), 255 deletions(-) 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..e7e8220 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,282 @@ -/* 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 << """ -
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    -""" - } - } - - 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 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 << """ +
      +
      +
      +
      +
      +
      +
      + +
      +
      +
      +""" + } + } + + 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()) { + var errorTarget = \$('#' + element[0].name + '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, 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 + } +} + + From 35705352694c0821d5194cdcf02904e4bf550576 Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Fri, 26 Jun 2015 18:38:17 -0500 Subject: [PATCH 4/9] Made compatible with Grails 2.4 and Asset Pipeline plugin. --- JqueryValidationUiGrailsPlugin.groovy | 2 +- application.properties | 4 +- .../javascripts}/grails-validation-methods.js | 0 .../jquery-validation/additional-methods.js | 617 +++++++++ .../additional-methods.min.js | 1 + .../jquery-validation/jquery.validate.js | 1231 +++++++++++++++++ .../jquery-validation/jquery.validate.min.js | 1 + .../javascripts/jqueryValidationUiPlugin.js | 3 + .../jqueryValidationUiPluginQTip.js | 2 + .../assets/javascripts}/qTip/jquery.qtip.js | 0 .../assets/stylesheets}/errors.css | 0 .../jqueryValidationUiPluginQTip.css | 3 + .../assets/stylesheets}/main.css | 0 .../assets/stylesheets}/mobile.css | 0 .../assets/stylesheets}/qTip/jquery.qtip.css | 0 grails-app/conf/BuildConfig.groovy | 34 +- .../conf/JqueryValidationUiResources.groovy | 12 - .../ui/JQueryValidationUiTagLib.groovy | 25 - web-app/images/apple-touch-icon-retina.png | Bin 14986 -> 0 bytes web-app/images/apple-touch-icon.png | Bin 5434 -> 0 bytes web-app/images/favicon.ico | Bin 10134 -> 0 bytes web-app/images/grails_logo.jpg | Bin 8065 -> 0 bytes web-app/images/grails_logo.png | Bin 10172 -> 0 bytes web-app/images/leftnav_btm.png | Bin 3859 -> 0 bytes web-app/images/leftnav_midstretch.png | Bin 2883 -> 0 bytes web-app/images/leftnav_top.png | Bin 3317 -> 0 bytes web-app/images/skin/database_add.png | Bin 658 -> 0 bytes web-app/images/skin/database_delete.png | Bin 659 -> 0 bytes web-app/images/skin/database_edit.png | Bin 767 -> 0 bytes web-app/images/skin/database_save.png | Bin 755 -> 0 bytes web-app/images/skin/database_table.png | Bin 726 -> 0 bytes web-app/images/skin/exclamation.png | Bin 701 -> 0 bytes web-app/images/skin/house.png | Bin 806 -> 0 bytes web-app/images/skin/information.png | Bin 778 -> 0 bytes web-app/images/skin/shadow.jpg | Bin 300 -> 0 bytes web-app/images/skin/sorted_asc.gif | Bin 835 -> 0 bytes web-app/images/skin/sorted_desc.gif | Bin 834 -> 0 bytes web-app/images/spinner.gif | Bin 2037 -> 0 bytes web-app/images/springsource.png | Bin 9109 -> 0 bytes web-app/js/application.js | 9 - web-app/js/qTip/jquery.qtip.pack.js | 19 - 41 files changed, 1886 insertions(+), 77 deletions(-) rename {web-app/js/jquery-validation-ui => grails-app/assets/javascripts}/grails-validation-methods.js (100%) create mode 100644 grails-app/assets/javascripts/jquery-validation/additional-methods.js create mode 100644 grails-app/assets/javascripts/jquery-validation/additional-methods.min.js create mode 100644 grails-app/assets/javascripts/jquery-validation/jquery.validate.js create mode 100644 grails-app/assets/javascripts/jquery-validation/jquery.validate.min.js create mode 100644 grails-app/assets/javascripts/jqueryValidationUiPlugin.js create mode 100644 grails-app/assets/javascripts/jqueryValidationUiPluginQTip.js rename {web-app/js => grails-app/assets/javascripts}/qTip/jquery.qtip.js (100%) rename {web-app/css => grails-app/assets/stylesheets}/errors.css (100%) create mode 100644 grails-app/assets/stylesheets/jqueryValidationUiPluginQTip.css rename {web-app/css => grails-app/assets/stylesheets}/main.css (100%) rename {web-app/css => grails-app/assets/stylesheets}/mobile.css (100%) rename {web-app/css => grails-app/assets/stylesheets}/qTip/jquery.qtip.css (100%) delete mode 100644 grails-app/conf/JqueryValidationUiResources.groovy delete mode 100644 web-app/images/apple-touch-icon-retina.png delete mode 100644 web-app/images/apple-touch-icon.png delete mode 100644 web-app/images/favicon.ico delete mode 100644 web-app/images/grails_logo.jpg delete mode 100644 web-app/images/grails_logo.png delete mode 100644 web-app/images/leftnav_btm.png delete mode 100644 web-app/images/leftnav_midstretch.png delete mode 100644 web-app/images/leftnav_top.png delete mode 100644 web-app/images/skin/database_add.png delete mode 100644 web-app/images/skin/database_delete.png delete mode 100644 web-app/images/skin/database_edit.png delete mode 100644 web-app/images/skin/database_save.png delete mode 100644 web-app/images/skin/database_table.png delete mode 100644 web-app/images/skin/exclamation.png delete mode 100644 web-app/images/skin/house.png delete mode 100644 web-app/images/skin/information.png delete mode 100644 web-app/images/skin/shadow.jpg delete mode 100644 web-app/images/skin/sorted_asc.gif delete mode 100644 web-app/images/skin/sorted_desc.gif delete mode 100644 web-app/images/spinner.gif delete mode 100644 web-app/images/springsource.png delete mode 100644 web-app/js/application.js delete mode 100644 web-app/js/qTip/jquery.qtip.pack.js diff --git a/JqueryValidationUiGrailsPlugin.groovy b/JqueryValidationUiGrailsPlugin.groovy index 7d5267f..1f988ec 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.3" // 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