Struts Tip #12 - Use smart forwarding to create menuing systems

Ted Husted

 

 

One things that has made the World Wide Web so popular is ease of navigation. Any swatch of text on a page can be turned into a hyperlink. The user just needs to point-and-click, and off they go to the target page. Behind the swatch of text is a path to the page, which may be long and cumbersome, but the user doesn't need to know that. They just click on the description, and the system does the rest.

Besides the ubiquitous hyperlink, HTML also provides us with an assortment of user interface widgets, like radio buttons, check boxes, and select lists. Like hyperlinks, they allow us to display a plain language description to the user, but return a technical descriptor to the server. Most often, these controls are used to make it easier to fill-out a form, but they can also be used to create menu systems. The use selects a location from the select list, and the system wisks them off to the relevant page.

The simplest way to build a menu system would be to just embed the page locations, or URLs, into the control. Any many, many web applications have been written using that approach, especially those using CGI systems like Perl. But, embedding systems paths is not the way we build appilcation with Struts. We want to design pages using logical identifiers, and let Struts do the matching between identifers with system paths. This way we can move things around without the pages being any the wiser.

The fundamental way Struts matches identifiers with system paths is through the Struts configuration (struts-config.xml). Struts applications continually use the configuration to math up ids like "success" and "failure" to various locations with the application. Now how do the same thing to support menuing systems?

In Tip #11, we looked at our a standard RelayAction can be used to select between multiple submit buttons. Now lets look at how we can use the RelayAction and some other standard actions, to select between multiple options on a select list.

The simplest instance would be selecting between various locations in an application. Here's a an example with two options

<html:select property="dispatch" > 
<html:option value="reload">Reload Config</html:option>
<html:option value="create">Create Resources</html:option>
</html:select>

This controls can be used just like the multiple submit buttons in Tip #11. The form's dispatch property is set to whatever is selected. The form is submitted to a RelayAction with a local forward for each option. The RelayAction then forwards the request along to whatever path is indicated by the forward.

<action 
  path="/menu/Manager" 
  type="org.apache.scaffold.struts.RelayAction" 
  name="menuForm" 
  validate="false">
<forward name="reload" path="/do/admin/Reload"/>
<forward name="createResources" path="/do/admin/CreateResources"/>
</action>

This is great for simple requests, but what if we need to include a parameter with the action?

If the control is being used to select parameters for the same action, or actions that use the same parameter name, you can just give the option the parameter name. Here's a radio button control that displays identifiers like "Day" and "Week" but passes the corresponding number of hours to the action.

<html:form action="/find/Hours">
 <P>List articles posted in the last:</P>
 <P>
<INPUT type="radio" name="hours" value="24">Day 
<INPUT type="radio" name="hours" value="168">Week 
<INPUT type="radio" name="hours" value="720">Month 
</P>
 <P> <html:submit property="submit" value="GO"/> </P> 
</html:form>

When they submit the form, the browser will generate a URI like

/find/Hours?hours=24 

or

/find/Hours?hours=168

or

/find/Hours?hours=720

depending on which radio button is selected.

In practice, we might want to write this control from a collection, using code like

<html:form action="/find/Hours">
<P>List articles posted in the last:</P>
<P><html:options collection="FIND" property="value" labelProperty="label"/></P>
<P><html:submit property="submit" value="GO"/></P>
</html:form>

But, that would not be an instructive example. So, we show our options hardcoded instead, even if that is not what we would do in practice.

Hardcoding a parameter, or passing it down with a collection, works fine when all our options go to the same option, or goto actions that use the same parameter name. But what if the parameters names are different. We may have a number of actions for looking up a record based on this field or that field, and may need to provide the field as the parameter name, like this:

/do/find/Title?title=Struts

/do/find/Author?creator=husted

/do/find/Content?content=menus

/do/article/View?article=12

There are many times when we would like to provide locations like these as a single combo control, that lets us select the search type (Title, Author, Content, ID), and then provide a user-supplied parameter, like those shown.

In each case, all we really need to do is paste the parameter to the end of the URI. The form would still need to submit the parameter under the same name, but if we could take something like

/do/menu/Find?dispatch=title&value=Struts

and turn it into

/do/find/Title?title=Struts

we'd be in business.

Since this is a common need, its worth creating a standard action to do just this. Here's a the source for a simple parameter action that pastes a value at the end of a partial URI.

// Get "dispatch" parameter 
String parameter = request.getParameter(Tokens.DISPATCH); 
// Get parameter name for this mapping 
String paramName = mapping.getParameter();

