| Section 5: Handling Forms |
|---|
html: tags to build HTML forms that
have two important characteristics:
| 5.1 Summary of Techniques Introduced in This Section |
|---|
Displaying HTML forms whose values are associated with Java objects requires the following steps not covered in earlier sections:
input attribute to the action element
in struts-config.xml.name (which should match a name
in the form-bean element) with the input form.
scope in the action declaration.
Use scope="request" if you want to prepopulate a form only (i.e.,
display initial values). Use scope="session" if also you want to
redisplay the form with previous values intact (i.e.,
redisplay an incomplete form without making the user reenter values they
already entered).
input attribute of the action element
from the previous step.
Using the Struts html:form element instead of the
standard HTML FORM element yields four results:
html:form,
a bean of the type specified by form-bean
is automatically associated with the form. If the scope
in the action element is request, a new
bean is created each time. If the scope is
session, a preexisting bean can be used, if one
is found for the current user.
<html:form action="/actions/..."><FORM ACTION="/webAppPrefix/actions/..." ...>.
<html:form action="/actions/blah"><FORM ACTION="/webAppPrefix/actions/blah.do" ...>.
POST, not GET, is the default METHOD.
That is, you say<html:form action="/actions/blah"><FORM ACTION="/webAppPrefix/actions/blah.do"
METHOD="POST">.
NAME of each input field is taken from the bean
property name, and the VALUE is taken from the bean property value.
For example, using<html:text property="firstName"><INPUT TYPE="TEXT" NAME="firstName"
VALUE="<%= theBean.getFirstName() %>">.
Not only does this provide initial values for your formfields, but it
also makes it easier for you to be sure that the field names match the bean property names.
Since, in the execute method of the Action object,
the form-bean is filled in by matching up request parameter names with
bean property names, it is critical that the names stay in synch.
By having the field names come directly from the bean property
names, it is easier to notice when you update the bean without
updating the form, since fields that do not correspond to
bean properties will be blank.
| 5.2 The Six Basic Steps in Using
Jakarta Struts: Recap with New Steps Incorporated |
|---|
With Struts, the normal processing flow is that a form
submits data to a URL of the form blah.do.
The new wrinkle in this section is that this form uses the
html:form tag to associate a bean with the form to give the form
initial values. That blah.do address is mapped
by struts-config.xml to an
Action
object, whose
execute
method handles the request. One of the arguments to
execute is a form bean that is automatically created
and whose properties are automatically populated with the
incoming form data. The Action object then invokes
business logic and data-access logic, placing the results
in normal beans stored in request, session, or application
scope. The Action uses mapping.findForward to return
a condition, and the conditions are mapped by
struts-config.xml
to various JSP pages. The system forwards the request to the
appropriate JSP page, where the
bean:write tag is used
to output properties of the form bean and results beans.
Optionally, both the input form and the final JSP pages
can use
bean:message to output standard messages
and labels as defined in a system properties file.
The first section outlines the six basic steps needed to implement this process, and gives examples that illustrate four of the steps (the ones not involving beans). The second section shows how to use beans and properties files, and provides examples of the other two steps. This section shows how to improve the initial form so that its values can be taken from a Java object either for the initial display or for redisplay when the form is submitted with incomplete data.
Here is a summary of the steps once the html:form
tags are used.
action and forward elements
to specify the Action object and destination URLs, and
we still use the form-bean element to declare form beans.
However, in addition to the path, type,
name and scope
attributes of the action element, we add a fifth
attribute: input. This attribute tells the
system to associate a form-bean of the type designated by
name (which should match a name
in the form-bean element) with the input form.
Finally, as before,
we can still use a message-resources element to declare a properties
file containing standard messages, names, and labels.
execute method
of the Action and (optionally) in the final
JSP pages, the bean will also be used in the initial input form
to give names and values to the various input elements.
RequestDispatcher,
and are created and used in the same way as described in the previous section.
Action object to handle requests.request.getParameter
explicitly, the execute method casts
the ActionForm argument to the specific form bean class,
then uses getter methods to access the properties of the object.
FORM and
INPUT tags, we now use
html:form and
html:text (and related tags) to build the input form.
The html:form tag automatically associates a bean with the form,
and html:text automatically uses the bean property
names for each textfield NAME and the bean property
values for each textfield VALUE.
In addition, as in the previous section, this form can still use
the bean:message tag to output
standard messages and text labels that are defined
in the properties file that is
declared with message-resources
in struts-config.xml.
bean:write tag to output
properties of the form and result beans. It may also
use the bean:message tag to output
standard messages and text labels that are defined
in the standard properties file.
| 5.3 Details & Example: Prepopulating Forms |
|---|
In many cases, you want the initial form to be based on data defined in your application. If the application data changes, you want the initial values of the form fields to change automatically. To implement this behavior:
action element should have an input attribute
listing the relative address of the JSP page containing the input form.
action element should specify scope="request".
html:form, and should specify
action="/path/blah", not
action="/webAppPrefix/path/blah.do". Also, recall
that POST is the default method when you use html:form.
<html:text property="propertyName"/>.
to declare input textfields.
Each textfield NAME will be taken from the bean property
name, and each textfield VALUE will be taken from the
bean property value.
html:button, html:checkbox,
html:textarea,etc., to declare submit
buttons, checkboxes, text areas, etc.
See the Apache docs for the
complete list
of available page construction tags.
Action object to handle requests.
Action classes to handle requests for blah.do.
As before, we use the action element.
forward elements
within the action element.
name and type
attributes in the form-bean declaration.
As before, we also add name (the bean name
as given in form-bean) and scope
(request since we aren't redisplaying the form)
attributes to the action declaration.
Finally, to tie the bean to the input form, we add
one new attribute of action: input.
The input attribute specifies the relative address
of the JSP page that contains the input form.
Here is an example.
<action path="/actions/signup1"
type="coreservlets.SignupAction1"
name="contactFormBean"
scope="request"
input="/forms/signup1.jsp">
...
</action>
struts-config.xml (Excerpt) <?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE struts-config PUBLIC ... > <struts-config> <form-beans> <form-bean name="contactFormBean" type="coreservlets.ContactFormBean"/> </form-beans> <action-mappings> <action path="/actions/signup1" type="coreservlets.SignupAction1" name="contactFormBean" scope="request" input="/forms/signup1.jsp"> <forward name="missing-value" path="/WEB-INF/results/missing-value.jsp"/> <forward name="success" path="/WEB-INF/results/confirmation.jsp"/> </action> ... </action-mappings> </struts-config>
execute method. In addition, a new
instance of the form bean will be created and used to fill
in the fields of the input form, with the NAME of the
input field coming from the bean property name and the
VALUE coming from the bean property value.
For example, here is a form bean corresponding to
contact information for a person that will be
added to an email/fax list in our application.
It contains properties for first name, last name,
email address, etc.
(Download the source code here).
ContactFormBean.java package coreservlets; import org.apache.struts.action.*; public class ContactFormBean extends ActionForm { private String firstName = "First name"; private String lastName = "Last name"; private String email = "user@host"; private String faxNumber = "xxx-yyy-zzzz"; public String getFirstName() { return(firstName); } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return(lastName); } public void setLastName(String lastName) { this.lastName = lastName; } public String getEmail() { return(email); } public void setEmail(String email) { this.email = email; } public String getFaxNumber() { return(faxNumber); } public void setFaxNumber(String faxNumber) { this.faxNumber = faxNumber; } ... }
MessageBean) that will be used to store simple
error messages. (Download the source code here.)
MessageBean.java package coreservlets; public class MessageBean { private String message = ""; public String getMessage() { return(message); } public void setMessage(String message) { this.message = message; } }
Action
object to handle requests.mapping.findForward to return a
"missing-value" condition, which is mapped
by struts-config.xml to
an error page that contains a message saying which parameter was missing.
If all parameters are present, we return "success",
which results in an order-confirmation page.
This example is similar to the previous one. Again, we do
do not call request.getParameter explicitly, but instead
extract the request parameters from the already populated form bean.
Specifically, we take the ActionForm argument supplied to
execute, cast it to ContactFormBean (our concrete class
that extends ActionForm), and then call getter methods
on that bean. Also, we pass the error message to the JSP page
by creating a MessageBean and storing it
in request scope. Here is the code
(download the source code here).
SignupAction1.java package coreservlets; import javax.servlet.http.*; import org.apache.struts.action.*; public class SignupAction1 extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { ContactFormBean userBean = (ContactFormBean)form; String firstName = userBean.getFirstName(); String lastName = userBean.getLastName(); String email = userBean.getEmail(); String faxNumber = userBean.getFaxNumber(); MessageBean messageBean = new MessageBean(); request.setAttribute("messageBean", messageBean); if (isMissing(firstName)) { makeWarning(request, "first name"); return(mapping.findForward("missing-value")); } else if (isMissing(lastName)) { makeWarning(request, "last name"); return(mapping.findForward("missing-value")); } else if ((isMissing(email)) || (email.indexOf("@") == -1)) { makeWarning(request, "email address"); return(mapping.findForward("missing-value")); } else if (isMissing(faxNumber)) { makeWarning(request, "fax number"); return(mapping.findForward("missing-value")); } else { return(mapping.findForward("success")); } } private boolean isMissing(String value) { return((value == null) || (value.trim().equals(""))); } protected void makeWarning(HttpServletRequest request, String message) { MessageBean messageBean = (MessageBean)request.getAttribute("messageBean"); messageBean.setMessage(message); } }
FORM tag, we
import and use the html:form tag. The Web application
prefix, the .do suffix, and the POST
method are all generated automatically. So, we use<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <html:form action="/actions/signup1"> ... </html:form>to get something equivalent to:
<FORM ACTION="/signup/actions/signup1.do" METHOD="POST"> ... </FORM>Since the location of the input form matches the
input attribute of the
action declaration in
struts-config.xml, a bean is automatically
created when the input page is accessed. The type of bean created is
determined by looking at the name that goes
with the input attribute, and finding the
form-bean with the same name.
In this case, this process results in a
ContactFormBean being created.
After declaring the form and associating it with a bean, we use
html:text to build input elements whose
NAME and VALUE are taken from the
form bean property names and values. So, for example,
First name: <html:text property="firstName"/>results in something similar to:
<% coreservlets.ContactFormBean contactBean =
new coreservlets.ContactFormBean(); %>
First name:
<INPUT TYPE="TEXT" NAME="firstName"
VALUE="<%= contactBean.getFirstName() %>">
Here is the code (download the source code here).
signup1.jsp <!DOCTYPE ...> <HTML> <HEAD><TITLE>Sign Up</TITLE></HEAD> <BODY BGCOLOR="#FDF5E6"> <H1 ALIGN="CENTER">Sign Up</H1> Looking to receive late-breaking unverified virus alerts, unknown ebay secrets, can't miss stock tips, works-in-your-sleep diet plans, and all sorts of medications? Want to get them both by email <I>and</I> by fax? Look no further: the Single Provider of Alert Memos system lets you sign up for them all in one easy request! <P> <CENTER> <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <html:form action="/actions/signup1"> First name: <html:text property="firstName"/><BR> Last name: <html:text property="lastName"/><BR> Email address: <html:text property="email"/><BR> Fax number: <html:text property="faxNumber"/><BR> <html:submit value="Sign Me Up!"/> </html:form> </CENTER> </BODY></HTML>
MessageBean to customize the error
messages in the page that is used for missing input. That
way, there does not need to be a separate page for
each type of error.
As in the previous example, the bean:write
tag is used to output bean properties without having
to resort to explicit Java code.
Here are the two files
(download the source code here).
missing-value.jsp <!DOCTYPE ...> <HTML> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> <HEAD><TITLE>Missing <bean:write name="messageBean" property="message"/> </TITLE></HEAD> <BODY BGCOLOR="#FDF5E6"> <CENTER> <H2>Missing <bean:write name="messageBean" property="message"/>!</H2> Please <A HREF="../forms/signup1.jsp">try again</A>. </CENTER> </BODY></HTML>confirmation.jsp <!DOCTYPE ...> <HTML> <HEAD><TITLE>Confirmation</TITLE></HEAD> <BODY BGCOLOR="#FDF5E6"> <CENTER> <H1>Confirmation</H1> Congratulations. You are now signed up for the Single Provider of Alert Memos network! <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> <UL> <LI>First name: <bean:write name="contactFormBean" property="firstName"/> <LI>Last name: <bean:write name="contactFormBean" property="lastName"/> <LI>Email address: <bean:write name="contactFormBean" property="email"/> <LI>Fax number: <bean:write name="contactFormBean" property="faxNumber"/> </UL> To be removed from the network, send email <A HREF="mailto:blackhole@spam-network.com">here</A>. </CENTER> </BODY></HTML>
ContactFormBean bean properties.
action in the html:form element is
/actions/signup1, and this results in the URL
http://localhost/signup/actions/signup1.do.
This address is mapped by struts-config.xml
to the SignupAction1 class, whose
execute method is invoked. This method examines the
first name, last name, email address, and fax number to see
if any are missing.
If so, it returns mapping.findForward with
a value of "missing-value". That value is mapped by
struts-config.xml to
/WEB-INF/results/missing-value.jsp.
which is the final result displayed to the user
for missing input. However, since the MessageBean
stores the specific error message, the error page is different
in each of the cases. For example, the following figures show
the results when the form is submitted with a missing first and
last name, respectively.
action in the html:form element is
/actions/signup1, and this results in the URL
http://localhost/signup/actions/signup1.do.
This address is mapped by struts-config.xml
to the SignupAction1 class, whose
execute method is invoked. This method examines the
first name, last name, email address, and fax number to see
if any are missing.
Since none are, it returns mapping.findForward with
a value of "success". That value is mapped by
struts-config.xml to
/WEB-INF/results/confirmation.jsp.
which is the final result displayed to the user, as shown
below.
| 5.4 Details & Example: Redisplaying Forms |
|---|
In the previous example, we show how to display a form
whose initial values come from a newly-created
(i.e., request-scoped) JavaBeans
component. In this example, we show how to redisplay
a form and base its values on a session-scoped bean.
This second technique lets you submit a form, check
if all the required data is supplied, and redisplay the form
if any of the data is missing.
Most importantly, when you redisplay the form, you can maintain
the previous values that the end user entered.
To implement this behavior, you declare a form-bean
in struts-config.xml as before,
use the input attribute of action
to designate the URL of the input page (so that a bean
can be created) as before,
and again use the html:form and html:text
tags in the input form. However, there are three differences:
action element should specify scope="session"
instead of scope="request". The reason that the bean must
be stored in the HttpSession object is so that it
can be filled in by the Action when the form is
submitted, yet still be accessed later when the input form
is redisplayed. Here is an example:
<action path="/actions/signup2"
type="coreservlets.SignupAction2"
name="contactFormBean"
scope="session"
input="/forms/signup2.jsp">
...
</action>
forward entry corresponding to missing
data should supply the address of the input form
for the path, rather than supplying the
address of a new JSP page as in the previous examples.
It is also common to use
redirect="true" so that the system uses
response.sendRedirect instead of
RequestDispatcher.forward to transfer to
the input form. Using a redirect is slightly slower since
it involves a roundtrip network connection, but it exposes
the URL of the input form to the users, which is more
familiar since they use that URL when they originally
access the form. Here is an example:
<forward name="missing-value"
path="/forms/signup2.jsp"
redirect="true"/>
public class ContactFormBean extends ActionForm {
private String warning = "";
...
public String getWarning() {
return(warning);
}
public void setWarning(String baseWarning) {
this.warning =
"<H2><FONT COLOR=RED>Missing " +
baseWarning + "!</FONT></H2>";
}
}
Here is an example of how to output the error message.
Notice that we use filter="false" because
the error message contains HTML tags.
<bean:write name="contactFormBean" property="warning"
filter="false"/>
Action object to handle requests.
Action classes to handle
requests for blah.do.
As before, we use the action element.
forward elements
within the action element. However, for
the "missing-value" entry, we specify
the location of the original input form, rather
than the location of a new JSP page. We also use
redirect="true" so that the system uses
an HTTP redirect, not RequestDispatcher.forward,
to transfer to the input form. That way, the URL of the
input form is shown to the user, which looks more familiar
and lets the user bookmark the address.
Here is the action declaration:
<action path="/actions/signup2"
type="coreservlets.SignupAction2"
name="contactFormBean"
scope="session"
input="/forms/signup2.jsp">
<forward name="missing-value"
path="/forms/signup2.jsp"
redirect="true"/>
<forward name="success"
path="/WEB-INF/results/confirmation.jsp"/>
</action>
name and type
attributes in the form-bean declaration.
As before, we also add name,
input, and scope
attributes to the action declaration.
However, in this case we use scope="session"
instead of scope="request" so that the
values are available to the input-form-page when we
redirect to it.
struts-config.xml (Excerpt) <?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE struts-config PUBLIC ... > <struts-config> <form-beans> <form-bean name="contactFormBean" type="coreservlets.ContactFormBean"/> </form-beans> <action-mappings> ... <action path="/actions/signup2" type="coreservlets.SignupAction2" name="contactFormBean" scope="session" input="/forms/signup2.jsp"> <forward name="missing-value" path="/forms/signup2.jsp" redirect="true"/> <forward name="success" path="/WEB-INF/results/confirmation.jsp"/> </action> </action-mappings> </struts-config>
ContactFormBean
as the previous example. However, in this example we make use
of the warning property, which does not correspond
to a request parameter but rather is used to send missing-entry-warnings
to the input form. Here is the code
(Download the source code here).
ContactFormBean.java package coreservlets; import org.apache.struts.action.*; public class ContactFormBean extends ActionForm { private String firstName = "First name"; private String lastName = "Last name"; private String email = "user@host"; private String faxNumber = "xxx-yyy-zzzz"; private String warning = ""; public String getFirstName() { return(firstName); } public void setFirstName(String firstName) { this.firstName = firstName; } ... // lastName, emailAddress, and faxNumber properties } public String getWarning() { return(warning); } public void setWarning(String baseWarning) { this.warning = "<H2><FONT COLOR=RED>Missing " + baseWarning + "!</FONT></H2>"; } }
Action
object to handle requests.execute
method, but replace the showWarning method with
a version that stores the warnings in the form-bean (intended
for the original input page) rather than in the
MessageBean (intended for a custom error page).
Here is the code
(download the source code here).
SignupAction2.java package coreservlets; import javax.servlet.http.*; public class SignupAction2 extends SignupAction1 { protected void makeWarning(HttpServletRequest request, String message) { HttpSession session = request.getSession(); ContactFormBean contactFormBean = (ContactFormBean)session.getAttribute("contactFormBean"); contactFormBean.setWarning(message); } }
html:form and html:text
elements to build an HTML form whose formfield values are
derived from bean properties. However, this time the bean
is session-scoped, so when the form is redisplayed the bean
property values are the ones that the user previously entered.
Furthermore, we output a warning message that reminds the user
which field they omitted. The default warning message is an
empty string, so rather than testing to see if the form
is being initially displayed or redisplayed, we always
output the error message (with filter="false"
because the error message can contain HTML tags).
Here is the code (download the source code here).
signup2.jsp <!DOCTYPE ...> <HTML> <HEAD><TITLE>Sign Up</TITLE></HEAD> <BODY BGCOLOR="#FDF5E6"> <H1 ALIGN="CENTER">Sign Up</H1> Looking to receive late-breaking unverified virus alerts, unknown ebay secrets, can't miss stock tips, works-in-your-sleep diet plans, and all sorts of medications? Want to get them both by email <I>and</I> by fax? Look no further: the Single Provider of Alert Memos system lets you sign up for them all in one easy request! <P> <CENTER> <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> <html:form action="/actions/signup2"> <bean:write name="contactFormBean" property="warning" filter="false"/> First name: <html:text property="firstName"/><BR> Last name: <html:text property="lastName"/><BR> Email address: <html:text property="email"/><BR> Fax number: <html:text property="faxNumber"/><BR> <html:submit value="Sign Me Up!"/> </html:form> </CENTER> </BODY></HTML>
execute method of
the Action object: "missing-value"
and "success". Since the "missing-value"
is mapped by struts-config.xml back
to the original input page, there is only one real
results page. Since the results in this example are exactly
the same as in the previous example, we use the same
JSP page.
Here it is, repeated verbatim from the previous example
(download the source code here).
confirmation.jsp <!DOCTYPE ...> <HTML> <HEAD><TITLE>Confirmation</TITLE></HEAD> <BODY BGCOLOR="#FDF5E6"> <CENTER> <H1>Confirmation</H1> Congratulations. You are now signed up for the Single Provider of Alert Memos network! <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> <UL> <LI>First name: <bean:write name="contactFormBean" property="firstName"/> <LI>Last name: <bean:write name="contactFormBean" property="lastName"/> <LI>Email address: <bean:write name="contactFormBean" property="email"/> <LI>Fax number: <bean:write name="contactFormBean" property="faxNumber"/> </UL> To be removed from the network, send email <A HREF="mailto:blackhole@spam-network.com">here</A>. </CENTER> </BODY></HTML>
ContactFormBean properties, as illustrated in
the following figures.
action in the html:form element is
/actions/signup2, and this results in the URL
http://localhost/signup/actions/signup2.do.
This address is mapped by struts-config.xml
to the SignupAction2 class, whose
execute method is invoked. This method examines the
first name, last name, email address, and fax number to see
if any are missing.
If so, it returns mapping.findForward with
a value of "missing-value". That value is mapped by
struts-config.xml back to
the original input form. And, since redirect="true"
is used, the user is transferred there by an HTTP redirect
(as with response.sendRedirect), and the URL
of the input page is displayed to the user.
The execute method stores warnings about
missing values in the warning property of
the form bean (which defaults to an empty string), so
notifying the user that they omitted a value is merely
a matter of outputting that property.
For example, the following figures show
the results when the form is submitted with a missing first and
last name, respectively.
action in the html:form element is
/actions/signup2, and this results in the URL
http://localhost/signup/actions/signup2.do.
This address is mapped by struts-config.xml
to the SignupAction2 class, whose
execute method is invoked. This method examines the
first name, last name, email address, and fax number to see
if any are missing.
Since none are, it returns mapping.findForward with
a value of "success". That value is mapped by
struts-config.xml to
/WEB-INF/results/confirmation.jsp,
the same confirmation page as used in the previous example.
Here is a representative result.
| 5.5 Source Code for this Section |
|---|
The following list gives the source code to all files described in this section, listed in the order that they appear. These examples are part of a Web application called signup, which is distinct from the struts-test application used in the previous examples.
To download the code, right-click on the links and choose "Save Target As". Be sure to reproduce the same directory structure as given here. The easiest approach is to download and unjar the entire signup WAR file, but this takes some time since this WAR file includes the Struts JAR files in addition to the sample code.
Action subclass.
Action subclass.
| More Information |
|---|
|
|