Struts + Tiles
— print (last updated: Nov 5, 2009) print

Select font size:
To run this sample project Eclipse, download and install the WAR file: StrutsNTiles.war. In NetBeans, download the StrutsNTiles.zip archive. These JAR files are needed for NetBeans:
Struts Essentials:
  commons-fileupload-1.2.1.jar
  commons-io-1.4.jar
  commons-logging-1.1.1.jar
  freemarker-2.3.15.jar
  ognl-2.7.3.jar
  struts2-core-2.1.8.jar
  xwork-core-2.1.6.jar
Tiles Plugin:
  commons-beanutils-1.8.1.jar
  commons-digester-2.0.jar
  struts2-tiles-plugin-2.1.8.jar
  tiles-api-2.1.4.jar
  tiles-core-2.1.4.jar
  tiles-jsp-2.1.4.jar
  tiles-servlet-2.1.4.jar

Tiles Configuration

Tiles is a tool for specifying a general layout in which there are parts, each a JSP file such that the parts are rendered automatically without explicitly doing so on each web page. The home page for tiles is
http://tiles.apache.org/
For our simple situation, what we have in mind is an automatic rendering of the navigational links in a page in the form of the JSP script nav.jsp, without explicitly having to include it.

The initial preparation for tiles usage is in the basic web.xml file:

web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app ... > <context-param> <param-name> org.apache.tiles.impl.BasicTilesContainer.DEFINITIONS_CONFIG </param-name> <param-value>/WEB-INF/tiles.xml</param-value> </context-param> <listener> <listener-class>org.apache.struts2.tiles.StrutsTilesListener</listener-class> </listener> ... </web-app>
In particular, this specifies the tiles definitions to be found in /WEB-INF/tiles.xml:

tiles.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE ... > <tiles-definitions> <definition name="baseLayout" template="/layout.jsp"> <put-attribute name="nav" value="/nav.jsp"/> </definition> <definition name="welcome" extends="baseLayout"> <put-attribute name="title" value="Welcome"/> <put-attribute name="content" value="/welcome.jsp"/> </definition> <definition name="first" extends="baseLayout"> <put-attribute name="title" value="First Page"/> <put-attribute name="content" value="/first.jsp"/> </definition> <definition name="another" extends="baseLayout"> <put-attribute name="title" value="Second Page"/> <put-attribute name="nav" value="/nav2.jsp"/> <put-attribute name="content" value="/another.jsp"/> </definition> </tiles-definitions>
The most important feature is the layout, specified by the file:

layout.jsp
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title><tiles:insertAttribute name="title" /></title> <link rel="stylesheet" href="css/common.css" type="text/css" /> </head> <body> <div id="nav"> <tiles:insertAttribute name="nav" /> </div> <h1><tiles:insertAttribute name="title" /></h1> <div id="content"> <tiles:insertAttribute name="content" /> </div> </body> </html>
The layout is specified by three attributes: title, nav, content and the layout is defined by the values of each using the statement
<tiles:insertAttribute name="..." />
In tiles.xml, the baseLayout definition indicates that, unless overridden, the nav attribute will be represented by the file nav.jsp. The other three definitions are used as result outcomes of action tags defined within struts.xml:

struts.xml
<!DOCTYPE struts PUBLIC ... > <struts> <constant name="struts.action.extension" value="htm" /> <package name="default" extends="struts-default"> <result-types> <result-type name="tiles" class="org.apache.struts2.views.tiles.TilesResult" /> </result-types> <action name="welcome" class="action.Welcome"> <result type="tiles">welcome</result> </action> <action name="first" method="first" class="action.Sample"> <result name="first" type="tiles">first</result> </action> <action name="another" method="another" class="action.Another"> <result name="another" type="tiles">another</result> </action> </package> </struts>
In particular, when using Tiles, one must defer to a new result type:
<result-type name="tiles" 
             class="org.apache.struts2.views.tiles.TilesResult" />
Using this as the type of a result, the view corresponding to the response output is to be found as a "tiles" view within the tiles.xml definitions file.

Setting the action extension

One of the things we've done in this example is to use ".htm" as the action extension, rather than the default ".action". This is easily changed by the line:
<constant name="struts.action.extension" value="htm" />

Nav scripts

These new Struts2 tags are introduced here:
s:bean
s:push
s:iterator
s:url
In particular, we make links and homeLinks properties of the util.Nav bean available by explicitly pushing the bean on the valueStack.

util/Nav.java
package util; import java.util.*; public class Nav { private Map<String, String> links = new LinkedHashMap<String, String>(), home_links = new LinkedHashMap<String,String>(); public Nav() { home_links.put("welcome", "Home"); links.put("welcome", "Home"); links.put("first", "First Example"); links.put("another", "Second Example"); } public Map<String, String> getLinks() { return links; } public Map<String,String> getHomeLinks() { return home_links; } }

