欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Grails的Javascript验证DIY

程序员文章站 2022-03-14 12:44:49
...
看到Grails的validation标签居然是没实现,一下子大受打击!很郁闷,但是没办法,偶们还是自己来实现一下这个重要的功能把.
Grails的tag其实一般都在\grails-app\taglib下面,可以自行修改源程序来修改tag的表现.validation标签就在ValidationTagLib.groovy这个文件里面.只要修改一下代码就可以了,由于实现过程比较繁杂,直接贴出源代码,需要注意的是:
第一个地方:” import org.geszjava.grails.utils.ChineseProperty as CP;”这条语句,偶是自己写一个类来进行属性名和表单说明的转换,这一步算是偷懒了.有好处也有坏处,好处是不用写那么多Local信息了,直接拿default的来就可以了.坏处是Locale的处理上有问题.当然我现在的项目只支持中文.:),以后有时间可以考虑本地化问题.BTW:grails自己的本地化支持目前好像还没有?不太清楚.呵呵.
第二个地方:”size”这种Type用的是”minLength,maxLength”,而不是原先的intRange,具体怎么回事用脑袋想想也知道:两者在javascript上的实现天差地远.
第三个地方:splitType(),由于有了”minLength,maxLength”这种类型,那么整合的时候当然不能直接写成validationMinLength,maxLength()这样的东东,可以说是个比较严重的错误,所以这里顺便更改了一下
第四个地方: 看看这段代码
switch(vt) {
	                    case 'mask': 
	                    	out << "function ${form}_mask() {";
		                    break;
	                    case 'intRange': 
	                    	out << "function ${form}_intRange() {";
	                    	break;
	                    case 'floatRange': 
	                    	out << "function ${form}_floatRange() {";
	                    	break;
	                    case 'maxLength': 
	                    	out << "function ${form}_maxlength() {";
	                    	break;
	                    case 'minLength': 
	                    	out << "function ${form}_minlength() {";
	                    	break;
	                    case 'email':
	                    	out << "function ${form}_email() {";
	                    	break;
	                    case 'creditCard':
	                    	out << "function ${form}_creditCard() {";
	                    	break;
	                    case 'required':
	                    	out << "function ${form}_required() {";
	                    	break;           
	                    default:
	                    	out << "function ${form}_${vt}() {";
	                    	break;
               		}

原先的代码太粗略,minlength和maxlength不能工作,所以写个比较麻烦的代码,其实修改一下那个MAP就可以了,不过那样修改的东西太多了,不合算.


/* 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 c;pWARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import java.text.MessageFormat
import org.springframework.validation.Errors;
import org.springframework.context.NoSuchMessageException;
import org.springframework.web.servlet.support.RequestContextUtils as RCU;
import org.codehaus.groovy.grails.commons.GrailsClassUtils as GCU;
import org.geszjava.grails.utils.ChineseProperty as CP;


/**
*  A  tag lib that provides tags to handle validation and errors
*
* @author Graeme Rocher
* @since 17-Jan-2006
*/

class ValidationTagLib {
    /**
     * Checks if the request has errors either for a field or global errors
     */
    def hasErrors = { attrs, body ->
        def model = attrs['model']
        def checkList = []
        if(model) {
            checkList = model.findAll { k,v ->
                grailsApplication.isGrailsDomainClass(v.class)
            }
        }
        if(attrs['bean']) {
            checkList << attrs['bean']
        }
        else {
			if(request.attributeNames) {
				request.attributeNames.each { ra ->
					if(ra) {
                        if(ra instanceof Errors)
                            checkList << ra
                        else if(grailsApplication.isGrailsDomainClass(ra.class))
                            checkList << ra
					}
				}
			}
        }

        for(i in checkList) {
            def errors = null
            if(grailsApplication.isGrailsDomainClass(i.class)) {
                if(i.hasErrors())
                    errors = i.errors
            }
            else if(i instanceof Errors) {
               errors = i
            }
            if(errors) {
                if(attrs['field']) {
                    if(errors.hasFieldErrors(attrs['field'])) {
                        body()
                    }
                }
                else {
                    body()
                }
            }
        }
    }

