Tapestry 5
页面导航
本质上,Tapestry
应用是许多相关的工作在一起的页面的集合。从某种程度来说,每一个页面就是一个应用。
任何单个的请求对应一个页面。请求来源于两种形式:
- 动作请求(Action Requests)指向一个特定的页面组件,在组件内部触发一个事件。
- 呈现请求(render requests)指向一个特定的页面,将页面的HTML标记流输出到客户端。
动作请求与呈现请求的划分是Tapestry 5
中新出现的概念,它的某些方面是基于Portlet
规范的思想,区分出这两种类型的请求减轻了许多在传统web
应用中当用户使用浏览器的后退按钮或点击刷新按钮时的问题。
动作请求
这两种情况,事件处理方法返回的值控制着送往用户端web
浏览器的响应。
动作请求的URL
包含页面名字、组件的嵌套id
和在组件中触发事件的名字(通常是"action"
)。此外,一个动作请求可以包含额外的上下文信息(context information
),以提供给事件处理方法(与方法参数有关)。
这些URL
暴露了一些应用的内部结构。随着时间的推移,一个应用在维护中组件的id
可能会改变,这就是说动作请求的URL
不能被收藏(存为书签)。幸运的是,用户很少有这种机会来改变组件的id
(参见下文):
以下是动件请求事件处理方法(动作的监听方法)的不同返回说明。
Null response
如果一个事件处理方法没有返回值或者返回null
,此时当前页面(包含该组件的页面)会呈现响应(即响应请求)。
当前页面的页面呈现链接被创建并重定向到客户端。客户端浏览器随后提交的将是一个产生新页面的新请求。用户将会在浏览器看到一个新产生的内容。地址栏里的URL
将会是一个呈现请求的URL
(render request URL
)。呈现请求的URL
简短且包含较少的应用结构信息(比如,他不包含组件id
和事件类型)。呈现请求URL
是可供用户收藏的。动作请求URL
是暂时性的,仅当应用处于激活状态时有意义,这并不意味着可以用于随后的session
。
String response
当返回一个字符串时,此字符串应该是页面的逻辑名(而非页面的全类名)。另外,页面的名字不区分大小写。
其次,呈现请求的URL
被构造并重定向到客户端。
Page response
我们也可以返回一个页面实例,而非一个页面名字。
通常,我们会在页面返回前以某种方式设置页面(举例如下,即对页面属性对象赋值)。
你也可以返回页面内的一个组件,但些时将会产生一个运行时警告。
Link response
事件处理方法可以直接返回一个
Link
实例,Link
被转换为URL
然后重定向URL
到客户端。
Stream response
事件处理方法也能返回一个
StreamResponse
对象,这个对象封装了一个送往客户端浏览器的流(stream
)。这对把组件产生的image
或PDF
送往客户端是很有用的。
Object response
事件处理方法返回任何其他对象类型都是错误的。
页面呈现请求(
Page Render Requests
)
呈现请求在结构与行为上要比动作请求简单。简单情况下,URL
仅仅是页面的逻辑名。
页面可以有一个活化上下文(
activation context
)。活化上下文表现为页面状态的持久化信息。实际上,活化上下文通常是一些数据库持久化对象的id
。
当一个页面有活化上下文时,上下文的值被追加到URL
路径中。并不是所有的页面都有活化上下文。
当呈现请求链接被创建时,活化上下文可以被显式设值(PageLink
组件有一个context
参数可以用来设值)。当没有提供显式活化上下文时,页面会自己查找它的活化上下文。
这种查找表现为事件的触发。事件名字为passivate
(我们马上会看到它的一个相对应的名字activate
)。方法返回的值被用做上下文。
For example:
public class ProductDetail
{
private Product _product;
. . .
long onPassivate() { return _product.getId(); }
}
活化上下文可以由一连串的值组成,即方法返回的是数组或List
列表。
页面激活Page activation
当一个页面呈现请求到来时,页面会在它呈现前被激活。
激活主要为了两个目的:
- 它允许页面把内部状态数据编码到URL中(上面讨论到的活化上下文)。
- It provides coarse approach to validating access to the page.它提拱了粗略的方法来校验页面是否可被访问。
后者提到的校验,通常涉及到用户标识及访问;如果我们的页面仅允许特定的用户访问,就可以用页面的激活事件来负责这个访问的校验。
一个页面的激活事件处理器(activate event handler
)对应着它的钝化事件处理器(passivate handler
):
. . .
void onActivate(long productId)
{
_product = _productDAO.getById(productId);
}
. . .
在此相关的部分是:当页面呈现时,它可能包含更多的动作请求URL
(链接与表单)。那些链接与表单的动作请求也一样通过激活页面开始,然后再执行其他的操作。这形成了一个不断的包含同样活化上下文请求链。
激活事件处理器(方法)也可以返回一个值,它与动作请求的事件触发器返回的值是一样的。这通常用于访问校验场景。
页面导航模式(Page Navigation Patterns)
动件链接和上下文及页面上下文可以以许多方式组合在一起。
让我们通过一个产品分类页来介绍一个经典的主列表/
详细页关系的例子。在这个例子中,ProductListing
页面是产品的列表页,ProductDetails
页是相应的显示特定产品的详细页。
动作请求/
持久化数据
这种模式下,ProductListing
页面使用动作事件,ProductDetails
页面使用了持久化属性。
ProductListing.html:
<t:loop source="products" value="product">
<a t:type="actionlink" t:id="select" context="product.id">${product.name}</a>
</t:loop>
ProductListing.java:
@InjectPage
private ProductDetails _details;
Object onActionFromSelect(long productId)
{
_details.setProductId(productId);
return _details;
}
ProductDetails.java:
@Inject
private ProductDAO _dao;
private Product _product;
@Persist
private long _productId;
public void setProductId(long productId) { _productId = productId; }
void onActivate()
{
_product = _dao.getById(_productId);
}
以上代码极少,也许对原型来说已经足够好了。
它还有一些次要的缺点:
- 它需要一个session(用来在请求之间保存_productId属性)。
- 如果ProductDetails页面在被访问前已设置了一个有效的产品id,就有可能失败(原有值丢失)。
- URL并没有指出产品的标识,如果用户收藏了这个URL,随后他们会引发先前的情况(没有有效的产品id)。
动作请求/
活化上下文(Action Requests / Persistent Data
??)
我们可以使用passivation
和 activation
上下文来避免使用session
,使得链接更可收藏,从而在不改变ProductListing
页面的情况下改进先前的实例。
ProductDetails.java:
@Inject
private ProductDAO _dao;
private Product _product;
private long _productId;
public void setProductId(long productId) { _productId = productId; }
void onActivate(long productId)
{
_productId = productId;
_product = _dao.getById(_productId);
}
long onPassivate() { return _productId; }
此种方法的优点是页面到页面的连接发生在类型安全的Java
代码中——ProductListing
页面的onActionFromSelect
方法。它也有不好的地方就是点击一个链接服务端需要两次往返过程(由两个页面完成这一请求)。
呈现请求一次
这是一件最通用的主列表/
详细页的版本。
ProductListing.html:
<t:loop source="products" value="product">
<a t:type="pagelink" page="productdetails" context="product.id">${product.name}</a>
</t:loop>
ProductListing.java:
不需要代码来处理链接。
ProductDetails.java:
@Inject
private ProductDAO _dao;
private Product _product;
private long _productId;
void onActivate(long productId)
{
_productId = productId;
_product = _dao.getById(_productId);
}
long onPassivate() { return _productId; }
不再需要setProductId()
方法。
局限性
随着我们应用流程的扩展,我们可能会发现找不到一个合理的方式去避免在不同的请求间(在页面活化上下文之外)持久化某些数据。比如:如果从ProductDetails
页面开始,允许用户导航到相关的一些页面然后又返回到ProductDetails
页面,这就开始需要在页面到页面再到页面间保持传递的产品id
。
在某些方面,持久化值更有意义。随后,我们将会实现客户端持久化策略(client-side persistence strategy
),它将对持久化数据进行编码,如产品id
属性将自动的加入到查询参数中(和隐藏的表单域)。