/**
* Sencha GXT 3.0.0b - Sencha for GWT
* Copyright(c) 2007-2012, Sencha, Inc.
* licensing@sencha.com
*
* http://www.sencha.com/products/gxt/license/
*/
package com.sencha.gxt.explorer.client.binding;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import javax.validation.ConstraintViolation;
import com.google.gwt.core.client.Callback;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.editor.client.Editor;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.shared.SimpleEventBus;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryEditorDriver;
import com.google.web.bindery.requestfactory.shared.Receiver;
import com.google.web.bindery.requestfactory.shared.RequestContext;
import com.google.web.bindery.requestfactory.shared.ServerFailure;
import com.sencha.gxt.core.client.ValueProvider;
import com.sencha.gxt.data.shared.ModelKeyProvider;
import com.sencha.gxt.data.shared.PropertyAccess;
import com.sencha.gxt.data.shared.TreeStore;
import com.sencha.gxt.data.shared.loader.ChildTreeStoreBinding;
import com.sencha.gxt.data.shared.loader.DataProxy;
import com.sencha.gxt.data.shared.loader.TreeLoader;
import com.sencha.gxt.examples.resources.client.images.ExampleImages;
import com.sencha.gxt.examples.resources.shared.ExampleRequestFactory;
import com.sencha.gxt.examples.resources.shared.FolderProxy;
import com.sencha.gxt.examples.resources.shared.FolderRequest;
import com.sencha.gxt.examples.resources.shared.MusicProxy;
import com.sencha.gxt.examples.resources.shared.MusicRequest;
import com.sencha.gxt.examples.resources.shared.NamedProxy;
import com.sencha.gxt.explorer.client.model.Example.Detail;
import com.sencha.gxt.widget.core.client.button.TextButton;
import com.sencha.gxt.widget.core.client.container.FlowLayoutContainer;
import com.sencha.gxt.widget.core.client.event.SelectEvent;
import com.sencha.gxt.widget.core.client.event.SelectEvent.SelectHandler;
import com.sencha.gxt.widget.core.client.form.FieldLabel;
import com.sencha.gxt.widget.core.client.form.FormPanelHelper;
import com.sencha.gxt.widget.core.client.form.TextField;
import com.sencha.gxt.widget.core.client.tree.Tree;
@Detail(name = "RequestFactory Binding", icon = "advancedbinding", category = "Binding", classes = {
NamedProxy.class, FolderProxy.class, MusicProxy.class, ExampleRequestFactory.class, MusicRequest.class,
FolderRequest.class})
public class RequestFactoryBindingExample implements Editor<MusicProxy>, IsWidget, EntryPoint {
interface Driver extends RequestFactoryEditorDriver<MusicProxy, RequestFactoryBindingExample> {
}
private final ExampleRequestFactory rf = GWT.create(ExampleRequestFactory.class);
interface NamedProxyProperties extends PropertyAccess<NamedProxy> {
ModelKeyProvider<NamedProxy> stableId();
ValueProvider<NamedProxy, String> name();
}
private NamedProxyProperties properties = GWT.create(NamedProxyProperties.class);
// This is a custom data proxy, designed to serve as the interface between the
// client's needs and the server's capabilities. If the server and client were
// in complete agreement, it would be possible to write this as a
// RequestFactoryProxy impl
private final DataProxy<NamedProxy, List<NamedProxy>> proxy = new DataProxy<NamedProxy, List<NamedProxy>>() {
@Override
public void load(final NamedProxy loadConfig, final Callback<List<NamedProxy>, Throwable> callback) {
Receiver<List<? extends NamedProxy>> receiver = new Receiver<List<? extends NamedProxy>>() {
@Override
public void onSuccess(List<? extends NamedProxy> response) {
if (response.size() == 0) {
// assuming that only folders OR music will be returned.
return;
}
callback.onSuccess(new ArrayList<NamedProxy>(response));
}
};
if (loadConfig == null) {
rf.folder().getRootFolder().fire(new Receiver<FolderProxy>() {
@Override
public void onSuccess(FolderProxy response) {
callback.onSuccess(Collections.<NamedProxy>singletonList(response));
}
});
} else {
FolderRequest req = rf.folder();
req.getChildren().using((FolderProxy) loadConfig).to(receiver);
req.getSubFolders().using((FolderProxy) loadConfig).to(receiver);
req.fire();
}
}
};
private final TreeLoader<NamedProxy> loader = new TreeLoader<NamedProxy>(proxy) {
public boolean hasChildren(NamedProxy parent) {
return parent instanceof FolderProxy;
}
};
private final TreeStore<NamedProxy> treeStore = new TreeStore<NamedProxy>(properties.stableId());
private Driver driver = GWT.create(Driver.class);
/*
* Fields (i.e. sub-editors) that the editor driver will bind to. If using UI
* binder, these will be created and configured in xml instead of in code.
*/
TextField name;
TextField author;
TextField genre;
private TextButton save;
private HorizontalPanel hp;
public RequestFactoryBindingExample() {
// Using a simple, very local, event bus, just for this simple example.
// Typically this would be an application-wide event bus, so other parts of
// the app can monitor changes made on the server
rf.initialize(new SimpleEventBus());
loader.addLoadHandler(new ChildTreeStoreBinding<NamedProxy>(treeStore));
}
@Override
public Widget asWidget() {
if (hp == null) {
hp = new HorizontalPanel();
hp.setSpacing(10);
final Tree<NamedProxy, String> tree = new Tree<NamedProxy, String>(treeStore, properties.name());
tree.setLoader(loader);
tree.getStyle().setLeafIcon(ExampleImages.INSTANCE.music());
tree.getSelectionModel().addSelectionHandler(new SelectionHandler<NamedProxy>() {
@Override
public void onSelection(SelectionEvent<NamedProxy> event) {
if (event.getSelectedItem() instanceof MusicProxy) {
// When a Music object is selected, edit it
// TODO disallow editing in cases where the last has not been saved?
MusicProxy music = (MusicProxy) event.getSelectedItem();
startEdit(music);
save.setEnabled(true);
} else {
name.setValue("");
author.setValue("");
genre.setValue("");
save.setEnabled(false);
}
}
});
hp.add(tree);
hp.setCellWidth(tree, "260px");
FlowLayoutContainer lc = new FlowLayoutContainer();
// Create the fields
name = new TextField();
name.setWidth(200);
lc.add(new FieldLabel(name, "Name"));
author = new TextField();
author.setWidth(200);
lc.add(new FieldLabel(author, "Author"));
genre = new TextField();
genre.setWidth(200);
lc.add(new FieldLabel(genre, "Genre"));
// Clicking this save button will check for errors and save the request
save = new TextButton("Save");
save.setEnabled(false);
save.addSelectHandler(new SelectHandler() {
@Override
public void onSelect(SelectEvent event) {
RequestContext req = driver.flush();
if (req.isChanged() && !driver.hasErrors()) {
save.setEnabled(false);
req.fire(new Receiver<Void>() {
@Override
public void onSuccess(Void response) {
save.setEnabled(true);
}
@Override
public void onFailure(ServerFailure error) {
save.setEnabled(true);
}
});
}
}
});
lc.add(save);
hp.add(lc);
// Now that all of the sub-editors exist, bind the driver to them
driver.initialize(rf, this);
}
return hp;
}
@Override
public void onModuleLoad() {
RootPanel.get().add(this);
}
private void startEdit(MusicProxy model) {
MusicRequest req = rf.music();
req.persist().using(model).to(new Receiver<MusicProxy>() {
@Override
public void onConstraintViolation(Set<ConstraintViolation<?>> violations) {
if (driver.setConstraintViolations(violations)) {
super.onConstraintViolation(violations);
}
}
@Override
public void onSuccess(MusicProxy response) {
treeStore.update(response);
// must start new edit after flush has been called
// is this correct? without this, request is locked and flush does
// nothing
startEdit(response);
}
});
FormPanelHelper.reset(hp);
driver.edit(model, req);
}
}