Make DynaTableRf use a ListEditor for the favorites.
Fix potential NPE's in AED.Chain.
Widen RequestFactoryEditorDriver's type bound to allow it to drive more than just EntityProxy types.
Patch by: bobv
Review by: rjrjr
Review at http://gwt-code-reviews.appspot.com/888801
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8810 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/FavoritesWidget.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/FavoritesWidget.java
index 997ed6d..605cc69 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/FavoritesWidget.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/FavoritesWidget.java
@@ -16,6 +16,8 @@
package com.google.gwt.sample.dynatablerf.client.widgets;
import com.google.gwt.core.client.GWT;
+import com.google.gwt.editor.client.adapters.EditorSource;
+import com.google.gwt.editor.client.adapters.ListEditor;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.requestfactory.client.RequestFactoryEditorDriver;
@@ -34,8 +36,10 @@
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Widget;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
import java.util.Set;
/**
@@ -47,22 +51,48 @@
interface Binder extends UiBinder<Widget, FavoritesWidget> {
}
- interface Driver extends RequestFactoryEditorDriver<PersonProxy, NameLabel> {
+ interface Driver extends RequestFactoryEditorDriver<List<PersonProxy>, //
+ ListEditor<PersonProxy, NameLabel>> {
}
interface Style extends CssResource {
String favorite();
}
+ /**
+ * This is used by a ListEditor.
+ */
+ private class NameLabelSource extends EditorSource<NameLabel> {
+ @Override
+ public NameLabel create(int index) {
+ NameLabel label = new NameLabel(eventBus);
+ label.setStylePrimaryName(style.favorite());
+ container.insert(label, index);
+ return label;
+ }
+
+ @Override
+ public void dispose(NameLabel subEditor) {
+ subEditor.removeFromParent();
+ subEditor.cancelSubscription();
+ }
+
+ @Override
+ public void setIndex(NameLabel editor, int index) {
+ container.insert(editor, index);
+ }
+ }
+
@UiField
FlowPanel container;
@UiField
Style style;
+
+ private final List<PersonProxy> displayed;
private final EventBus eventBus;
private final RequestFactory factory;
private FavoritesManager manager;
- private final Map<EntityProxyId, NameLabel> map = new HashMap<EntityProxyId, NameLabel>();
private HandlerRegistration subscription;
public FavoritesWidget(EventBus eventBus, RequestFactory factory,
@@ -70,7 +100,27 @@
this.eventBus = eventBus;
this.factory = factory;
this.manager = manager;
+
+ // Create the UI
initWidget(GWT.<Binder> create(Binder.class).createAndBindUi(this));
+
+ // Create the driver which manages the data-bound widgets
+ Driver driver = GWT.<Driver> create(Driver.class);
+
+ // Use a ListEditor that uses our NameLabelSource
+ ListEditor<PersonProxy, NameLabel> editor = ListEditor.of(new NameLabelSource());
+
+ // Configure the driver
+ ListEditor<PersonProxy, NameLabel> listEditor = editor;
+ driver.initialize(eventBus, factory, listEditor);
+
+ /*
+ * Notice the backing list is essentially anonymous.
+ */
+ driver.display(new ArrayList<PersonProxy>());
+
+ // Modifying this list triggers widget creation and destruction
+ displayed = listEditor.getList();
}
@Override
@@ -103,21 +153,16 @@
}
if (event.isFavorite()) {
- if (!map.containsKey(person.stableId())) {
- NameLabel label = new NameLabel(eventBus);
- Driver driver = GWT.create(Driver.class);
- driver.initialize(eventBus, factory, label);
- driver.edit(person, null);
- label.setStylePrimaryName(style.favorite());
-
- container.add(label);
- map.put(person.stableId(), label);
- }
+ displayed.add(person);
} else {
- NameLabel toRemove = map.remove(person.stableId());
- if (toRemove != null) {
- container.remove(toRemove);
- }
+ displayed.remove(person);
}
+
+ // Sorting the list of PersonProxies will also change the UI display
+ Collections.sort(displayed, new Comparator<PersonProxy>() {
+ public int compare(PersonProxy o1, PersonProxy o2) {
+ return o1.getName().compareTo(o2.getName());
+ }
+ });
}
}
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/NameLabel.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/NameLabel.java
index d9063b1..3bea339 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/NameLabel.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/NameLabel.java
@@ -17,7 +17,6 @@
import com.google.gwt.editor.client.EditorDelegate;
import com.google.gwt.editor.client.ValueAwareEditor;
-import com.google.gwt.editor.client.adapters.HasTextEditor;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.shared.EventBus;
@@ -32,15 +31,14 @@
* the displayed object.
*/
class NameLabel extends Composite implements ValueAwareEditor<PersonProxy> {
- private final Label label = new Label();
- final HasTextEditor nameEditor = HasTextEditor.of(label);
+ final Label nameEditor = new Label();
private PersonProxy person;
private HandlerRegistration subscription;
public NameLabel(final EventBus eventBus) {
- initWidget(label);
+ initWidget(nameEditor);
- label.addClickHandler(new ClickHandler() {
+ nameEditor.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
eventBus.fireEvent(new EditPersonEvent(person));
}
@@ -55,6 +53,7 @@
}
public void setDelegate(EditorDelegate<PersonProxy> delegate) {
+ assert subscription == null;
subscription = delegate.subscribe();
}
@@ -63,11 +62,10 @@
}
/**
- * Unhook event notifications when not attached to the DOM.
+ * Unhook event notifications when being permanently disposed of by
+ * FavoritesWidget.
*/
- @Override
- protected void onUnload() {
- super.onUnload();
+ protected void cancelSubscription() {
if (subscription != null) {
subscription.removeHandler();
}
diff --git a/user/src/com/google/gwt/editor/client/impl/AbstractEditorDelegate.java b/user/src/com/google/gwt/editor/client/impl/AbstractEditorDelegate.java
index e200642..5af1c44 100644
--- a/user/src/com/google/gwt/editor/client/impl/AbstractEditorDelegate.java
+++ b/user/src/com/google/gwt/editor/client/impl/AbstractEditorDelegate.java
@@ -52,21 +52,38 @@
}
public void attach(R object, S subEditor) {
- AbstractEditorDelegate<R, S> subDelegate = createComposedDelegate();
- map.put(subEditor, subDelegate);
+ AbstractEditorDelegate<R, S> subDelegate = map.get(subEditor);
+
@SuppressWarnings("unchecked")
Editor<Object> temp = (Editor<Object>) subEditor;
- initializeSubDelegate(subDelegate, path
- + composedEditor.getPathElement(temp), object, subEditor, delegateMap);
+ String subPath = path + composedEditor.getPathElement(temp);
+
+ if (subDelegate == null) {
+ subDelegate = createComposedDelegate();
+ map.put(subEditor, subDelegate);
+ initializeSubDelegate(subDelegate, subPath, object, subEditor,
+ delegateMap);
+ } else {
+ subDelegate.path = subPath;
+ subDelegate.refresh(object);
+ }
}
public void detach(S subEditor) {
- map.remove(subEditor).flush(errors);
+ AbstractEditorDelegate<R, S> subDelegate = map.remove(subEditor);
+ if (subDelegate != null && subDelegate.shouldFlush()) {
+ subDelegate.flush(errors);
+ }
}
public R getValue(S subEditor) {
AbstractEditorDelegate<R, S> subDelegate = map.get(subEditor);
- subDelegate.flush(errors);
+ if (subDelegate == null) {
+ return null;
+ }
+ if (subDelegate.shouldFlush()) {
+ subDelegate.flush(errors);
+ }
return subDelegate.getObject();
}
@@ -261,6 +278,14 @@
protected abstract void setObject(T object);
/**
+ * Indicates whether or not calls to {@link #flush} are expected as part of
+ * normal operation.
+ */
+ protected boolean shouldFlush() {
+ return true;
+ }
+
+ /**
* Collect all paths being edited.
*/
protected abstract void traverse(List<String> paths);
diff --git a/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java b/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java
index 4179cbd..1611d1e 100644
--- a/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java
+++ b/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java
@@ -18,7 +18,6 @@
import com.google.gwt.editor.client.Editor;
import com.google.gwt.editor.client.EditorError;
import com.google.gwt.event.shared.EventBus;
-import com.google.gwt.requestfactory.shared.EntityProxy;
import com.google.gwt.requestfactory.shared.RequestFactory;
import com.google.gwt.requestfactory.shared.RequestObject;
import com.google.gwt.requestfactory.shared.Violation;
@@ -53,7 +52,12 @@
* @param <E> the type of Editor that will edit the Record
* @see com.google.gwt.requestfactory.client.testing.MockRequestFactoryEditorDriver
*/
-public interface RequestFactoryEditorDriver<P extends EntityProxy, E extends Editor<? super P>> {
+public interface RequestFactoryEditorDriver<P, E extends Editor<? super P>> {
+ /**
+ * Initialize the Editor and its sub-editors with data for display-only mode.
+ */
+ void display(P proxy);
+
/**
* Initialize the Editor and its sub-editors with data.
*/
@@ -65,6 +69,8 @@
* in a depth-first manner.
*
* @return the RequestObject passed into {@link #edit}
+ * @throws IllegalStateException if {@link #edit(Object, RequestObject)} has
+ * not been called with a non-null {@link RequestObject}
*/
<T> RequestObject<T> flush();
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
index ce6a8f3..35ae178 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
@@ -35,7 +35,7 @@
* @param <R> the type of Record
* @param <E> the type of Editor
*/
-public abstract class AbstractRequestFactoryEditorDriver<R extends EntityProxy, E extends Editor<R>>
+public abstract class AbstractRequestFactoryEditorDriver<R, E extends Editor<R>>
implements RequestFactoryEditorDriver<R, E> {
private static final DelegateMap.KeyMethod PROXY_ID_KEY = new DelegateMap.KeyMethod() {
@@ -56,6 +56,10 @@
private RequestFactory requestFactory;
private RequestObject<?> saveRequest;
+ public void display(R object) {
+ edit(object, null);
+ }
+
public void edit(R object, RequestObject<?> saveRequest) {
checkEditor();
this.saveRequest = saveRequest;
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryEditorDelegate.java b/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryEditorDelegate.java
index 80382cc..4ae8773 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryEditorDelegate.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryEditorDelegate.java
@@ -121,4 +121,9 @@
((RequestFactoryEditorDelegate<R, S>) subDelegate).initialize(eventBus,
factory, path, object, subEditor, delegateMap, request);
}
+
+ @Override
+ protected boolean shouldFlush() {
+ return request != null;
+ }
}
diff --git a/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java b/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java
index 452ea84..3dbe060 100644
--- a/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java
+++ b/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java
@@ -19,7 +19,6 @@
import com.google.gwt.editor.client.EditorError;
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.RequestFactory;
import com.google.gwt.requestfactory.shared.RequestObject;
import com.google.gwt.requestfactory.shared.Violation;
@@ -34,8 +33,8 @@
* @param <P> the Proxy type being edited
* @param <E> the Editor type
*/
-public class MockRequestFactoryEditorDriver<P extends EntityProxy, E extends Editor<P>>
- implements RequestFactoryEditorDriver<P, E> {
+public class MockRequestFactoryEditorDriver<P, E extends Editor<P>> implements
+ RequestFactoryEditorDriver<P, E> {
private static final String[] EMPTY_STRING = new String[0];
private EventBus eventBus;
@@ -47,6 +46,13 @@
/**
* Records its arguments.
*/
+ public void display(P proxy) {
+ this.proxy = proxy;
+ }
+
+ /**
+ * Records its arguments.
+ */
public void edit(P proxy, RequestObject<?> saveRequest) {
this.proxy = proxy;
this.saveRequest = saveRequest;
diff --git a/user/src/com/google/gwt/user/client/ui/Label.java b/user/src/com/google/gwt/user/client/ui/Label.java
index 9099a7f..340cafa 100644
--- a/user/src/com/google/gwt/user/client/ui/Label.java
+++ b/user/src/com/google/gwt/user/client/ui/Label.java
@@ -17,6 +17,9 @@
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
+import com.google.gwt.editor.client.IsEditor;
+import com.google.gwt.editor.client.LeafValueEditor;
+import com.google.gwt.editor.client.adapters.HasTextEditor;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.DoubleClickEvent;
@@ -64,7 +67,7 @@
public class Label extends Widget implements HasDirectionalText, HasWordWrap,
HasDirection, HasClickHandlers, HasDoubleClickHandlers, SourcesClickEvents,
SourcesMouseEvents, HasAllMouseHandlers, HasDirectionEstimator,
- HasAutoHorizontalAlignment {
+ HasAutoHorizontalAlignment, IsEditor<LeafValueEditor<String>> {
/**
* Creates a Label widget that wraps an existing <div> or <span>
@@ -261,6 +264,10 @@
ListenerWrapper.WrappedMouseWheelListener.add(this, listener);
}
+ public LeafValueEditor<String> asEditor() {
+ return HasTextEditor.of(this);
+ }
+
/**
* {@inheritDoc}
*/