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);
                    }
                });
            }
        });
    }
}