nav.jsp
<%@taglib uri="/struts-tags" prefix="s"%> <s:bean name="util.Nav" var="nav" /> <s:push value="nav"> <table class="nav"> <tr> <s:iterator var="entry" value="links"> <td><a href="<s:url action="%{#entry.key}" />">${entry.value}</a> </td> </s:iterator> </tr> </table> </s:push>

nav2.jsp
... <s:iterator var="entry" value="homeLinks"> ...

Welcome

We want the initial behavior of the application to be the effect of the Struts2 action call, welcome.htm. Presumably this can only be done by redirection from some actual file, and so we do it as follows:

index.jsp
<meta http-equiv="Refresh" CONTENT="0; url=welcome.htm"/>
This call maps, through as the welcome action through struts.xml and the Welcome action bean;

action/Welcome.java
package action; public class Welcome { public String execute() { return "success"; } }
The result is the tiles welcome result using the minimalistic content:

welcome.jsp
Welcome.

Sample Bean & first.jsp

These new Struts2 tags are introduced here:
s:textarea
s:password
s:property
s:if
The s:property tag is similar to the JSTL c:out tag except that the value is presumed to be a property access. If we wanted to use a literal string, say "testing", we would use this:
<s:property value="%{'testing'}" />
The language used by Struts is called OGNL. It is similar, but not completely compatible with EL. Note the usage of the "%" to create an immediate distinction between with the EL format "${..}"

first.jsp
<%@taglib uri="/struts-tags" prefix="s"%> <s:form action="first" cssClass="firstForm"> <s:textfield name="tf" label="textfield, tf" /> <s:textarea name="ta" rows="5" label="textarea, ta" /> <s:password name="pwd" label="password, pwd" size="40" /> <s:submit name="button" /> </s:form> <s:if test="%{button != null}"> <div> tf: <s:property value="tf" /> <br /> ta: <s:property value="ta" /> <br /> info: <s:property value="info" /> </div> </s:if>
The associated action class is action.Sample. The entire form is reentrant and what is being illustrated is this:
  1. The most common usage is the way the textarea is handled with both get/set properties done in a transparent way. Doing so creates a stable element.
  2. Creating an unstable form element is more unusual. In particular, we undoubtedly want to capture the data entered, and this is done by the setter method. The getter method then, however, must return null to ensure that the field is cleared. In this example we "prove" that the information from the field is actually sent to the action class by way of the getInfo method, which returns the value capture by the setter.

action.Sample
package action; public class Sample { public String first() { return "first"; } private String button; public String getButton() { return button; } public void setButton(String button) { this.button = button; } private String ta; public String getTa() { return ta; } public void setTa(String ta) { this.ta = ta; } private String tf; public String getTf() { return null; } public void setTf(String tf) { this.tf = tf; } public String getInfo() { return tf; } }

Another Bean & another.jsp

Observe the change of navigational menu when we activate this second example. This is effected by the tiles definition (in tiles.xml) by changing the navigational script to nav2.jsp:
<definition name="another" extends="baseLayout">
  <put-attribute name="title"  value="Second Page"/>           
  <put-attribute name="nav"   value="/nav2.jsp"/>
  <put-attribute name="content"  value="/another.jsp"/>      
</definition>
These new Struts2 tags are introduced here:
s:radio
s:select
s:checkboxlist
s:set
The s:set tag is similar to the JSTL c:set tag with the difference in how a value is expressed. As we indicated above this would have to be used:
<s:set var="x" value="%{'testing'}" />
to set the "variable" x to the literal value "testing". When x is accessed in a value context, it must be distinguished from being a bean property by prefacing as "#x".

These elements are all generated by lists. In most cases we uses the value of getChoices, a map, to generate the list. Different from before, the iteration is implicitly done by the list attribute, and stability is the norm. Like we do explicitly in other examples, the map's key becomes the underlying value and the map's value becomes the visible content.

Note that the list choices can also be created literally as is done in the s:radio group:
<s:radio name="gender" list="{'Male','Female'}" label="gender" />
The s:select selection list creation allows the creation of a "header" which is common to many situations.

another.jsp
<%@taglib uri="/struts-tags" prefix="s" %> <s:form action="another"> <s:radio name="rg" label="Choices, rg" list="choices" /> <s:radio name="gender" list="{'Male','Female'}" label="gender" /> <s:select name="sel" list="choices" label="Choices: sel" /> <s:select name="sela" list="choices" label="Choices: sela" headerValue="--- choose ---" headerKey="0" /> <s:checkboxlist name="cb" list="choices" label="Choices: cb" /> <s:submit /> </s:form> <div> gender: <s:property value="gender"/> (alt: ${gender}) <br /> rg: <s:property value="rg"/> (alt: ${rg}) <br /> <s:set var="selVal" value="sel" /> sel: <s:property value="%{#selVal}" /> <br /> sela: <s:property value="sela" /> <br /> cb: <s:iterator var="i" begin="0" end="2" > <s:property value="%{cb[#i]}"/> </s:iterator> </div>
As can be expected, the s:checkboxlist generates a checkbox group for the multi-valued parameter "cb". The type associated with cb must be some list type, in this case an array.

action.Another
package action; import java.util.*; public class Another { public String another() { return "another"; } private String gender; public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } private Map<Integer,String> choices = new LinkedHashMap<Integer,String>(); public Map<Integer,String> getChoices() { return choices; } public void setChoices(Map<Integer,String> choices) { this.choices = choices; } public Another() { choices.put(22, "Choice 1"); choices.put(33, "Choice 2"); choices.put(44, "Choice 3"); } private Integer rg; public int getRg() { return rg; } public void setRg(int rg) { this.rg = rg; } private Integer sel; public int getSel() { return sel; } public void setSel(int sel) { this.sel = sel; } private Integer sela; public int getSela() { return sela; } public void setSela(int sela) { this.sela = sela; } private Integer[] cb; public Integer[] getCb() { return cb; } public void setCb(Integer[] cb) { this.cb = cb; } }


© Robert M. Kline