Ajax validation with Struts 2

From:

http://www.javaworld.com/javaworld/jw-10-2008/jw-10-struts2validation.html?page=1

 

Support for Ajax and JavaScript takes the pain out of Web-form validation

Writing code to validate Web-form input can be even more of a chore than implementing form-processing logic. But help is at hand, thanks to the Struts 2 framework. Oleg Mikheev looks under the hood of the Struts 2 validation mechanism and shows you how its Java, JavaScript, and Ajax support can take the pain out of Web-form validation.

Web applications often require user input, which can range from simple username/password values to data entered into a complex form with dependent fields. The task of validating Web-form input is often more complex than implementing the logic to execute on the data after it's submitted. A validation framework can help simplify validation coding -- and the more complex your validation rules, the more pain the framework can spare you. Apache Struts was one of the first Web application frameworks designed to automate Web forms processing, and it is the best-known. This article explores the powerful form-validation options offered by Struts' successor, Struts 2. I'll delve briefly into Struts 2's underlying validation architecture, then show you how the framework supports both server- and client-side validation, including advanced Ajax validation. The article features an example Web application with full source code and binaries ready for deployment into a servlet container.

Struts 2's validation architecture

Struts 2 is based on WebWork, another powerful but less popular Web application framework. Struts 2 shares WebWork's approach to validation. The only difference is that WebWork relies on the validation framework in XWork 1, while Struts 2 uses the improved and customized XWork 2. XWork is a comprehensive command-pattern framework that does a huge part of the work related to Struts 2 configuration, instantiation, and runtime processing. (Although XWork is mostly known for its connection with WebWork and Struts 2, it can be used as a separate framework.)

The key component in XWork is Action. Action is basically a class that contains the code that you want to execute on a specific request originating from the browser. Another important XWork component is Interceptor. As its name suggests, Interceptor intercepts calls to Action to do more processing on them -- a mechanism quite similar to the concept of aspect-oriented programming (AOP). XWork comes with a number of already implemented interceptors, and Struts 2 adds some more of its own. You are free to implement your own custom interceptors too.

Struts 2 actions and interceptors are configured in a struts.xml file. Struts 2 looks for struts.xml in WEB-INF/classes (unlike Struts 1, which stores its struts.xml in the WEB-INF folder). Configurations can be grouped in a package -- another XWork component that represents a logical configuration unit that groups other components. Packages can be extended and overridden by "sub" packages. In most cases you won't create your Struts 2 configuration from scratch; instead it will extend the default configuration that is stored in the struts-default package. The Struts 2 default configuration is stored in the struts-core.jar archive's root folder in the struts-default.xml file. You can examine this file if you are interested in deeper exploration of Struts 2 defaults and internals.

Struts 2 interceptors can be grouped into an InterceptorStack. The struts-default package defines a number of interceptor stacks, but in most cases you'll use the defaultStack. Struts 2 validation is handled by a validation interceptor that is included in the defaultStack.

 

Taking action

Let's consider a simple Struts 2 action, called FirstAction, with three fields:

  • name:String
  • age:Integer
  • gender:Gender<Enum>

