/* * Copyright 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.google.gwt.place.shared; import com.google.gwt.core.client.GWT; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.event.shared.LegacyHandlerWrapper; import com.google.gwt.user.client.History; import com.google.web.bindery.event.shared.EventBus; import com.google.web.bindery.event.shared.HandlerRegistration; import java.util.logging.Logger; /** * Monitors {@link PlaceChangeEvent}s and * {@link com.google.gwt.user.client.History} events and keep them in sync. */ public class PlaceHistoryHandler { private static final Logger log = Logger.getLogger(PlaceHistoryHandler.class.getName()); /** * Default implementation of {@link Historian}, based on {@link History}. */ public static class DefaultHistorian implements Historian { public com.google.gwt.event.shared.HandlerRegistration addValueChangeHandler( ValueChangeHandler<String> valueChangeHandler) { return History.addValueChangeHandler(valueChangeHandler); } public String getToken() { return History.getToken(); } public void newItem(String token, boolean issueEvent) { History.newItem(token, issueEvent); } } /** * Optional delegate in charge of History related events. Provides nice * isolation for unit testing, and allows pre- or post-processing of tokens. * Methods correspond to the like named methods on {@link History}. */ public interface Historian { /** * Adds a {@link com.google.gwt.event.logical.shared.ValueChangeEvent} * handler to be informed of changes to the browser's history stack. * * @param valueChangeHandler the handler * @return the registration used to remove this value change handler */ com.google.gwt.event.shared.HandlerRegistration addValueChangeHandler( ValueChangeHandler<String> valueChangeHandler); /** * @return the current history token. */ String getToken(); /** * Adds a new browser history entry. Calling this method will cause * {@link ValueChangeHandler#onValueChange(com.google.gwt.event.logical.shared.ValueChangeEvent)} * to be called as well. */ void newItem(String token, boolean issueEvent); } private final Historian historian; private final PlaceHistoryMapper mapper; private PlaceController placeController; private Place defaultPlace = Place.NOWHERE; /** * Create a new PlaceHistoryHandler with a {@link DefaultHistorian}. The * DefaultHistorian is created via a call to GWT.create(), so an alternative * default implementation can be provided through <replace-with> rules * in a {@code gwt.xml} file. * * @param mapper a {@link PlaceHistoryMapper} instance */ public PlaceHistoryHandler(PlaceHistoryMapper mapper) { this(mapper, (Historian) GWT.create(DefaultHistorian.class)); } /** * Create a new PlaceHistoryHandler. * * @param mapper a {@link PlaceHistoryMapper} instance * @param historian a {@link Historian} instance */ public PlaceHistoryHandler(PlaceHistoryMapper mapper, Historian historian) { this.mapper = mapper; this.historian = historian; } /** * Handle the current history token. Typically called at application start, to * ensure bookmark launches work. */ public void handleCurrentHistory() { handleHistoryToken(historian.getToken()); } /** * Legacy method tied to the old location for {@link EventBus}. * * @deprecated use {@link #register(PlaceController, EventBus, Place)} */ @Deprecated public com.google.gwt.event.shared.HandlerRegistration register(PlaceController placeController, com.google.gwt.event.shared.EventBus eventBus, Place defaultPlace) { return new LegacyHandlerWrapper(register(placeController, (EventBus) eventBus, defaultPlace)); } /** * Initialize this place history handler. * * @return a registration object to de-register the handler */ public HandlerRegistration register(PlaceController placeController, EventBus eventBus, Place defaultPlace) { this.placeController = placeController; this.defaultPlace = defaultPlace; final HandlerRegistration placeReg = eventBus.addHandler(PlaceChangeEvent.TYPE, new PlaceChangeEvent.Handler() { public void onPlaceChange(PlaceChangeEvent event) { Place newPlace = event.getNewPlace(); historian.newItem(tokenForPlace(newPlace), false); } }); final HandlerRegistration historyReg = historian.addValueChangeHandler(new ValueChangeHandler<String>() { public void onValueChange(ValueChangeEvent<String> event) { String token = event.getValue(); handleHistoryToken(token); } }); return new HandlerRegistration() { public void removeHandler() { PlaceHistoryHandler.this.defaultPlace = Place.NOWHERE; PlaceHistoryHandler.this.placeController = null; placeReg.removeHandler(); historyReg.removeHandler(); } }; } /** * Visible for testing. */ Logger log() { return log; } private void handleHistoryToken(String token) { Place newPlace = null; if ("".equals(token)) { newPlace = defaultPlace; } if (newPlace == null) { newPlace = mapper.getPlace(token); } if (newPlace == null) { log().warning("Unrecognized history token: " + token); newPlace = defaultPlace; } placeController.goTo(newPlace); } private String tokenForPlace(Place newPlace) { if (defaultPlace.equals(newPlace)) { return ""; } String token = mapper.getToken(newPlace); if (token != null) { return token; } log().warning("Place not mapped to a token: " + newPlace); return ""; } }