    /**
     * Loops through each error for either field or global errors
     */
    def eachError = { attrs, body ->
        def model = attrs['model']
        def errorList = []
        if(model) {
            errorList = model.findAll { k,v ->
                grailsApplication.isGrailsDomainClass(v.class)
            }
        }
        if(attrs['bean']) {
            errorList << attrs['bean']
        }
        else {
            request.attributeNames.each {
                def ra = request[it]
                if(ra) {
                    if(ra instanceof Errors)
                        errorList << ra
                    else if(grailsApplication.isGrailsDomainClass(ra.class))
                        errorList << ra
                }
            }
        }

        for(i in errorList) {
            def errors = null
            if(grailsApplication.isGrailsDomainClass(i.class)) {
                if(i.hasErrors())
                    errors = i.errors
            }
            else if(i instanceof Errors) {
               errors = i
            }
            if(errors && errors.hasErrors()) {
                if(attrs['field']) {
                    if(errors.hasFieldErrors(attrs['field'])) {
                        errors.getFieldErrors( attrs["field"] ).each {
                            body(it)
                        }
                    }
                }
                else {
                    errors.allErrors.each {
                        body( it )
                    }
                }
            }
        }
    }

    /**
     * Loops through each error and renders it using one of the supported mechanisms (defaults to "list" if unsupported)
     */
    def renderErrors = { attrs, body ->
        def renderAs = attrs.remove('as')
        if(!renderAs) renderAs = 'list'

        if(renderAs == 'list') {
            out << "<ul>"
            eachError(attrs, {
                out << "<li>"
                message(error:it)
                out << "</li>"
              }
            )
            out << "</ul>"
        }
    }
    /**
     * Resolves a message code for a given error or code from the resource bundle
     */
    def message = { attrs ->
          def messageSource = grailsAttributes
                                .getApplicationContext()
                                .getBean("messageSource")

          def locale = RCU.getLocale(request)

          if(attrs['error']) {
                def error = attrs['error']
                def defaultMessage = ( attrs['default'] ? attrs['default'] : error.defaultMessage )
                def message = messageSource.getMessage( error.code,
                                                        error.arguments,
                                                        defaultMessage,
                                                        locale )
                if(message) {
                    out << message
                }
                else {
                    out << error.code
                }
          }
          if(attrs['code']) {
                def code = attrs['code']
                def defaultMessage = ( attrs['default'] ? attrs['default'] : code )

                def message = messageSource.getMessage( code,
                                                        null,
                                                        defaultMessage,
                                                        locale )
                if(message) {
                    out << message
                }
                else {
                    out << code
                }
          }
    }
    // Maps out how Grails contraints map to Apache commons validators
    static CONSTRAINT_TYPE_MAP = [ email : 'email',
                                             creditCard : 'creditCard',
                                             match : 'mask',
                                             blank: 'required',
                                             nullable: 'required',
                                             maxSize: 'maxLength',
                                             minSize: 'minLength',
                                             range: 'intRange',
                                             size: 'maxLength,minLength',
                                             length: 'maxLength,minLength' ]
    /**
     * Validates a form using Apache commons validator javascript against constraints defined in a Grails
     * domain class
     *
     * TODO: This tag is a work in progress
     修改
     */
    def validate = { attrs, body ->
        def form = attrs["form"]
        def againstClass = attrs["against"]
        if(!form)
            throwTagError("Tag [validate] is missing required attribute [form]")

        if(!againstClass) {
            againstClass = form.substring(0,1).toUpperCase() + form.substring(1)
        }

        def app = grailsAttributes.getGrailsApplication()
        def dc = app.getGrailsDomainClass(againstClass)

        if(!dc)
            throwTagError("Tag [validate] could not find a domain class to validate against for name [${againstClass}]")

        def constrainedProperties = dc.constrainedProperties.collect { k,v -> return v }
        def appliedConstraints = []

        constrainedProperties.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'
        def scriptNameUtil = "org/apache/commons/validator/javascript/validateUtilities.js"
            
        def inStreamUtil = getClass().classLoader.getResourceAsStream(scriptNameUtil)
        if(inStreamUtil) {
            out << inStreamUtil.text
        }        
        
        fieldValidations.each { k,v ->
           def validateType = k

           if(validateType) {

                def validateTypes = [validateType]

                if(validateType.contains(",")) {
                    validateTypes = validateType.split(",")
                }

                validateTypes.each { vt ->
                    // 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
                    }
                    
                    switch(vt) {
	                    case 'mask': 
	                    	out << "function ${form}_mask() {";
		                    break;
	                    case 'intRange': 
	                    	out << "function ${form}_intRange() {";
	                    	break;
	                    case 'floatRange': 
	                    	out << "function ${form}_floatRange() {";
	                    	break;
	                    case 'maxLength': 
	                    	out << "function ${form}_maxlength() {";
	                    	break;
	                    case 'minLength': 
	                    	out << "function ${form}_minlength() {";
	                    	break;
	                    case 'email':
	                    	out << "function ${form}_email() {";
	                    	break;
	                    case 'creditCard':
	                    	out << "function ${form}_creditCard() {";
	                    	break;
	                    case 'required':
	                    	out << "function ${form}_required() {";
	                    	break;           
	                    default:
	                    	out << "function ${form}_${vt}() {";
	                    	break;
               		}
                    
                    //out << "function ${form}_${vt.toLowerCase()}() {"
                    	
                    v.each { constraint ->
                           out << "this.${constraint.propertyName} = new Array("
                           out << "'${constraint.propertyName}'," // the field
                           //out << '"Test message"' // TODO: Resolve the actual message
                           def clazzName = againstClass?.toLowerCase()
                           out << getMessage(vt, clazzName, constraint.propertyName, constraint)

                           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': 
                                	if (!isSizeConstraint(constraint)) {
	                                	out << ",function() {return ${constraint.maxSize};}";
                                	} else {
                                		out << ",function() {return ${constraint.range.to};}";
                                	}
                                	break;
                                case 'minLength': 
                                	if (!isSizeConstraint(constraint)) {
	                                	out << ",function() {return ${constraint.minSize};}";
                                	} else {
                                		out << ",function() {return ${constraint.range.from};}";
                                	}
                                	break;
                           }
                           out << ');\n'
                    }
                    out << "}\n"
                }
            }
        }
        out << 'function validateForm(form) {\n'
         fieldValidations.each { k,v ->
         	   def splittedTypes = splitType(k);
         	   for (spt in splittedTypes) {
	               def validateType = spt.substring(0,1).toUpperCase() + spt.substring(1)
	               out << "if(!validate${validateType}(form)) return false;\n"
         	   }
         }
        out << 'return true;\n';
        out << '}\n'
      	out << "document.forms['${attrs['form']}'].onsubmit = function() {return validateForm(this);}\n"
        out << '</script>'
    }
    
    //添加
    def static messageMap = [
    	"mask" : "{0}不符合正则表达式[{1}]",
    	"creditCard" : "{0}不是一个合法的信用卡号",
    	"email" : "{0}不是一个合法的Email地址",
    	"intRange" : "{0}不在{1}到{2}之内",
    	"floatRange" : "{0}不在{1}到{2}之内",
    	"maxLength" : "{0}的长度大于{1}",
    	"minLength" : "{0}的长度小于{1}",
    	"required" : "{0}不能为空",
     ]                           
     
     //添加
    def static splitType(type) {
    	 if (type.indexOf(",") >= 0) {
    		 StringTokenizer st = new StringTokenizer(type, ",");
    		 def retList = []
    		 while (st.hasMoreTokens()) {
    			 retList << st.nextToken();
    		 }
    		 return retList;
    	 } else {
    		 return [type];
    	 }
    }
    
    def static isSizeConstraint(constraint) {
    	if (constraint.getClass().getName() == "org.codehaus.groovy.grails.validation.ConstrainedProperty\$SizeConstraint") 
    		return true
    	else return false
    }
     //添加
	def getMessage(vt, clazzName, propertyName, constraint) {
    	def cn = CP.getChinesePropertyName(clazzName + "." + propertyName)
    	if (!cn) cn = propertyName
    	Object[] args = new Object[3]
		args[0] = cn
		def omsg = messageMap[vt]
		if (!omsg) omsg = "存在未知错误"
    	def msg = "";
    	
		switch(vt) {
			case 'email': 
				break
			case 'creditCard': 
				break
			case 'mask': 
				args[1] = constraint.regex
				break
			case 'intRange': 
				args[1] = constraint.range.from
				args[2] = constraint.range.to
				break
			case 'floatRange': 
				args[1] = constraint.range.from
				args[2] = constraint.range.to
				break;
			case 'maxLength': 
				if (!isSizeConstraint(constraint)) {
					args[1] = constraint.maxSize
				} else {
					args[1] = constraint.range.to
				}
				break
			case 'minLength': 
				if (!isSizeConstraint(constraint)) {
					args[1] = constraint.minSize
				} else {
					args[1] = constraint.range.from
				}
 				break				
    	}

		return "\"" + MessageFormat.format(omsg, args) + "\""
    }
}