Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Apache Struts - From Novice To Professional (2006)

.pdf
Скачиваний:
57
Добавлен:
17.08.2013
Размер:
11.68 Mб
Скачать

108 C H A P T E R 1 0 M O R E T A G S

This chapter covers two more Struts tag libraries, and some of the relevant overlaps between Struts and JSTL. We’ll also look at an effort to get Struts tags to accept EL expressions. We’ll describe JSF and Shale in Chapter 20.

But first, here are a couple of guiding lights to lead the way.

Best Practices

It’s easy to get lost in the morass of tags and tag libraries, so I’d like you to consider two rules of thumb I believe are useful in deciding which tag library to go with when you have

achoice:

Beware the scriptlet: Scriptlets are not inherently “evil,” but if you find yourself using them to overcome deficiencies in a tag, it’s time to consider scouting for an alternative.

Use “custom-built generic solutions” (pardon the oxymoron): Use generic solutions like JSTL whenever possible.

I hope the rationale behind these two heuristics is obvious. Custom tags are there to obviate the need to use scriptlets. So, if you find yourself having to use scriptlets, the custom tags you’re using aren’t doing their job well enough.

The 2 + 1 Remaining Struts Libraries

In Chapter 8, you saw how to use the HTML and Bean tag libraries. There are three other Struts tag libraries: the Logic, Nested, and Tiles tag libraries.

Note In older versions of Struts, prior to 1.2, you might come across a “template” tag library. The Tiles

library supersedes this older “template” tag library.

In this chapter, we’ll only cover the Logic and Nested tag libraries. The Tiles tags warrant an entire chapter of their own (Chapter 14).

C H A P T E R 1 0 M O R E T A G S

109

The Logic Tag Library

The Logic library has tags for

Iteration: The iterate tag

Conditional processing: The equal, present, match, empty, notEqual, notPresent, notMatch, notEmpty, messagesNotPresent, messagesPresent, lessThan, lessEqual, greaterThan, and greaterEqual tags

Flow control: The redirect and forward tags

Note All of these tags, apart from the ones for flow control, have counterparts in JSTL, which we will cover later in this chapter. You should use the JSTL versions whenever possible.

Let’s tackle these three classes of tags next.

Iteration

The <logic:iterate> tag can be used to iterate over collections, enumerations, arrays, or iterators. We’ll refer to any of these as an “iteratable” object. To use the <logic:iterate> tag, you have to

1.Create the iteratable object: This is done in your Action subclass, since this is Controller code. Never forget the MVC.

2.Place the iteratable object on the request: Recall that in your Action’s execute() function, you have a reference to the request object. You can place objects on this request object, or on its associated session object, giving the object a name.

3.Forward to the JSP page that contains the <logic:iterate> tag: The JSP page called by the Action would be able to access these objects by the name you gave them.

These three steps are the same or similar even if you’re using JSTL for iteration, so be sure to study carefully the following code listings that illustrate these steps.

We’ll take up the LILLDEP webapp you’ve been working on throughout this book. The requirement is to create a simple search page for Contacts by postcode. The user keys in the postcode and the page returns a list of Contacts. This is obviously a contrived example, but it keeps things simple!

110 C H A P T E R 1 0 M O R E T A G S

Also, for the sake of clarity, we’ll present only the relevant, nonobvious portions of code. First, Listing 10-1 shows the Action subclass, aptly named PostcodeSearchAction, which receives the postcode the user wants to search by.

Listing 10-1. A Section of PostcodeSearchAction

public ActionForward execute(...){

PostcodeSearchForm psForm = (PostcodeSearchForm) form;

String postcode = form.getPostcode();

//Step 1: create iteratable object

//Retrieve a list of Contacts matching the given postcode.

//If you find the following code confusing, read Appendix A.

Criteria crit = new Criteria(); crit.add(Contact.POSTCODE,postcode);

Iterator results = ContactPeer.doSelect(crit);

//Step 2: put iteratable on request

//Save the list on the results. You might want to refer to

//Appendix B for some the functions on HttpServletRequest.

request.setAttribute("search_results", results);

//Step 3: forward to the JSP

//In this case, struts-config.xml maps "success" to

//Display.jsp

return new ActionForward(mapping.findForward("success"));

}

So, an Iterator of Contacts is located on the request object. This Iterator is called search_results. Listing 10-2 shows the section of Display.jsp that lists the Contacts.

Listing 10-2. A Section of Display.jsp

<logic:iterate name="search_results" id="contact" indexId="cnt"> <p>

Contact number <bean:write name="cnt"/> <bean:write name="contact" property="name"/> <bean:write name="contact" property="company"/> <bean:write name="contact" property="tel"/>

</p>

</logic:iterate>

C H A P T E R 1 0 M O R E T A G S

111

In Listing 10-2, the name, company, and telephone number of each Contact is listed. The <logic:iterate> tag has three main attributes:

name: Refers to the iteratable object you’ve placed on the request. In this instance, it’s search_results.

id: Specifies a name that represents a single element on the iteratable object. In this case, the id is contact. This attribute is required, even if you don’t use it.

