blob: 4800b67982c31523adca63729a64fa09e4bf1227 [file] [log] [blame]
/*
* 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.app.place;
import com.google.gwt.app.place.ProxyPlace.Operation;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.requestfactory.client.RequestFactoryEditorDriver;
import com.google.gwt.requestfactory.shared.EntityProxy;
import com.google.gwt.requestfactory.shared.EntityProxyId;
import com.google.gwt.requestfactory.shared.Receiver;
import com.google.gwt.requestfactory.shared.Request;
import com.google.gwt.requestfactory.shared.RequestFactory;
import com.google.gwt.requestfactory.shared.ServerFailure;
import com.google.gwt.requestfactory.shared.Violation;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import java.util.Set;
/**
* <p>
* <span style="color:red">Experimental API: This class is still under rapid
* development, and is very likely to be deleted. Use it at your own risk.
* </span>
* </p>
* Abstract activity for editing a record.
*
* @param <P> the type of Proxy being edited
*/
public abstract class AbstractProxyEditActivity<P extends EntityProxy>
implements Activity, ProxyEditView.Delegate {
private final boolean creating;
private final RequestFactory requests;
private final PlaceController placeController;
private RequestFactoryEditorDriver<P, ?> editorDriver;
private final ProxyEditView<P, ?> view;
private AcceptsOneWidget display;
private EntityProxyId<P> proxyId;
private Class<P> proxyClass;
private boolean waiting = false;
/**
* Create an activity to edit or create proxy. Must provide either a proxyId
* (to be edited) or a proxyClass (to create a new proxy). If proxyId is
* provided, proxyClass will be ignored.
*/
protected AbstractProxyEditActivity(EntityProxyId<P> proxyId,
Class<P> proxyClass, RequestFactory requests,
PlaceController placeController, ProxyEditView<P, ?> view) {
if (proxyId == null && proxyClass == null) {
throw new IllegalArgumentException(
"Must provide either proxyId or proxyClass");
}
this.creating = proxyId == null;
this.proxyClass = proxyClass;
this.proxyId = proxyId;
this.placeController = placeController;
this.requests = requests;
this.view = view;
editorDriver = view.createEditorDriver(null, requests);
}
public void cancelClicked() {
String unsavedChangesWarning = mayStop();
if ((unsavedChangesWarning == null)
|| Window.confirm(unsavedChangesWarning)) {
editorDriver = null;
exit(false);
}
}
public EntityProxyId<P> getEntityProxyId() {
return proxyId;
}
public ProxyEditView<P, ?> getView() {
return view;
}
public boolean isCreating() {
return creating;
}
public String mayStop() {
if (isWaiting()
|| (editorDriver != null && editorDriver.flush().isChanged())) {
return "Are you sure you want to abandon your changes?";
}
return null;
}
public void onCancel() {
onStop();
}
public void onStop() {
this.display = null;
}
public void saveClicked() {
assert editorDriver != null;
Request<Object> request = editorDriver.flush();
if (!request.isChanged()) {
return;
}
setWaiting(true);
request.fire(new Receiver<Object>() {
// Do nothing if display is null, we were stopped in midflight
@Override
public void onFailure(ServerFailure error) {
if (display != null) {
setWaiting(false);
super.onFailure(error);
}
}
@Override
public void onSuccess(Object ignore) {
if (display != null) {
// We want no warnings from mayStop, so:
// Defeats isChanged check
editorDriver = null;
// Defeats call-in-flight check
setWaiting(false);
exit(true);
}
}
@Override
public void onViolation(Set<Violation> errors) {
if (display != null) {
setWaiting(false);
editorDriver.setViolations(errors);
}
}
});
}
public void start(AcceptsOneWidget startDisplay, EventBus eventBus) {
this.display = startDisplay;
view.setDelegate(this);
view.setCreating(isCreating());
/*
* Lock ourselves until we actually have a proxy to edit
*/
setWaiting(true);
if (isCreating()) {
P newRecord = requests.create(proxyClass);
proxyId = getProxyId(newRecord);
doStart(newRecord);
} else {
Request<P> findRequest = requests.find(getEntityProxyId());
findRequest.with(editorDriver.getPaths()).fire(new Receiver<P>() {
@Override
public void onSuccess(P proxy) {
if (display != null) {
doStart(proxy);
}
}
});
}
}
/**
* Called when the user cancels or has successfully saved. This default
* implementation tells the {@link PlaceController} to show the details of the
* edited record, or clears the display if a creation was canceled.
* <p>
* Call {@link getId()} for the id of the proxy that has been edited or
* created.
*
* @param saved true if changes were comitted
*/
protected void exit(boolean saved) {
if (!saved && isCreating()) {
display.setWidget(null);
} else {
placeController.goTo(new ProxyPlace(proxyId, Operation.DETAILS));
}
}
protected abstract Request<?> getPersistRequest(P proxy);
private void doStart(P proxy) {
setWaiting(false);
Request<?> request = getPersistRequest(proxy);
editorDriver.edit(proxy, request);
display.setWidget(view);
}
@SuppressWarnings("unchecked")
private EntityProxyId<P> getProxyId(P newRecord) {
/*
* We could make this cast go away if EntityProxy were typed to itself,
* EntityProxy<P extends EntityProxy<P>>, but the ripples this causes
* throughout the API are very, very unpleasant.
*/
return (EntityProxyId<P>) newRecord.stableId();
}
/**
* @return true if we're waiting for an rpc response.
*/
private boolean isWaiting() {
return waiting;
}
/**
* While we are waiting for a response, we cannot poke setters on the proxy
* (that is, we cannot call editorDriver.flush). So we set the waiting flag to
* warn ourselves not to, and to disable the view.
*/
private void setWaiting(boolean wait) {
this.waiting = wait;
view.setEnabled(!wait);
}
}