1. 高级 tile 主题
记住 Tiles 框架定义了一个称为“tile 范围”的附加范围,它与页面范围类似。像页面范围一样,tile 范围比请求范围更私有。Tile 范围允许 tile 用户给 tile 传递变量(称为参数)。本质上,它使得页面像函数一样可调用。
记住,jsp:include
允许您调用一个页面,同时传递给它一个请求参数(jsp:param)。tiles:insert
标签类似 jsp:include
,不过前者更强大。 tiles:insert
标签允许您调用一个页面,同时向它传递子页面(称为 tile)和属性。Tile 范围本质上允许您传递仅对该 tile 布局可用的变量。
如果知道 tile 范围是如何实现的,您就能够理解它。我曾创建过一个名为 listTileScope 的调试实用程序,它允许我打印出 tile 范围中的变量,如下面的代码片段所示:
import org.apache.struts.taglib.tiles.ComponentConstants;
import org.apache.struts.tiles.ComponentContext;
public static void listTileScope(PageContext context)
throws JspException, IOException {
JspWriter out = context.getOut();
ComponentContext compContext =
(ComponentContext)context.getAttribute(
ComponentConstants.COMPONENT_CONTEXT,
PageContext.REQUEST_SCOPE);
out.println("--- TILE Attributes --- <br />");
if (compContext!=null){
Iterator iter = compContext.getAttributeNames();
while(iter.hasNext()){
String name = (String)iter.next();
Object value = compContext.getAttribute(name);
printNameValueType(name, value, out);
}
}else{
out.println("---TILE Attributes NOT FOUND---<br />");
}
out.println("--------------------------- <br />");
}
private static void printNameValueType(
String name,
Object value,
JspWriter out)
throws IOException{
if (value !=null){
out.println(
name + " = " + value +
" type (" +
value.getClass().getName()+ ") " +
"<br /><br />");
}else{
out.println(name + " = " + value +
"<br /><br />");
}
}
注意 ComponentContext
类实现了 tile 范围。 ComponentContext
类位于 ComponentConstants.COMPONENT_CONTEXT
键下面的请求范围中。这种 tile 机制确保每个 tile 获得它自己的组件环境。
嵌套的 tile 不会和它们的父亲共享相同的 tile(我费了好大的劲才了解到这点)。当前 tile 的 tile 范围已在显示嵌套的 tile 之前得到保存。在嵌套的 tile 结束之后,父亲的 tile 范围将恢复到请求中。这个神奇的特性是在 InsertTag (org.apache.struts.taglib.tiles.InsertTag)
类的嵌套类 InsertHandler
中实现的。
到目前为止,您已经向对应于子 tile 的 tile 布局传递过属性或传递简单的字符串。可以将您希望的任意 bean 类型作为属性传入 tile 布局。然后在那个 tile 布局内使用该属性。
假设您的应用程序具有这样一个操作,它将一个 User
对象放入会话范围,或许是在用户登录系统之后:
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
// Default target to success
String target = new String("success");
// If login successful.
UserDomainObject user = new UserDomainObject();
...
request.getSession().setAttribute("user", user);
return (mapping.findForward(target));
}
接下来您将该用户传递到您正在插入的 tile。这个例子将使用您在 tile 布局(siteLayout2.jsp
)内所使用的那个 tile:
<tiles:insert attribute="header" ignore="true">
<tiles:put name="title" beanName="title" beanScope="tile"/>
<tiles:put name="user" beanName="user"
beanScope="session"/>
</tiles:insert>
该 tile 布局通过指定 session
的范围和 user
的 bean 名称,从而将 user
bean 传递给页眉 tile。使用这项技术,您可以在任何 JSP 范围中将任意 bean 传递给 tile 或 tile 布局,这样该 tile 范围就变成了另一个范围。这与以前并没有什么不同。
为了在 header.jsp
中使用这个 user
bean,可把它从 tile 范围复制到一个其他 bean 能够理解的范围。这可以使用 tiles:useAttribute
标签来实现。 tiles:useAttribute
标签类似于 jsp:useBean
操作,只不过仅适用于 tile 范围(header2.jsp
):
<tiles:useAttribute id="user" name="user" classname="rickhightower.UserDomainObject" />
因此 tiles:useAttribute
将把 user 对象从 tile 范围复制到页面范围。一旦 bean 得到定义,您就能够像使用页面范围中定义的任何 bean 一样使用它:
<bean:write name="user" property="userName"/>
接下来,让我们看一下新的 header2.jsp
文件的完整清单:
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<center>
<table>
<tr>
<tiles:useAttribute id="user"
name="user"
classname="rickhightower.UserDomainObject"
/>
<td width="33%" bgcolor="#36566E">
<div align='left'>
<font size="1" color="orange">
currently logged in as
<bean:write name="user" property="userName"/>
</font>
</div>
</td>
<td width="33%">
<font color="#36566E">
<tiles:getAsString name="title" ignore="true"/>
</font>
</td>
<td width="33%" bgcolor="#36566E">
<div align='left'>
<font size="1" color="white">
<blockquote>
<bean:write name="user" property="firstName"/>
<br />
<bean:write name="user" property="lastName"/>
<br />
</blockquote>
</font>
</div>
</td>
</tr>
</table>
</center>
可以看到,页眉现在显示了关于当前登录站点的用户信息 ―― 这是一个强大的特性。可以创建专门用于显示域对象的 tile,然后在应用程序的许多部分重用那些 tile。考虑到这点,弄清为什么人们原先考虑将 Tiles 框架称为“组件”就很容易了:您事实上能够创建显示组件。与自定义标签(JSP 2.0 之前的版本)不同,这些组件全都是在 JSP 页面中创建的。
2. 列表
经常会遇到必须传递多个参数的情况。例如,您可能想要传递一个参数列表,以显示 tile 布局的导航区域中的链接。
回顾前面的“雇员清单”例子,您可能有一个 Web 应用程序需要显示公司的分公司、部门和雇员。当在雇员名单视图中时,employeeListing.jsp
将处于 tile 布局的内容区域,而当前分公司的部门链接将处于导航区域中。当用户单击某个部门链接时,该部门的雇员的新名单将会显示出来。当在部门视图中时, deptListing.jsp
将处于 tile 布局的内容区域,而当前公司的分公司链接列表将处于导航区域中。当用户单击某个分公司链接时,新的部门名单就会显示出来。因此,每个页面(employeeListing.jsp
和 deptListing.jsp
)都将传入一个新的链接列表。您可以使用 putList
来完成这个任务。
Tile 允许用户使用 putList
子元素传入链接 ―― 适合于在 XML 和 JSP 定义中使用,或在对定义的调用或 JSP 中的 tile 布局中使用。
假设您想使用一组标准导航链接作为站点布局。可以在 tile 配置文件中使用 putList
子元素来指定这样的链接,如下所示(tiles-def.xml
):
<definition name="siteLayoutDef3" path="/siteLayout3.jsp">
<put name="title" value="Rick Hightower Stock Quote System" />
<put name="header" value="/header2.jsp" />
<put name="footer" value="/footer.jsp" />
<put name="content" type="string">
Content goes here
</put>
<putList name="items" >
<item value="Home"
link="/index.html" />
<item value="Wiley"
link="http://www.wiley.com" />
<item value="Trivera Technologies"
link="http://www.triveratech.com/" />
<item value="Virtuas"
link="http://www.virtuas.com/" />
<item value="Rick Hightower"
link="http://www.rickhightower.com" />
<item value="Rick's Blog"
link="http://rickhightower.blogspot.com/" />
</putList>
</definition>
putList
元素允许您指定与链接相关联的项的一个列表。在上面的清单中,putList
定义了六个链接。
items
列表(java.util.List
)被放入 tile 范围。名称 items
使用 putList
元素的 name
属性来设置。
item
元素通过把 org.apache.struts.tiles.beans.MenuItem
的一个实例插入该列表来定义一个链接。value 属性对应于链接上的标签(label),而 link 则指向链接的 URL。
图标与工具提示
item
元素还有为链接指定工具提示和图标的元素。通过查看 Struts 源代码中的 DTD (tiles-config_1_1.dtd
),可以了解有关 item
元素和 putList
的更多内容。
要使用这种链接列表,必须修改 tile 布局,如下所示(siteLayout3.jsp
):
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<tiles:importAttribute />
<html>
<head>
<logic:present name="title">
<title>
<tiles:getAsString name="title" ignore="true"/>
</title>
</logic:present>
</head>
<body>
<table width="500" border="0" cellspacing="0" cellpadding="0">
<tr bgcolor="#36566E">
<td height="68" width="70%">
<div align="left">
<img src="images/hp_logo_rickhightower.gif" width="220" height="74">
</div>
</td>
</tr>
<tr>
<td height="68" width="2000">
<tiles:insert attribute="header" ignore="true">
<tiles:put name="title"
beanName="title" beanScope="tile"/>
<tiles:put name="user"
beanName="user" beanScope="session"/>
</tiles:insert>
</td>
</tr>
<table>
<tr>
<td width="50%">
<ul>
<logic:iterate id="item" name="items"
type="org.apache.struts.tiles.beans.MenuItem" >
<li>
<bean:define id="link" name="item" property="link"
type="java.lang.String"/>
<logic:match name="link"
location="start" value="/" >
<html:link page="<%=link%>" >
<bean:write name="item"
property="value"/>
</html:link>
</logic:match>
<logic:notMatch name="link"
location="start" value="/" >
<html:link href="<%=link%>">
<bean:write name="item"
property="value"/>
</html:link>
</logic:notMatch>
</li>
</logic:iterate>
</ul>
</td>
<td width="50%">
<div align="center">
<tiles:insert attribute="content"/>
</div>
</td>
</tr>
</table>
<tr>
<td>
<tiles:insert attribute="footer" ignore="true"/>
</td>
</tr>
</table>
</body>
</html>
特别要注意在列表上进行迭代的代码段:
<ul>
<logic:iterate id="item" name="items"
type="org.apache.struts.tiles.beans.MenuItem" >
<li>
<bean:define id="link" name="item" property="link" type="java.lang.String"/>
<logic:match name="link" location="start" value="/" >
<html:link page="<%=link%>" >
<bean:write name="item" property="value"/>
</html:link>
</logic:match>
<logic:notMatch name="link" location="start" value="/" >
<html:link href="<%=link%>">
<bean:write name="item" property="value"/>
</html:link>
</logic:notMatch>
</li>
</logic:iterate>
</ul>
后面会对此进行简化。
tiles:importAttribute
标签将 tile 范围中的属性导入到页面范围。它类似于 tiles:useAttrribute
标签,但它更接近猎枪而不是解剖刀。它是懒散的、肮脏的和便宜的;我一直用它(这说明了我什么呢?)。这有效地将条目列表从 tile 范围拷贝到页面范围。
注意: tiles:importAttribute
可拷贝到任何指定的范围。
默认情况下,tiles:importAttribute
将所有这些属性拷贝到页面范围。你也可以通过使用范围属性将这些属性拷贝到其他范围。
一旦条目列表在页面范围中,您就可以使用标准 Struts 标签访问它,如下所示(siteLayout3.jsp
):
<logic:iterate id="item" name="items"
type="org.apache.struts.tiles.beans.MenuItem" >
...
</logic:iterate>
注意使用 logic
标签实现的逻辑用于显示链接。可以检查链接是否以“/”开始,从而确定链接是否是相对的。如果链接 是 相对的,使用 html:link
标签的 page
属性。否则,如果链接指向绝对 URL 的话,使用 html:link
标签的 href
属性,如下所示(siteLayout3.jsp
):
<bean:define id="link" name="item" property="link" type="java.lang.String"/>
<logic:match name="link" location="start" value="/" >
<html:link page="<%=link%>" >
<bean:write name="item" property="value"/>
</html:link>
</logic:match>
<logic:notMatch name="link" location="start" value="/" >
<html:link href="<%=link%>">
<bean:write name="item" property="value"/>
</html:link>
</logic:notMatch>
如您所想到的那样,您可能想要使用这一显示逻辑来在不止一个位置显示菜单项。这就是说,您可能想要在这个页面的范围之外重用它。在稍后部分,您将看到如何通过将一个 tile 布局嵌套进另一个 tile 布局来实现这一点。
除了能向 tile 定义中的列表添加条目之外,还可以使用 tiles:putList
元素和它的 tiles:add
子元素向 JSP 中的列表添加条目(index6.jsp
):
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %>
<%@ page import="org.apache.struts.tiles.beans.SimpleMenuItem" %>
<tiles:insert definition="siteLayoutDef4">
<tiles:put name="title" type="string"
value="Get Rick Hightower Stock Quote6" />
<tiles:put name="content" value="indexContent5.jsp"/>
<tiles:putList name="items" >
<jsp:useBean id="item1" class="SimpleMenuItem"/>
<jsp:setProperty name="item1" property="link"
value="/index.html"/>
<jsp:setProperty name="item1" property="value"
value="Home" />
<tiles:add beanName="item1"/>
</tiles:putList>
</tiles:insert>
上面的清单使用 jsp:useBean
来创建 SimpleMenuItem
的实例。然后使用 jsp:setProperty
来设置 SimpleMenuItem
bean 的链接和值属性。最后,使用 tiles:add
将这个 bean 添加到列表中。
在上面的例子中,添加了一个 SimpleMenuItem
,它细分了 tile 布局使用的 MenuItem
。然而,您可以添加任何 bean 类型。
注意: 在 XML 中添加任何类型的 bean。
要在 tiles XML 定义中添加任何类型的 bean,可使用 putList
的子元素 bean。这个 bean 元素带有一个 id
和 classtype
。对于简单类型,您也可以使用 putList
的 add
子元素。请参阅 tiles configuration DTD (tiles-config_1_1.dtd
) 以获取更多信息。
(前一篇) 掌握Tiles框架 (二)-- Tiles布局和定义 (后一篇)掌握Tiles 框架(四) --高级定义概念,结束,参考资料及作者