indexId (optional): Specifies a number representing the current iteration. The only reason I used it here is to write out the iteration number using <bean:write>. The indexId is important when you want to use indexed properties, a topic I will come to in the next section.

The <bean:write>s write out the name, company, and telephone of a given Contact. Note that the name attribute of <bean:write> refers to the single element exposed by the id attribute of <logic:iterate>. Refer to Appendix C for a thorough explanation of <bean:write>.

ITERATING OVER HASHMAPS

If the iteratable object is a HashMap or a class implementing a Map, the elements are instances of Map.Entry. This interface contains getKey() and getValue() functions, so you can use

<bean:write name="xxx" property="key"/>

to display the key, and

<bean:write name="xxx" property="value"/>

to display the corresponding value.

I’d like to reiterate (pardon the pun) that the functionality provided by <logic:iterate> overlaps with JSTL’s <forEach> tag. We’ll come to this in a later section.

Simple, Nested, Indexed, and Mapped Properties

Many Struts tags accept a property attribute, which refers to a property on the object they’re operating on. We’ve seen this in action, for example, in the <bean:write> tag in the previous section:

<bean:write name="contact" property="company"/>

As it’s used here, <bean:write> attempts to display the return value of getCompany(), which is called on the object referred to as contact. This is an example of a simple property.

112 C H A P T E R 1 0 M O R E T A G S

Simple properties are those that implicitly call a getXXX() function on the object currently under consideration.

Struts supports three other types of properties: nested, indexed, and mapped properties. Nested properties allow you to refer to a property of a property. For example, if in our previous example the return value of getCompany() were a JavaBean object, then nested properties would allow you to refer to a property on this object:

<bean:write name="contact" property="company.name"/>

As it’s used here, <bean:write> attempts to display the return value of getCompany().getName().

Indexed properties are similar to simple properties, but they assume that the function that should be called is getXXX(int index) instead of just getXXX(). So, if the contact object supported a getCompany(index) function, this is how you would call it:

<bean:write name="contact" property="company[41]"/>

As it’s used here, <bean:write> attempts to display the return value of getCompany(41). This example begs the question of how to use indexed properties with <logic:iterator>, for example, to display a list of values. This is how you’d do it:

<logic:iterate name="companies" id="company" indexId="cnt"> <bean:write name="companies" property="company[<%=cnt%>]"/>

</logic:iterate>

This example assumes that the companies object has a getCompany(index) function.

This example should also set off alarm bells in your head: it uses a scriptlet! There’s got to be a better way—and there is. In this particular example, you have two choices: use <forEach> and <out> JSTL tags (which we’ll cover later) or use the Struts-EL tags (we’ll also cover these later), which are souped-up versions of the ordinary Struts tags:

<logic-el:iterate name="companies" id="company" indexId="cnt"> <bean-el:write name="companies" property="company[${cnt}]"/>

</logic-el:iterate>

In this example, the change is hardly perceptible, but in more realistic cases the resulting code would be much simpler to read.

Mapped properties are similar to indexed properties, except that the function called is assumed to be getXXX(String s). For example, if the contact object in the previous examples had a getMobile(String mobileType) function, you could use

<bean:write name="contact" property="mobile(home)"/> <bean:write name="contact" property="mobile(office)"/>

These examples would call getMobile("home") and getMobile("office"), respectively. You can also mix the various types of properties:

C H A P T E R 1 0 M O R E T A G S

113

<bean:write name="contacts" property="contact[5772].company"/>

which is an implicit call to getContact(5772).getCompany(). Table 10-1 summarizes the various property types.

Table 10-1. Simple, Nested, Indexed, and Mapped Properties

Type

Usage

Function Called

simple

property="myProperty"

getMyProperty()

nested

property="myProperty.mySubProperty"

getMyProperty().getMySubProperty()

indexed

property="myProperty[5772] "

getMyProperty(5772)

mapped

property="myProperty(myString) "

getMyProperty("myString")

 

 

 

Conditional Processing

The Logic tag library has a number of tags for performing conditional processing. These may be grouped into three categories:

Tests: present, empty, messagesPresent

Comparisons: equal, lessThan, lessEqual, greaterThan, and greaterEqual

String matching: match

Some of these tags (for tests and string matching) have negative counterparts: notPresent, notEmpty, messagesNotPresent, and notMatch. All these tags follow a common structure in their usage. In pseudocode:

<tag>

// conditionally processed code here. </tag>

In addition, all these tags have two attributes in common:

name: Specifies the name of the object that the tag operates on. It is assumed that a previous Action had placed the object on the request. The earlier section on iteration shows how this might be done.

property: Corresponds to a simple, nested, indexed, or mapped property on the object referred to by the name attribute. This attribute is optional.

Tags that require a comparison or a match will also accept an additional value attribute. Let’s look at a couple of examples of how these tags are used:

114 C H A P T E R 1 0 M O R E T A G S

<logic:empty name="search_results">

<i>Sorry, there are no contacts with this postcode</i> </logic:empty>

<logic:notEmpty name="search_results">

