Gwt-Platform event best practices (revisited)

Not too long ago, I started using GWT and GWTP and found it pretty hard to understand its event system.

I still can’t find much recent information about it, so I am writing this post to hopefully help others. Disclaimer: There many ways to achieve what I do in this post, but this is the way I prefer.

All right! Let’s demonstrate how events and the event bus work by creating a CSI-Hacking Dashboard. You can get the code on GitHub.

Post_gwtp_Howto_v1-03

We have ComputerPresenter, a HackerPresenter and a root Presenter that holds/creates other widgets. We also have a ComputerHackedEvent and ComputerHackedHandler. The event will be raised to signal that the computers got hacked, and the handler code will be executed after that.

Here’s the classic implementation of a GWT event. Our event will only carry the hacker’s name.

public class ComputerHackedEvent extends GwtEvent<ComputerHackedEvent.ComputerHackedHandler> {
    public interface ComputerHackedHandler extends EventHandler {
        void onSystemHacked(ComputerHackedEvent event);
    }

    public static final Type<ComputerHackedHandler> TYPE = new Type<>();

    private final String hackerName;

    public ComputerHackedEvent(String hackerName) {
        this.hackerName = hackerName;
    }

    public static void fire(String hackerName, HasHandlers source) {
        source.fireEvent(new ComputerHackedEvent(hackerName));
    }

    public String getHackerName() {
        return hackerName;
    }

    @Override
    public Type<ComputerHackedHandler> getAssociatedType() {
        return TYPE;
    }

    @Override
    protected void dispatch(ComputerHackedHandler handler) {
        handler.onSystemHacked(this);
    }
}

I usually declare my handler as a nested interface in the event declaration. That’s only a matter of personal preference though.

Next we have to raise the event. That will be done by our HackerPresenter. A really simple way of becoming a computer hacker, is by adding a button to the screen and pressing it, right CSI?

Here’s the code of the view and the presenter.

public class HackerView extends ViewWithUiHandlers<HackerUiHandlers>
        implements HackerPresenter.MyView {
    interface Binder extends UiBinder<Widget, HackerView> {
    }

    @UiField
    Button hackThePlanet;

    @Inject
    HackerView(Binder binder) {
        initWidget(binder.createAndBindUi(this));
    }

    @UiHandler("hackThePlanet")
    public void initiateHacking(ClickEvent event) {
        getUiHandlers().onInitiateHacking();
    }
}
public class HackerPresenter extends PresenterWidget<MyView>
        implements HackerUiHandlers {
    public interface MyView extends View, HasUiHandlers<HackerUiHandlers> {
    }

    private final String hackerName;

    @Inject
    HackerPresenter(
            EventBus eventBus,
            MyView view,
            @Assisted String hackerName) {
        super(eventBus, view);

        this.hackerName = hackerName;

        getView().setUiHandlers(this);
    }

    @Override
    public void onInitiateHacking() {
        ComputerHackedEvent.fire(hackerName, this);
    }
}

So we have a button, and when we click on it, it raises an event. There are a couple of ways to raise an event, but the one I usually use, is the static fire() method on the event. (I’ll talk about the other ways of firing an event later on.)

Now we have to handle the event somewhere. We want to know when the computers get hacked, so we’ll represent the computers with the ComputerPresenter. Its role will be to print in the console when it gets hacked, and by which hacker. Here’s the presenter code:

public class ComputerPresenter extends PresenterWidget<MyView>
        implements ComputerHackedEvent.ComputerHackedHandler {
    public interface MyView extends View {
        void setComputerName(String computerName);

        void displayStatus(String computerName, String hackerName);
    }

    private final String computerName;

    @Inject
    ComputerPresenter(
            EventBus eventBus,
            MyView view,
            @Assisted String computerName) {
        super(eventBus, view);

        this.computerName = computerName;

        view.setComputerName(computerName);
    }

    @Override
    protected void onBind() {
        super.onBind();

        addRegisteredHandler(ComputerHackedEvent.TYPE, this);
    }

    @Override
    public void onSystemHacked(ComputerHackedEvent event) {
        getView().displayStatus(computerName, event.getHackerName());
    }
}

This way, when a hacker clicks on the “start hacking” button, all the computers that are listening to the event will print something. As you can see, the ComputerPresenter registers itself as a handler for the ComputerHackedEvent through the addRegisteredHandler method.

This is a convenience method provided by GWTP. Using this method instead of registering directly on the EventBus will make the event registration part of GWTP’s lifecycle and unbind the event handler when the presenter is unbound. That means that if the presenter is unbound and rebound, you’ll have to re-register event handlers. This is why the onBind method is a good place to register handlers.

Here’s the code of the root presenter:

public class RootPresenter extends Presenter<RootPresenter.MyView, RootPresenter.MyProxy> {
    interface MyView extends View {
    }

