More Struts

I sometimes read comments, made in good faith, about perceived Struts limitations. Statements such as "selection lists don't work with dynamic forms" are frustrating to read, especially when the facts don't support such a position. This article will deal with some fairly straightforward user interface objects and how to code them using the Struts framework.

Let's address lists first. These are implemented as HTML select tags which enclose a number of option tags. Single selects are typically rendered using the single line containing the current choice and an arrow button on the right side which will pop-up the choices when selected. Multiple selects take the form of a box which encloses all the possibilities. Using the Ctrl-click allows the user to add selections.

The following two examples will use a org.apache.struts.action.DynaActionForm as this seems to cause the most difficulty for programmers. Here's an excerpt from the struts-config.xml file:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation/DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
	<form-beans>
		<form-bean name="listForm"
		  type="org.apache.struts.action.DynaActionForm">
			<form-property name="listSelection"
			  type="java.lang.String"/>
		</form-bean>
	</form-beans>
	<action-mappings>
		<action name="listForm"
		  path="/listDemo"
		  type="com.onlinetrs.ers.ListDemoServlet"
		  scope="request"
		  validate="false">
			<forward name="continue"
			  path="/WEB-INF/jsp/ListDemo.jsp"/>
		</action>
	</action-mappings>
</struts-config>

Here's the source for the com.onlinetrs.ers.ListDemoServlet class:

package	com.onlinetrs.ers;

import	javax.servlet.http.HttpServletRequest;
import	javax.servlet.http.HttpServletResponse;
import	javax.servlet.http.HttpSession;
import	org.apache.struts.action.Action;
import	org.apache.struts.action.ActionForm;
import	org.apache.struts.action.ActionForward;
import	org.apache.struts.action.ActionMapping;
import	org.apache.struts.action.DynaActionForm;

public class ListDemoServlet extends Action {

	public ActionForward execute( ActionMapping mapping,
	  ActionForm actionForm, HttpServletRequest req,
	  HttpServletResponse resp ) throws NullPointerException {
		Vector	v = new Vector();
		DynaActionForm	form = (DynaActionForm) actionForm;
		Object	o = form.get( "listSelection" );

		if( o instanceof String ) {
			String	val = (String) o;
			if( val != null )
				System.err.println( "ListDemoServlet: " +
				  "listSelection = '" + val + "'" );
		}
		if( o instanceof String[] ) {
			String	val[] = (String[]) o;
			if( val != null ) {
				System.err.println( "ListDemoServlet: " +
				  "listSelection length = " + val.length );
				for( int i = 0; i < val.length; i++ )
					System.err.println(
					  "ListDemoServlet: val[" + i +
					  "] = '" + val[i] + "'" );
			}
		}

		for( int i = 1; i <= 10; i++ )
			v.add( new ListLabel( "label " + i, "" + i ) );
		req.setAttribute( "LISTLABELS", v );
		return( mapping.findForward( "continue" ) );
	}
}

Note that I test for the type of object returned by the get method of the org.apache.struts.action.DynaActionForm. This is so we can use the same servlet code later. Apart from logging the form value(s), all we do is populate a java.util.Vector and use the setAttribute method to add it to the request scope. Each element is a com.onlinetrs.ers.ListLabel and here's the class source:

package	com.onlinetrs.ers;

public class ListLabel {

	String	label;
	String	value;

	public ListLabel( String label, String value ) {
		this.label = label;
		this.value = value;
	}

	public String getLabel() {
		return( label );
	}

	public void setLabel( String label ) {
		this.label = label;
	}

	public String getValue() {
		return( value );
	}

	public void setValue( String value ) {
		this.value = value;
	}
}

This is just a very simple bean for encapsulating the value and the label for the selections. Finally, here's the JSP source:

<%@ taglib uri="html" prefix="html" %>
<head>
<title>List Demo</title>
</head>
<body>
<html:form action="/listDemo.do" method="POST">
<html:select property="listSelection">
<html:options collection="LISTLABELS" property="value" labelProperty="label"/>
</html:select>
<br>
<html:submit/>
</html:form>
</body>

