Author: lukaszlenart Date: Fri May 24 08:56:41 2013 New Revision: 1485978 URL: http://svn.apache.org/r1485978 Log: WW-600 Enables Client-side validation for visitor validations
Modified: struts/struts2/trunk/core/src/main/java/org/apache/struts2/components/Form.java struts/struts2/trunk/core/src/main/resources/template/xhtml/form-close-validate.ftl struts/struts2/trunk/core/src/test/java/org/apache/struts2/components/FormTest.java struts/struts2/trunk/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-2.txt struts/struts2/trunk/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-22.txt struts/struts2/trunk/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-24.txt Modified: struts/struts2/trunk/core/src/main/java/org/apache/struts2/components/Form.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/java/org/apache/struts2/components/Form.java?rev=1485978&r1=1485977&r2=1485978&view=diff ============================================================================== --- struts/struts2/trunk/core/src/main/java/org/apache/struts2/components/Form.java (original) +++ struts/struts2/trunk/core/src/main/java/org/apache/struts2/components/Form.java Fri May 24 08:56:41 2013 @@ -31,16 +31,22 @@ import com.opensymphony.xwork2.intercept import com.opensymphony.xwork2.util.ValueStack; import com.opensymphony.xwork2.validator.ActionValidatorManager; import com.opensymphony.xwork2.validator.FieldValidator; +import com.opensymphony.xwork2.validator.ValidationException; import com.opensymphony.xwork2.validator.ValidationInterceptor; import com.opensymphony.xwork2.validator.Validator; -import org.apache.struts2.StrutsConstants; +import com.opensymphony.xwork2.validator.ValidatorContext; +import com.opensymphony.xwork2.validator.validators.VisitorFieldValidator; +import org.apache.commons.lang3.StringUtils; +import org.apache.struts2.dispatcher.mapper.ActionMapping; import org.apache.struts2.views.annotations.StrutsTag; import org.apache.struts2.views.annotations.StrutsTagAttribute; -import org.apache.commons.lang3.StringUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.util.*; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Set; /** @@ -116,14 +122,17 @@ public class Form extends ClosingUIBean super(stack, request, response); } + @Override protected boolean evaluateNameValue() { return false; } + @Override public String getDefaultOpenTemplate() { return OPEN_TEMPLATE; } + @Override protected String getDefaultTemplate() { return TEMPLATE; } @@ -153,6 +162,7 @@ public class Form extends ClosingUIBean * Revised for Portlet actionURL as form action, and add wwAction as hidden * field. Refer to template.simple/form.vm */ + @Override protected void evaluateExtraParams() { super.evaluateExtraParams(); if (validate != null) { @@ -210,6 +220,7 @@ public class Form extends ClosingUIBean * <li>if an 'action' attribute is specified, it will be used as the id.</li> * </ol> */ + @Override protected void populateComponentHtmlId(Form form) { if (id != null) { addParameter("id", escape(id)); @@ -262,21 +273,145 @@ public class Form extends ClosingUIBean return Collections.EMPTY_LIST; } - List<Validator> all = actionValidatorManager.getValidators(actionClass, (String) getParameters().get("actionName")); + String formActionValue = findString(action); + ActionMapping mapping = actionMapper.getMappingFromActionName(formActionValue); + String actionName = mapping.getName(); + String methodName = mapping.getMethod(); + + List<Validator> actionValidators = actionValidatorManager.getValidators(actionClass, actionName, methodName); List<Validator> validators = new ArrayList<Validator>(); - for (Validator validator : all) { + + findFieldValidators(name, actionClass, actionName, actionValidators, validators, ""); + + return validators; + } + + private void findFieldValidators(String name, Class actionClass, String actionName, + List<Validator> validatorList, List<Validator> retultValidators, String prefix) { + + for (Validator validator : validatorList) { if (validator instanceof FieldValidator) { FieldValidator fieldValidator = (FieldValidator) validator; - if (fieldValidator.getFieldName().equals(name)) { - validators.add(fieldValidator); + + if (validator instanceof VisitorFieldValidator) { + VisitorFieldValidator vfValidator = (VisitorFieldValidator) fieldValidator; + Class clazz = getVisitorReturnType(actionClass, vfValidator.getFieldName()); + if (clazz == null) { + continue; + } + + List<Validator> visitorValidators = actionValidatorManager.getValidators(clazz, actionName); + String vPrefix = prefix + (vfValidator.isAppendPrefix() ? vfValidator.getFieldName() + "." : ""); + findFieldValidators(name, clazz, actionName, visitorValidators, retultValidators, vPrefix); + } else if ((prefix + fieldValidator.getFieldName()).equals(name)) { + if (StringUtils.isNotBlank(prefix)) { + //fixing field name for js side + FieldVisitorValidatorWrapper wrap = new FieldVisitorValidatorWrapper(fieldValidator, prefix); + retultValidators.add(wrap); + } else { + retultValidators.add(fieldValidator); + } } } } + } - return validators; + /** + * Wrap field validator, add visitor's field prefix to the field name. + * Javascript side is not aware of the visitor validators + * and does not know how to prefix the fields. + */ + /* + * Class is public because Freemarker has problems accessing properties. + */ + public static class FieldVisitorValidatorWrapper implements FieldValidator { + private FieldValidator fieldValidator; + private String namePrefix; + public FieldVisitorValidatorWrapper(FieldValidator fv, String namePrefix) { + this.fieldValidator = fv; + this.namePrefix = namePrefix; + } + public String getValidatorType() { + return "field-visitor"; + } + public String getFieldName() { + return namePrefix + fieldValidator.getFieldName(); + } + public FieldValidator getFieldValidator() { + return fieldValidator; + } + public void setFieldValidator(FieldValidator fieldValidator) { + this.fieldValidator = fieldValidator; + } + public String getDefaultMessage() { + return fieldValidator.getDefaultMessage(); + } + public String getMessage(Object object) { + return fieldValidator.getMessage(object); + } + public String getMessageKey() { + return fieldValidator.getMessageKey(); + } + public String[] getMessageParameters() { + return fieldValidator.getMessageParameters(); + } + public ValidatorContext getValidatorContext() { + return fieldValidator.getValidatorContext(); + } + public void setDefaultMessage(String message) { + fieldValidator.setDefaultMessage(message); + } + public void setFieldName(String fieldName) { + fieldValidator.setFieldName(fieldName); + } + public void setMessageKey(String key) { + fieldValidator.setMessageKey(key); + } + public void setMessageParameters(String[] messageParameters) { + fieldValidator.setMessageParameters(messageParameters); + } + public void setValidatorContext(ValidatorContext validatorContext) { + fieldValidator.setValidatorContext(validatorContext); + } + public void setValidatorType(String type) { + fieldValidator.setValidatorType(type); + } + public void setValueStack(ValueStack stack) { + fieldValidator.setValueStack(stack); + } + public void validate(Object object) throws ValidationException { + fieldValidator.validate(object); + } + public String getNamePrefix() { + return namePrefix; + } + public void setNamePrefix(String namePrefix) { + this.namePrefix = namePrefix; + } } /** + * Return type of visited object. + * @param actionClass + * @param visitorFieldName + * @return + */ + @SuppressWarnings("unchecked") + protected Class getVisitorReturnType(Class actionClass, String visitorFieldName) { + if (visitorFieldName == null) { + return null; + } + String methodName = "get" + org.apache.commons.lang.StringUtils.capitalize(visitorFieldName); + try { + Method method = actionClass.getMethod(methodName, new Class[0]); + return method.getReturnType(); + } catch (NoSuchMethodException e) { + return null; + } + } + + + /** * Get a incrementing sequence unique to this <code>Form</code> component. * It is used by <code>Form</code> component's child that might need a * sequence to make them unique. Modified: struts/struts2/trunk/core/src/main/resources/template/xhtml/form-close-validate.ftl URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/resources/template/xhtml/form-close-validate.ftl?rev=1485978&r1=1485977&r2=1485978&view=diff ============================================================================== --- struts/struts2/trunk/core/src/main/resources/template/xhtml/form-close-validate.ftl (original) +++ struts/struts2/trunk/core/src/main/resources/template/xhtml/form-close-validate.ftl Fri May 24 08:56:41 2013 @@ -36,6 +36,26 @@ END SNIPPET: supported-validators <#if ((parameters.validate!false == true) && (parameters.performValidation!false == true))> <script type="text/javascript"> function validateForm_${parameters.id?replace('[^a-zA-Z0-9_]', '_', 'r')}() { + <#-- + In case of multiselect fields return only the first value. + --> + var getFieldValue = function(field) { + var type = field.type ? field.type : field[0].type; + if (type == 'select-one' || type == 'select-multiple') { + return (field.selectedIndex == -1 ? "" : field.options[field.selectedIndex].value); + } else if (type == 'checkbox' || type == 'radio') { + if (!field.length) { + field = [field]; + } + for (var i = 0; i < field.length; i++) { + if (field[i].checked) { + return field[i].value; + } + } + return ""; + } + return field.value; + } form = document.getElementById("${parameters.id}"); clearErrorMessages(form); clearErrorLabels(form); @@ -43,27 +63,36 @@ END SNIPPET: supported-validators var errors = false; var continueValidation = true; <#list parameters.tagNames as tagName> - <#list tag.getValidators("${tagName}") as validator> - // field name: ${validator.fieldName} - // validator name: ${validator.validatorType} - if (form.elements['${validator.fieldName}']) { - field = form.elements['${validator.fieldName}']; + <#list tag.getValidators("${tagName}") as aValidator> + // field name: ${aValidator.fieldName} + // validator name: ${aValidator.validatorType} + if (form.elements['${aValidator.fieldName}']) { + field = form.elements['${aValidator.fieldName}']; + <#if aValidator.validatorType = "field-visitor"> + <#assign validator = aValidator.fieldValidator > + //visitor validator switched to: ${validator.validatorType} + <#else> + <#assign validator = aValidator > + </#if> + var error = "${validator.getMessage(action)?js_string}"; + var fieldValue = getFieldValue(field); + <#if validator.validatorType = "required"> - if (field.value == "") { + if (fieldValue == "") { addError(field, error); errors = true; <#if validator.shortCircuit>continueValidation = false;</#if> } <#elseif validator.validatorType = "requiredstring"> - if (continueValidation && field.value != null && (field.value == "" || field.value.replace(/^\s+|\s+$/g,"").length == 0)) { + if (continueValidation && fieldValue != null && (fieldValue == "" || fieldValue.replace(/^\s+|\s+$/g,"").length == 0)) { addError(field, error); errors = true; <#if validator.shortCircuit>continueValidation = false;</#if> } <#elseif validator.validatorType = "stringlength"> - if (continueValidation && field.value != null) { - var value = field.value; + if (continueValidation && fieldValue != null) { + var value = fieldValue; <#if validator.trim> //trim field value while (value.substring(0,1) == ' ') @@ -79,28 +108,28 @@ END SNIPPET: supported-validators } } <#elseif validator.validatorType = "regex"> - if (continueValidation && field.value != null && !field.value.match("${validator.regex?js_string}")) { + if (continueValidation && fieldValue != null && !fieldValue.match("${validator.regex?js_string}")) { addError(field, error); errors = true; <#if validator.shortCircuit>continueValidation = false;</#if> } <#elseif validator.validatorType = "email"> - if (continueValidation && field.value != null && field.value.length > 0 && field.value.match("${validator.regex?js_string}")==null) { + if (continueValidation && fieldValue != null && fieldValue.length > 0 && fieldValue.match("${validator.regex?js_string}")==null) { addError(field, error); errors = true; <#if validator.shortCircuit>continueValidation = false;</#if> } <#elseif validator.validatorType = "url"> - if (continueValidation && field.value != null && field.value.length > 0 && field.value.match(/^(ftp|http|https):\/\/((%[A-F0-9]{2}|[A-Z0-9-._~!$&'()*+,;=:])+@)?((%[A-F0-9]{2}|[A-Z0-9-._~!$&'()*+,;=])+)(:[0-9]+)?((\/(%[A-F0-9]{2}|[A-Z0-9-._~!$&'()*+,;=:@])*)*)(\?(%[A-F0-9]{2}|[A-Z0-9-._~!$&'()*+,;=:@/?])*)?(#(%[A-F0-9]{2}|[A-Z0-9-._~!$&'()*+,;=:@/?])*)?$/gi)==null) { + if (continueValidation && fieldValue != null && fieldValue.length > 0 && fieldValue.match("${validator.regex?js_string}")==null) { addError(field, error); errors = true; <#if validator.shortCircuit>continueValidation = false;</#if> } <#elseif validator.validatorType = "int" || validator.validatorType = "short"> - if (continueValidation && field.value != null) { - if (<#if validator.min??>parseInt(field.value) < + if (continueValidation && fieldValue != null) { + if (<#if validator.min??>parseInt(fieldValue) < ${validator.min?c}<#else>false</#if> || - <#if validator.max??>parseInt(field.value) > + <#if validator.max??>parseInt(fieldValue) > ${validator.max?c}<#else>false</#if>) { addError(field, error); errors = true; @@ -108,8 +137,8 @@ END SNIPPET: supported-validators } } <#elseif validator.validatorType = "double"> - if (continueValidation && field.value != null) { - var value = parseFloat(field.value); + if (continueValidation && fieldValue != null) { + var value = parseFloat(fieldValue); if (<#if validator.minInclusive??>value < ${validator.minInclusive?c}<#else>false</#if> || <#if validator.maxInclusive??>value > ${validator.maxInclusive?c}<#else>false</#if> || <#if validator.minExclusive??>value <= ${validator.minExclusive?c}<#else>false</#if> || @@ -126,4 +155,4 @@ END SNIPPET: supported-validators return !errors; } </script> -</#if> +</#if> \ No newline at end of file Modified: struts/struts2/trunk/core/src/test/java/org/apache/struts2/components/FormTest.java URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/test/java/org/apache/struts2/components/FormTest.java?rev=1485978&r1=1485977&r2=1485978&view=diff ============================================================================== --- struts/struts2/trunk/core/src/test/java/org/apache/struts2/components/FormTest.java (original) +++ struts/struts2/trunk/core/src/test/java/org/apache/struts2/components/FormTest.java Fri May 24 08:56:41 2013 @@ -21,18 +21,17 @@ package org.apache.struts2.components; -import java.util.List; - +import com.opensymphony.xwork2.Action; +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.ActionProxy; +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.validator.validators.RequiredFieldValidator; import org.apache.struts2.TestAction; import org.apache.struts2.views.jsp.AbstractUITagTest; import org.easymock.EasyMock; -import com.opensymphony.xwork2.validator.validators.RequiredFieldValidator; -import com.opensymphony.xwork2.config.entities.ActionConfig; -import com.opensymphony.xwork2.ActionInvocation; -import com.opensymphony.xwork2.ActionProxy; -import com.opensymphony.xwork2.Action; -import com.opensymphony.xwork2.ActionContext; +import java.util.List; /** * <code>FormTest</code> @@ -45,6 +44,7 @@ public class FormTest extends AbstractUI Form form = new Form(stack, request, response); container.inject(form); form.getParameters().put("actionClass", TestAction.class); + form.setAction("actionName"); List v = form.getValidators("foo"); assertEquals(1, v.size()); assertEquals(RequiredFieldValidator.class, v.get(0).getClass()); Modified: struts/struts2/trunk/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-2.txt URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-2.txt?rev=1485978&r1=1485977&r2=1485978&view=diff ============================================================================== --- struts/struts2/trunk/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-2.txt (original) +++ struts/struts2/trunk/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-2.txt Fri May 24 08:56:41 2013 @@ -32,12 +32,32 @@ </script> <script type="text/javascript"> + function validateForm_myAction() { + var getFieldValue = function(field) { + var type = field.type ? field.type : field[0].type; + if(type == 'select-one' || type == 'select-multiple') { + return (field.selectedIndex == -1 ? "" : field.options[field.selectedIndex].value); + } else if(type=='checkbox'||type=='radio') { + if(!field.length){ + field=[field]; + } + for(var i=0; i < field.length; i++){ + if(field[i].checked) { + return field[i].value; + } + } + return ""; + } + return field.value; + } form = document.getElementById("myAction"); clearErrorMessages(form); clearErrorLabels(form); var errors = false; var continueValidation=true; - return !errors; } + return !errors; + } + </script> Modified: struts/struts2/trunk/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-22.txt URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-22.txt?rev=1485978&r1=1485977&r2=1485978&view=diff ============================================================================== --- struts/struts2/trunk/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-22.txt (original) +++ struts/struts2/trunk/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-22.txt Fri May 24 08:56:41 2013 @@ -33,23 +33,41 @@ <script type="text/javascript"> function validateForm_myAction() { + var getFieldValue = function(field) { + var type = field.type ? field.type : field[0].type; + if(type == 'select-one' || type == 'select-multiple') { + return (field.selectedIndex == -1 ? "" : field.options[field.selectedIndex].value); + } else if(type == 'checkbox' || type == 'radio') { + if(!field.length) { + field = [field]; + } + for(var i = 0; i < field.length; i++) { + if(field[i].checked) { + return field[i].value; + } + } + return""; + } + return field.value; + } form = document.getElementById("myAction"); clearErrorMessages(form); clearErrorLabels(form); var errors = false; var continueValidation = true; - //fieldname:myUpDownSelectTag - //validatorname:int - if(form.elements['myUpDownSelectTag']){ - field=form.elements['myUpDownSelectTag']; - var error="bar must be between 6000 and 10000."; - if(continueValidation && field.value!=null){ - if(parseInt(field.value)<6000||parseInt(field.value)>10000){ - addError(field,error); - errors=true; - } - } - } - return!errors; - }</script> - + //fieldname:myUpDownSelectTag + //validatorname:int + if(form.elements['myUpDownSelectTag']) { + field = form.elements['myUpDownSelectTag']; + var error = "bar must be between 6000 and 10000."; + var fieldValue = getFieldValue(field); + if(continueValidation && fieldValue != null) { + if(parseInt(fieldValue) < 6000 || parseInt(fieldValue) > 10000) { + addError(field, error); + errors = true; + } + } + } + return !errors; + } +</script> \ No newline at end of file Modified: struts/struts2/trunk/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-24.txt URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-24.txt?rev=1485978&r1=1485977&r2=1485978&view=diff ============================================================================== --- struts/struts2/trunk/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-24.txt (original) +++ struts/struts2/trunk/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-24.txt Fri May 24 08:56:41 2013 @@ -33,24 +33,43 @@ <script type="text/javascript"> function validateForm_myAction() { + var getFieldValue = function(field) { + var type = field.type ? field.type : field[0].type; + if(type == 'select-one' || type == 'select-multiple') { + return (field.selectedIndex == -1 ? "" : field.options[field.selectedIndex].value); + } else if(type == 'checkbox' || type == 'radio') { + if(!field.length) { + field=[field]; + } + for(var i = 0; i < field.length; i++) { + if(field[i].checked) { + return field[i].value; + } + } + return""; + } + return field.value; + } form = document.getElementById("myAction"); clearErrorMessages(form); clearErrorLabels(form); var errors = false; var continueValidation = true; - //fieldname:myUpDownSelectTag - //validatorname:double - if(form.elements['myUpDownSelectTag']){ - field=form.elements['myUpDownSelectTag']; - var error="bar must be between 6000.1 and 10000.1."; - if(continueValidation && field.value!=null){ - var value = parseFloat(field.value); - if(value<6000.1||value>10000.1||false||false){ - addError(field,error); - errors=true; - } - } - } - return!errors; - }</script> + //fieldname:myUpDownSelectTag + //validatorname:double + if(form.elements['myUpDownSelectTag']) { + field = form.elements['myUpDownSelectTag']; + var error = "bar must be between 6000.1 and 10000.1."; + var fieldValue = getFieldValue(field); + if(continueValidation && fieldValue != null) { + var value = parseFloat(fieldValue); + if(value < 6000.1 || value > 10000.1 || false || false) { + addError(field, error); + errors = true; + } + } + } + return !errors; + } +</script>