»
January 17, 2010
»

GWT History Management

This is a first post in two post series about one history management approach for non-trivial GWT applications. This approach I deem done right (in contrast to good enough).

In the following I assume that general application architecture follows MVP (or similar) design and uses some global event or notification system (for example EventBus based on HandlerManager or something similar to NSNotificationCenter) used in Cocoa applications. Examples has GIN annotations where appropriate.

Fictional Application

Lets agree on one concrete application as a basis of discussion of history and places.

history-1/01.png

The application consists of two sections (managed by “root” WorkspacePresenter) that are represented with two top tabs, each section has root presenter and people section has nested child presenters:

Section Container Presenter
Index Content Index
People Master Person list
Detail Blank
Show person
Edit person

Screen shot is shows People section with “Show person” detail presenter open, linked site uses Places library (see “Terrible Footnote” below, but basic concepts described here apply).

History token — Place

From previous table we can gather a list of application states where permanent links (using history token) are needed. Lets call those states places.

Section State History token / Place pattern
Index /index
People No person selected /people
Showing person /person/{key}/show 1
Showing edit person form /person/{key}/edit 1

1 where {key} is Person model primary key.

In GWT the history management can be implemented using History singleton class what manages the part of the URL that comes after #. It allows us to:

  • register to token value changes,
  • change token (that will fire value change event) and
  • fire current token state.

When History#fireCurrentHistoryState() is called, it notifies handlers about new token value. Token itself is a String that can be parsed and reacted upon (think routes in Rails applications or Servlet mapping in web.xml).

This can be done in HistoryService class that must know about each and every history token pattern used in application and should be able to react on them. This way it is easy to implement GWT.runAsync based application code splitting where application initial download contains only HistoryService and split point loaders that initiates additional code loading when needed.

SimpleHistoryManager

Lets take a look at History manager simplified implementation (in next blog post this class will be replaced with sane implementation, other concepts still apply):

@Singleton
public class SimpleHistoryManager implements ValueChangeHandler<String> {

  private final EventBus eventBus;

  @Inject
  public SimpleHistoryManager(EventBus eventBus) {
    this.eventBus = eventBus;
  }

  public void bind() {
    History.addValueChangeHandler(this);
    History.fireCurrentHistoryState();
  }

  public void onValueChange(ValueChangeEvent<String> stringValueChangeEvent) {
    String token = stringValueChangeEvent.getValue();
    onTokenChanged(token);
  }

  private void onTokenChanged(String token) {
    if (token.equals("/index") || token.equals("")) {
      showIndexSection();
    } else if (token.equals("/people")) {
      showPeopleSection();
    } else if (token.startsWith("/person/")) {
      String[] array = token.split("/");
      String key = array[2];
      String action = array[3];

      if (action.equals("/show")) {
        showPerson(key);
      } else if (action.equals("/edit")) {
        showEditPerson(key);
      }
    }
  }

  private void showIndexSection() {
  }

  private void showPeopleSection() {
  }

  private void showPerson(String key) {
  }

  private void showEditPerson(String key) {
  }

}

When bind() is called, the instance registers itself as token value change handler and fires first value change event which in turn invokes onTokenChanged() for the first time in application current life cycle. onTokenChanged parses (parsing is incomplete and without any error handling) token string and delegates actual work to methods listed below. Fine, what’s next?

Events.

To presenter “Index section”:

  • WorkspacePresenter must show IndexPresenter by setting IndexPresenter#getView() to it’s View#setContentView(BaseView),
  • MenuPresenter must set “index” as active menu item.

Menu is easy:

eventBus.fireEvent(new MenuSetActiveEvent("index"));

But before we implement WorkspacePresenterIndexPresenter relations we have one design decision to make about Presenter hierarchy.

Presenter Hierarchy

Does WorkspacePresenter keeps reference to Provider<IndexPresenter> and all other presenter providers it may need?