If you've used Struts before or read my previous articles then none of this will be unfamiliar. The property and labelProperty attribute values in the options tag map to the accessor method names in com.onlinetrs.ers.ListLabel. The collections attribute names the java.util.Vector which we added to request scope in the servlet. The property attribute of the select tag specifies the field name as defined in the form-property tag in struts-config.xml.

So how do we extend this to provide multi-selection capability? Trivially! First we have to change the select tag in the JSP to the following:

<html:select property="listSelection" multiple="true">

Then we just need to alter the form-property tag to indicate that the type is an array of java.lang.String like this:

			<form-property name="listSelection"
			  type="java.lang.String[]"/>

Now that we've seen how easy selects can be, let's tackle something a little more difficult. Image submit buttons actually generate three request parameters. Here's an example image submit button in a JSP:

<html:image src="home.gif" value="Home" property="submit"/>

This will generate the following request parameters if the user clicks on the image:

(where x and y are the selected coordinates within the image)

The problem here is that the naming convention doesn't map to the beans standard. And so this is where we run into a divergence between approaches. We can implement the field as a java.lang.String in a org.apache.struts.action.DynaActionForm but we have to use an object when extending org.apache.struts.action.ActionForm. A look at the source will be more revealing. Here's the struts-config.xml form definition for the dynamic form:

                <form-bean name="listForm"
                  type="org.apache.struts.action.DynaActionForm">
                        <form-property name="submit"
                          type="java.lang.String"/>
                </form-bean>

Here's how we access the value in the servlet:

        public ActionForward execute( ActionMapping mapping,
          ActionForm actionForm, HttpServletRequest req,
          HttpServletResponse resp ) throws NullPointerException {
                Vector  v = new Vector();
                DynaActionForm  form = (DynaActionForm) actionForm;
                String submit = (String) form.get( "submit" );

We have to test for a null or empty result. Taking a clue from Ted Husted, the alternative approach requires an object which implements mutators for the x and y coordinates. Here's the code for such a class:

package	com.onlinetrs.ers;

public class ImageSubmitButton {

	private String	x;
	private String	y;

	public ImageSubmitButton() {
		x = y = null;
	}

	public void setX( String s ) {
		x = s;
	}

	public String getX() {
		return( x );
	}

	public void setY( String s ) {
		y = s;
	}

	public String getY() {
		return( y );
	}

	public boolean isSelected() {
		return( ( x != null ) || ( y != null ) );
	}
}

Here's the modified struts-config.xml:

                <form-bean name="listForm"
                  type="com.onlinetrs.ers.ListDemoForm"/>

Here's the code for the form:

package	com.onlinetrs.ers;

import	org.apache.struts.action.ActionForm;
import	org.apache.struts.action.ActionMapping;
import	javax.servlet.http.HttpServletRequest;

public class ListDemoForm extends ActionForm {

	private ImageSubmitButton	submit;

	public void reset( ActionMapping map, HttpServletRequest req ) {
		submit = new ImageSubmitButton();
	}

	public ImageSubmitButton getSubmit() {
		return( submit );
	}
}

There's no mutator for the submit field because Struts works like this (using pseudocode):

        ImageSubmitButton   obj = form.getSubmit();
        obj.setX( x );
        obj.setY( y );

So the string value is used when using org.apache.struts.action.DynaActionForm but the x and y coordinates are used when extending org.apache.struts.action.ActionForm.

Finally, here's the servlet code for this approach:

        public ActionForward execute( ActionMapping mapping,
          ActionForm actionForm, HttpServletRequest req,
          HttpServletResponse resp ) throws NullPointerException {
                Vector  v = new Vector();
                ListDemoForm  form = (ListDemoForm) actionForm;
                ImageSubmitButton submit = form.getSubmit();
		if( submit.isSelected() ) {
                        // user clicked on the image
		}

These examples have used more sophisticated Struts capabilities, namely arrays and objects versus simple strings. You've already seen indexed properties in the advanced struts article so you should now be able to apply your knowledge to almost any challenge.

NOTE: The author is available for short- or long-term contract work. While we are unable to guarantee a reply due to the volume of e-mail received, you are most welcome to posit additional questions to pselby@selbyinc.com.

Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 by Phil Selby. All rights reserved.