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} */