(I'll explain later why objects are preferred to simple Java types.) Depending on the value of gender:Gender<Enum>, FirstAction opens either Male.jsp or Female.jsp, as shown in Listing 1:

Listing 1. Struts configuration
 <struts> <package name="test" extends="struts-default"> <action name="First" class="struts2validation.FirstAction"> <interceptor-ref name="defaultStack"/> <result name="female">/Female.jsp</result> <result>/Male.jsp</result> </action> </package> </struts>  

The name attribute can be omitted from one of the two <result> tags. That result's name automatically defaults to success.

Server-side validation

Once an HTML form is submitted, the request is intercepted by all interceptors from the defaultStack, including validation, which is an XWork ValidationInterceptor. Starting with Struts 2.0.4, the XWork ValidationInterceptor is extended by AnnotationValidationInterceptor, which adds functionality to disable validation of action methods that are annotated by a @SkipValidation annotation.

The validation framework looks for an XML validation configuration whose name is ActionClass-validation.xml, where ActionClass is the name of the related action class. The validation configuration must be located in the same package as the action class itself. Because a single action class could be used in different actions, it is possible to have a validation configuration for each of the actions, in which case the file should be named ActionClass-ActionName-validation.xml. Struts 2 supports object-oriented programming concepts and considers validation configurations for all classes that the action extends and all interfaces that it implements.

Now I'll demonstrate a simple validation. I'll create a validation configuration for the FirstAction class and call it FirstAction-validation.xml.

First, though, I need to create a FirstInput.jsp file with a form, using Struts 2 custom tags. Custom tags use templates for rendering HTML code. Themes are collections of templates grouped by their output methods. I'll discuss themes later. Right now, you only need to know that four themes come with Struts 2:

  • simple (so simple that it doesn't provide validation support)
  • xhtml
  • css_html
  • ajax

The example form will use the xhtml theme. The <s:actionerror> tag displays any action errors. The <form> tag produces an HTML form with two HTML inputs (a <s:textfield> tag and a <s:submit> tag for a Submit button). Listing 2 shows the form for FirstInput.jsp:

Listing 2. FirstInput.jsp form
 <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="s" uri="/struts-tags" %> <html> <body><s:actionerror/> <s:form action="First" theme="xhtml"> <s:textfield label="Name" name="name" required="true"/> <s:textfield label="Age" name="age" required="true"/> <s:textfield label="Gender (Male/Female)" name="gender"/> <s:submit/> </s:form> </body> </html> 

Note that the required attribute in the <s:textfield> element has nothing to do with validation. It is used by some templates to display an asterisk next to the field.

Gender is a Java Enum, which needs to be handled specifically. Type conversion in Struts 2 is beyond this article's scope, so I'll just create a FirstAction-conversion.properties file for FirstAction in the same folder, with this content:

 gender=com.opensymphony.xwork2.util.EnumTypeConverter 

FirstAction, shown in Listing 3, uses simple logic to distinguish between male and female:

Listing 3. FirstAction class
来源:(http://blog.sina.com.cn/s/blog_5184f1fd0100btjy.html) - Ajax validation with Struts 2 (1)_马鸣风小小_新浪博客
 public class FirstAction extends ActionSupport { public String execute() throws Exception { if(Gender.Female.equals(gender)) { return "female"; } else { return SUCCESS; } } ... 

If the user enters Male in the form, the success result (which is mapped to Male.jsp) returns. Even without seeing any validation work yet, you can probably already get the feel of Struts 2 validation. If you enter a nonnumeric value in the age field and submit the form, a validation message will appear: "Invalid field value." The same is true for the gender field: you can only enter Male or Female there; otherwise a validation message appears. This is the result of the conversionError interceptor from the defaultStack making sure that all parameters are of the type that the action can accept.

 

Now it's time to introduce some custom validation into the form. The simplest XWork validator that comes with Struts 2 is the required validator. It only checks for null values and is often confused with requiredstring, which checks for empty strings and can be instructed to trim strings before the check. Since in the case of the example all three parameters are always present in the request, there is no need for the required validator, and I'll use requiredstring. I'll put it into the FirstAction-validation.xml file for the FirstAction, as shown in Listing 4:

Listing 4. Action validation configuration: requiredstring validator
 <validators> <validator type="requiredstring"> <param name="fieldName">name</param> <message>Name is required</message> </validator>... 

This results in a "Name is required" message appearing after form submission if nothing was entered in the name input field.

You can configure a field to have several validations applied. To make the configuration file more readable and maintainable, you can configure validators in two ways:

  • Field-wise: You nest <field-validator> entries in the <field> entry.
  • Validator-wise: You create a standalone <validator> entry. The standalone validator can either work on its own or be tied to a certain field with a <param name=fieldName> entry.

A name-validation configuration doing exactly the same job as Listing 4, but using field-wise validation, could look like Listing 5:

Listing 5. Action validation configuration: Field-wise validation
 <field name="name"> <field-validator type="requiredstring"> <param name="trim">true</param> <message>Name is required</message> </field-validator> </field>... 

To take advantage of multiple validators per one field, Listing 6 introduces a requirement for age to be a required field whose value must be between 21 and 122 inclusive:

Listing 6. Action validation configuration: Multiple validations per field
 <field name="age"> <field-validator type="required"> <message>Age is required</message> </field-validator> <field-validator type="int"> <param name="min">21</param> <message>You must be an adult</message> </field-validator> <field-validator type="int"> <param name="max">122</param> <message>The oldest human ever was 122 years old</message> </field-validator> </field>... 

The required validator in Listing 6 tries to receive a value from the object graph. This value will already be of the exact type defined in the action. That's why it is impossible to check simple Java types with the required validator. Even if field values are empty (null), they are cast to a corresponding value, 0 in case of int.

 

Validating dependent fields

The real power of the Struts 2 validation framework is unveiled when it comes to more-advanced validation use cases. In lots of situations a field value depends on another field value. For example, suppose you want a special age rule applied if name field's value is Joe. Struts 2 has a special validator capable of resolving Object Graph Navigation Language (OGNL) expressions. OGNL is a powerful expression language that lets you crawl Java object graphs. All Struts 2 action fields can be considered an object graph with action as a root. A special expression validator takes an OGNL expression as a parameter, evaluates it, and outputs an error message if the evaluated result is false. Listing 7 applies a rule for Joe to be no younger than 30:

Listing 7. Action validation configuration: Expression validation
 <validator type="expression"> <param name="expression">!(name==Joe && age<30)</param> <message>Joe must be at least 30 yrs old</message> </validator>

In this example, the expression validator is not tied to any field and will report an action error (which will be displayed by a <actionerror> Struts 2 custom tag). To tie expression validations to a field on the form, you should use a fieldexpression validator.

The most advanced validations are those involving business logic executed on the server side, most likely to access a data layer or legacy system. XWork provides several interfaces that can be implemented by custom validators. Instead of directly implementing all interfaces, your custom validator should extend the ValidatorSupport class, which implements all them for convenience. The validation logic should be placed in the overridden validate(object:Object) method.

For this article's example use case, the custom validator in Listing 8 checks Joe's gender and produces an error message if Joe claims to be a female. The validator method gets two values -- name and gender -- from the object graph. An error is added then if needed.

Listing 8. Custom validator
 public void validate(Object object) throws ValidationException { String name = (String)getFieldValue("name", object); Gender gender = (Gender)getFieldValue("gender", object); if("Joe".equals(name) && Gender.Female.equals(gender)) addFieldError("gender", object); } 

Once a custom validator is written it needs to be registered with Struts 2. The validators.xml file, which must be present in WEB-INF/classes, contains definitions of all custom validators in the Web application. Listing 9 shows the custom validator configuration:
Listing 9. Custom validator configuration
 <validators> <validator name="genderValidator" class="struts2validation.GenderValidator"/> </validators> 

Listing 10. Action validation configuration: Gender validator
 <field name="gender"> <field-validator type="genderValidator"> <message>Joe is not a Female</message> </field-validator> </field> 

Not all methods in a Struts 2 action get validated. As I mentioned already, you can specifically annotate action methods to skip validation. Also, you can instruct the validation interceptor to exclude methods with an excludeMethods parameter from validation. The defaultStack (from struts-default.xml) sets a number of predefined method names to be excluded from validation: input, back, cancel, and browse.
In situations when the initial form display depends on data generated by an action, you must exclude certain methods from the validation process. In this case the struts.xml definition could look like Listing 11:
Listing 11. Struts configuration: <action> with input result
 <action name="First_*" method="{1}" class="struts2validation.FirstAction"> <interceptor-ref name="defaultStack"/> <result name="input">/FirstInput.jsp</result> <result name="female">/Female.jsp</result> <result>/Male.jsp</result> </action> 

Listing 11 makes it possible to access the FirstInput.jsp form through an action with a URL of FirstAction_input.action. The input method needs to return input (which is the default input implementation in the ActionSupport class). The <form> tag must reflect the change in action name:
来源:(http://blog.sina.com.cn/s/blog_5184f1fd0100btk1.html) - Ajax validation with Struts 2 (2)_马鸣风小小_新浪博客
 <s:form action="First_" theme="xhtml"> 

Now the logic from the input action method will be executed with no preceding validation.
 

Plain client-side validation


It's possible to validate forms on the client side using Struts 2. JavaScript client-side validation is configured exactly the same way as the server-side validation; no special configuration is required. The key point of client-side validation is that it is handled by JavaScript validators instead of Java XWork ones. The list of available JavaScript validators is shorter than the server-side list. It is predefined and can't be expanded with reasonable effort.
Client-side validation is triggered with additional validate attribute in the <form> tag:
<s:form action="First_" theme="xhtml" validate="true"> 

So, what happens in our use case with the validation-configuration XML file containing validators that are available in JavaScript (requiredstring and int) and those implemented on the server-side only (fieldexpression and gender)? Struts 2 validates the form on the client side with validators implemented in JavaScript, then passes the validation process to the remaining validators on the server side.

Ajax validation


Ajax validation combines the power of server-side and plain client-side validation. Advanced server-side validators can be used without submitting the form or reloading the Web page. In Struts 2.0.x, Ajax validation depends on Direct Web Remoting (DWR). Keep in mind that Struts 2.1 (in beta as of this writing) will remove DWR dependency.
DWR needs to be set up in two steps. First, you must register the DWR servlet in web.xml, as shown in Listing 12:
Listing 12. Servlet configuration: DWR
 <servlet> <servlet-name>dwr</servlet-name> <servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>dwr</servlet-name> <url-pattern>/dwr/*</url-pattern> </servlet-mapping> 

Then, the DWR JAR file (not a part of the Struts 2 distribution but present in some Struts 2 example applications) must be present in WEB-INF/lib. The last DWR set-up step is to put the dwr.xml DWR configuration file in the WEB-INF folder. Listing 13 shows the contents of drw.xml:
Listing 13. dwr.xml
 <!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN" "http://www.getahead.ltd.uk/dwr/dwr10.dtd"> <dwr> <allow> <create creator="new" javascript="validator"> <param name="class" value="org.apache.struts2.validators.DWRValidator"/> </create> <convert converter="bean" match="com.opensymphony.xwork2.ValidationAwareSupport"/> </allow> <signatures> <![CDATA[ import java.util.Map; import org.apache.struts2.validators.DWRValidator; DWRValidator.doPost(String, String, Map<String, String>); ]]> </signatures> </dwr> 

All that's required to make Ajax validation work now is to change the theme to ajax in the <form>:
<s:form action="First_" theme="ajax" validate="true"> 

Field validation is triggered once focus leaves the field input, and nonfield validation (the type that produces action errors) is triggered on form submit.
 

Advanced Ajax validation


As I've mentioned, Struts 2 themes are collections of templates (Freemarker templates) that custom tags use to render HTML. When it comes to validation and forms, all themes try to manage the form's layout. The xhtml and ajax themes use HTML tables, and the css_xhtml theme uses CSS styles. Disappointingly, this fact makes application design unpredictable.
The way Struts 2 suggests to apply custom design to the HTML produced by custom tags is to learn Freemarker and extend or modify the default templates. If you're comfortable with that approach, you can skip the rest of this article.
Why Struts 2 modifies form layouts
Struts 2 themes modify form layout so that the right error labels appear in the right places. For example, the xhtml theme creates an HTML table for a form with a row for each form field, and then uses JavaScript functions to add and remove an extra row dynamically with an error message above the field row. The odds are that not every Web application will follow the table-layout approach for form layout, making the themeless approach a useful alternative.

Unfortunately, for a small- or middle-scale project, when a product is to be delivered within a strict timeframe, learning Freemarker is not an optimal approach. Furthermore, in most applications, different forms have different designs. Fortunately, for client-side validation (both plain and Ajax) you can apply no-theme approach if a well-designed Web form needs to become live quickly.
Let's copy the output produced by a Struts 2 ajax theme template into a separate AdvancedInput.jsp file and apply a custom form layout, as shown in Listing 14:
Listing 14. FirstInput.jsp form: Advanced Ajax validation
 <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="s" uri="/struts-tags" %> <html> <head> <!--scripts required for validation--> <script type="text/javascript" src="struts/validationClient.js"></script> <script type="text/javascript" src="dwr/interface/validator.js"></script> <script type="text/javascript" src="dwr/engine.js"></script> <script type="text/javascript" src="struts/ajax/validation.js"></script> <script type="text/javascript" src="struts/xhtml/validation.js"></script> <style type="text/css"> .err {color: red; font-size: 10px;} .custom {text-align: center;} </style> </head> <body><s:actionerror/> <form id="First_" name="First_" action="First_.action" namespace="/" class="custom"> Name:<br><input type="text" name="name" id="First__name" onblur="validate(this);"><br> Age:<br><input type="text" name="age" id="First__age" onblur="validate(this);"><br> Gender (Male/Female):<br><input type="text" name="gender" id="First__gender" onblur="validate(this);"><br> <input type="submit" dojoType="struts:Bind" event="onclick" value="Submit" id="First__0"> </form> </body> </html> 

One of JavaScript's (few) benefits is that you can replace any function with a custom one. So, once you know which function is responsible for rendering the error messages, you can easily replace it with your own -- one that makes error messages appear at the exact position that your designer envisions.
Generally, just two JavaScript functions are responsible for error display in Struts 2 validation: clearErrorMessages(form) and addError(e, errorText). The clearErrorMessages(form) function is called to clear all error labels for a given form. The addError(e, errorText) function is called to put an errorText error label that is related to the HTML e element. Listing 15 adds customized error handling to AdvancedInput.jsp:
Listing 15. FirstInput.jsp form: Added JavaScript
 <script type="text/javascript"> function addError(e, errorText) { var errDiv = document.createElement('div'); errDiv.setAttribute('errorFor', e.getAttribute('id')); errDiv.innerHTML = errorText; errDiv.className = 'err'; e.parentNode.insertBefore(errDiv,e); } function clearErrorMessages(form) { var prevErrDivs = form.getElementsByTagName_r('div'); for(var i=0; i<prevErrDivs.length; i++) { if(prevErrDivs[i].getAttribute('errorFor')) { prevErrDivs[i].parentNode.removeChild(prevErrDivs[i]); i--; // prevErrDivs colection changes as elements removed } } } </script> 

Now the free-style designed form is live with Ajax validation.

In conclusion


In this article you've learned how to do both server-and client-side validation with Struts 2 -- and how to combine the two for advanced Ajax applications. Struts 2 isn't the only Java Web application framework to offer a validation framework, but it's the one that goes beyond Java to provide integrated support for plain JavaScript and advanced Ajax validation. Its out-of-the-box validators and custom-validator support are a welcome salve for validation pain.

About the author


Oleg Mikheev is a Sun Certified Java Developer and IBM WebSphere Portal Developer with 10 years of experience with Java technologies. Oleg is currently employed as systems analyst by Gemini Systems, and is a postgraduate student at St.-Petersburg State Polytechnical University, Russia.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值