    @ProxyStandard
    @NameToken(NameTokens.home)
    interface MyProxy extends ProxyPlace<RootPresenter> {
    }

    public static final Object SLOT_COMPUTERS = new Object();
    public static final Object SLOT_HACKERS = new Object();

    private final WidgetsFactory widgetsFactory;

    @Inject
    RootPresenter(
            EventBus eventBus,
            MyView view,
            MyProxy proxy,
            WidgetsFactory widgetsFactory) {
        super(eventBus, view, proxy, ApplicationPresenter.SLOT_SetMainContent);

        this.widgetsFactory = widgetsFactory;
    }

    @Override
    protected void onBind() {
        super.onBind();

        HackerPresenter zeroCool = widgetsFactory.createHacker("Zer0C00L");
        HackerPresenter acidBurn = widgetsFactory.createHacker("AcidBurn");

        addToSlot(SLOT_HACKERS, zeroCool);
        addToSlot(SLOT_HACKERS, acidBurn);

        ComputerPresenter computerA = widgetsFactory.createComputer("A");
        ComputerPresenter computerB = widgetsFactory.createComputer("B");

        addToSlot(SLOT_COMPUTERS, computerA);
        addToSlot(SLOT_COMPUTERS, computerB);
    }
}

We just create one hacker named Zer0C00L and 2 computers to hack. And now the view:

public class RootView extends ViewImpl
        implements RootPresenter.MyView {
    interface Binder extends UiBinder<Widget, RootView> {
    }

    @UiField
    HTMLPanel computers;
    @UiField
    HTMLPanel hackers;

    @Inject
    RootView(Binder uiBinder) {
        initWidget(uiBinder.createAndBindUi(this));
    }

    @Override
    public void addToSlot(Object slot, IsWidget content) {
        super.addToSlot(slot, content);

        if (slot == RootPresenter.SLOT_COMPUTERS) {
            computers.add(content);
        } else if (slot == RootPresenter.SLOT_HACKERS) {
            hackers.add(content);
        }
    }

    @Override
    public void removeFromSlot(Object slot, IsWidget content) {
        super.removeFromSlot(slot, content);

        if (slot == RootPresenter.SLOT_COMPUTERS) {
            computers.remove(content);
        } else if (slot == RootPresenter.SLOT_HACKERS) {
            hackers.remove(content);
        }
    }
}

Post_gwtp_Howto_v1-04

Nice! When I click on “Hack the planet!” I see the following result in the Javascript console:

I got hacked. (A) by Zer0C00L
I got hacked. (B) by Zer0C00L

I should start writing in l33tsp33k now.

Now what if you remove one of the ComputerWidgets from the DOM by calling removeFromSlot(SLOT_COMPUTERS, computerB); and still try to hack the planet?

If you read the output of the console you will see:

I got hacked. (A) by Zer0C00L
I got hacked. (B) by Zer0C00L

Wait… What? The handler for the computer B is still registered, the presenter wasn’t unbound, it was only removed from the DOM.

What if we want computer B to stop listening to the events when it’s not present in the DOM? Well that’s a job for addVisibleHandler. So instead of registering the handler using addRegisteredHandler we’ll use addVisibleHandler that will handle this for us. This way, when a presenter is considered “not visible” in GWTP’s lifecycle perspective (read: not visible as in “visible in the DOM”), the event will not reach the handler. The new output should now be:

I got hacked. (A) by Zer0C00L

There’s still a problem though. What if there were too many computers for a single hacker? I think at some point we’ll have to add someone to the team. Let’s do it!

// in the RootPresenter's OnBind method
HackerPresenter acidBurn = widgetsFactory.createHacker("AcidBurn");
addToSlot(SLOT_HACKERS, acidBurn);

You should see 2 buttons saying “Hack the planet!” and when you click them both, the output is:

I got hacked. (A) by Zer0C00L
I got hacked. (B) by Zer0C00L
I got hacked. (A) by AcidBurn
I got hacked. (B) by AcidBurn

All computers are reacting to every hacker, which is not what we want. This is happening because of the way we registered the handlers earlier. What we want is for the computers to react a specific hacker’s ComputerHackedEvent.

Since we can have a reference to the said hacker, that is pretty easy to accomplish. We have to delegate the handler registration to the concerned presenter. From the RootPresenter we’ll delegate the task, but first let’s create an interface :

import com.google.web.bindery.event.shared.HandlerRegistration;

public interface HasComputerHackedHandlers {
    HandlerRegistration addComputerHackedHandler(ComputerHackedEvent.ComputerHackedHandler handler, Object source);
}

We can then let ComputerPresenter implement it.

