Revision: 8810
Author: [email protected]
Date: Fri Sep 17 07:40:00 2010
Log: 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

http://code.google.com/p/google-web-toolkit/source/detail?r=8810

Modified:
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/FavoritesWidget.java /trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/NameLabel.java /trunk/user/src/com/google/gwt/editor/client/impl/AbstractEditorDelegate.java /trunk/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java /trunk/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java /trunk/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryEditorDelegate.java /trunk/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java
 /trunk/user/src/com/google/gwt/user/client/ui/Label.java

=======================================
--- /trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/FavoritesWidget.java Wed Sep 15 06:48:28 2010 +++ /trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/FavoritesWidget.java Fri Sep 17 07:40:00 2010
@@ -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());
+      }
+    });
   }
 }
=======================================
--- /trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/NameLabel.java Mon Sep 13 09:30:34 2010 +++ /trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/NameLabel.java Fri Sep 17 07:40:00 2010
@@ -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);
-
-    label.addClickHandler(new ClickHandler() {
+    initWidget(nameEditor);
+
+    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();
     }
=======================================
--- /trunk/user/src/com/google/gwt/editor/client/impl/AbstractEditorDelegate.java Wed Sep 15 06:48:28 2010 +++ /trunk/user/src/com/google/gwt/editor/client/impl/AbstractEditorDelegate.java Fri Sep 17 07:40:00 2010
@@ -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();
     }

@@ -259,6 +276,14 @@
   protected abstract void setEditor(E editor);

   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.
=======================================
--- /trunk/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java Wed Sep 15 02:26:39 2010 +++ /trunk/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java Fri Sep 17 07:40:00 2010
@@ -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();

=======================================
--- /trunk/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java Wed Sep 15 02:26:39 2010 +++ /trunk/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java Fri Sep 17 07:40:00 2010
@@ -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;
=======================================
--- /trunk/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryEditorDelegate.java Wed Sep 15 06:48:28 2010 +++ /trunk/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryEditorDelegate.java Fri Sep 17 07:40:00 2010
@@ -121,4 +121,9 @@
     ((RequestFactoryEditorDelegate<R, S>) subDelegate).initialize(eventBus,
         factory, path, object, subEditor, delegateMap, request);
   }
-}
+
+  @Override
+  protected boolean shouldFlush() {
+    return request != null;
+  }
+}
=======================================
--- /trunk/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java Wed Sep 15 02:26:39 2010 +++ /trunk/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java Fri Sep 17 07:40:00 2010
@@ -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;
@@ -43,6 +42,13 @@
   private P proxy;
   private RequestObject<?> saveRequest;
   private RequestFactory requestFactory;
+
+  /**
+   * Records its arguments.
+   */
+  public void display(P proxy) {
+    this.proxy = proxy;
+  }

   /**
    * Records its arguments.
=======================================
--- /trunk/user/src/com/google/gwt/user/client/ui/Label.java Fri Sep 10 07:37:39 2010 +++ /trunk/user/src/com/google/gwt/user/client/ui/Label.java Fri Sep 17 07:40:00 2010
@@ -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 &lt;div&gt; or &lt;span&gt;
@@ -260,6 +263,10 @@
   public void addMouseWheelListener(MouseWheelListener listener) {
     ListenerWrapper.WrappedMouseWheelListener.add(this, listener);
   }
+
+  public LeafValueEditor<String> asEditor() {
+    return HasTextEditor.of(this);
+  }

   /**
    * {...@inheritdoc}

--
http://groups.google.com/group/Google-Web-Toolkit-Contributors

Reply via email to