The last of the MVC components to discuss are the view components. Arguably, they are the easiest to understand. The view components typically employed in a Struts application are:
3.5.1 Using the Struts ActionFormStruts ActionForm objects are used in the framework to pass client input data back and forth between the user and the business layer. The framework automatically collects the input from the request and passes this data to an Action using a form bean, which then can be passed along to the business layer. To keep the presentation layer decoupled from the business layer, you should not pass the ActionForm itself to the business layer; rather, create the appropriate DTO using the data from the ActionForm. The following steps illustrate how the framework processes an ActionForm for every request:
For every HTML page where form data is posted, you should use an ActionForm. The same ActionForm can be used for multiple pages if necessary, as long as the HTML fields and ActionForm properties match up. Example 3-5 shows the com.oreilly.struts.banking.form.LoginForm that is used with the banking application. Example 3-5. The LoginForm used by the banking applicationpackage com.oreilly.struts.banking.form; import javax.servlet.http.HttpServletRequest; import org.apache.struts.action.Action; import org.apache.struts.action.ActionError; import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionMapping; import org.apache.struts.util.MessageResources; /** * This ActionForm is used by the online banking appliation to validate * that the user has entered an accessNumber and a pinNumber. If one or * both of the fields are empty when validate( ) is called by the * ActionServlet, error messages are created. */ public class LoginForm extends ActionForm { // The user's private ID number private String pinNumber; // The user's access number private String accessNumber; public LoginForm( ) { super( ); resetFields( ); } /** * Called by the framework to validate the user has entered values in the * accessNumber and pinNumber fields. */ public ActionErrors validate(ActionMapping mapping, HttpServletRequest req ){ ActionErrors errors = new ActionErrors( ); // Get access to the message resources for this application. // There's no easy way to access the resources from an ActionForm. MessageResources resources = (MessageResources)req.getAttribute( Action.MESSAGES_KEY ); // Check and see if the access number is missing. if(accessNumber == null || accessNumber.length( ) == 0) { String accessNumberLabel = resources.getMessage( "label.accessnumber" ); ActionError newError = new ActionError("global.error.login.requiredfield", accessNumberLabel ); errors.add(ActionErrors.GLOBAL_ERROR, newError); } // Check and see if the pin number is missing. if(pinNumber == null || pinNumber.length( ) == 0) { String pinNumberLabel = resources.getMessage( "label.pinnumber" ); ActionError newError = new ActionError("global.error.login.requiredfield", pinNumberLabel ); errors.add(ActionErrors.GLOBAL_ERROR, newError); } // Return the ActionErrors, if any. return errors; } /** * Called by the framework to reset the fields back to their default values. */ public void reset(ActionMapping mapping, HttpServletRequest request) { // Clear out the accessNumber and pinNumber fields. resetFields( ); } /** * Reset the fields back to their defaults. */ protected void resetFields( ) { this.accessNumber = ""; this.pinNumber = ""; } public void setAccessNumber(String nbr) { this.accessNumber = nbr; } public String getAccessNumber( ) { return this.accessNumber; } public String getPinNumber( ) { return this.pinNumber; } public void setPinNumber(String nbr) { this.pinNumber = nbr; } } The ActionForm class provided by the Struts framework implements several methods, but by far the two most important are the reset() and validate() methods: public void reset( ActionMapping mapping, HttpServletRequest request ); public ActionErrors validate( ActionMapping mapping, HttpServletRequest request ); The default implementation for both methods in the Struts ActionForm class doesn't perform any default logic. You'll need to override these two methods in your ActionForm classes, as was done in the LoginForm class shown in Example 3-5. The controller calls the reset() method right before it populates the ActionForm instance with values from the request. It gives the ActionForm a chance to reset its properties back to the default state. This is very important, as the form bean instance may be shared across different requests or accessed by different threads. However, if you are using an ActionForm instance across multiple pages, you might not want to implement the reset() method so that the values don't get reset until you're completely done with the instance. Another approach is to implement your own resetFields() method and call this method from the Action class after a successful update to the business tier. The validate() method is called by the controller after the values from the request have been inserted into the ActionForm. The ActionForm should perform any input validation that can be done and return any detected errors to the controller. Business logic validation should be performed in the business objects, not in the ActionForm. The validation that occurs in the ActionForm is presentation validation only. Where to perform certain types of validation logic will be covered in detail in Chapter 6 and Chapter 7. The validate() method in the LoginForm in Example 3-5 checks to see if the access number and/or pin number is missing and creates error messages if they are. If no errors are generated, the controller passes the ActionForm and several other objects to the execute() method. The Action instance can then pull the information out of the ActionForm.
Once you have coded your ActionForm classes, you need to inform your Struts application that they exist and tell it which action mappings should use which ActionForms. This is done in the configuration file. The first step is to configure all of the ActionForms for your application in the form-beans section of the configuration file. The following fragment from the banking configuration file informs Struts of the three ActionForm beans used by the banking application: <form-beans> <form-bean name="loginForm" type="com.oreilly.struts.banking.form.LoginForm"/> <form-bean name="accountInformationForm" type="org.apache.struts.action.DynaActionForm"> <form-property name="accounts" type="java.util.ArrayList"/> </form-bean> <form-bean name="accountDetailForm" type="org.apache.struts.action.DynaActionForm"> <form-property name="view" type="com.oreilly.struts.banking.view.AccountDetailView"/> </form-bean> </form-beans> The name attribute for each form bean must be unique, and the type attribute must define a fully qualified Java class that extends the Struts ActionForm class. The next step is to use one of the form-bean names from the form-beans section in one or more action elements. The following fragment shows the mapping for the LoginAction, which you've already seen earlier in this chapter: <action path="/login" type="com.oreilly.struts.banking.action.LoginAction" scope="request" name="loginForm" validate="true" input="/login.jsp"> <forward name="Success" path="/action/getaccountinformation" redirect="true"/> <forward name="Failure" path="/login.jsp" redirect="true"/> </action> Notice that the name attribute of the login mapping matches one of the names in the form-beans section from earlier.
One of the new features in Struts 1.1 is shown in the previous form-beans fragment. With previous versions of the framework, you always had to extend the ActionForm class with your own subclass, even if the ActionForm performed very generic behavior. With Struts 1.1, a new type of action form, called org. apache.struts.action.DynaActionForm, has been added. This class can be configured for an action mapping and will automatically handle the data passed from the HTML form to the Action. The DynaActionForm is able to deal with the data generically because it uses a Map to store the values internally. Chapter 7 will cover the DynaActionForm in more detail. You may be wondering what the difference is between an ActionForm and the DTOs mentioned earlier. This is a good question, and one that is a little confusing for developers new to Struts. The view components can use both ActionForms and DTOs to populate dynamic content. When no ActionForm is configured for a mapping, you can use DTOs to build the views. And when a form bean is defined for the mapping, there are several ways to handle extracting the data from the bean. One approach is to always wrap a form bean around one or more DTOs returned from the business tier and to force the Struts view components to access the DTO data through the form bean. Likewise, when a client submits an HTML page, Struts will invoke the form bean setter methods, which can shove the data back into the DTO after the validation method has completed successfully. This provides a single, cohesive interface to which the views can retrieve and submit the HTML data to. We'll discuss the various pros and cons of this and other approaches in Chapter 7. 3.5.2 Using JSP for PresentationJSP pages make up the majority of what has to be built for the Struts view components. Combined with custom tag libraries and HTML, JSP makes it easy to provide a set of views for an application. Although JSP is the technology most commonly used by organizations and developers to display dynamic content, it's not the only option. Many developers feel that JSP has the following problems:
Some developers do not see these issues as a major problem, and many sites have been built using the JSP technology. However, for those that want alternatives, there are other forms of presentation technologies that can be combined with the Struts framework. One popular alternative is the XML/XSLT combination. This model combines the controller servlet from the Struts framework with XSLT and beans serialized from DTOs to render the views. 3.5.3 Using Custom Tag LibrariesThe Struts framework provides six core tag libraries that your applications can use. Each one has a different purpose and can be used individually or alongside others. You also may extend the Struts tags or create your own custom tags if you need additional functionality. The custom tag libraries that are included with the framework are the HTML, Bean, Logic, Template, Nested, and Tiles tag libraries. To use these libraries in your application, you first need to register them with the web application in the web.xml file. You need to register only the tag libraries that you plan to use in your application. For example, if you are planning on using the HTML and Logic tag libraries, you must add the following fragment to the deployment descriptor for the web application: <web-app> <taglib> <taglib-uri>/WEB-INF/struts-html.tld</taglib-uri> <taglib-location>/WEB-INF/struts-html.tld</taglib-location> </taglib> <taglib> <taglib-uri>/WEB-INF/struts-logic.tld</taglib-uri> <taglib-location>/WEB-INF/struts-logic.tld</taglib-location> </taglib> </web-app> More information on installing and configuring Struts for your application is provided in Appendix B. The next step is to create your JSP pages and include the necessary taglib elements, depending on which tag libraries the page will need: <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %> Once this is done and the JAR files are in the web application's CLASSPATH, you can use the custom tags in your JSP pages. Example 3-6 illustrates the usage of several of the Struts custom tags inside the login.jsp page for the banking application. Example 3-6. The login.jsp page used by the banking application<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <html:html> <head> <html:base/> <title><bean:message key="title.login"/></title> <link rel="stylesheet" href="stylesheets/login_style_ie.css" type="text/css"> </head> <body topmargin="0" leftmargin="5" marginheight="0" marginwidth="0" bgcolor="#6699FF"> <html:form action="login" focus="accessNumber"> <table border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#6699FF"> <tr><td> <html:img srcKey="image.logo" width="79" height="46" altKey="image.logo.alt" border="0"/> </td></tr> </table> <table border="0" cellpadding="0" cellspacing="0" width="100%"> <tr><td bgcolor="#000000"> <table border="0" cellpadding="0" cellspacing="0" width="1" height="2"></table> </td></tr> </table> <table border="0" cellpadding="0" cellspacing="0" width="1" height="1"> <tr><td></td></tr> </table> <table> <tr><td></td></tr> </table> <table border="0" cellpadding="0" cellspacing="0" width="590"> <tr><td width="15" height="31"></td><td width="12"></td></tr> <tr> <td width="15"></td> <td width="575" bgcolor="#FFFFFF" colspan="2"> <table cellpadding="0" cellspacing="0" border="0" width="575" height="3"> <tr><td></td></tr> </table> </td> </tr> </table> <table border="0" cellpadding="0" cellspacing="0" width="590" bgcolor="#ffffff"> <tr> <td width="15" bgcolor="#6699FF"></td> <td width="15"></td><td width="379"></td> <td width="15"></td> <td width="15"></td> <td width="15"></td> </tr> <tr> <td bgcolor="#6699FF" width="15"></td> <td></td> <td valign="top"> <table border="0" cellpadding="0" cellspacing="0"> <tr > <td><bean:message key="label.accessnumber"/></td> </tr > <tr> <td><html:text property="accessNumber" size="9" maxlength="9"/></td> <td ><html:errors/></td> </tr> <tr ><td height="10"></td></tr> <tr ><td><bean:message key="label.pinnumber"/></td></tr> <tr > <td><html:password property="pinNumber" size="4" maxlength="4"/></td> </tr> <tr><td height="10"></td></tr> <tr><td><html:submit style value="Login"/></td></tr> <tr><td></td></tr> </table> </td> <td width="151" valign="top"> <html:img srcKey="image.strutspower" altKey="image.strutspower.alt"/> </td> </tr> </table> <%@include file="include/footer.jsp"%> <br> </html:form> </body> </html:html> One of the first things that should strike you about the login page in Example 3-6 is that there's no Java code in it. Instead, you see mostly HTML formatting tags and several uses of Struts tag libraries. This is exactly the purpose of using custom tag libraries. No Java programming is necessary, so HTML designers can work freely with the page layout without being burdened by the programming aspects of the page. The other nice feature is that many JSP pages can use the same tags. For more information on tag libraries, see Chapter 8. 3.5.4 Using Message Resource BundlesOne of the hardest customizations that developers face is to quickly and effortlessly customize a web application for multiple languages. Java has several built-in features that help support internationalization; Struts builds on those features to provide even more support. The Java library includes a set of classes to support reading message resources from either a Java class or a properties file. The core class in this set is the java.util. ResourceBundle. The Struts framework provides a similar set of classes, based around the org.apache.struts.util.MessageResources class, that provides similar functionality but allows for more of the flexibility that the framework requires. With a Struts application, you must provide a resource bundle for each language you want to support. The name of the class or properties file must adhere to the guidelines listed in the JavaDocs for the java.util.ResourceBundle class. Example 3-7 shows the properties file used by the example banking application. Example 3-7. The resource bundle for the banking application# Labels label.accessnumber=Access Number label.pinnumber=Pin Number label.accounts=Accounts label.balance=Balance label.totalassets=Total Assets label.account=Account label.balance=Available Balance label.description=Description label.amount=Amount label.deposits=Deposits label.withdrawls=Withdrawls label.openingbalance=Opening Balance # Links link.customeragreement=Customer Agreement link.privacy=Privacy link.security=Security link.viewaccountdetail=View Account Detail # Page Titles title.login=Struts Online Banking - Account Login title.accountinfo=Struts Online Banking - Account Information title.accountdetail=Struts Online Banking - Account Detail # Button Labels label.button.login=Login # Error messages global.error.invalidlogin=<li>Invalid Access Number and/or Pin</li> global.error.login.requiredfield=<li>The {0} is required for login</li> # Images image.logo=images/logo.gif image.logo.alt=Struts Online Banking image.logout=images/logout.gif image.logout.alt=Logout image.strutspower=images/struts-power.gif image.strutspower.alt=Powered By Struts image.transfer=images/transfer.gif image.transfer.alt="Transfer Funds" image.clear=images/clear.gif If you look back at the login.jsp page in Example 3-6, you can see how the messages from the bundle are used. For example, the following fragment from the login page illustrates the key title.login from Example 3-6 being used and inserted between the HTML title tags in the page. <title><bean:message key="title.login"/></title> The Struts org.apache.struts.taglib.bean.MessageTag is one of several custom tags included in the framework that can take advantage of the resource bundle. JSP pages can retrieve values from the resource bundle using the MessageTag based on a key, as shown in the login page in Example 3-6. The key in the message tag must correspond to a value on the left side of the equals sign in the bundle. Case is important, and the value must match exactly.
With Struts 1.1, you can define multiple MessageResources for an application. This allows you to isolate certain types of resources into separate bundles. For example, you might want to store the image resources for an application in one bundle and the rest of the resources in another. How you organize your application's resources is up to you, but you now have the flexibility to separate them based on some criteria. For example, some applications choose to separate along component lines: all resources relating to the catalog go into one bundle, order and shopping-cart resources go into another, and so on. |