public HandlerRegistration addComputerHackedHandler(ComputerHackedEvent.ComputerHackedHandler handler, Object source) {
    HandlerRegistration hr = getEventBus().addHandlerToSource(ComputerHackedEvent.TYPE, source, handler);
    registerHandler(hr);
    return hr;
}

Note that instead of registerHandler() you can also use registerVisibleHandler().

And finally, when you click on both buttons, the output should be:

I got hacked. (A) by Zer0C00L
I got hacked. (B) by AcidBurn

All right! We’re ready to hack the planet! Are we?

Post_gwtp_Howto_v1-02

Remember when I said I would talk about the ways of firing events? If you are new to GWT and GWTP, you might have noticed that there are multiple methods available to fire events and register handlers.

// GWT
eventBus.addHandler(eventType, handler)
eventBus.addHandlerToSource(eventType, source, handler)

eventBus.fireEvent(event)
eventBus.fireEventFromSource(event, source)

// GWTP
presenterWidget.addHandler(eventType, handler) // deprecated
presenterWidget.addRegisteredHandler(eventType, handler)
presenterWidget.addVisibleHandler(eventType, handler)
presenterWidget.registerHandler(handlerRegistration)
presenterWidget.registerVisibleHandler(handlerRegistration)

presenterWidget.fireEvent(event)

Confused yet? I can say I was after seeing this. If you dig down you can see that a presenter widget gets an event bus injected and delegates most of its job to it. The only difference is that GWTP manages the handler registrations with its lifecycle (i.e: when a presenter gets unbound, the registered handlers get cleared). Also, if you dig for the fireEvent method, you’ll see GWTP delegates to eventBus.fireEventFromSource(). You may want to call the original fireEvent() if you want to match the following case (taken from the javadoc) “Fires the event from no source. Only unfiltered handlers will receive it”. Honestly, I’ve never faced that situation.

Here’s my cheat sheet of events in GWTP:

– Do I need to fire an event globally on the event bus? (i.e: everything registered to the event will handle it)
Y: presenterWidget.addRegisteredHandler() + SomeEvent.fire()
N: Go next

– Do I need to filter the handlers by the visibility of the handler?
Y: presenterWidget.addVisibleHandler() + SomeEvent.fire()
N: Go next

– Do I need specific handlers to handle events from a specific source?
Y: Create an interface called HasXXXHandlers and make your handling presenter implement it. GWTP gives PresenterWidget the capability to register handlers via registerHandler() and registerVisibleHandler(). Finally, fire the event with SomeEvent.fire()
N: That’s it. I usually don’t need more options, so the decision tree ends here. If you have another situation that doesn’t fit, let me know!

Post_gwtp_Howto_v1-05

A Guaranteed Future for gQuery!

Arcbees is proud to officially support g(wt)-Query (gQuery), a jQuery-clone API written in Java for GWT. We hope this will helps gQuery grow even further, promoting clear direction and a strong future for this awesome product – one that we ourselves are using in all our projects.

We are firmly committed to keeping gQuery an Open Source product, available under the Apache 2.0. licence. The gQuery project continues to be led by the top three contributors to the codebase: Ray CromwellManuel Carrasco Moñino and Julien Dramaix.gQuerY-1

What does our official support of gQuery mean to you? First, gQuery will be part of ArcBees’ other Open Source product offerings, and gQuery support will be included in the support package offered by Arcbees for GWTP. Also, Arcbees is planning a complete re-write of the documentation. This new documentation will follow Arcbees new conventions and will be hosted on github.io.

There is more. This is just the tip of the iceberg! The Arcbees development ecosystem is evolving fast, and you can hear all about it at GWT.create this year!

gQuerY-2

Le futur de gQuery est assuré!

Arcbees peut se targuer d’être la 1ère entreprise à supporter officiellement g(wt)Query, une librairie similaire à jQuery, mais pour GWT. Fort de cet appui, nous espérons que le produit va continuer de croître en plus d’en assurer sa pérennité, surtout que nous en sommes nous-mêmes de fiers utilisateurs!

Essentiellement, gQuery va demeurer le même produit Open Source offert sous licence Apache 2.0. que vous avez toujours connu et son développement va continuer d’être mené par ses top contributeurs, soit messieurs Manuel Carrasco Moñino, Ray Cromwell et Julien Dramaix.

Vous l’avez peut-être déjà constaté, mais les sources ont été transférées sous Arcbees (github) et d’importants changements se pointent à l’horizon puisque la ré-écriture complète de la documentation est en cours et que gQuery s’ajoute à la gamme de produits Open Source déjà offerte par Arcbees, faisant en sorte qu’il sera désormais couvert par le forfait de support pour GWTP.

Plusieurs autres modifications sont à venir et vous pourrez en apprendre bien davantage sur le sujet et sur Arcbees lors du prochain GWT.create et lors du dévoilement de notre nouvelle image qui approche à grands pas!

gQuerY-4