»
January 04, 2010
»

GWT History Manager Almost Done

The same I wrote few days ago. Now with better API:

// One actual "place" in app. This class can handle some events and `invoke` itself 
// then handle it's place request no matter if it comes from History string token or 
// place change in app.
@Singleton
public class ShowPersonPlace implements Place {

  private final PresenterProvider<PeoplePresenter> people;
  private final PresenterProvider<ShowPresenter> show;

  @Inject
  public ShowPersonPlace(PresenterProviderFactory f, Provider<PeoplePresenter> people, 
      Provider<ShowPresenter> show) {
    // PresenterProvider allows reuse of already bound presenter. More on this later.
    this.people = f.create(PeoplePresenter.class, people);
    this.show = f.create(ShowPresenter.class, show);
  }

  public String getPattern() {
    // matches for example "/person/show/zeeba" history token and 
    // creates that token from {"key"=>"zeeba"} parameters
    return "/person/show/{key}";
  }

  public boolean isPatternAbsolute() {
    // Will be useful when parent-child relationships will be implemented in Place.
    // Place will be able to use <parent pattern> + <this pattern> for matching.
    return true;
  }

  // The "application" side of history. When application fires some event this Place can
  // handle it and cause History.newItem be called.
  public void bind(final PlaceBindingContext binding) {
    binding.addHandler(ListClickEvent.getType(), new ListClickHandler() {
      public void onListClick(ListClickEvent event) {
        navigateTo(binding, event.getKey());
      }
    });

    binding.addHandler(ShowPersonEvent.getType(), new ShowPersonHandler() {
      public void onShowPerson(ShowPersonEvent event) {
        navigateTo(binding, event.getKey());
      }
    });
  }

  private void navigateTo(PlaceBindingContext binding, String key) {
    // Takes current history state (no matter if its this Place or some other) and 
    // overwrites parameter(s) then invokes it what calls History.newItem under the hood.
    binding.state().set("key", key).invoke();
  }

  public void unbind() {
    // binding.addHandler takes care of HandlerRegistration but if there's need to 
    // unbind something else, this can be done here.
  }

  // This method is called when History state has changed and requested 
  // token matches given Place.
  public void invoke(final PlaceInvocationContext invocation) {
    // if place pattern has parameters in it (/foo/{bar} -- bar is parameter)
    final String key = invocation.get("key");

    // Events with Command on completion (sometimes wraps DeferredCommand to 
    // escape current event execution stack).
    // fireEvent method is delegate to EventBus. here just to simplify things a little.
    invocation.fireEvent(new WorkspaceSetContentEvent(people, new Command() {
      public void execute() {
        // this "people" will come from somewhere when 
        // parent-child relationships will be implemented
        invocation.fireEvent(new MenuSetActiveEvent("people"));
        invocation.fireEvent(new PeopleSetContentEvent(show, new Command() {
          public void execute() {
            invocation.fireEvent(new ListSetActiveEvent(key));
            invocation.fireEvent(new PersonShowEvent(key));
          }
        }));
      }
    }));
  }

}

On my TODO list:

  • Place parent-child relationships (all Place implementations shouldn’t fire WorkspaceSetContentEvent for example)
  • 100% Test coverage
  • Nice AbstractPlace class what implements Place interface but allows accessing PlaceBindingContext and PlaceInvocationContext methods directly.
  • Release as a module in bones library.

Live demo

 
Internet Explorer 6
Are you serious?