Apache Wicket - 更新共享特定模型的所有组件的最佳方式
Apache Wicket - Best way to update all components sharing a certain model
使用 Apache Wicket 9 - 刷新所有 Component 共享 Model[ 的最佳方式是什么=20=] 其内容已被后台进程故意修改?
以下代码有效,但恐怕还有更好的(内置)解决方案我不知道。特别是没有为每个 Model 调用 visitChildren() 修改内容:
<wicket:panel>
<span wicket:id="child"></span><br>
<a wicket:id="update">update</a><br>
</wicket:panel>
public class Test extends Panel {
public Test(String id) {
super(id);
AtomicInteger i = new AtomicInteger(); // arbitrary content within the model
IModel<String> model = () -> i.toString();
add(new Label("child", model).setOutputMarkupId(true));
add(new AjaxLink<>("update") {
@Override
public void onClick(AjaxRequestTarget target) {
i.incrementAndGet(); // change content within the model
updateComponents(target, model, getPage());
}
});
}
// for all components in page use root = getPage()
public static void updateComponents(AjaxRequestTarget target, IModel<?> model, MarkupContainer root) {
root.visitChildren(Component.class, (c, ivisit) -> {
if (c.getDefaultModel() == model) target.add(c);
});
}
}
Wicket 不提供任何与此相关的开箱即用的东西(目前最新版本 - Wicket 9.2.0)。
我觉得你的实现非常好!我也会这样做。
您可以考虑的一项改进是使用 AjaxRequestTarget.IListener,您可以全局注册,即对于整个应用程序,如果您在多个地方需要相同的逻辑。
在 MyApplication#init() 方法中:
getAjaxRequestTargetListeners().add(new AjaxRequestTarget.IListener() {
@Override public void onBeforeRespond(final Map<String, Component> map, final AjaxRequestTarget target) {
// map's values are the Components what you have already added to AjaxRequestTarget
// I see your second approach:
// You can use RequestCycle's MetaData to store flags which types
// of components/behaviors/models need to be added to the target
// as well. I.e. in #onClick() do
// `RequestCycle.get().setMetaData(BEHAVIOR_TYPE, State2Listener.class)`
// and here use `Class<?> behaviorClass = `RequestCycle.get().getMetaData(BEHAVIOR_TYPE)`
}
});
虽然我原来的方法是有效的,但我发现它受到组件模型必须设计为适合比较这一事实的限制。为了克服这个限制,我想展示另一个基于简单自定义 Behaviors 的解决方案。这样,我们就可以相应地刷新所有需要的组件,而无需对组件模型进行任何假设,如下例所示:
<wicket:panel>
<span wicket:id="child1"/> + <span wicket:id="child2"/> = <span wicket:id="sum"/><br>
<a wicket:id="update1">update1</a><br>
<a wicket:id="update2">update2</a><br>
</wicket:panel>
public class Test2 extends Panel {
// example data class
static class Pojo implements Serializable {
int i1, i2; // some mutable data
}
// data instance
Pojo data = new Pojo();
// listeners
public class State1Listener extends Behavior {}
public class State2Listener extends Behavior {}
public Test2(String id) {
super(id);
add(new Label("child1")
.add(new State1Listener()) // mark as depending on i1
.setDefaultModel(() -> Integer.toString(data.i1))
.setOutputMarkupId(true));
add(new Label("child2")
.add(new State2Listener()) // mark as depending on i2
.setDefaultModel(() -> Integer.toString(data.i2))
.setOutputMarkupId(true));
add(new Label("sum")
.add(new State1Listener()) // mark as depending on i1
.add(new State2Listener()) // and i2
.setDefaultModel(() -> Integer.toString(data.i1 + data.i2))
.setOutputMarkupId(true));
add(new AjaxLink<>("update1") {
@Override
public void onClick(AjaxRequestTarget target) {
data.i1++; // do some update on i1
// we know that i1 was (potentially) modified,
// so refresh all components depending on i1
updateComponentsByBehavior(target, State1Listener.class, getPage());
}
});
add(new AjaxLink<>("update2") {
@Override
public void onClick(AjaxRequestTarget target) {
data.i2++; // do some update on i2
// we know that i2 was (potentially) modified,
// so refresh all components depending on i2
updateComponentsByBehavior(target, State2Listener.class, getPage());
}
});
}
// static helper method
public static void updateComponentsByBehavior(AjaxRequestTarget target, Class<? extends Behavior> behaviorClazz, MarkupContainer root) {
root.visitChildren((c, ivisit) -> {
if (!c.getBehaviors(behaviorClazz).isEmpty()) target.add(c);
});
}
}
根据 martin-g 的回答,这是对后者基于 Behaviors 的方法的另一个改进。这次使用 RequestCycle.get/setMetaData() 而不是重复调用 visitChildren()。要获得 Test2 示例 运行 此设置, updateComponentsByBehavior(...) 的调用必须替换为 registerComponentsByBehavior(...):
public class MyApplication extends WebApplication {
private static final MetaDataKey<List<Class<? extends Behavior>>> BEHAVIOR_TYPE
= new MetaDataKey<>() {};
public static void registerComponentsByBehavior(Class<? extends Behavior> behaviorClazz) {
// register behavior class in current cycle
RequestCycle.get().getMetaData(WicketApplication.BEHAVIOR_TYPE).add(behaviorClazz);
}
@Override
public void init() {
...
getRequestCycleListeners().add(new IRequestCycleListener() {
@Override
public void onBeginRequest(RequestCycle cycle) {
// clear list of registered behavior classes for current cycle
RequestCycle.get().setMetaData(BEHAVIOR_TYPE, new ArrayList<>());
}
});
getAjaxRequestTargetListeners().add(new AjaxRequestTarget.IListener() {
@Override
public void onBeforeRespond(final Map<String, Component> map,
final AjaxRequestTarget target) {
List<Class<? extends Behavior>> list =
RequestCycle.get().getMetaData(BEHAVIOR_TYPE);
target.getPage().visitChildren((c, ivisit) -> {
for (Behavior b : c.getBehaviors()) {
// add components if behavior has been previously
// registered in current cycle
if (list.contains(b.getClass())) target.add(c);
}
});
}
});
}
}
使用 Apache Wicket 9 - 刷新所有 Component 共享 Model[ 的最佳方式是什么=20=] 其内容已被后台进程故意修改?
以下代码有效,但恐怕还有更好的(内置)解决方案我不知道。特别是没有为每个 Model 调用 visitChildren() 修改内容:
<wicket:panel>
<span wicket:id="child"></span><br>
<a wicket:id="update">update</a><br>
</wicket:panel>
public class Test extends Panel {
public Test(String id) {
super(id);
AtomicInteger i = new AtomicInteger(); // arbitrary content within the model
IModel<String> model = () -> i.toString();
add(new Label("child", model).setOutputMarkupId(true));
add(new AjaxLink<>("update") {
@Override
public void onClick(AjaxRequestTarget target) {
i.incrementAndGet(); // change content within the model
updateComponents(target, model, getPage());
}
});
}
// for all components in page use root = getPage()
public static void updateComponents(AjaxRequestTarget target, IModel<?> model, MarkupContainer root) {
root.visitChildren(Component.class, (c, ivisit) -> {
if (c.getDefaultModel() == model) target.add(c);
});
}
}
Wicket 不提供任何与此相关的开箱即用的东西(目前最新版本 - Wicket 9.2.0)。
我觉得你的实现非常好!我也会这样做。
您可以考虑的一项改进是使用 AjaxRequestTarget.IListener,您可以全局注册,即对于整个应用程序,如果您在多个地方需要相同的逻辑。
在 MyApplication#init() 方法中:
getAjaxRequestTargetListeners().add(new AjaxRequestTarget.IListener() {
@Override public void onBeforeRespond(final Map<String, Component> map, final AjaxRequestTarget target) {
// map's values are the Components what you have already added to AjaxRequestTarget
// I see your second approach:
// You can use RequestCycle's MetaData to store flags which types
// of components/behaviors/models need to be added to the target
// as well. I.e. in #onClick() do
// `RequestCycle.get().setMetaData(BEHAVIOR_TYPE, State2Listener.class)`
// and here use `Class<?> behaviorClass = `RequestCycle.get().getMetaData(BEHAVIOR_TYPE)`
}
});
虽然我原来的方法是有效的,但我发现它受到组件模型必须设计为适合比较这一事实的限制。为了克服这个限制,我想展示另一个基于简单自定义 Behaviors 的解决方案。这样,我们就可以相应地刷新所有需要的组件,而无需对组件模型进行任何假设,如下例所示:
<wicket:panel>
<span wicket:id="child1"/> + <span wicket:id="child2"/> = <span wicket:id="sum"/><br>
<a wicket:id="update1">update1</a><br>
<a wicket:id="update2">update2</a><br>
</wicket:panel>
public class Test2 extends Panel {
// example data class
static class Pojo implements Serializable {
int i1, i2; // some mutable data
}
// data instance
Pojo data = new Pojo();
// listeners
public class State1Listener extends Behavior {}
public class State2Listener extends Behavior {}
public Test2(String id) {
super(id);
add(new Label("child1")
.add(new State1Listener()) // mark as depending on i1
.setDefaultModel(() -> Integer.toString(data.i1))
.setOutputMarkupId(true));
add(new Label("child2")
.add(new State2Listener()) // mark as depending on i2
.setDefaultModel(() -> Integer.toString(data.i2))
.setOutputMarkupId(true));
add(new Label("sum")
.add(new State1Listener()) // mark as depending on i1
.add(new State2Listener()) // and i2
.setDefaultModel(() -> Integer.toString(data.i1 + data.i2))
.setOutputMarkupId(true));
add(new AjaxLink<>("update1") {
@Override
public void onClick(AjaxRequestTarget target) {
data.i1++; // do some update on i1
// we know that i1 was (potentially) modified,
// so refresh all components depending on i1
updateComponentsByBehavior(target, State1Listener.class, getPage());
}
});
add(new AjaxLink<>("update2") {
@Override
public void onClick(AjaxRequestTarget target) {
data.i2++; // do some update on i2
// we know that i2 was (potentially) modified,
// so refresh all components depending on i2
updateComponentsByBehavior(target, State2Listener.class, getPage());
}
});
}
// static helper method
public static void updateComponentsByBehavior(AjaxRequestTarget target, Class<? extends Behavior> behaviorClazz, MarkupContainer root) {
root.visitChildren((c, ivisit) -> {
if (!c.getBehaviors(behaviorClazz).isEmpty()) target.add(c);
});
}
}
根据 martin-g 的回答,这是对后者基于 Behaviors 的方法的另一个改进。这次使用 RequestCycle.get/setMetaData() 而不是重复调用 visitChildren()。要获得 Test2 示例 运行 此设置, updateComponentsByBehavior(...) 的调用必须替换为 registerComponentsByBehavior(...):
public class MyApplication extends WebApplication {
private static final MetaDataKey<List<Class<? extends Behavior>>> BEHAVIOR_TYPE
= new MetaDataKey<>() {};
public static void registerComponentsByBehavior(Class<? extends Behavior> behaviorClazz) {
// register behavior class in current cycle
RequestCycle.get().getMetaData(WicketApplication.BEHAVIOR_TYPE).add(behaviorClazz);
}
@Override
public void init() {
...
getRequestCycleListeners().add(new IRequestCycleListener() {
@Override
public void onBeginRequest(RequestCycle cycle) {
// clear list of registered behavior classes for current cycle
RequestCycle.get().setMetaData(BEHAVIOR_TYPE, new ArrayList<>());
}
});
getAjaxRequestTargetListeners().add(new AjaxRequestTarget.IListener() {
@Override
public void onBeforeRespond(final Map<String, Component> map,
final AjaxRequestTarget target) {
List<Class<? extends Behavior>> list =
RequestCycle.get().getMetaData(BEHAVIOR_TYPE);
target.getPage().visitChildren((c, ivisit) -> {
for (Behavior b : c.getBehaviors()) {
// add components if behavior has been previously
// registered in current cycle
if (list.contains(b.getClass())) target.add(c);
}
});
}
});
}
}