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

Beginning Apache Struts - From Novice To Professional (2006)

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

168

C H A P T E R 1 4 T I L E S

<definition name=".simple.admin.main" extends=".simple.admin" > <tiles:put name="body" value="/admin/body.jsp" />

</definition>

In this case, you have no need for a JSP page in which to embed the definition, since this new definition defines all attributes needed to display the layout. You may use this definition by calling it from either a <forward> in struts-config.xml:

<forward name="success" path=".simple.admin.main" />

or as an input page like so:

<action path="/MyFormHandler" input=".simple.admin.main" ...

Using Stylesheets with Layouts

Most professional web apps use Cascading Style Sheets (CSS) to achieve common fonts and colors. When you attempt to use stylesheets with Tiles layouts, you might run into problems. For example, suppose your stylesheet is \styles\styles.css, and your layout references this stylesheet, as shown in Listing 14-8.

Listing 14-8. Snippet of simple-layout.jsp with Static Stylesheet Reference

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

<head>

<style type="text/css" media="screen"> @import "./styles/styles.css";

</style>

<title> <tiles:getAsString name="title"/> </title> </head>

...

This stylesheet declaration is fine if all your JSPs that use this layout are in the root folder of the web application. This may not be feasible for larger applications. For example, you might want to place “admin” pages in a separate \admin\ subfolder. Doing so would mean that the reference to the stylesheet is now ../styles/styles.css (note the leading

..). The static stylesheet reference in Listing 14-8 won’t do.

A useful hack is to use the getContextPath() function on the request object to get around this limitation. The stylesheet declaration of Listing 14-8 now becomes:

<style type="text/css" media="screen">

@import "<%= request.getContextPath() %>/styles/styles.css"; </style>

C H A P T E R 1 4 T I L E S

169

Each time the layout is used, the right path to the stylesheet is computed. A poorer way of doing the same is to hard-code the full URL:

<style type="text/css" media="screen">

@import "http://www.mycompany.com/myapp/styles/styles.css"; </style>

This method is less effective because the port used on the server or the application name might be changed upon deployment. In either case, the hard-coded solution won’t work.

Tiles Components

In the previous sections, you’ve seen how to develop layouts using Tiles. Next, we’ll explore a quite different use of Tiles: building components.

In Tiles, a component is a rectangular area displayed within the user’s web browser (this is why “Tiles” is so named), capable of rendering dynamic content. Examples are components that display selected stock prices, or ads, or a user search list, or a list of pages the user has visited... The possibilities are endless!

There are two main points to note concerning Tiles components:

Easy embedding: A Tiles component can be embedded into any JSP page using the handy <tiles:insert> tag. Want a weather report Tiles component on your JSP page? Slap in a single <tiles:insert> tag with the right attributes.

Independence: To be useful, a Tiles component should function independently of its environment—the JSP page in which it is embedded. There are times when this rule can be broken (as you will see in the lab section of this chapter), but this is always at the expense of reusability.

Creating a Tiles Component

Creating a Tiles component is more involved compared to creating and declaring a Tiles layout. Five steps are involved:

1.Create the Tiles controller. This is a Java class that creates dynamic content or processes user input.

2.Declare the Tiles controller in struts-config.xml.

3.Create the Tiles “view.” This is the JSP page that displays the results of the Tiles controller’s output.

170

C H A P T E R 1 4 T I L E S

4.Declare a Tiles <definition> that associates the JSP page with the controller.

A controller may be used in more than one definition. This is exactly analogous to the way an Action subclass may be used in more than one form handler.

5.Use the component on your JSP pages by embedding the <tiles:insert> tag in them.

To create the Tiles controller, you subclass the TilesAction base class:

org.apache.struts.tiles.actions.TilesAction

Notice that it’s tiles.actions (plural). I’ll describe these five steps in more detail next and then wrap up with a concrete example application.

Step 1: Creating the Tiles Controller

The function you need to override is called (surprise!) execute(), but with a slightly different signature compared to Action.execute(), as shown in Listing 14-9.

Listing 14-9. execute() on TilesAction

public ActionForward execute(ComponentContext context, ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)