<!-- code to display contacts goes here --> </logic:notEmpty>

The empty and notEmpty tags simply check if the given iteratable object contains elements, and therefore don’t require a value attribute. Here’s another example:

<logic:lessThan name="myConstants" property="pi" value="3.14"> <i>Some of your pi is missing!</i>

</logic:lessThan>

This code displays the message “Some of your pi is missing” if myConstants.getPi() is less than 3.14.

Table 10-2 summarizes these tags.

Table 10-2. Summary of the Conditional Processing Tags

Tag

Meaning

present

Tests if a variable is present on the request.

empty

Tests if an array or Collection is empty.

messagesPresent

Tests if a message (an ActionMessage instance) is on the request.

 

The name of the message is given in the value attribute.

Comparison tags

Tests if the given property passes the given comparison. The value

(equal, lessEqual, etc.)

to be compared with is provided in the value attribute.

match

Checks if the given property has the value attribute as a substring.

 

 

The negative counterparts of the tags in Table 10-2 are notPresent, notEmpty, messagesNotPresent, and notMatch). Appendix C describes all these tags in more detail.

Lastly, note that JSTL has an <if> tag that you can use in place of many of these tags. We’ll come to this later on in this chapter.

Flow Control

The two tags that perform flow control (actually, redirection) are <logic:redirect> and the simplified version of it, called <logic:forward>. Their use on a JSP page causes control to be passed to a different page. For example:

<logic:forward name="someOtherPage"/>

would redirect control to a global forward (refer to Chapter 9) called someOtherPage.

C H A P T E R 1 0 M O R E T A G S

115

<logic:redirect> also redirects control to a different page, but gives you more options for performing the redirect. For more details on each of these tags, refer to Appendix C.

Note that there are no equivalent JSTL tags that can utilize global forwards declared in struts-config.xml.

The Nested Tag Library

The Nested tag library allows you to apply tags relative to an object. In some instances, this can greatly simplify your server-side ActionForm code, as you’ll see in the lab sessions of this chapter.

Note If you’ve programmed in Visual Basic, you might have used the With keyword. The Nested tag library is an inelegant implementation of this.

To illustrate how the Nested tag library works, let’s first consider the class diagram shown in Figure 10-1.

Figure 10-1. The MyActionForm, Monkey, and Banana classes

116 C H A P T E R 1 0 M O R E T A G S

Figure 10-1 describes an ActionForm subclass and its functions. The data on MyActionForm is held by two objects: Monkey, which in turn has a Banana object. Our webapp has to allow the user to key in a name for the Monkey, and a species for the Monkey’s Banana (Figure 10-2).

Figure 10-2. Form to enter the Monkey’s name and the Banana species

As Listing 10-3 shows, the Nested tag library allows you to do this without having to include additional getters and setters for the name and species in MyActionForm.

Listing 10-3. MonkeyPreferences.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib uri="/tags/struts-html" prefix="html" %>

<%@ taglib uri="/tags/struts-nested" prefix="nested" %> <html:html>

<body>

<html:form action="MonkeyPreferencesForm.do">

<nested:nest property="monkey">

Monkey's name:

<nested:text property="name" size="60" />

<br>

<nested:nest property="banana"> Species of Banana:

<nested:text property="species" size="60" /> </nested:nest>

</nested:nest>

<html:submit/>

</html:form>

</body>

</html:html>

C H A P T E R 1 0 M O R E T A G S

117

The <html:form> tag implicitly sets the root object as MyActionForm. This means that all properties are calculated relative to the MyActionForm instance. The <nested:nest> tag

<nested:nest property="monkey"> Monkey's name:

<nested:text property="name" size="60" />

makes the property references relative to the monkey property instead. So the <nested:text>’s property tag is really monkey.name.

Your eyes should widen at the <nested:text>. Yes, that’s right. The Nested tag library contains “nested” counterparts for all of the HTML tag library, most of the bean tag library, and all of the Logic tag library apart from the flow control tags. Appendix C lists the tags in the HTML, bean, and logic tag libraries supported by the Nested tag library.

Obviously, there’s nothing you can’t achieve with the Nested tag library that you can’t already do with nested properties (see the earlier section). For example, Listing 10-4 shows how Listing 10-3 could be rewritten without using nested tags. In complicated scenarios, though, the use of the Nested tag library makes your code a little neater.

Listing 10-4. MonkeyPreferences.jsp, Take 2

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib uri="/tags/struts-html" prefix="html" %>

<html:html>

<body>

<html:form action="MonkeyPreferencesForm.do"> Monkey's name:

<html:text property="monkey.name" size="60" />

<br>

Species of Banana:

<html:text property="monkey.banana.species" size="60" />

<html:submit/>

</html:form>

</body>

</html:html>

Lastly, there are times when you want to specify a different root object than the default one. You use the <nested:root> tag to do this:

<nested:root name="myNewRoot">

<!-- new root object is myNewRoot --> </nested:root>

Of course, this example assumes that the object named myNewRoot has been placed on the request. This is usually done in your Action subclass. Refer to Listing 10-1 and the accompanying discussion to see how this is done.