GWT 2.1 Places – Part II

Translation available in: French.

Updated 2010-10-16: updated to GWT 2.1.0.

I previously described the core of GWT 2.1 Places and will now focus on the optional features.

User confirmation before navigating

In many applications, you want to have confirmation of the user before navigating away; this is mostly used when the user made changes and didn't yet save them. This can be easily accomplished with GWT 2.1 places: you'll register a PlaceChangeRequestEvent.Handler on the event bus and call the PlaceChangeRequestEvent's setWarning method with a non-null value that will be used to ask confirmation to the user.

When calling goTo on the PlaceController, before changing the current place, it actually first fires a PlaceChangeRequestEvent. Once the event has been dispatched to all the handlers, the current place will actually be changed and the PlaceChangeEvent fired only in two cases:

Additionally, when navigating away or closing the window or tab (i.e. quitting the application), such a PlaceChangeRequestEvent is also fired (with the next place set to Place.NOWHERE), and setting a non-null warning value will also ask the user confirmation (for those interested in the gory details, this is accomplished by setting the Window.ClosingEvent's message).

Integration with RequestFactory

GWT 2.1 does not come with built-in direct integration with RequestFactory. You'll find a preview of what'll come in a later release in the code for the Expenses sample, in the form of two concrete places that can help you when a place is tied to a EntityProxy instance or type: the ProxyListPlace represents the list of records of a given type, while the ProxyPlace models an operation (create, show details, or edit) on a particular record instance.

Integration with the browser's history

Integration with the browser's history means:

  1. serializing the current place to the URL so it can be bookmarked and creating an entry in the browser's navigation history; and
  2. deserializing the URL back to a place, and fire the appropriate events to the event bus to finally update the current place.

#1 is done when the place changes due to a call to the PlaceController's goTo, while #2 is triggered by the browser when the user either navigates in its history through the browser's "previous page" and "next page" buttons, or loads a previously bookmarked URL generated by #1.

To use this feature, you'll first create PlaceTokenizers to map history tokens to/from places, and then an (empty) interface, extending PlaceHistoryMapper and annotated with @WithTokenizers to declare the PlaceTokenizers to be used, that you'll GWT.create() and finally register, through a PlaceHistoryHandler, with the PlaceController, the EventBus and a default place (the place to use when there's no token, or when the token cannot be mapped to a place).

@WithTokenizers(FooPlaceTokenizer.class, BarPlaceTokenizer.class)
interface MyPlaceHistoryMapper extends PlaceHistoryMapper {
}

PlaceHistoryMapper historyMapper = GWT.create(MyPlaceHistoryMapper.class);
PlaceHistoryHandler historyHandler = new PlaceHistoryHandler(historyMapper);
historyHandler.register(placeController, eventBus, defaultPlace);

How does the PlaceHistoryHandler maps history tokens to places?

Each PlaceTokenizer is associated with a token prefix using a @Prefix annotation. The PlaceHistoryHandler splits the history token on the first colon, compares the left-hand part to the prefixes it knows, and then GWT.create() the matched PlaceTokenizer class and call its getPlace method with the right-hand part as the argument.

How does the PlaceHistoryHandler maps places to history tokens?

PlaceTokenizer is a generic class actually defined as PlaceTokenizer<P extends Place>. When the code generator runs on the PlaceHistoryHandler, it examines the PlaceTokenizers and extracts their P generic type argument. This information is then used at runtime to map a place to the appropriate PlaceTokenizer class, so that the PlaceHistoryHandler can GWT.create() an instance of it and call its getToken method. The returned value will them be prepended with the PlaceTokenizer's @Prefix and a colon as a separator, and the resulting String is used as the history token.

Using a factory for your PlaceTokenizer

There are times where your PlaceTokenizer has a dependency on other objects, but there's no place for customization in the above algorithm, instantiating a PlaceTokenizer every time they need one and throwing it away right after that. Here comes the PlaceHistoryHandlerWithFactory: you'll inherit from this interface instead of PlaceHistoryHandler and then call setFactory with a factory (or provider) of PlaceTokenizers right before initializing it.

PlaceHistoryHandlerWithFactory takes a generic type argument, which the code generator will introspect to collect all zero-argument methods whose return type is assignable to PlaceTokenizer. The generated class will call these methods instead of using GWT.create() when it needs a PlaceTokenizer; the methods can also be annotated with @Prefix to override the annotation possibly present on the PlaceTokenizer subclass; finally, PlaceTokenizer classes that your factory can provide mustn't be specified in the @WithTokenizer annotation, which therefore becomes optional.

Your factory can really be any class or interface, which means you can even use a Ginjector if you're using GIN for dependency-injection.

Using your own mapper, without PlaceTokenizer

You're actually not required to GWT.create() a ProxyHistoryMapper, and rely on the generated code based on PlaceTokenizers, you can very well implement the interface yourself, the way you want it. And not only it is technically possible, but it is a supported use case (believe me on this one, I'm the one who submitted the patch).

What's next?

The next article will be about GWT 2.1 activities, which sits above places, abstracting events' handling, and is probably what the GWT team call the "MVP framework" even though, actually, it helps in doing MVP but doesn't enforce its use, and you'll probably also need to build MVP components that are not activities… Still, GWT 2.1 activities are a wonderful API to work with, stay tuned!