我们遇到Tapestry(5.7.2版本)和从组件中刷新区域的问题。
我们有一个包含(分区)组件循环的页面,其中每个组件都有一个异步事件(见代码)。
我们想要实现的是,通过XHR刷新,我们刷新组件区域和页面包含的另一个区域,我们通过接口获得。
在这个代码示例中,当我们单击第一个组件时,它会刷新区域,但会"忘记"使用第二个组件值的@Persist注释字段。
如果我们点击第二个组件,它也会刷新第一个组件。
我们做错了什么?似乎微不足道的区域刷新,但我们没有得到它,我们尝试了不同的方法,但必须后退,以一种不那么优雅的方式处理这部分。
页面代码:
@InjectComponent
private Zone listingZone;
@Persist
private String[] names;
@Property
private String lastRefreshZoneName;
@Property
private String _name;
Object onActivate() throws Exception {
names = new String[]{"first","second"}; //eg. loaded from DB
return null;
}
public LocalDateTime getTime() {
return LocalDateTime.now();
}
public String[] getNames() {
return names;
}
@Override
public Zone getOuterZone() {
return listingZone;
}
@Override
public void onTriggerOn(String name) {
lastRefreshZoneName = name;
}
用简单的TML:
<t:zone t:id="pageZone">
Page zone: ${time}<br/>
<hr/>
<t:loop source="names" value="name">
<t:attribute.zonedcomponent t:parameter="${name}"/>
</t:loop>
<hr/>
<t:zone t:id="listingZone">
Listing zone, last refresh: ${time}<br/>
Last refresh zone name: ${lastRefreshZoneName}
</t:zone>
</t:zone>
在本例中,ZonedComponent是一个带有zone和事件链接的组件,如下所示:
@Inject
private Request request;
@Inject
private AjaxResponseRenderer ajaxResponseRenderer;
@Inject
private ComponentResources resources;
@InjectComponent
private Zone componentZone;
@Parameter(defaultPrefix = BindingConstants.LITERAL)
private String parameter;
@Persist
@Property
private String name;
void setupRender() {
this.name = parameter;
}
public LocalDateTime getTime() {
return LocalDateTime.now();
}
void onTrigger(String name) {
if (request.isXHR()) {
// this.name = name; // if we don't uncomment this, then it doesnt even propagate the 'name' correctly
SomeInterface page = (SomeInterface)resources.getPage();
page.onTriggerOn(this.name);
ajaxResponseRenderer.addRender(componentZone)
.addRender(page.getOuterZone());
}
}
public static interface SomeInterface {
ClientBodyElement getOuterZone();
void onTriggerOn(String name);
}
的TML区域如下:
<t:zone t:id="componentZone" style="border:1px solid black">
Component name: ${name}<br/>
Component zone: ${time}<br/>
<t:eventlink t:event="trigger" t:context="${name}" async="true">
async event from ${name}
</t:eventlink>
</t:zone>
从你的例子中有三个要点值得学习。
-
组件(服务器端)id与客户端id当使用Ajax和zone时,组件事件处理程序需要知道客户端元素id。您可以简单地在模板文件中硬编码一个。但是,当在循环中使用组件时,id不再是唯一的,事件处理程序无法知道要更新(不更新)哪个客户端元素。一个解决方案是使用
JavaScriptSupport
服务来分配客户端id。 -
事件冒泡组件事件不必在它们被触发的组件内进行处理。它们实际上可以从嵌套组件"冒泡"到外部组件/页面。在两个地方处理事件也是可能的。这使您可以大大简化代码:不需要获取包含组件/页面,也不需要调用必须引入的接口的方法来使其工作。更多详细信息,请参阅Tapestry文档中Components Events页面上的事件冒泡部分。
-
组件参数—组件参数为Java类型。传递参数值时,只需引用属性即可。只有在需要将表达式转换为字符串时才使用
${...}
语法。参见"不要使用${…}语法!
学习了上面的内容,你的例子可以重写如下:
页面类:
@Property
private String[] names;
@Property
private String name;
@Property
private String lastRefreshZoneName;
@Inject
AjaxResponseRenderer ajaxResponseRenderer;
@InjectComponent
private Zone listingZone;
void onActivate() {
names = new String[] { "first", "second" }; // eg. loaded from DB
}
public LocalDateTime getTime() {
return LocalDateTime.now();
}
public void onTrigger(String name) {
lastRefreshZoneName = name;
ajaxResponseRenderer.addRender(listingZone);
}
页面模板:
Page rendered at: ${time}
<br />
<hr />
<t:loop source="names" value="name">
<t:zonedComponent t:name="name" />
</t:loop>
<hr />
<t:zone t:id="listingZone" id="listingZone">
Listing zone, last refresh: ${time}
<br />
Last refresh zone name: ${lastRefreshZoneName}
</t:zone>
组件类:
@Parameter(defaultPrefix = BindingConstants.PROP)
@Property
private String name;
@Inject
private Request request;
@Inject
private AjaxResponseRenderer ajaxResponseRenderer;
@InjectComponent
private Zone componentZone;
@Inject
JavaScriptSupport jsSupport;
@Inject
private ComponentResources resources;
@Property
String clientId;
void setupRender() {
clientId = jsSupport.allocateClientId(resources);
}
public LocalDateTime getTime() {
return LocalDateTime.now();
}
boolean onTrigger(String name, String clientId) {
if (request.isXHR()) {
// Since the fields were cleared after the original rendering of the
// page, they need to be assigned again so that they are available
// when the XHR response is rendered.
this.name = name;
this.clientId = clientId;
// Queue only this component's zone. The containing page takes care
// of its one zone.
ajaxResponseRenderer.addRender(componentZone);
}
return false; // Bubble up: allow containing page/component to do some
// more handling
}
}
组件模板:
<t:zone t:id="componentZone" id="${clientId}" style="border:1px solid black" >
Client id: ${clientId}
<br />
Component name: ${name}
<br />
Component zone: ${time}
<br />
<t:eventlink t:id="link" t:event="trigger" t:context="[name,clientId]" async="true">
async event from ${name}
</t:eventlink>
</t:zone>