/*
* Copyright 2012 Cedric Hauber
*
* 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 org.jboss.errai.mvp.client.proxy;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.user.client.Command;
import com.google.web.bindery.event.shared.EventBus;
import org.jboss.errai.mvp.client.events.*;
import org.jboss.errai.mvp.client.places.Place;
import org.jboss.errai.mvp.client.places.PlaceManager;
import org.jboss.errai.mvp.client.places.PlaceRequest;
import org.jboss.errai.mvp.client.presenters.Presenter;
/**
* A useful mixing class to define a {@link Proxy} that is also a {@link Place}.
* You can usually inherit from the simpler form {@link ProxyPlace}.
* <p />
*
* @param <P> The Presenter's type.
* @param <Proxy_> Type of the associated {@link Proxy}.
*
* @author David Peterson
* @author Philippe Beaudoin
* @author Christian Goudreau
*/
public class ProxyPlaceAbstract<P extends Presenter<?>, Proxy_ extends Proxy<P>>
implements ProxyPlace<P> {
protected Place place;
protected PlaceManager placeManager;
protected Proxy_ proxy;
private EventBus eventBus;
/**
* Creates a {@link ProxyPlaceAbstract}. That is, the {@link Proxy} of a
* {@link Presenter} attached to a {@link Place}. This presenter can be
* invoked by setting a history token that matches its name token in the URL
* bar.
*/
public ProxyPlaceAbstract(Proxy_ proxy, Place place) {
this.proxy = proxy;
this.place = place;
}
@Override
public boolean canReveal() {
return place.canReveal();
}
// /////////////////////
// Inherited from Proxy
@Override
public final boolean equals(Object o) {
return place.equals(o);
}
@Override
public void fireEvent(GwtEvent<?> event) {
getEventBus().fireEventFromSource(event, this);
}
@Override
public final EventBus getEventBus() {
return eventBus;
}
@Override
public String getNameToken() {
return place.getNameToken();
}
// /////////////////////
// Inherited from Place
@Override
public void getPresenter(NotifyingAsyncCallback<P> callback) {
proxy.getPresenter(callback);
}
@Override
public Class<P> getPresenterClass() {
return proxy.getPresenterClass();
}
@Override
public final int hashCode() {
return place.hashCode();
}
@Override
public boolean matchesRequest(PlaceRequest request) {
return place.matchesRequest(request);
}
// /////////////////////
// Protected methods that can be overridden
@Override
public final String toString() {
return place.toString();
}
// /////////////////////
// Private methods
/**
* Injects the various resources and performs other bindings.
* <p />
* Never call directly, it should only be called by GIN. Method injection is
* used instead of constructor injection, because the latter doesn't work well
* with GWT generators.
*
* @param placeManager The {@link PlaceManager}.
* @param eventBus The {@link com.google.web.bindery.event.shared.EventBus}.
*/
public void bind(final PlaceManager placeManager, EventBus eventBus) {
this.eventBus = eventBus;
this.placeManager = placeManager;
eventBus.addHandler(PlaceRequestInternalEvent.getType(),
new PlaceRequestInternalHandler() {
@Override
public void onPlaceRequest(PlaceRequestInternalEvent event) {
if (event.isHandled()) {
return;
}
PlaceRequest request = event.getRequest();
if (matchesRequest(request)) {
event.setHandled();
if (canReveal()) {
handleRequest(request, event.shouldUpdateBrowserHistory());
} else {
event.setUnauthorized();
}
}
}
});
eventBus.addHandler(GetPlaceTitleEvent.getType(),
new GetPlaceTitleHandler() {
@Override
public void onGetPlaceTitle(GetPlaceTitleEvent event) {
if (event.isHandled()) {
return;
}
PlaceRequest request = event.getRequest();
if (matchesRequest(request)) {
if (canReveal()) {
event.setHandled();
getPlaceTitle(event);
}
}
}
});
}
/**
* Obtains the title for this place and invoke the passed handler when the
* title is available. By default, places don't have a title and will invoke
* the handler with {@code null}, but override this method to provide your own
* title.
*
* @param event The {@link GetPlaceTitleEvent} to invoke once the title is
* available.
*/
protected void getPlaceTitle(GetPlaceTitleEvent event) {
event.getHandler().onSetPlaceTitle(null);
}
/**
* Prepares the presenter with the information contained in the current
* request, then reveals it. Will refuse to reveal the display and do nothing
* if {@link #canReveal()} returns <code>false</code>.
*
* @param request The request to handle. Can pass <code>null</code> if no
* request is used, in which case the presenter will be directly
* revealed.
* @param updateBrowserUrl {@code true} If the browser URL should be updated, {@code false}
* otherwise.
*/
private void handleRequest(final PlaceRequest request, final boolean updateBrowserUrl) {
proxy.getPresenter(new NotifyingAsyncCallback<P>(eventBus) {
@Override
public void success(final P presenter) {
// Everything should be bound before we prepare the presenter from the
// request,
// in case it wants to fire some events. That's why we will do this in a
// deferred command.
addDeferredCommand(new Command() {
@Override
public void execute() {
PlaceRequest originalRequest = placeManager.getCurrentPlaceRequest();
presenter.prepareFromRequest(request);
if (originalRequest == placeManager.getCurrentPlaceRequest()) {
// User did not manually update place request in prepareFromRequest, update it here.
placeManager.updateHistory(request, updateBrowserUrl);
}
NavigationEvent.fire(placeManager, request);
if (!presenter.useManualReveal()) {
// Automatic reveal
manualReveal(presenter);
}
}
});
}
});
}
@Override
public void manualReveal(Presenter<?> presenter) {
// Reveal only if there are no pending navigation requests
if (!placeManager.hasPendingNavigation()) {
if (!presenter.isVisible()) {
// This will trigger a reset in due time
presenter.forceReveal();
} else {
// We have to do the reset ourselves
ResetPresentersEvent.fire(this);
}
}
placeManager.unlock();
}
@Override
public void manualRevealFailed() {
placeManager.unlock();
}
/**
* This method allows unit test to handle deferred command with a mechanism that doesn't
* require a GWTTestCase.
*
* @param command The {@Command} to defer, see {@link com.google.gwt.user.client.DeferredCommand}.
*/
void addDeferredCommand(Command command) {
Scheduler.get().scheduleDeferred(command);
}
}