Passivity, or “How to help your views leave the nest”
Google Web Toolkit has proven an interesting beast to code with. It’s not every web framework that encourages you to use passive view so strongly. That’s thanks partially to gwt-presenter which makes it pretty easy for views to be stupid. In most cases, the presenter even handles wiring up event handlers for the widgets.
Here’s a small excerpt of a view:
public class LiquorStoreListView extends Composite implements LiquorStoreListDisplay { Anchor addStore = new Anchor( "add store" ); public LiquorStoreListView( ) { FlowPanel panel = new FlowPanel( ); panel.add( addStore ); initWidget( panel ); } public HasClickHandlers getAddStore( ) { return addStore; } }
This is missing a bunch of extraneous stuff as is the presenter below:
public class LiquorStoreListPresenter extends WidgetPresenter{ LiquorStoreListDisplay display; EventBus eventBus; @Inject public LiquorStoreListPresenter( LiquorStoreListDisplay display, EventBus eventBus ) { this.display = display; this.eventBus = eventBus; bind( ); } @Override public void onBind( ) { display.getAddStore( ).addClickHandler( new ClickHandler( ) { public void onClick( ClickEvent event ) { Window.alert( "Adding a new store" ); } } ); } }
The magic is in the use of interfaces for the widgets. In this case, the Anchor widget implements HasClickHandlers which means the presenter doesn't need to know anything about the widget except that it implements a method, addClickHandler.
This falls apart a little when you have more dynamic user interfaces though. For example, what if each liquor store in our list contains an edit and delete button? Maybe we can expose them as a couple of ArrayList
The reason exposing the edit and delete buttons as an ArrayList to the presenter is icky is because you need to figure out which row was selected. Which leads to some pretty funky implementations, some of which require you to maintain a separate list of click handlers and/or domain objects on the presenter which need to be kept in sync with the view.
Instead, I've started delegating a tiny bit more responsibility to the view. That is, it will wire up the click handlers which in turn, fire off custom events that have more useful data in them.
Here's a sample method in the view:
public void addLiquorStore( LiquorStore store ) { int lastRow = storeTable.getRowCount( ); storeTable.setText( lastRow, 0, store.Name ); storeTable.setText( lastRow, 1, store.Address ); Anchor edit = new Anchor( "edit" ); edit.addClickHandler( new EditStoreClickHandler( store ) ); private class EditStoreClickHandler implements ClickHandler { LiquorStore store; public EditStoreClickHandler( LiquorStore store ) { this.store = store; } public void onClick( ClickEvent event ) { eventBus.fireEvent( new EditStoreEvent( store ) ); } } }
Then in the presenter:
public void onBind( ) { // ... eventBus.addHandler( EditStoreEvent.TYPE, new EditStoreEventHandler( ) { public void onEditStore( EditStoreEvent event ) { LiquorStore store = event.getStore( ); // Get the store data and display it } } ); }
May seem like more work but I like the separation a little better. Alternative is to store the store ID in a hidden field and do some DOM manipulation to get it. But I always feel dirty if I have to do that in GWT.
Kyle the Impassive