zaterdag 6 december 2008

Events in GWT Composite Widgets

This blog post is on event handling in composite widgets in the Google Web Toolkit. For a general description on event handling in GWT, please see my earlier post: Where do GWT events come from?

I have been working with Google Web Toolkit (GWT) for a while, but i never created any custom widgets yet. Now, that i'm trying to build one, i'm kind of confused about the way composite widgets deal with events.

Now, if i create a composite widget in GWT, what happens with the events? A number of questions come up:
  • Can the composite widget be an event source?
  • Can the composite widget listen to external event sources?
  • Can the composite widget listen to internal event sources?
  • Can a wrapped widget within the composite listen to external events?
  • Can a wrapped widget within the composite source events to external listerers, bypassing the composite widget?
Below, i'll answer these questions one by one...

Q: Can the composite widget be an event source?

A: Yes. The Composite widget can pass on the events generated by a contained widget and as such, be an event source by itself. GWT actually provides special classes and methods to support this: This is done through the DelegatingXXXListener approach. As an example, consider the following Composite widget, that contains only a Button.

public class DelegatingClickWidgetDemo extends Composite implements
SourcesClickEvents {
Button button1;

DelegatingClickListenerCollection clickListeners;

public DelegatingClickWidgetDemo(String caption) {

VerticalPanel verticalPanel = new VerticalPanel();
button1 = new Button(caption);
verticalPanel.add(new HTML("Composite widget delegatingclicklisterer"));
verticalPanel.add(button1);

initWidget(verticalPanel);
}

public void addClickListener(ClickListener listener) {
if (clickListeners == null) {
clickListeners = new DelegatingClickListenerCollection(this,
button1);
}
clickListeners.add(listener);
}

public void removeClickListener(ClickListener listener) {
if (clickListeners != null)
clickListeners.remove(listener);
}

}
There are three things to notice about this code (marked in red in the listing):
  1. The class implements the SourcesClickEvents interface, thus is must implement addClickListener and removeClickListener.
  2. The class maintains a member of class DelegatingClickListenerCollection. This object delegates click listeners to another object, in this case the warpped Button.
  3. When DelegatingClickListenerCollection is instantiated, it is given the Button object it should delegate to as an argument.
So, the Composite widget maintains a Listener collection that actually listens to a contained widget!

(Sideways remark: It seems difficult to maintain separate Listener collections for multiple contained widgets at the Composite level, since there is only one addXXXListener method. But there may be a workaround.)

(Sideways remark 2: i don't know whether the composite can also be an event source detached from its contained widgets. Since the rock-bottom source of GWT events are DOM browser events, it seems that all GWT events must come from widgets that are associated with a browser object. So i guess the answer is no.)

If you want to use this class you must instantiate the composite widget and register an event listener:

public class DemoGwtComposite implements EntryPoint {

public void onModuleLoad() {
VerticalPanel vPanel = new VerticalPanel();
vPanel.setWidth("100%");
vPanel.setHorizontalAlignment(VerticalPanel.ALIGN_CENTER);
RootPanel.get().add(vPanel);

DelegatingClickWidgetDemo dcwd = new delegatingClickWidgetDemo("DCWD");
vPanel.add(dcwd);

// add a click listener to the COMPOSITE object, not to the button
// itself!
dcwd.addClickListener(new ClickListener() {
public void onClick(Widget sender) {
sender
.setTitle("This title was set by a delegating click listener");
}
});
}
}
If you run this example you will see that the click-listener registered with the composite widget handles the button's clicks!

Q: Can the composite widget listen to external event sources?


A: Sure, why not? It must just implement the methods required to be an event listerer... E.g., for click events it must implement the onClick method.

Q: Can the composite widget listen to internal event sources?

Yes. There is no difference between external and contained widgets in this respect. A composite can register itself as a listener to events generated by wrapped widgets.

Q: Can a widget within the composite listen to external events?

Yes, as long as it implements the required interfaces and registers itself as a listener. I did not test this.

Q: Can a widget within the composite source events to external listerers, bypassing the composite widget?

I think that in principe, this should be possible. However, this against the intention of Composite widgets, since a composite widget shields the methods of its contained widgets from the outside world. Thus, it is impossible to register listeners with the contained widget directly. (Instead, one can use the DelegatingXXXListener approach outlines above.)

If, against the spirit of composite widgets, you want a contained widget to source events to the outside world, you must make its addXXXListener methods available to the outside world somehow. You could do that by creating helper methods on the Composite object, and/or by passing a reference to the contained object to the outside world.

Geen opmerkingen:

Een reactie posten