Component Events
组建事件
组件事件是组件意识到用户行为的方法,就像点击链接和提交表单。
组件事件被用来两种用途:
- 他们表现为用户在客户端浏览器中触发链接或提交表单时发起的请求。这些在页面导航page navigation和请求处理requst processing中被更全面的描述。
- 他们表现为请求中的控制流,允许一个组件去通知他的容器关于一些详情(“一个表单被提交”), 或者从容器中收集一些片断数据。
经常,一个导航请求(由用户发起)将产生许多控制流请求。例如:一个表单组件将被一个动作请求触发,并且随后将在表单提交被要处理时发出一个通知事件,无论成功与否。
在Tapestry4里,你可以使用方法名配置一个组件参数,当某一事件发生时该方法被调用,通常是一个客户端请求。
这有一些局限性,包括事实上仅有一个方法被调用。
Tapestry5引入了事件处理器方法概念,他通过命名约定或者OnEvent标注来确定。事件处理器方法可以拥有任何可见度,甚至是私有的(通常他们被提供包级别的可见度,以支持测试)。
你指定一个或多个方法去监听组件事件要优于配置一个组件去调用特定的方法。单独的一个事件处理器方法可以接收多个不同组件的通知事件
例如,这是页面的一部分(让我们称他为“Chooser”),用来让用户选择从1到10的数字。
- <p> Choose a number from 1 to 10: p>
- <p>
- <t:count end="10" value="index">
- <a t:id="select" t:type="actionlink" context="index">${index}t:comp>
- t:count>
- p>
ActionLink组件产生一个动作URL.
URL标识了页面包含的组件("chooser"),事件类型(除非他是“action”,默认的和最常用的事件类型),页面中的组件id(“select”),加上附加的上下文值(s)。
例: URL: http://localhost:8080/chooser.select/3.
当有附加的上下文值时,他们被追加到路径里。
这里示范一个Tapestry和传统框架动作的关键区别。这里的URL并不是说链接被点击时发生了什么,而是标识哪个组件来响应。
从URL到一小段代码没有简单的映射;这里通过调用事件处理器方法的形式来替代组件发送通知。
当组件链接被用户点击的时候,一个Java方法被调用。
- @OnEvent(component = "select")
- void valueChosen(int value)
- {
- _value = value;
- }
Tapestry 在这要做两件事情:
- 确认valueChosen()方法作为被调用的方法。
- 将上下文值从字符串转换为整形并交给方法处理。
在上面的例子中,valueChosen()方法将被choose组件产生任何事件时调用(至少有一个上下文值)。
因为ActionLink组件仅产生单个事件类型,即"action",这不会有任何问题。
有些组件能产生多种事件类型,些时你将需要更多的细节参数:
- @OnEvent(value = "action", component = "select")
- void valueChosen(int value)
- {
- _value = value;
- }
OnEvent标注的属性值和事件名相匹配。
"action"是默认的事件类型名;ActionLink和表单组件都适用这个事件类型。如果你省略了OnEvent标注的组件部分,那么你将收到所有包含的组件通知事件,可能包括嵌套的组件(由于事件的冒泡机制)。
你可以明确限定希望接收哪个组件的事件。
事件处理器方法命名约定
作为使用标注的一种替代,你可以以指定的方式命名事件,Tapestry将会调用你的方法就像他们被标注了一样。
这种方式的命名方式是在事件控制器方法前面使用"on"前缀,跟着是动作名(首字母大写)。接下来你应该追加"From"和一个首字母大写的组件id.
前面的例子可以被重写成:
- void onActionFromSelect(int value)
- {
- _value = value;
- }
如果事件类型命名为"onAny",它将接受所有事件类型,你很少需要此种方式!
Howard的提示:我发现我更喜欢命名约定,保留标注只是为了其他不适合的情形。
事件上下文
上下文值(ActionLink组件的context参数)可以是任何对象,然而,仅发生一个简单的字符串转换。与Tapestry 4相比,他有一个精细的类型机制有一个古怪的名字叫"DataSqueezer"。
此外,不管你的值是什么(string, number, date),它都会被转换为无格式字符串。这将形成一个更容易读的URL。
如果你有多个上下文值(通过将一个对象list或数组绑定到ActionLink的上下文参数),则每一个值都将按序地被追加到URL中。
当一个事件处理器方法被调用,将发生一个强制从字符串到实际类型的转换。事件处理器方法仅当上下文值的数量至少与方法参数数量一致时被调用,带有过多参数的方法将被忽略。
另外,一个事件处理方法还可以带上一个java.lang.Object[]类型的参数。这个参数会接收整个上下文数组。这是有用的,例如,这在上下文不同时间为不同长度时。我们可以使用一个个显式的参数或者单个的Object[]类型的参数。
访问本地信息
上面讨论了创建什么样的文件和把他们放到哪里,但没涉及到如何使用这些信息。
可以有两种方式访问信息:
- 在组件模板中使用message:绑定前缀。
- 通过注入组件信息对象。
在第一种方案里,你可以使用message:绑定前缀给组件参数,或使用模板表达式。
- <t:layout title="message:page-title">
- ${message:greeting}, ${user.name}!
- . . .
- t:layout>
这里,page-title的信息从消息目录中萃取而来并被交给Border组件的title参数。
另外,greeting信息被萃取和写入响应作为模板的一部分。
“prop”照样作为默认的绑定前缀,因此,user.name是一个属性路径,不是信息key值
你可以用一组属性文件扩展这些内容。
page-title=Your Account
greeting=Welcome back
或许,一个法国版是:
page-title=Votre Compte
greeting=Bienvenue en arriere
你可以注入你的组件信息目录在你的类中,当作一个Message接口的实例
- @Inject
- private Messages _messages;
随后你可以get()信息,或者format()它们。
- public String getCartSummary()
- {
- if (_items.isEmpty())
- return _messages.get("no-items");
- return _messages.format("item-summary", _items.size());
- }
Format()操作使用java.text.Formatter工作,输出你期望的漂亮的格式。
no-items=Your shopping cart is empty.
item-summary=You have %d items in your cart.
在Tapestry模板中做同样简单,但有时它更容易在Java代码中使用.
遗失key值
如果你关联了一个在信息目录中没有的key值,Tapestry不会抛出异常(那样做会让刚开始开发应用非常沮丧)。当一个key值不能被定位,一个”placeholder”信息被产生,例如"[[missing key: key-not-found]]"。
重载
如果你在信息目录中更改了一个属性文件,你将立刻看到改变。就像组件类和组件模板一样。
Asset(是一些web应用中用到的资源,如图象,视频等)本地化
一般,你的代码将用到SetupRender, BeginRender, AfterRender 和CleanupRender阶段,通常是这些阶段中的一个或两个。
用方法名代替标注
如果你偏爱不用标注在你的方法上,你可通过为你的方法提供特定的名字这么做。需要的方法名是首字母小写的标注名:setupRender(), beginRender(),等。同标注呈现阶段方法一样,Tapestry对于可见度,返回值和参数是灵活的。
使用这个机制,先前的例子可以被重写成
@Persist
private int _value;
被标注的字段域将在请求之间存储他们的状态。一般来说,这意味着这个值被存储在session内。
无论何时你改变了持久化的字段域,他的值都被存储。
在稍后的请求里,这个持久化字段域的值从存储库中被重载。
- public class MyPage
- {
- @ApplicationState