It surely can be done (by using SectionService#getSectionByKey(String) or something like that) but it unnecessary complicates presenter and sometimes makes it harder to implement history support and GWT.runAsync’able code splitting. Instead of WorkspacePresenter (and presenters in general), HistoryManager should manage dynamic presenter hierarchy. By dynamic I mean those child presenters that can be replaced by different presenters in application runtime in contrast to static ones that can be safely bound in parent — for example MenuPresenter and LogPresenter is bound directly in WorkspacePresenterImpl.

So now we need an event that SimpleHistoryManager fires and WorkspacePresenter handles, gets Presenter instance from it, calls bind() and sets in its own View as sub-view:

BasePresenter<?> indexPresenter = ...;
eventBus.fireEvent(new WorkspaceSetContentEvent(indexPresenter));
public void onWorkspaceSetContent(final WorkspaceSetContentEvent event) {
  setContent(event.getPresenter());
}

public void setPresenter(BasePresenter<?> presenter) {
  presenter.bind();
  view.setContentView(presenter.getView());
  presenter.show();
}

But there is one small caveat with HandlerManager which nearly always is used as EventBus implementation. WorkspacePresenter content Presenter may register itself to @Singleton EventBus as an event handler. But if event handler is added to HandlerManager inside fired event from the same HandlerManager, that handler will be live only after original event handler has returned.

Basically it means that:

eventBus.addHandler(WorkspaceSetContentEvent.getType(), new WorkspaceSetContentHandler() {
  public void onWorkspaceSetContent(WorkspaceSetContentEvent event) {

    // this handler will handle PersonShowEvent only after this 
    // WorkspaceSetContentEvent handler will return
    eventBus.addHandler(PersonShowEvent.getType(), new PersonShowHandler() {
      public void onPersonShow(PersonShowEvent event) {
      }
    });

    // this event won't be handled by previous handler
    eventBus.fireEvent(new PersonShowEvent("foo"));
  }
});

To overcome this we need small design pattern. I’ll even give it a fancy name.

On-Ready Event Callback

Simply put it is an GwtEvent with additional Command parameter. This Command callback must be called when presenter’s bind() is called and bound presenter is ready to handle events (by using DeferredCommand if needed).

public class WorkspaceSetContentEvent extends GwtEvent<WorkspaceSetContentHandler> {

  // TYPE, ivars removed -- less noise

  public WorkspaceSetContentEvent(PresenterProvider<?> presenter, Command callback) {
    this.presenter = presenter;
    this.callback = callback;
  }

  public PresenterProvider<?> getPresenter() {
    return presenter;
  }

  public Command getCallback() {
    return callback;
  }

}

See WorkspaceSetContentEvent for full source code of this Event.

Lets ignore PresenterProvider interface for a moment — it needs it’s own small section.

Now we can implement all SimpleHandlerManager#show* methods:

private void showIndexSection() {
  fireMenuSetActive("index");
  fireWorkspaceSetContent(indexPresenterProvider, null);
}

private void showPeopleSection() {
  showPeopleSection(blankPresenterProvider, null, null);
}

private void showPerson(final String key) {
  showPeopleSection(showPresenterProvider, key, new Command() {
    public void execute() {
      eventBus.fireEvent(new PersonShowEvent(key));
    }
  });
}

private void showEditPerson(final String key) {
  showPeopleSection(editPresenterProvider, key, new Command() {
    public void execute() {
      eventBus.fireEvent(new PersonEditEvent(key));
    }
  });
}

private void showPeopleSection(final PresenterProvider<?> content, final String key, 
    final Command command) {
    
  fireMenuSetActive("people");
  fireWorkspaceSetContent(peoplePresenterProvider, new Command() {
    public void execute() {
      eventBus.fireEvent(new ListSetActiveEvent(key));
      eventBus.fireEvent(new PeopleSetContentEvent(content, command));
    }
  });
}

private void fireMenuSetActive(String key) {
  eventBus.fireEvent(new MenuSetActiveEvent(key));
}

private void fireWorkspaceSetContent(PresenterProvider<?> presenter, Command command) {
  eventBus.fireEvent(new WorkspaceSetContentEvent(presenter, command));
}

And for example WorkspaceSetContentHanlder inside WorkspacePresenter looks like this:

public void onWorkspaceSetContent(final WorkspaceSetContentEvent event) {
  content.set(event.getPresenter(), event.getCallback());
}

Next we need to define what is:

  • content from previous handler code listing,
  • PresenterProvider<P extends BasePresenter<?>>

They both forms one simple concept described in next section.

PresenterProvider & PresenterSwitch

Let’s start with some reasons behind PresenterProvider interface and what part it plays in overall architecture. Let’s return to two place implementations: “showing person” and “showing edit person form”:

private void showPerson(final String key) {
  showPeopleSection(showPresenterProvider, new Command() {
    public void execute() {
      eventBus.fireEvent(new PersonShowEvent(key));
    }
  });
}

private void showEditPerson(final String key) {
  showPeopleSection(editPresenterProvider, new Command() {
    public void execute() {
      eventBus.fireEvent(new PersonEditEvent(key));
    }
  });
}

private void showPeopleSection(final PresenterProvider<?> content, final Command command) {
  fireMenuSetActive("people");
  fireWorkspaceSetContent(peoplePresenterProvider, new Command() {
    public void execute() {
      eventBus.fireEvent(new PeopleSetContentEvent(content, command));
    }
  });
}

As we can see from code, both place implementations sets PeoplePresenter as WorkspacePresenter content and only changes PeoplePresenter content.

For the time being we have taken into consideration only one of two possible times when History value change handler is invoked:

  • after History.fireCurrentHistoryState() as a part of EntryPoint#onModuleLoad invocation,
  • any time after History.newItem(String token) is called.

When application is launched, WorkspacePresenter content view will be empty. It is History manager responsibility to fill it with requested-by-history-token Presenter-View. But when switching for example from “Showing person” to “Showing edit person form” places only PersonPresenter content needs to be switched from ShowPresenter to EditPresenter. The WorkspacePresenter content must not be replaced by new instance of the same PersonPresenter (otherwise content will blink because of DeferredCommand that is needed after Presenter#bind() and just because this is simply plain useless).

Though SimpleHistoryManager shouldn’t and doesn’t keep track of current presenter hierarchy the decision if “current” presenter in all presenter hierarchy levels needs to be done Presenters side because it is easier to implement and allows us to modify presenter hierarchy also without history support if such need arises (as not all application states needs history token).

PresenterProvider

GIN allows to inject class dependencies in two ways:

  • Instance directly
  • Instance via provider (using Provider<T>)

The Provider<T> interface declaration is:

public interface Provider<T> {
  T get();
}

This interface requires us construct a new Presenter in order to get the instance class that can be used in comparison between “current” Presenter and Presenter which is received as a part of *SetContentEvent. To remove this possibly unnecessary Presenter construction the minor extension to Provider<T> interface is needed:

public interface PresenterProvider<P extends BasePresenter<?>> 
  extends Provider<P>, HasInstanceClass {
}

public interface HasInstanceClass {
  Class<?> getInstanceClass();
}

Then by using PresenterProvider<P> we can efficiently handle *SetContentEvents:

private Class<?> currentPresenterInstanceClass;

public void onAnySetContentEvent(AnySetContentEvent event) {
  PresenterProvider<?> presenterProvider = event.getPresenter();

  Class<?> nextInstanceClass = presenterProvider.getInstanceClass();
  if (currentPresenterInstanceClass != nextInstanceClass) {
    BasePresenter<?> next = presenterProvider.get();
    currentPresenterInstanceClass = nextInstanceClass;
    // unbind previous presenter
    // bind, view.setSomethingView(next.getView()), show
    // DeferredCommand.addCommand(event.getCallback());
  }
}

Now we can construct and initialize Presenter instance only if it is not the same as current. But there is too much noise in event handler.

PresenterSwitch

Let’s create PresenterSwitch helper class that (fully, comparing to previous code snippet) implements and encapsulates the logic which resides in onAnySetContentEvent in previous snippet:

public class PresenterSwitch {

  public interface SwitchCallback {
    void onSetView(BasePresenter<?> presenter);
  }

  private SwitchCallback callback;
  private Class<?> presenterClass;
  private BasePresenter<?> presenter;

  public void bind(SwitchCallback callback) {
    this.callback = callback;
  }

  public void unbind() {
    callback = null;
    presenterClass = null;
    if (presenter != null) {
      presenter.unbind();
      presenter = null;
    }
  }

  public void set(PresenterProvider<?> provider, Command onBoundCommand) {
    Class<?> instance = provider != null ? provider.getInstanceClass() : null;
    if (presenterClass != instance) {
      final BasePresenter<?> next = provider != null ? provider.get() : null;

      if (presenter != null) {
        presenter.unbind();
      }

      presenter = next;
      presenterClass = instance;

      if (next != null) {
        next.bind();
        callback.onSetView(next);
        next.show();
        if (onBoundCommand != null)
          addDeferredCommand(onBoundCommand);
      } else {
        callback.onSetView(null);
        if (onBoundCommand != null)
          onBoundCommand.execute();
      }
    } else {
      if (onBoundCommand != null)
        onBoundCommand.execute();
    }
  }

  private void addDeferredCommand(Command command) {
    DeferredCommand.addCommand(command);
  }

}

And bind it inside WorkspacePresenter:

public class WorkspacePresenterImpl extends AbstractBasePresenter<WorkspaceView> 
  implements WorkspacePresenter, WorkspaceSetContentHandler {

  private final PresenterSwitch content = new PresenterSwitch();

  @Inject
  public WorkspacePresenterImpl(WorkspaceView view, EventBus eventBus) {
    super(view, eventBus);
  }

  @Override
  public void onBind() {
    register(eventBus.addHandler(WorkspaceSetContentEvent.getType(), this));
    content.bind(new SwitchCallback() {
      public void onSetView(BasePresenter<?> presenter) {
        view.setContentView(presenter != null ? presenter.getView() : null);
      }
    });
  }

  @Override
  public void onUnbind() {
    content.unbind();
  }

  @Override
  public void onShow() {
  }

  public void onWorkspaceSetContent(final WorkspaceSetContentEvent event) {
    content.set(event.getPresenter(), event.getCallback());
  }

}

The same goes with PeoplePresenter and its PeopleSetContentEvent.

Lastly we need to construct PresenterProvider instances for each Presenter in SimpleHistoryManager so lets create a PresenterProviderFactory interface:

public interface PresenterProviderFactory {

  <P extends BasePresenter<?>> PresenterProvider<P> create(Class<?> clazz, 
                                                           Provider<P> provider);

}

and define full constructor for SimpleHistoryManager:

private final EventBus eventBus;
private final PresenterProvider<IndexPresenter> indexPresenterProvider;
private final PresenterProvider<PeoplePresenter> peoplePresenterProvider;
private final PresenterProvider<BlankPresenter> blankPresenterProvider;
private final PresenterProvider<ShowPresenter> showPresenterProvider;
private final PresenterProvider<EditPresenter> editPresenterProvider;

@Inject
public SimpleHistoryManager(EventBus eventBus, PresenterProviderFactory f,
                            Provider<IndexPresenter> indexPresenterProvider,
                            Provider<PeoplePresenter> peoplePresenterProvider,
                            Provider<BlankPresenter> blankPresenterProvider,
                            Provider<ShowPresenter> showPresenterProvider,
                            Provider<EditPresenter> editPresenterProvider) {
  this.eventBus = eventBus;

  this.indexPresenterProvider = f.create(IndexPresenter.class, indexPresenterProvider);
  this.peoplePresenterProvider = f.create(PeoplePresenter.class, peoplePresenterProvider);
  this.blankPresenterProvider = f.create(BlankPresenter.class, blankPresenterProvider);
  this.showPresenterProvider = f.create(ShowPresenter.class, showPresenterProvider);
  this.editPresenterProvider = f.create(EditPresenter.class, editPresenterProvider);
}

Looks terrible, doesn’t it? See “Terrible Footnote” below.

Also note that get() may or may not be instance of getInstanceClass(). This actually calls for rename of getInstanceClass() to something different more adequate.

Changing place — Place Events

So we have implemented the complicated side of history management — reaction to history tokens. Last thing what’s missing is adding new history items.

If SimpleHistoryService parses history token, it also must create new tokens to encapsulate token string related functionality in one place and to hide token strings from other parts of application. If it creates tokens, it needs to know when create them. So we need few new events that can be fired from application presenters and handled by SimpleHistoryService. Full list for this application is:

  • MenuClickEvent
  • PeopleShowPlaceEvent
  • PeopleEditPlaceEvent
  • PersonShowPlaceEvent

For example MenuClickEvent is fired from MenuPresenter:

String key = ...;
eventBus.fireEvent(new MenuClickEvent(key)); 

And other events are fired similarly. Lets add bindHandlers() method to service and call it from bind():

public void bind() {
  History.addValueChangeHandler(this);
  History.fireCurrentHistoryState();

  bindHandlers();
}

private void bindHandlers() {
  eventBus.addHandler(PeopleShowPlaceEvent.getType(), new PeopleShowPlaceHandler() {
    public void onPeopleShowPlace(PeopleShowPlaceEvent event) {
      newItem("/people");
    }
  });
  eventBus.addHandler(PersonEditPlaceEvent.getType(), new PersonEditPlaceHandler() {
    public void onPersonEditPlace(PersonEditPlaceEvent event) {
      newItem("/person/" + event.getKey() + "/edit");
    }
  });
  eventBus.addHandler(PersonShowPlaceEvent.getType(), new PersonShowPlaceHandler() {
    public void onPersonShowPlace(PersonShowPlaceEvent event) {
      newItem("/person/" + event.getKey() + "/show");
    }
  });
  eventBus.addHandler(MenuClickEvent.getType(), new MenuClickHandler() {
    public void onMenuClick(MenuClickEvent event) {
      newItem("/" + event.getKey());
    }
  });
}

private void newItem(String token) {
  History.newItem(token);
}

Each of those event handlers will add new History item that will fire History value change handler logic inside onTokenChanged() method.

And this prototype of history aware application is working just fine.

That Terrible Footnote

Regarding those ugly SimpleHistoryManager bindHandlers(), constructor and string parsing: next blog post will be about Places library which will replace SimpleHistoryManager with more sophisticated solution — list of hierarchic Place instances where each manages only few Presenter providers, few events and only sub-part of history token pattern with or without parameters.

Update: Second post is online

 
Internet Explorer 6
Are you serious?