Custom event dispatchers in Apache Wicket, part 1

Since version 1.5 of Wicket there is a way for components to send and listen to events, called inter-component events. You can read about the basics here. This is a very powerful and useful system, especially for ajax heavy pages where an action in one component can trigger a change in another without any coupling or without them even knowing of each other.

The way it works is that the sender implements the IEventSource interface and the receiver implements the IEventSink interface. All Wicket components does this already. The sender then uses the following to send an event, as an example:

send(getPage(), Broadcast.BREADTH, myPayloadObject);

So the event is sent to the page and then broadcasted to components according to breadth first traversal. The payload is the object we want to send to the other component(s). Now the receiver has to override the following method:

void onEvent(IEvent event);

All events will now be delivered to the method, but this specific component might only be interested in one type. So we have to do something like this:

@Override
public onEvent(IEvent event) {
if (event instanceof MyPayloadObject) {
// do the work
}
}

So that is it, we now have inter-component events working. But this is only the standard way of doing it. The Wicket devs have allowed us to implement and register our own event dispatchers that can work in other ways. So below is an example of another way of doing it.

The first thing we do before we start hacking away at our new event dispatcher is to set up a few requirements:

  • The only thing we are interested in is the payload, we don’t care about the IEvent
  • We should not have to use instanceof to check what event we have received
  • We should be able to have multiple methods in a component that receives different types of events
  • The event dispatcher should not have to check every method in every component for a method that accepts a specific event
  • We should be able to control it with annotations

So we begin by creating an annotation that we can place on a method in a component to indicate that we want to receive events:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnEventPayload {
Class[] value() default { };
}

Then we create an annotation that we place on the component itself:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EventPayloadReceiver {
Class[] value() default { };
}

And this is an example on how we can use them:

@EventPayloadReceiver({MyPayloadObject1.class, MyPayloadObject2.class})
public class MyPanel extends Panel {
// normal content here
  @OnEventPayload(MyPayloadObject1.class)
public void onMyPayloadObject1(MyPayloadObject1 payload) {
// do the work
}
  @OnEventPayload(MyPayloadObject2.class)
public void onMyPayloadObject2(MyPayloadObject2 payload) {
// do the work
}
}

So the component indicates, by using @EventPayloadReceiver, that it can take two types of events. Our event dispatcher sees this and if the event it is delivering is matching any of those it can then check the annotated methods. It then selecteds the method that has the @OnEventPayload annotation and the matching payload object. This means that all of our requirements are met.

This example could have been even simpler by removing the @EventPayloadReceiver annotation and check all methods in all components that the event is broadcasted to. But then the dispatcher has to check, if we take the example above, all the methods with the @OnEventPayload annoation if it receives an event of type MyPayloadObject3 only to realize that there is no method for it. This might be ok if you have a few events and not many components at a given time, but with many events, components and methods it seems a bit wasteful.

The @OnEventPayload annotation also allows multiple events for one method if you want to do the same thing for multiple events. But then you should probably have all your payloads exend the same base class or you would have to take Object as a parameter for the method. This could be useful, for example, when the only thing the component should to is target itself with the ajax request when any of a number of events is delivered.

So now we have the logic of the event dispatcher. This is a simple example that could fulfill our requirements (see the inline comments for details):

https://gist.github.com/crudh/18de0d2114b52a6efbe9

And the final step is to register the the event dispatcher in the Wicket application, in the init method:

getFrameworkSettings().add(new MyPayloadEventDispatcher());