package org.activityinfo.ui.client.widget;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.Widget;
import org.activityinfo.promise.Promise;
import org.activityinfo.ui.client.widget.loading.LoadingPanelView;
import org.activityinfo.ui.client.widget.loading.LoadingState;
import org.activityinfo.ui.client.widget.loading.PageLoadingPanel;
import javax.annotation.Nullable;
import javax.inject.Provider;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* SimpleLayoutPanel for widgets that need to be asynchronously
* created
*/
public class LoadingPanel<V> implements IsWidget {
private static final Logger LOGGER = Logger.getLogger(LoadingPanel.class.getName());
/**
* We get confused when things fail to quickly. I mean really, it's like you're not
* even trying...
*/
public static final int DELAY_MS = 1000;
/**
* A function which provides the widget based on the resolved value
*/
private Function<? super V, ? extends DisplayWidget<? super V>> widgetProvider;
/**
* Provides the value to be displayed by the display widget. We use a provider so that
* can retry if it fails at first.
*/
private Provider<Promise<V>> valueProvider;
private final LoadingPanelView loadingView;
private int currentRequestNumber = 0;
private HandlerRegistration retryHandler;
public LoadingPanel() {
this(new PageLoadingPanel());
}
public LoadingPanel(PageLoadingPanel view) {
this.loadingView = view;
}
public void setDisplayWidget(DisplayWidget<? super V> widget) {
this.widgetProvider = Functions.constant(widget);
}
public void setDisplayWidgetProvider(Function<V, ? extends DisplayWidget<? super V>> function) {
this.widgetProvider = function;
}
public Promise<Void> show(Provider<Promise<V>> provider) {
this.valueProvider = provider;
return tryLoad();
}
public <T> Promise<Void> show(final Function<T, Promise<V>> function, final T argument) {
return show(new Provider<Promise<V>>() {
@Override
public Promise<V> get() {
return function.apply(argument);
}
});
}
private Promise<Void> tryLoad() {
Promise<V> promisedValue = valueProvider.get();
// make sure we only react to the last request submitted...
final int requestNumber = currentRequestNumber+1;
this.currentRequestNumber = requestNumber;
loadingView.onLoadingStateChanged(LoadingState.LOADING, null);
Promise<Void> loadResult = promisedValue.then(new Function<V, Void>() {
@Override
public Void apply(@Nullable V result) {
if (requestNumber == currentRequestNumber) {
try {
showWidget(requestNumber, result);
} catch (Throwable e) {
showLoadFailure(requestNumber, e);
}
}
return null;
}
});
// handle failure (including the failure of the show() method of our
// display widget)
loadResult.then(new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
if (requestNumber == currentRequestNumber) {
showLoadFailure(requestNumber, caught);
}
}
@Override
public void onSuccess(Void result) {
// already handled above
}
});
return loadResult;
}
private void showLoadFailure(final int requestNumber, final Throwable caught) {
LOGGER.log(Level.SEVERE, "Load failed", caught);
// the failure may have been caught upstream
if(!loadingView.asWidget().isAttached()) {
return;
}
if(retryHandler == null) {
retryHandler = loadingView.getRetryButton().addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
tryLoad();
}
});
}
Scheduler.get().scheduleFixedDelay(new Scheduler.RepeatingCommand() {
@Override
public boolean execute() {
loadingView.onLoadingStateChanged(LoadingState.FAILED, caught);
return false;
}
}, DELAY_MS);
}
private void setWidgetWithDelay(final int requestNumber, final IsWidget widget) {
Scheduler.get().scheduleFixedDelay(new Scheduler.RepeatingCommand() {
@Override
public boolean execute() {
if(requestNumber == currentRequestNumber) {
loadingView.setWidget(widget);
}
return false;
}
}, DELAY_MS);
}
private void showWidget(final int requestNumber, V result) {
assert widgetProvider != null : "No widget/provider has been set!";
try {
final DisplayWidget<? super V> displayWidget = widgetProvider.apply(result);
displayWidget.show(result).then(new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
showLoadFailure(requestNumber, caught);
}
@Override
public void onSuccess(Void result) {
setWidgetWithDelay(requestNumber, displayWidget);
}
});
} catch(Throwable caught) {
showLoadFailure(requestNumber, caught);
}
}
@Override
public Widget asWidget() {
return loadingView.asWidget();
}
}