Search code examples
modelrefreshwicket

Apache Wicket - Best way to update all components sharing a certain model


Using Apache Wicket 9 - what is the best way for refreshing all Component's sharing a Model whose content have been knowingly modified by a background process?

The following code works but I'm afraid that there is a better (built-in) solution I'm not aware of. Especially without calling visitChildren() for each Model with modified content:

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

Solution

  • Wicket does not provide anything related to this out of the box (in the latest version of the moment - Wicket 9.2.0).

    Your implementation looks quite good to me! I'd do it the same way.

    One improvement you could consider is to use AjaxRequestTarget.IListener that you could register globally, i.e. for the whole application, if you need the same logic in several places.

    In MyApplication#init() method:

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