Friday, July 16, 2010

How to create a custom event handler in GWT

After struggling for longer than I should have I need to document the process partly for my own benefit and because if I struggled then someone else probably will as well.   So what am I documenting.

Scenario:
You have created a custom control Composite and want to implement a custom event for your control.  For example in the same way a button has a click event which is handled by the controls that use it.

Technology:
GWT 2.0, Java

Solution Outline:
In this example we are using a paging control as the example.  This is a custom control that aims to replicate the kind of functionality as seen in the next, first, prev, last and message functionality when navigating between emails.

Step 1: Create the custom event - PageChangedEvent

The PageChangedEvent will be fired when ever a paging action takes place.  For instance if a move next, prev, first or last action is called.  The code is pretty self explanatory but for clarity I have separated the code into the boiler-plate section and the code specific to this event.



public class PageChangedEvent extends GwtEvent<PageControlHandler> {
// Boiler plate code required to make your event work correctly in the GWT system
private static final Type<PageControlHandler> TYPE = new Type<PageControlHandler>();

@Override
public com.google.gwt.event.shared.GwtEvent.Type<PageControlHandler> getAssociatedType() {
return TYPE;
}
@Override
protected void dispatch(PageControlHandler handler) {
handler.onPageChanged(this);
}
// Code specific to this custom event

public enum PageChangeType {
first, prev, next, last
}
private final PageChangeType pageChangeType;

public PageControlEvent(PageChangeType pageChangeType) {
this.pageChangeType = pageChangeType;
}

public static Type<PageControlHandler> getType() {
return TYPE;
}
public PageChangeType getPageChangeType(){
return pageChangeType;
}

}


Step 2 : Advertise that this control has this types of events
In the PageControl which has these kinds of events we need to advertise this fact to the other classes, therefore the control will advertise this by implementing an interface called HasPageChangedHandler:


public class PageControl extends Composite implements ClickHandler, HasPageChangedHandler {


However this interface has yet to exist so we need to create it:


import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.event.shared.HasHandlers;


public interface HasPageChangedHandler extends HasHandlers {


  public void addPageControlHandler(PageChangedHandler handler);
}


This invites other parent controls to use this event by registering a PageChangedHandler with this control

Step 4 : Create the PageChangedHandler Interface
The handler interface indicates what type of events, or methods are required when implementing a handler of this type:


import com.google.gwt.event.shared.EventHandler;


public interface PageChangedHandler extends EventHandler{

 void onPageChanged(PageChangedEvent event);


}


Step 5 : Implement the methods in the composite control
In Step 2 we implemented an interface, and Step 3&4 we created those interfaces now we need to go back to the PageControl and the unimplemented methods.


@Override
public void addPageChangedHandler(PageChangedHandler handler) {
addHandler(handler, PageChangedEvent.getType());
}



This is an important step.  This registers the handler for this type of event.  The "addHandler" method is part of the GWT Widget class and is native to GWT.  Therefore this method doesn't need to be created.

Step 6 : Wire up the parent control to respond to onPageChangedEvents
There are a myriad of ways of doing this but assuming the parent control has only one page control then I wired it up by implementing the PageChangedHandler onto the main class:


public class MainSearchPresenter extends PresenterImpl<MainSearchPresenter.MyView, MainSearchPresenter.MyProxy>
implements SearchChangedHandler, ContactAddedHandler, ShowSearchResultsHandler, PageChangedHandler {

Which then shows the method that is run when a PageChangedEvent is called:
@Override
public void onPageChanged(PageChangedEvent event) {
if (event.getPageChangeType() == PageChangeType.next) {
getView().setupPager(1l, totalRecords, 20l);
}
}

However this will not work yet as we haven't registered this class as being ready to accept incoming onPageChanged requests from the pageControl.  Therefore whereever you bind up your controls, which will differ depending on what framework you use then you will need to add something like this:

pageControl.addPageChangedHandler(this);

If you were implementing this an anonymous inner class then this kind of setup would be used, which is very similar to the click handlers which are often implemented.

pageControl.addPageChangedHandler( new PageChangedHandler(){



@Override
public void onPageChanged(PageChangedEvent event) {
if (event.getPageChangeType() == PageChangeType.next) {
getView().setupPager(1l, totalRecords, 20l);
}
}
});


Step 7 : But wait - how do I fire this event from the child control?
Using whatever constructor arguments you require depending on the  type of event to fire then you simply call the composites native fireEvent method.



  this.fireEvent(new PageChangedEvent(PageChangeType.first));




Conclusion
This took me a day to figure out.  Ouch.  A lot of it had to do with getting confused with the old listeners method.  Which you should ignore if you are using 2.0 GWT or higher.  But there were plenty of code examples, which weren't quite complete.  I am hoping that this might help a newbie in the future like myself understand this process better.





9 comments:

  1. What happened to step 3? :)

    Could a simple fully working example be published at http://code.google.com/p/gwt-20-demos/ ?
    It would be great if it won't use GWTP (you mention Presenter at some point).

    Thanks for the sushi :)

    ReplyDelete
  2. I must have been so excited about writing the article I forgot all about Step 3. Thanks for the update. The Presenter is not really incorporated in this example as it is just the page that implements the paging handler interface. I will add a GWTP free example to the demos this week.

    Thanks for the interest in the blog. :)

    Gene

    ReplyDelete
  3. In step 1 I see a PageControlHandler. Is this correct or should it be PageChangedHandler? I ask because this is my first go at it but this part has me somewhat confused. Is it a typo or do I need a PageControlHandler too?

    Great info BTW, thanks!

    ReplyDelete
  4. Thanks for the compliment, and pointing out the typo. The concepts get very confusing when writing all this boilerplate code. But yes logically, after re-reading the post it makes more sense to have it as the PageChangedHandler.

    ReplyDelete
  5. Great, thanks for the clarification. Well I just got my first custom event handler working and I couldn't have done it without this info. Thanks!!

    ReplyDelete
  6. No problem. You may also be interested in my latest article which also talks about implementing a ClickHandler in a Composite control.

    http://gwtsushi.blogspot.com/2010/08/four-easy-steps-to-implementing.html

    ReplyDelete
  7. Hello,

    thank you very, very, very much!!! I am new to this topic and without your absolute great posting I would not have been able to understand this. Thanks!!!!

    ReplyDelete
  8. HI, I am having a lot of trouble getting my head wrapped around this topic and getting it to work in my project? Do you have the source code of a working example of this topic. I looked at your download page and looked through the samples and couldn't find anything that matched with this topic.

    Thanks in advance for the help.

    ReplyDelete
  9. It works for me only if the addPageControlHandler method return a HandlerRegistration.

    Thanks for the post :-).

    ReplyDelete