Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: 95673bbc5e
Fetching contributors…

Cannot retrieve contributors at this time

445 lines (403 sloc) 16.736 kb
/* Copyright 2004-2005 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.codehaus.groovy.grails.plugins.web.taglib
import groovy.xml.MarkupBuilder
import java.beans.PropertyEditor
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import org.apache.commons.lang.StringEscapeUtils
import org.codehaus.groovy.grails.plugins.codecs.HTMLCodec
import org.springframework.beans.PropertyEditorRegistry
import org.springframework.context.MessageSourceResolvable
import org.springframework.context.NoSuchMessageException
import org.springframework.validation.Errors
import org.springframework.web.context.request.RequestContextHolder
import org.springframework.web.servlet.support.RequestContextUtils as RCU
/**
* Tags to handle validation and errors.
*
* @author Graeme Rocher
*/
class ValidationTagLib {
static returnObjectForTags = ['message', 'fieldError', 'formatValue']
/**
* Renders an error message for the given bean and field.<br/>
*
* eg. &lt;g:fieldError bean="${book}" field="title" /&gt;
*
* @attr bean REQUIRED The bean to check for errors
* @attr field REQUIRED The field of the bean or model reference to check
* @attr message The object to resolve the message for. Objects must implement org.springframework.context.MessageSourceResolvable.
* @attr encodeAs The name of a codec to apply, i.e. HTML, JavaScript, URL etc
* @attr locale override locale to use instead of the one detected
*/
def fieldError = { attrs, body ->
def bean = attrs.bean
def field = attrs.field
if (bean && field) {
if (bean.metaClass.hasProperty(bean, 'errors')) {
return messageImpl(error: bean.errors?.getFieldError(field), encodeAs: "HTML")
}
}
return ''
}
/**
* Obtains the value of a field either from the original errors.<br/>
*
* eg. &lt;g:fieldValue bean="${book}" field="title" /&gt;
*
* @attr bean REQUIRED The bean to check for errors
* @attr field REQUIRED The field of the bean or model reference to check
*/
def fieldValue = { attrs, body ->
def bean = attrs.bean
def field = attrs.field?.toString()
if (bean && field) {
if (bean.metaClass.hasProperty(bean,'errors')) {
Errors errors = bean.errors
def rejectedValue = errors?.getFieldError(field)?.rejectedValue
if (rejectedValue == null) {
rejectedValue = bean
for (String fieldPart in field.split("\\.")) {
rejectedValue = rejectedValue?."$fieldPart"
}
}
if (rejectedValue != null) {
out << formatValue(rejectedValue, field)
}
}
else {
def rejectedValue = bean
for (String fieldPart in field.split("\\.")) {
rejectedValue = rejectedValue?."$fieldPart"
}
if (rejectedValue != null) {
out << formatValue(rejectedValue, field)
}
}
}
}
def extractErrors(attrs) {
def model = attrs.model
def checkList = []
if (attrs.containsKey('bean')) {
if (attrs.bean) {
checkList << attrs.bean
}
}
else if (attrs.containsKey('model')) {
if (model) {
checkList = model.findAll {it.value?.errors instanceof Errors}.collect {it.value}
}
}
else {
for (attributeName in request.attributeNames) {
def ra = request[attributeName]
if (ra) {
def mc = GroovySystem.metaClassRegistry.getMetaClass(ra.getClass())
if (ra instanceof Errors && !checkList.contains(ra)) {
checkList << ra
}
else if (mc.hasProperty(ra, 'errors') && ra.errors instanceof Errors && !checkList.contains(ra.errors)) {
checkList << ra.errors
}
}
}
}
def resultErrorsList = []
for (i in checkList) {
def errors = null
if (i instanceof Errors) {
errors = i
}
else {
def mc = GroovySystem.metaClassRegistry.getMetaClass(i.getClass())
if (mc.hasProperty(i, 'errors')) {
errors = i.errors
}
}
if (errors?.hasErrors()) {
// if the 'field' attribute is not provided then we should output a body,
// otherwise we should check for field-specific errors
if (!attrs.field || errors.hasFieldErrors(attrs.field)) {
resultErrorsList << errors
}
}
}
resultErrorsList
}
/**
* Checks if the request has errors either for a field or global errors.
*
* @attr bean REQUIRED The bean to check for errors
* @attr field The field of the bean or model reference to check
* @attr model The model reference to check for errors
*/
def hasErrors = { attrs, body ->
def errorsList = extractErrors(attrs)
if (errorsList) {
out << body()
}
}
/**
* Loops through each error for either field or global errors.
*
* @attr bean REQUIRED The bean to check for errors
* @attr field REQUIRED The field of the bean or model reference to check
* @attr model The model reference to check for errors
*/
def eachError = { attrs, body ->
eachErrorInternal(attrs, body, true)
}
def eachErrorInternal(attrs, body, boolean outputResult = false) {
def errorsList = extractErrors(attrs)
eachErrorInternalForList attrs, errorsList, body, outputResult
}
def eachErrorInternalForList(attrs, errorsList, body, boolean outputResult = false) {
def var = attrs.var
def field = attrs.field
def errorList = []
for (errors in errorsList) {
if (field) {
if (errors.hasFieldErrors(field)) {
errorList += errors.getFieldErrors(field)
}
}
else {
errorList += errors.allErrors
}
}
for (error in errorList) {
def result
if (var) {
result = body([(var):error])
}
else {
result = body(error)
}
if (outputResult) {
out << result
}
}
null
}
/**
* Loops through each error and renders it using one of the supported mechanisms (defaults to "list" if unsupported).
*
* @attr bean REQUIRED The bean to check for errors
* @attr field The field of the bean or model reference to check
* @attr model The model reference to check for errors
*/
def renderErrors = { attrs, body ->
def renderAs = attrs.remove('as')
if (!renderAs) renderAs = 'list'
if (renderAs == 'list') {
def codec = attrs.codec ?: 'HTML'
if (codec == 'none') codec = ''
def errorsList = extractErrors(attrs)
if(errorsList) {
out << "<ul>"
out << eachErrorInternalForList(attrs, errorsList, {
out << "<li>${message(error:it, encodeAs:codec)}</li>"
})
out << "</ul>"
}
}
else if (renderAs.equalsIgnoreCase("xml")) {
def mkp = new MarkupBuilder(out)
mkp.errors() {
eachErrorInternal(attrs, {
error(object: it.objectName,
field: it.field,
message: message(error:it)?.toString(),
'rejected-value': StringEscapeUtils.escapeXml(it.rejectedValue))
})
}
}
}
/**
* Resolves a message code for a given error or code from the resource bundle.
*
* @attr error The error to resolve the message for. Used for built-in Grails messages.
* @attr message The object to resolve the message for. Objects must implement org.springframework.context.MessageSourceResolvable.
* @attr code The code to resolve the message for. Used for custom application messages.
* @attr args A list of argument values to apply to the message, when code is used.
* @attr default The default message to output if the error or code cannot be found in messages.properties.
* @attr encodeAs The name of a codec to apply, i.e. HTML, JavaScript, URL etc
* @attr locale override locale to use instead of the one detected
*/
def message = { attrs ->
messageImpl(attrs)
}
def messageImpl(attrs) {
def messageSource = grailsAttributes.applicationContext.messageSource
def locale = attrs.locale ?: RCU.getLocale(request)
def text
def error = attrs.error ?: attrs.message
if (error) {
try {
text = messageSource.getMessage(error , locale)
}
catch (NoSuchMessageException e) {
if (error instanceof MessageSourceResolvable) {
text = error?.code
}
else {
text = error?.toString()
}
}
}
else if (attrs.code) {
def code = attrs.code
def args = attrs.args
def defaultMessage
if(attrs.containsKey('default')) {
defaultMessage = attrs['default']
} else {
defaultMessage = code
}
def message = messageSource.getMessage(code, args == null ? null : args.toArray(),
defaultMessage, locale)
if (message != null) {
text = message
}
else {
text = defaultMessage
}
}
if (text) {
return attrs.encodeAs ? text."encodeAs${attrs.encodeAs}"() : text
}
''
}
// Maps out how Grails contraints map to Apache commons validators
static CONSTRAINT_TYPE_MAP = [email : 'email',
creditCard : 'creditCard',
matches : 'mask',
blank: 'required',
nullable: 'required',
maxSize: 'maxLength',
minSize: 'minLength',
range: 'intRange',
size: 'intRange',
length: 'maxLength,minLength']
/**
* Validates a form using Apache commons validator javascript against constraints defined in a Grails
* domain class.<br/>
*
* TODO: This tag is a work in progress
*
* @attr form REQUIRED the HTML form name
* @attr againstClass REQUIRED the domain class name
*/
def validate = { attrs, body ->
def form = attrs.form
if (!form) {
throwTagError("Tag [validate] is missing required attribute [form]")
}
def againstClass = attrs.against ?: form.substring(0,1).toUpperCase() + form.substring(1)
def dc = grailsAttributes.grailsApplication.getDomainClass(againstClass)
if (!dc) {
throwTagError("Tag [validate] could not find a domain class to validate against for name [${againstClass}]")
}
def appliedConstraints = []
dc.constrainedProperties.values().each {
appliedConstraints += it.collect{ it.appliedConstraints }
}
appliedConstraints = appliedConstraints.flatten()
def fieldValidations = [:]
appliedConstraints.each {
def validateType = CONSTRAINT_TYPE_MAP[it.name]
if (validateType) {
if (fieldValidations[validateType]) {
fieldValidations[validateType] << it
}
else {
fieldValidations[validateType] = [it]
}
}
}
out << '<script type="text/javascript">\n'
fieldValidations.each { k,v ->
def validateType = k
if (validateType) {
def validateTypes = [validateType]
if (validateType.contains(",")) {
validateTypes = validateType.split(",")
}
for (vt in validateTypes) {
// import required script
def scriptName = "org/apache/commons/validator/javascript/validate" + vt.substring(0,1).toUpperCase() + vt.substring(1) + ".js"
def inStream = getClass().classLoader.getResourceAsStream(scriptName)
if (inStream) {
out << inStream.text
}
out << "function ${form}_${vt}() {"
for (constraint in v) {
out << "this.${constraint.propertyName} = new Array("
out << "document.forms['${form}'].elements['${constraint.propertyName}']," // the field
out << '"Test message"' // TODO: Resolve the actual message
switch (vt) {
case 'mask': out << ",function() { return '${constraint.regex}'; }";break;
case 'intRange': out << ",function() { if (arguments[0]=='min') return ${constraint.range.from}; else return ${constraint.range.to} }";break;
case 'floatRange': out << ",function() { if (arguments[0]=='min') return ${constraint.range.from}; else return ${constraint.range.to} }";break;
case 'maxLength': out << ",function() { return ${constraint.maxSize}; }";break;
case 'minLength': out << ",function() { return ${constraint.minSize}; }";break;
}
out << ');\n'
}
out << "}\n"
}
}
}
out << 'function validateForm(form) {\n'
fieldValidations.each { k,v ->
def validateType = k.substring(0,1).toUpperCase() + k.substring(1)
out << "if (!validate${validateType}(form)) return false;\n"
}
out << 'return true;\n';
out << '}\n'
// out << "document.forms['${attrs.form}'].onsubmit = function(e) {return validateForm(this)}\n"
out << '</script>'
}
/**
* Formats a given value for output to an HTML page by converting
* it to a string and encoding it. If the value is a number, it is
* formatted according to the current user's locale during the
* conversion to a string.
*/
def formatValue(value, String propertyPath = null) {
PropertyEditorRegistry registry = RequestContextHolder.currentRequestAttributes().getPropertyEditorRegistry()
PropertyEditor editor = registry.findCustomEditor(value.getClass(), propertyPath)
if (editor) {
editor.setValue(value)
return HTMLCodec.shouldEncode() && !(value instanceof Number) ? editor.asText?.encodeAsHTML() : editor.asText
}
if (value instanceof Number) {
def pattern = "0"
if (value instanceof Double || value instanceof Float || value instanceof BigDecimal) {
pattern = "0.00#####"
}
def locale = RCU.getLocale(request)
def dcfs = locale ? new DecimalFormatSymbols(locale) : new DecimalFormatSymbols()
def decimalFormat = new DecimalFormat(pattern, dcfs)
value = decimalFormat.format(value)
}
if (value instanceof MessageSourceResolvable) {
value = message(message: value)
}
return HTMLCodec.shouldEncode() ? value.toString().encodeAsHTML() : value
}
}
Jump to Line
Something went wrong with that request. Please try again.