Wicket: Loose Coupling of Componens for Ajax Updates

September 13th, 2008 by  |  Published in Wicket  |  6 Comments

While developing Wicket applications, one often has to write a simple input form (e.g. a search box) that updates the content of another panel (e.g. a search result panel). And of course, we all use Wicket’s AJAX support to search as it is so damn cool and easy to use.

Normally, you do this by either putting everything on one panel or by passing a reference of the list panel to the form panel.

public SearchBoxPanel(String id, SearchResultPanel results) {
_results = results;
// snip
}
 
public void onSubmit(AjaxRequestTarget target) {
_results.setQuery(getQuery()); // or use the same model ;)
target.add(_results);
}


Now, we would like to add a nice and shiny “Did you mean” panel to our search page. So what can we do? We could add another reference to the search box panel by refactoring it. However, nobody likes refactoring existing components over and over again. One would rather like to write components flexible enough from the beginning. Here, the observer pattern – or loose coupling – comes in handy, my hard-rocking amigos:

public SearchPage() {
_search = new SearchBoxPanel("search");
_didYouMean = new DidYouMeanPanel("didYouMean");
_results = new SearchResultsPanel("results");
_search.addObserver(_didYouMean);
_search.addObserver(_results);
// snip
}
 
// SearchBoxPanel class
public void addObserver(IAjaxObserver observer) {
_observers.add(observer);
}
 
public void onSubmit(AjaxRequestTarget target) {
// snip
for (IAjaxObserver observer : _observers) {
observer.notifyAjaxUpdate(target);
}
}

But let’s take this a step further. Wouldn’t it be cool, if simply adding a new component to the page would be sufficient? Yeah, just adding it, no magic or voodoo involved. It really can be that easy! One just has to realize that all components are already connected through the page. Dispatching events to all children of a page can therefore be as simple as visiting all components of a page:

public abstract class AjaxUpdateEvent implements IClusterable {
private final Component _source;
private final AjaxRequestTarget _target;
 
public AjaxUpdateEvent(final Component source,
final AjaxRequestTarget target) {
_source = source;
_target = target;
}
 
public Component getSource(){ return _source; }
public AjaxRequestTarget getTarget() { return _target; }
 
public final void fire() {
getSource().getPage().visitChildren(new NotifyVisitor(this));
}
 
public static interface IAjaxUpdateListener extends IClusterable {
public void notifyAjaxUpdate(final AjaxUpdateEvent event);
}
 
private static final class NotifyVisitor implements IVisitor {
private final AjaxUpdateEvent _event;
 
public NotifyVisitor(final AjaxUpdateEvent event) {
_event = event;
}
 
public Object component(final Component component) {
if (component instanceof IAjaxUpdateListener) {
((IAjaxUpdateListener) component).notifyAjaxUpdate(_event);
}
return IVisitor.CONTINUE_TRAVERSAL;
}
}
}

Now, all you have to do in your SearchBoxPanel is this:

public void onSubmit(AjaxRequestTarget target) {
// snip
new SearchEvent(this, target).fire();
}

Your “Did you mean” panel could look like this:

public class DidYouMeanPanel extends Panel implements IAjaxUpdateListeners {
 
public void notifyAjaxUpdateListeners(AjaxUpdateEvent event) {
if (event instanceof SearchEvent) {
setModel(event.getSource().getModel());
event.getTarget().addComponent(this);
}
}
 
}

Simple, isn’t it? I’m currently playing around with some ideas on using this event mechanism together with wicketstuff-push (or rather the upcoming release of wicketstuff-dojo-1.1 with integrated wicketstuff-push powers – Should be out this or next week. I’m working on it, I swear!). This could be a really powerful and easy to use programming model for cometd-based applications.

Now download the code and enjoy updating loosely coupled Wicket components 😉

EDIT: Wicket 1.5 supports very similar functionality out of the box. See Inter-component events from Wicket’s migration guide.

Responses

  1. Daan says:

    September 15th, 2008 at 4:57 pm (#)

    Clever thinking! And if you have the boilerplate ready, it is not that much code.Be careful to document your events, as with many events things get very complicated (and slow, especially if you have many nested components on your pages)

  2. Stefan Fußenegger says:

    September 15th, 2008 at 5:21 pm (#)

    It’s never bad to document things 😉 If it comes to speed, I’m not really concerned. Visiting 10,000+ components (would be quite a complex page, wouldn’t it?) for 10,000 events takes less than 8 seconds on my machine (Intel Quad Q9300 @2.5Ghz – lucky me) so visiting 10,000 components once would take 0.8 ms … nothing you should have to worry about 😉

  3. Anonymous says:

    September 15th, 2008 at 6:40 pm (#)

    Take a look at https://issues.apache.org/jira/browse/WICKET-1312It would be good to end with a common solution for Wicket 1.5.martin-g

  4. Stefan Fußenegger says:

    September 15th, 2008 at 10:54 pm (#)

    Thanks a lot for posting this link. I didn’t knew this issues existed. I added a comment there.Best regards

  5. kaksles.org says:

    September 16th, 2008 at 5:10 am (#)

    Nice work Stefan — I consider it a further verification for the WICKET-1312 implementation that you have come upon the essentially same solution independently 🙂 On the Observer usage, one could add (as I say in the text of the WICKET-1312 issue) that holding references to Component instances in Wicket can get you into trouble when instances are being replaced, for example recreating a repeater children.Great work!Best wishes,Timo

  6. Stefan Fußenegger says:

    September 16th, 2008 at 8:22 am (#)

    Hi Timo,Yes, it’s definitely a verification. I really like these event mechanisms as they really remove code for wiring components. I also consider observers problematic, that’s why I dropped that idea – did you read the text or did you just look at the code? ;)I think the question should be, whether the final solution should be model centered (event are fired when a model value changes) or not. I like the idea of adding a reference to the source component to the event. I’d then do a setModel(event.getSource().getModel()) to connect components on a model level.