3.5 Struts View Components

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:

·         HTML

·         Data transfer objects

·         Struts ActionForms

·         JavaServer Pages

·         Custom tags

·         Java resource bundles

3.5.1 Using the Struts ActionForm

Struts 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:

1.       Check the mapping for the action and see if an ActionForm has been configured.

2.       If an ActionForm is configured for the action, use the name attribute from the action element to look up the form bean configuration information.

3.       Check to see if an instance of the ActionForm already has been created.

4.       If an ActionForm instance is present in the appropriate scope and it's the same type as needed for the new request, reuse it.

5.       Otherwise, create a new instance of the required ActionForm and store it in the appropriate scope (set by the scope attribute for the action element).

6.       Call the reset( ) method on the ActionForm instance.

7.       Iterate through the request parameters, and populate every parameter name that has a corresponding setter method in the ActionForm with the value for that request parameter.

8.       Finally, if the validate attribute is set to "true", invoke the validate( ) method on the ActionForm instance and return any errors.

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 application
package 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( ) methodright 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.

You might have noticed that the execute( ) method in the Action class contains an argument that is always of the type ActionForm. You will need to cast this argument to the appropriate subclass to retrieve the needed properties. To see an example of this, look back at Example 3-3.

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.

Instances of the ActionForm class commonly are referred to as "form beans." I use this term throughout the book to refer to an object of type ActionForm.

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 ActionForm s 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 Presentation

JSP 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:

·         Developers are free to embed application logic into the JSP pages, which can lead to applications that are difficult to maintain (with JSP 2.0, you can configure JSP pages to not allow scriptlets).

·         Developers must learn the JSP syntax and how to program custom tags (although this will not be true when JSP 2.0 is final and the new expression language is available).

·         The container must recompile the JSP page when a change is made to the page.

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 Libraries

The 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 class="fieldlabel">
      <td><bean:message key="label.accessnumber"/></td>
    </tr class="fieldlabel">
       <tr>
      <td><html:text property="accessNumber" size="9" maxlength="9"/></td>
      <td class="error"><html:errors/></td>
    </tr>
       <tr class="fieldlabel"><td height="10"></td></tr>
       <tr class="fieldlabel"><td><bean:message key="label.pinnumber"/></td></tr>
       <tr class="fieldlabel">
      <td><html:password property="pinNumber" size="4" maxlength="4"/></td>
    </tr>     
       <tr><td height="10"></td></tr>
       <tr><td><html:submit styleClass="fieldlabel" 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.

As you can probably imagine by looking at the JSP page in Example 3-6, maintenance and customization are easy when you use custom tag libraries.

3.5.4 Using Message Resource Bundles

One 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.

A message resource bundle can be used for more than just localization—it also can save you time during application maintenance. For example, if you use the same text messages or labels throughout various parts of your web site or application, when one or more of these values need to change, you need make the change only in a single location. Even if you don't have internationalization requirements, you still should use resource bundles.

With Struts 1.1, you can define multiple MessageResource s 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.