throws IOException, ServletException{ ...

All you have to do is to override this one function to process user input or prepare data for output.

The org.apache.struts.tiles.ComponentContext instance passed in execute() functions as a storage area for data intended for your Tiles component. You can store and retrieve data with putAttribute() and getAttribute() on this class. Only the JSP page represented by the ActionForward return value has direct access to data placed on this ComponentContext instance. This helps preserve the independent existence of the Tiles component from its environment.

To summarize, the primary purpose of the ComponentContext is as a channel of communication from the Tiles controller to the JSP page it forwards to.

This is analogous to the way the HttpServletRequest is a means of communication between an Action subclass and the forwarded JSP. A Tile controller could also place data on the HttpServletRequest object, but this would break the independence of that Tiles component, since other Tiles components on the page might overwrite the data placed on the request object.

The last point to note is that this execute() is marked to throw IOException and javax. servlet.ServletException.

C H A P T E R 1 4 T I L E S

171

Step 2: Declaring the Controller

The declaration for a Tiles controller is exactly the same as for any form handler. For example, Listing 14-10 shows a simple controller that doesn’t accept any input data, and that has two possible views.

Listing 14-10. One Controller, Two Views

<action path="/MyTilesController" type="com.mycompany.myapp.tiles.MyTilesController">

<forward name="success" path="/tiles/my-tiles-view.jsp"/> <forward name="failure" path="/tiles/error.jsp"/>

</action>

Perhaps the only difference is that when you declare a Tiles controller, it is possible to omit declaring any <forward>s, if there is only one JSP view associated with the Tiles component. Listing 14-11 shows an example.

Listing 14-11. One Controller, One View

<action path="/MyTilesController" type="com.mycompany.myapp.tiles.MyTilesController" />

In this case, the controller’s execute() function (Listing 14-9) returns null, and the Tiles <definition> associated with this component is slightly different compared to the case where there are multiple JSP views associated with the Tiles component, as in Listing 14-10. I’ll describe this difference shortly.

Step 3: Creating the Tiles View

The Tiles view is just the JSP that displays the output of the Tiles controller. This is analogous to the “next” JSP page associated with an Action subclass.

The primary difference, of course, is that the JSP page typically will evaluate to an HTML fragment, not a complete HTML page. This is because the JSP page’s output will be embedded within a full HTML page.

Step 4: Declaring the Tiles <definition>

The Tiles <definition> enables the controller to be used conveniently in a <tiles:insert> tag. It is possible to use a Tiles controller already declared in struts-config.xml directly in a <tiles:insert>, just as a layout was used directly in Listing 14-4. For example:

<tiles:insert path="/MyTilesController.do"/>

The advantage of using a Tiles definition is that you can avoid hard-coding the handler’s path in all your JSPs.

172

C H A P T E R 1 4 T I L E S

The declared definition will depend on whether the component uses just a single view page (as in Listing 14-11) or multiple views (Listing 14-10).

In the single view case, which is typical, the definition is declared as shown in Listing 14-12.

Listing 14-12. A Tiles Component with a Single View

<definition name=".myTile" path="/tiles/my-tiles-view.jsp" controllerUrl="/MyTilesController.do">

<put name="title" value="This is My Tile"/>

</definition>

Declared this way, the return value of the controller’s execute() should be null, and the JSP page in the path attribute is used as a view. The controllerUrl attribute is used to specify the Tiles handler declared in struts-config.xml. You may define attributes (in Listing 14-12, the title attribute is defined) to be used by the view.

If the component has multiple views, they must all be declared in struts-config.xml as <forward>s, as in Listing 14-10. In this case, the Tiles <definition> is simpler, as Listing 14-13 shows.

Listing 14-13. A Tiles Component with Multiple Views

<definition name=".myTile" path="/MyTilesController.do">

<put name="title" value="This is My Tile"/>

</definition>

Notice that path now points to the Tiles controller declared in struts-config.xml. Again, you may define attributes to be used by any one of the views, using nested <put>s.

Step 5: Using the Component

As I said in the introduction to this section, using a tile is easy. All you have to do is to declare the use of the Tiles tag library in your JSP:

<%@ taglib uri="/tags/struts-tiles" prefix="tiles" %>

C H A P T E R 1 4 T I L E S

173

and then use a <tiles:insert definition="..."> anywhere on your JSP page. For example, to use the Tiles component declared in either Listing 14-12 or Listing 14-13, use the following:

<tiles:insert definition=".myTile"/>

You can also specify further attributes for the view page by using nested <tiles:put> tags, as in Listing 14-6.

Example: The “Login” Tiles

Tiles components are very useful. Used wisely, they can greatly simplify your code and code maintenance.

Now, the discussion so far has been a little abstract, and rightly so, because there are a few ways to create and use Tiles components, and I wanted you to have a bird’s-eye view of the whole thing.

In this section, I’ll describe how our old friend the Registration webapp can be made into a Tiles component. Why do this? Because it gives you the freedom to place a user login anywhere on your webapp. If a user must log in several places in your webapp, then a Tiles component for user login would be a natural choice.

What I’ll actually implement is a nontrivial variation on the Registration webapp. Figure 14-2 describes the control flow of this new souped-up version, perhaps better named a “Login” Tiles component.

Figure 14-2. Control flow of the Login component

174

C H A P T E R 1 4 T I L E S

As you can see in Figure 14-2, there are three possible views:

A “regular login” form with fields for user ID and password (see Figure 14-3)

A “new user” form for users who don’t have a user ID (Figure 14-4)

A “logoff” view, displayed if a user has already logged on (Figure 14-5)

Also, unlike the Tiles components we’ve discussed thus far, this component accepts user input—for example, a user ID and password.

Figure 14-3. The regular login form

C H A P T E R 1 4 T I L E S

175

Figure 14-4. The new user form

Figure 14-5. The logoff view

Because this is a more complex component, for pedagogical reasons I’ll describe each part of the component out of order, starting with the simplest.

First, Listing 14-14 shows how you’d embed the Login Tiles component in any JSP page.

176

C H A P T E R 1 4 T I L E S

Listing 14-14. index.jsp, a JSP Page with the Login Component Embedded in It

<%@ taglib uri="/tags/struts-tiles-el" prefix="tiles-el" %> <html>

<body>

In this section, I'll describe how our old friend the ...

<tiles-el:insert definition=".login"/>

As you can see from Figure 14-2, there are ...

</body>

</html>

As you can see from the taglib declaration, I’ll be using the Struts EL-enabled tags (see Chapter 10).

Figures 14-3 and 14-4 both show that the Login component accepts user input. This data is stored in an ActionForm subclass called LogonForm (see Listing 14-15).

Listing 14-15. LogonForm.java

package net.thinksquared.login.struts;

import javax.servlet.http.*; import org.apache.struts.action.*;

public class LogonForm extends ActionForm {

public static final int UNDEFINED = 0; public static final int LOGGED_ON = 1;

public static final int LOG_OFF

= 2;

public

static

final

int

REGULAR

=

3;

public

static

final

int

NEW_USER

=

4;

protected int

_status;

protected String

_userid;

protected String

_password;

protected String

_password2;

protected boolean _hasErrors;

C H A P T E R 1 4 T I L E S

177

public LogonForm(){ _status = UNDEFINED; _hasErrors = false;

}

public void

setStatus(int i){ _status = i; }

 

 

public int

getStatus(){ return _status; }

 

 

public void

setUserid(String s){ _userid = s; }

 

public String

getUserid(){ return _userid; }

 

 

public void

setPassword(String s){ _password

=

s; }

public String

getPassword(){ return _password;

}

 

public void

setPassword2(String s){ _password2 = s; }

public String

getPassword2(){ return _password2; }

public ActionErrors validate(ActionMapping mapping, HttpServletRequest request){

if(_status >= REGULAR){

ActionErrors errors = new ActionErrors();

if(_userid == null || _userid.length() < 1){ errors.add("userid",

new ActionMessage("userlogin.error.userid.blank")); }else if(_password == null ||

_password.length() < 6 || _password.length() > 12){ errors.add("password",

new ActionMessage("userlogin.error.password.length")); }else if(_status >= NEW_USER &&

(_password2 == null || !_password2.equals(_password))){ errors.add("password2",

new ActionMessage("userlogin.error.password.retype"));

}

if(!errors.isEmpty()){ _hasErrors = true; return errors;

}

}