rkcole.com
previous print
download
next
In step 1 a simple address book was created that did little more than collect address information and send it to the server where it was processed by the AddressBookAction. The name and address information was stored in an Address bean which was subsequently used to initialize the address-form. The name, address, state and zipcode were collected in free-form textfields contained within the address-form.
In this part of the tutorial, in order to ensure that the state abbreviation is an exact postal service code, the state textfield will be replaced with a select/options list to control the value sent to the server when the form is submitted. This will give the application complete control over the string stored in our Address bean to represent a US State.
The address-form, and thus the AddressBookAction, will require access to a collection of states in order to make it available in the client-side view. To accomplish this, we'll perform the following tasks:
Adding Spring support to our Struts-2 project requires the addition of the following libraries to our lib directory:
The Spring ContextLoaderListener is a bootstrap Listener that starts and initializes the Spring inversion of control (IOC) container. The listener must be defined in web.xml by adding the following lines:
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
The Spring ContextLoaderListener is configured by WEB-INF/applicationContext.xml. A bare-bones version of that file is shown below:
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans default-autowire="autodetect">
</beans>
Struts must be informed about the change in object factory. Add the following highlighted line to struts.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<include file="struts-default.xml"/>
<constant name="struts.enable.DynamicMethodInvocation" value="false" />
<constant name="struts.devMode" value="true" />
<constant name="struts.objectFactory" value="spring" />
<include file="crud.xml"/>
</struts>
The list of U.S. States is implemented as an ArrayList containing 50 State objects.
Create a new package called crud.model (src/main/java/crud/model). The two new files to be created in the new package are StatesList.java and State.java.
First we'll need a bean to hold an abbreviation and the full name of one of the US states.
package crud.model;
import java.io.Serializable;
/**
* State name and abbreviation.
*/
public class State implements Serializable
{
static final long serialVersionUID = 192412677248171422L;
private String name;
private String id;
public State( String name, String id )
{
this.name = name;
this.id = id;
}
public String getName() { return this.name; }
public String getId() { return this.id; }
}
Next we'll need an ArrayList bean to hold the collection of 50 State objects.
package crud.model;
import java.util.ArrayList;
import java.io.Serializable;
public class StatesList extends ArrayListimplements Serializable
{
private static final long serialVersionUID = -718976040813408212L;
public StatesList()
{
super();
add( new State("Alabama" "AL"));
add( new State("Alaska", "AK"));
add( new State("Arizona", "AZ"));
add( new State("Arkansas", "AR"));
.
.
.
add( new State("Wyoming", "WY"));
}
}
Our AddressBookAction is the controller component that must make the StatesList available to our address-form view. AddressBookAction could just create a new StatesList each time it is instantiated, but this is less effecient that using an inversion of control (IOC) container like Spring. With an IOC container, the action doesn't have to know the details of how a StatesList (or any other injected object) is constructed. Further, the IOC container has the option of caching dependent objects and reusing them.
We have two tasks to perform:
StatesList in AddressBookAction.StatesList collection. The AddressBookAction will require a setter to receive the StatesList from the IOC container and a getter to make StatesList available to the address-form view. The code that must be added to AddressBookAction is presented below:
private StatesList statesList;
public void setStatesList( StatesList list )
{
this.statesList = list;
}
public StatesList getStatesList()
{
return this.statesList;
}
Second, we inform the IOC container that it is to manage StatesList objects. That is done by adding the following highlighted line to WEB-INF/applictionContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans default-autowire="autodetect">
<bean id="statesList" class="crud.model.StatesList" />
</beans>
That's all there is to it. When step 2 is deployed and run, the StatesList will automagically show up in AddressBookAction.
No changes are required to the AddressBookActionTest from step1. The in-container test however will need to be updated in order to verify that the states-list select box has been populated and handled successfully.
The highlighted lines in the listing below indicate the changes made to AddressBookWebTest. The tests that accessed the state textfield have been updated to access the new select/options list.
package crud;
import crud.model.Address;
import net.sourceforge.jwebunit.junit.WebTestCase;
/** Perform in-container tests on crud.AddressBookAction.
*/
public class AddressBookWebTest extends WebTestCase
{
private static final Address TEST_ADDRESS = new Address();
static
{
TEST_ADDRESS.setName("R. K. Cole");
TEST_ADDRESS.setAddress("1234 2nd St.");
TEST_ADDRESS.setCity("Nashville");
TEST_ADDRESS.setState("TN");
TEST_ADDRESS.setZipcode("54321");
}
public void setUp()
{
getTestContext().setBaseUrl("http://localhost:8080/Struts2CrudStep2");
getTestContext().setResourceBundleName("package");
}
/** Test that the address form page appears and that the form
* contains the correct fields for address entry.
*/
public void testAddressFormPage() throws Exception
{
beginAt("/");
assertTitleEquals("Address Form");
assertFormPresent("AddressBook");
assertFormElementPresent("address.name");
assertFormElementPresent("address.address");
assertFormElementPresent("address.city");
assertFormElementPresent("address.state");
assertFormElementPresent("address.zipcode");
// check that the state field is initially blank (the blank option is on).
assertSelectedOptionsEqual("address.state", new String[] {""});
}
/** Test the "update" button.
*/
public void testUpdate() throws Exception
{
beginAt("/");
assertTitleEquals("Address Form");
setWorkingForm("AddressBook");
setTextField("address.name", TEST_ADDRESS.getName() );
setTextField("address.address", TEST_ADDRESS.getAddress() );
setTextField("address.city", TEST_ADDRESS.getCity() );
selectOptionByValue("address.state", TEST_ADDRESS.getState() );
setTextField("address.zipcode", TEST_ADDRESS.getZipcode() );
submit("method:update");
assertKeyPresent("addressbook.updated.message");
assertTextFieldEquals("address.name", TEST_ADDRESS.getName() );
assertTextFieldEquals("address.address", TEST_ADDRESS.getAddress() );
assertTextFieldEquals("address.city", TEST_ADDRESS.getCity() );
assertSelectedOptionValueEquals("address.state", TEST_ADDRESS.getState() );
assertTextFieldEquals("address.zipcode", TEST_ADDRESS.getZipcode() );
}
/** Test the "reset" button.
*/
public void testReset() throws Exception
{
beginAt("/");
assertTitleEquals("Address Form");
setWorkingForm("AddressBook");
setTextField("address.name", TEST_ADDRESS.getName() );
setTextField("address.address", TEST_ADDRESS.getAddress() );
setTextField("address.city", TEST_ADDRESS.getCity() );
selectOptionByValue("address.state", TEST_ADDRESS.getState() );
setTextField("address.zipcode", TEST_ADDRESS.getZipcode() );
submit("method:reset");
assertKeyPresent("addressbook.default.message");
assertTextFieldEquals("address.name", "");
assertTextFieldEquals("address.address", "");
assertTextFieldEquals("address.city", "");
assertSelectedOptionsEqual("address.state", new String[] {""});
assertTextFieldEquals("address.zipcode", "");
}
}
select TagThe Struts-2 tag library includes a select tag that creates a drop-down list from a collection of objects. Each object in the list can have two accessor methods to configure the displayed value in the form and the identifier which will be sent to the server when the form is submitted. The option list displayed in the drop-down by the select tag is produced by calling getStatesList() on the AddressBookAction instance and then iterating the returned list making calls getId() and getName() on each State object in the list.
The implementation of the select tag used in the address-form is show below:
<s:select
list="statesList"
name="address.state"
listKey="id"
listValue="name"
emptyOption="true"
/>
The parameters have the following meaning:
| Select Tag Parameters | |
|---|---|
| Parameter | Explanation |
| list | The name of the list containing the US states. The list is acquired by calling the getStatesList() method in the AddressBookAction instance. |
| name | The field in our Address bean that will be set to the value of the select-box when the form is submitted. This is set by a call to getAddress().setState() in the AddressBookAction instance. |
| listKey | The identifier of the selected value that will be sent to the server when the form is submitted. It is determined by a call to the State.getId() method for each of the State objects in the StatesList. |
| listValue | The string displayed in the drop-down to the user. It is set by a call to the State.getName() method for each of the State object in the StatesList. |
| emptyOption | Indicates whether the choice of a blank field will be available in our list. Including emptyOption=true forces the user to choose a state. It is less error-prone that allowing the user to take the default selection. |
Here is the updated address entry form:

Build the war file by running ant war. Copy the Struts2CrudStep2.war to your web container's webapp directory and point your browser to Struts 2 Crud - Step2.
Run the container-independent tests by entering:
step2>ant test-all
Buildfile: build.xml
init:
compile:
[javac] Compiling 1 source file to step2\build\classes
[javac] Compiling 1 source file to step2\build\test\classes
[copy] Copying 1 file to X:\demo\Struts2Crud\step2\build\classes
compile-tests:
[javac] Compiling 1 source file to step2\build\test\classes
test:
[junit] Testsuite: crud.AddressBookActionTest
[junit] Tests run: 4, Failures: 0, Errors: 0, Time elapsed: 0.703 sec
[junit]
test-all:
init:
compile:
[javac] Compiling 1 source file to step2\build\classes
[javac] Compiling 1 source file to step2\build\test\classes
compile-tests:
[javac] Compiling 1 source file to step2\build\test\classes
test:
[junit] Testsuite: crud.AddressBookWebTest
[junit] Tests run: 3, Failures: 0, Errors: 0, Time elapsed: 3.562 sec
[junit]
BUILD SUCCESSFUL
Total time: 10 seconds
step2>
This completes step 2 in the Struts CRUD Tutorial. In this step, the simple address book user interface that was constructed in step 1 was updated to include a Spring-injected list of U.S. States. Modifications were made to the unit tests to make them aware of the replacement of the state textfield with a select/options box.
In step 3, data storage and retrieval will be added using Hibernate.