WW-600 Enables Client-side validation for visitor validations

git-svn-id: https://svn.apache.org/repos/asf/struts/struts2/trunk@1485978 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Lukasz Lenart
2013-05-24 08:56:41 +00:00
parent 9d6e6a6b0f
commit 644dd2ed1c
6 changed files with 290 additions and 69 deletions
@@ -31,16 +31,22 @@ import com.opensymphony.xwork2.interceptor.MethodFilterInterceptorUtil;
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,20 +273,144 @@ 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) {
if (validator instanceof FieldValidator) {
FieldValidator fieldValidator = (FieldValidator) validator;
if (fieldValidator.getFieldName().equals(name)) {
validators.add(fieldValidator);
}
}
}
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 (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);
}
}
}
}
}
/**
* 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
@@ -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>
@@ -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 AbstractUITagTest {
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());
@@ -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>
@@ -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>
@@ -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>