StringBuffer path = new StringBuffer(64);

// Get stub URI from mapping (/do/whatever?paramName=) 
path.append(mapping.findForward(parameter).getPath());
 // Append the value passed (/do/whatever?paramName=paramProperty) 
path.append(request.getParameter(paramName));

// Return a new forward based on stub+value 
return new ActionForward(path.toString());

Like the Dispatch actions (see Tips #2 and #3), it needs to get the name of the parameter from the mapping's parameter property. Here's the corresponding mapping:

<action 
  path="/menu/Find" 
  type="org.apache.scaffold.struts.ParameterAction" 
  name="menuForm" 
  validate="false" 
  parameter="keyValue">
<forward name="title" path="/do/find/Title?title="/> 
<forward name="author" path="/do/find/Author?creator="/> 
<forward name="content" path="/do/find/Content?content="/> 
<forward name="article" path="/do/article/View?article="/> 
</action>

You may note that the action uses a form-bean called "menuForm". This is a simple bean with properties common to many menu items. Here's the source:

private String keyName = null; 
public String getKeyName() { 
  return this.keyName; 

public void setKeyName(String keyName) { 
  this.keyName = keyName; 
}

private String keyValue = null; 
public String getKeyValue() { 
  return this.keyValue; 

public void setKeyValue(String keyValue) { 
  this.keyValue = keyValue; 
}

private String dispatch = null; 
public String getDispatch() { 
  return this.dispatch; 

public void setDispatch(String dispatch) { 
  this.dispatch = dispatch; 
}

The version in the Scaffold package includes some other convenience properties, but these three are the important ones.

Of course, your form can still use all the properties it needs. They all go into the request and stay there for the duration. When one of the standard action forwards the request, all the original parameters go with it. You can populate as many ActionForms as you like from the same request. When the request arrives at the mapping for the target action, whatever form-bean it uses is populated normally.

So far, we've looked at using the RelayAction to select between different submit buttons or menu selections, and the ParameterAction to append a value to a query string. There is one more action like this in our repetiorie, the FindForwardAction.

The RelayAction relies on there being a parameter with a known name in a request. For example, [dispatch=save]. It looks for the parameter named "dispatch", then looks for a forward named "save". The FindForwardAction is even more dynamic. It runs through all the parameter names and checks to see if any are also the name of a forward. If so, it returns the matching ActionForward.

This can be a good way to match multiple submit buttons without using JavasScript to set the dispatch property. If you have buttons named save, create, and delete, and forwards also named save, create, and delete, the FindFowardAction will automatically match one with the other.

JSP: 
<html:submit name="save">SAVE</html:submit> 
<html:submit name="create">SAVE AS NEW</html:submitl> 
<html:submit name="delete">DELETE</html:submit>

config:
<action
   name="articleForm" 
   path="/do/article/Submit" 
   type="org.apache.scaffold.FindForwardAction"> 
  <forward name="create" path="/do/article/Create"/> 
  <forward name="save" path="/do/article/Store"/> 
  <forward name="delete" path="/do/article/Recycle"/> 
</action>

The FindForwardAction is a little mroe complicated than the others, but it still quite brief.

for (int i=0; i<forwards.length; i++) { 
  if (request.getParameter(forwards[i])!=null) { 
    // Return the required ActionForward instance 
    return mapping.findForward(forwards[i]); 
  } 
}

return null;

The only caveat here is that you have to manage your forward and control names more carefully. The FindForwardAction will check all the parameters on the form with all the available forwards, and the first one it finds wins. So if any of your control names match any of your forward names, it might come up with an unexpected match.

This can be useful when you cannot add a dispatch property to a form, or cannot use JavaScript to set the dispatch property. But the RelayAction should be preferred when possible, since it is more "deterministic".

Using these techiques together can fill a surprising number of your menuing needs and keeps all the flow control within the Struts configuration.

HTH, Ted.

<hr>

Struts Tips  are based on excerpts from the book Java Web Development with Struts. The tips released twice weekly on the MVC-Programmers List. To subscribe, visit BaseBean Engineering.

About Ted. Ted Husted is an active Struts Committer and co-author of Struts in Action and Professional JSP Site Design. Ted also moderates the Struts mailing list and the JGuru Struts FAQ.

Copyright Ted Husted 2002. All rights reserved.

 

0 Comments  (click to add your comment)
Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

About | Sitemap | Contact