Cleanups for the DynaTableRf sample.
Resolves GWT issue 5413.
Patch by: bobv
Review by: rjrjr

Review at http://gwt-code-reviews.appspot.com/975801


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9065 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/DynaTableRf.gwt.xml b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/DynaTableRf.gwt.xml
index 4dd6e1d..63af732 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/DynaTableRf.gwt.xml
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/DynaTableRf.gwt.xml
@@ -22,7 +22,7 @@
   
   <inherits name='com.google.gwt.logging.Logging'/>
   <set-property name="gwt.logging.enabled" value="TRUE"/> 
-  <set-property name="gwt.logging.logLevel" value="INFO"/>
+  <set-property name="gwt.logging.logLevel" value="SEVERE"/>
   <set-property name="gwt.logging.consoleHandler" value="ENABLED" />
   <set-property name="gwt.logging.developmentModeHandler" value="ENABLED" />
   <set-property name="gwt.logging.firebugHandler" value="ENABLED" />
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/DynaTableRf.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/DynaTableRf.java
index 4f78598..377e8b2 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/DynaTableRf.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/DynaTableRf.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * 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
@@ -28,7 +28,6 @@
 import com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
-import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.ui.RootLayoutPanel;
 import com.google.gwt.user.client.ui.Widget;
 
@@ -57,10 +56,12 @@
   @UiField(provided = true)
   DayFilterWidget filter;
 
+  /**
+   * This method sets up the top-level services used by the application.
+   */
   public void onModuleLoad() {
     GWT.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
       public void onUncaughtException(Throwable e) {
-        Window.alert("Error: " + e.getMessage());
         log.log(Level.SEVERE, e.getMessage(), e);
       }
     });
@@ -74,6 +75,7 @@
         return requests.loggingRequest();
       }
     };
+    Logger.getLogger("").addHandler(new ErrorDialog().getHandler());
     Logger.getLogger("").addHandler(
         new RequestFactoryLogHandler(provider, Level.WARNING,
             new ArrayList<String>()));
@@ -86,5 +88,13 @@
 
     RootLayoutPanel.get().add(
         GWT.<Binder> create(Binder.class).createAndBindUi(this));
+
+    // Fast test to see if the sample is not being run from devmode
+    if (GWT.getHostPageBaseURL().startsWith("file:")) {
+      log.log(Level.SEVERE, "The DynaTableRf sample cannot be run without its"
+          + " server component.  If you are running the sample from a"
+          + " GWT distribution, use the 'ant devmode' target to launch"
+          + " the DTRF server.");
+    }
   }
 }
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/ErrorDialog.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/ErrorDialog.java
new file mode 100644
index 0000000..63f0f8c
--- /dev/null
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/ErrorDialog.java
@@ -0,0 +1,84 @@
+/*
+ * 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.sample.dynatablerf.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.DialogBox;
+import com.google.gwt.user.client.ui.TextArea;
+
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+/**
+ * A simple glasspanel popup that terminates interaction with the application.
+ */
+class ErrorDialog {
+  interface Binder extends UiBinder<DialogBox, ErrorDialog> {
+  }
+
+  @UiField
+  DialogBox errorDialog;
+
+  @UiField
+  TextArea errorMessage;
+
+  public ErrorDialog() {
+    GWT.<Binder> create(Binder.class).createAndBindUi(this);
+  }
+
+  /**
+   * @return
+   */
+  public Handler getHandler() {
+    return new Handler() {
+      {
+        setLevel(Level.SEVERE);
+      }
+
+      @Override
+      public void close() {
+      }
+
+      @Override
+      public void flush() {
+      }
+
+      @Override
+      public void publish(LogRecord record) {
+        if (isLoggable(record)) {
+          errorMessage.setText(record.getMessage());
+          errorDialog.center();
+        }
+      }
+    };
+  }
+
+  @UiHandler("dismiss")
+  void onDismiss(ClickEvent event) {
+    errorDialog.hide();
+  }
+
+  @UiHandler("reload")
+  void onReload(ClickEvent event) {
+    Window.Location.reload();
+  }
+}
\ No newline at end of file
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/ErrorDialog.ui.xml b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/ErrorDialog.ui.xml
new file mode 100644
index 0000000..5ca904c
--- /dev/null
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/ErrorDialog.ui.xml
@@ -0,0 +1,39 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder' xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+  <ui:style>
+    .dialog {
+    	background: white;
+    	border: thin solid black;
+    	margin: 2px;
+    	overflow: hidden;
+    	padding: 5px;
+    	-moz-border-radius: 5px;
+    	-webkit-border-radius: 5px;
+    }
+    
+    .glass {
+    	filter: literal('alpha(opacity = 75)');
+    	opacity: 0.75;
+    	background-color: #000000;
+    }
+    
+    .message {
+    	height: 400px;
+    	width: 400px;
+    }
+  </ui:style>
+  <g:DialogBox ui:field="errorDialog" glassEnabled="true"
+    stylePrimaryName="{style.dialog}" glassStyleName="{style.glass}">
+    <g:caption>
+      <b>An unexpected application error has occurred</b>
+      <br />
+      You may wish to reload the application
+    </g:caption>
+    <g:HTMLPanel>
+      <g:TextArea ui:field="errorMessage" readOnly="true"
+        stylePrimaryName="{style.message}"></g:TextArea>
+      <br />
+      <g:Button ui:field="dismiss">Continue</g:Button>
+      <g:Button ui:field="reload">Reload</g:Button>
+    </g:HTMLPanel>
+  </g:DialogBox>
+</ui:UiBinder>
\ No newline at end of file
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/FavoritesManager.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/FavoritesManager.java
index b291835..a2696aa 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/FavoritesManager.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/FavoritesManager.java
@@ -82,14 +82,13 @@
     return favoriteIds.contains(person.stableId());
   }
 
-  public void setFavorite(PersonProxy person, boolean isFavorite) {
+  public void setFavorite(EntityProxyId<PersonProxy> id, boolean isFavorite) {
     if (isFavorite) {
-      favoriteIds.add(person.stableId());
+      favoriteIds.add(id);
     } else {
-      favoriteIds.remove(person.stableId());
+      favoriteIds.remove(id);
     }
 
-    eventBus.fireEventFromSource(new MarkFavoriteEvent(person, isFavorite),
-        this);
+    eventBus.fireEventFromSource(new MarkFavoriteEvent(id, isFavorite), this);
   }
 }
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/PersonEditorWorkflow.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/PersonEditorWorkflow.java
index 6e36f94..81dae75 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/PersonEditorWorkflow.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/PersonEditorWorkflow.java
@@ -30,8 +30,8 @@
 import com.google.gwt.sample.dynatablerf.client.events.EditPersonEvent;
 import com.google.gwt.sample.dynatablerf.client.widgets.PersonEditor;
 import com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory;
-import com.google.gwt.sample.dynatablerf.shared.PersonProxy;
 import com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory.PersonRequest;
+import com.google.gwt.sample.dynatablerf.shared.PersonProxy;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.uibinder.client.UiHandler;
@@ -99,38 +99,60 @@
     }, KeyUpEvent.getType());
   }
 
+  /**
+   * Called by the cancel button when it is clicked. This method will just tear
+   * down the UI and clear the state of the workflow.
+   */
   @UiHandler("cancel")
-  void onCancel(@SuppressWarnings("unused") ClickEvent event) {
+  void onCancel(ClickEvent event) {
     dialog.hide();
   }
 
+  /**
+   * Called by the edit dialog's save button. This method will flush the
+   * contents of the UI into the PersonProxy that is being edited, check for
+   * errors, and send the request to the server.
+   */
   @UiHandler("save")
-  void onSave(@SuppressWarnings("unused") ClickEvent event) {
-    // MOVE TO ACTIVITY END
+  void onSave(ClickEvent event) {
+    // Flush the contents of the UI
     RequestContext context = editorDriver.flush();
+
+    // Check for errors
     if (editorDriver.hasErrors()) {
       dialog.setText("Errors detected locally");
       return;
     }
+
+    // Send the request
     context.fire(new Receiver<Void>() {
       @Override
       public void onSuccess(Void response) {
+        // If everything went as planned, just dismiss the dialog box
         dialog.hide();
       }
 
       @Override
       public void onViolation(Set<Violation> errors) {
+        // Otherwise, show ConstraintViolations in the UI
         dialog.setText("Errors detected on the server");
         editorDriver.setViolations(errors);
       }
     });
   }
 
+  /**
+   * Called by the favorite checkbox when its value has been toggled.
+   */
   @UiHandler("favorite")
-  void onValueChanged(@SuppressWarnings("unused") ValueChangeEvent<Boolean> event) {
-    manager.setFavorite(person, favorite.getValue());
+  void onValueChanged(ValueChangeEvent<Boolean> event) {
+    manager.setFavorite(person.stableId(), favorite.getValue());
   }
 
+  /**
+   * Construct and display the UI that will be used to edit the current
+   * PersonProxy, using the given RequestContext to accumulate the edits.
+   */
   private void edit(RequestContext requestContext) {
     editorDriver = GWT.create(Driver.class);
     editorDriver.initialize(requestFactory, personEditor);
@@ -149,19 +171,23 @@
   private void fetchAndEdit() {
     // The request is configured arbitrarily
     Request<PersonProxy> fetchRequest = requestFactory.find(person.stableId());
-    // Add the paths that the EditorDelegate computes are necessary
+
+    // Add the paths that the EditorDrives computes
     fetchRequest.with(editorDriver.getPaths());
 
     // We could do more with the request, but we just fire it
-    fetchRequest.fire(new Receiver<PersonProxy>() {
+    fetchRequest.to(new Receiver<PersonProxy>() {
       @Override
       public void onSuccess(PersonProxy person) {
         PersonEditorWorkflow.this.person = person;
         // Start the edit process
         PersonRequest context = requestFactory.personRequest();
-        context.persist().using(person);
+        // Display the UI
         edit(context);
+        // Configure the method invocation to be sent in the context
+        context.persist().using(person);
+        // The context will be fire()'ed from the onSave() method
       }
-    });
+    }).fire();
   }
 }
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/events/MarkFavoriteEvent.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/events/MarkFavoriteEvent.java
index 0f04762..a63d1f0 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/events/MarkFavoriteEvent.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/events/MarkFavoriteEvent.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.event.shared.EventHandler;
 import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.requestfactory.shared.EntityProxyId;
 import com.google.gwt.sample.dynatablerf.shared.PersonProxy;
 
 /**
@@ -32,11 +33,11 @@
 
   public static final Type<Handler> TYPE = new Type<Handler>();
 
-  private final PersonProxy person;
+  private final EntityProxyId<PersonProxy> id;
   private final boolean isFavorite;
 
-  public MarkFavoriteEvent(PersonProxy person, boolean isFavorite) {
-    this.person = person;
+  public MarkFavoriteEvent(EntityProxyId<PersonProxy> id, boolean isFavorite) {
+    this.id = id;
     this.isFavorite = isFavorite;
   }
 
@@ -45,8 +46,8 @@
     return TYPE;
   }
 
-  public PersonProxy getPerson() {
-    return person;
+  public EntityProxyId<PersonProxy> getId() {
+    return id;
   }
 
   public boolean isFavorite() {
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/AddressEditor.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/AddressEditor.java
index 3a210a2..f4578a6 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/AddressEditor.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/AddressEditor.java
@@ -38,7 +38,7 @@
   @UiField
   ValueBoxEditorDecorator<String> state;
   @UiField
-  ValueBoxEditorDecorator<Integer> zip;
+  ValueBoxEditorDecorator<String> zip;
 
   public AddressEditor() {
     initWidget(GWT.<Binder> create(Binder.class).createAndBindUi(this));
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/AddressEditor.ui.xml b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/AddressEditor.ui.xml
index 52c2792..992b10c 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/AddressEditor.ui.xml
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/AddressEditor.ui.xml
@@ -1,4 +1,5 @@
 <ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder' 
+    xmlns:dt='urn:import:com.google.gwt.sample.dynatablerf.client.widgets'
     xmlns:g='urn:import:com.google.gwt.user.client.ui'
     xmlns:e='urn:import:com.google.gwt.editor.ui.client'>
   <ui:style src="../common.css">
@@ -34,7 +35,7 @@
       <e:ValueBoxEditorDecorator ui:field="zip"
         stylePrimaryName="{style.editField}">
         <e:valuebox>
-          <g:IntegerBox stylePrimaryName="{style.editField}" />
+          <dt:ZipPlusFourBox stylePrimaryName="{style.editField}" />
         </e:valuebox>
       </e:ValueBoxEditorDecorator>
       <br />
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/DayFilterWidget.ui.xml b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/DayFilterWidget.ui.xml
index f6ec9f9..658a9ab 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/DayFilterWidget.ui.xml
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/DayFilterWidget.ui.xml
@@ -1,5 +1,5 @@
-<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder' xmlns:g='urn:import:com.google.gwt.user.client.ui'
-  xmlns:dt='urn:import:com.google.gwt.sample.dynatablerf.client.widgets'>
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:g='urn:import:com.google.gwt.user.client.ui' xmlns:dt='urn:import:com.google.gwt.sample.dynatablerf.client.widgets'>
   <ui:style>
     .all {
     	float: left;
@@ -14,23 +14,22 @@
     }
   </ui:style>
   <g:FlowPanel>
-    <dt:DayCheckBox ui:field="sunday" caption="Sunday" day="0"
-      styleName="{style.cb}" />
-    <dt:DayCheckBox ui:field="monday" caption="Monday" day="0"
-      styleName="{style.cb}" />
-    <dt:DayCheckBox ui:field="tuesday" caption="Tuesday" day="0"
-      styleName="{style.cb}" />
-    <dt:DayCheckBox ui:field="wednesday" caption="Wednesday" day="0"
-      styleName="{style.cb}" />
-    <dt:DayCheckBox ui:field="thursday" caption="Thursday" day="0"
-      styleName="{style.cb}" />
-    <dt:DayCheckBox ui:field="friday" caption="Friday" day="0"
-      styleName="{style.cb}" />
-    <dt:DayCheckBox ui:field="saturday" caption="Saturday" day="0"
-      styleName="{style.cb}" />
+    <dt:DayCheckBox ui:field="sunday" caption="Sunday"
+      day="0" styleName="{style.cb}" />
+    <dt:DayCheckBox ui:field="monday" caption="Monday"
+      day="1" styleName="{style.cb}" />
+    <dt:DayCheckBox ui:field="tuesday" caption="Tuesday"
+      day="2" styleName="{style.cb}" />
+    <dt:DayCheckBox ui:field="wednesday" caption="Wednesday"
+      day="3" styleName="{style.cb}" />
+    <dt:DayCheckBox ui:field="thursday" caption="Thursday"
+      day="4" styleName="{style.cb}" />
+    <dt:DayCheckBox ui:field="friday" caption="Friday"
+      day="5" styleName="{style.cb}" />
+    <dt:DayCheckBox ui:field="saturday" caption="Saturday"
+      day="6" styleName="{style.cb}" />
 
-    <g:Button ui:field="all" enabled="false" styleName="{style.all}">All</g:Button>
-    <g:Button ui:field="none" enabled="false" styleName="{style.none}">None</g:Button>
-    <g:Label>Not implemented yet</g:Label>
+    <g:Button ui:field="all" styleName="{style.all}">All</g:Button>
+    <g:Button ui:field="none" styleName="{style.none}">None</g:Button>
   </g:FlowPanel>
 </ui:UiBinder>
\ No newline at end of file
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 62e87d6..cc4b598 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
@@ -48,6 +48,10 @@
   interface Binder extends UiBinder<Widget, FavoritesWidget> {
   }
 
+  /**
+   * A driver that accepts a List of PersonProxy objects, controlled by a
+   * ListEditor of PersonProxy instances, displayed using NameLabels.
+   */
   interface Driver extends RequestFactoryEditorDriver<List<PersonProxy>, //
       ListEditor<PersonProxy, NameLabel>> {
   }
@@ -57,7 +61,7 @@
   }
 
   /**
-   * This is used by a ListEditor.
+   * This is used by the ListEditor to control the state of the UI.
    */
   private class NameLabelSource extends EditorSource<NameLabel> {
     @Override
@@ -86,10 +90,14 @@
   @UiField
   Style style;
 
+  /**
+   * This list is a facade provided by the ListEditor. Structural modifications
+   * to this list (e.g. add(), set(), remove()) will trigger UI update events.
+   */
   private final List<PersonProxy> displayedList;
   private final EventBus eventBus;
   private final RequestFactory factory;
-  private FavoritesManager manager;
+  private final FavoritesManager manager;
   private HandlerRegistration subscription;
 
   public FavoritesWidget(EventBus eventBus, RequestFactory factory,
@@ -111,9 +119,7 @@
     ListEditor<PersonProxy, NameLabel> listEditor = editor;
     driver.initialize(eventBus, factory, listEditor);
 
-    /*
-     * Notice the backing list is essentially anonymous.
-     */
+    // Notice the backing list is essentially anonymous.
     driver.display(new ArrayList<PersonProxy>());
 
     // Modifying this list triggers widget creation and destruction
@@ -122,19 +128,16 @@
 
   @Override
   protected void onLoad() {
+    // Subscribe to notifications from the FavoritesManager
     subscription = manager.addMarkFavoriteHandler(new MarkFavoriteEvent.Handler() {
       public void onMarkFavorite(MarkFavoriteEvent event) {
         FavoritesWidget.this.onMarkFavorite(event);
       }
     });
 
+    // Initialize the UI with the existing list of favorites
     for (EntityProxyId<PersonProxy> id : manager.getFavoriteIds()) {
-      factory.find(id).fire(new Receiver<PersonProxy>() {
-        @Override
-        public void onSuccess(PersonProxy person) {
-          onMarkFavorite(new MarkFavoriteEvent(person, true));
-        }
-      });
+      onMarkFavorite(new MarkFavoriteEvent(id, true));
     }
   }
 
@@ -143,31 +146,36 @@
     subscription.removeHandler();
   }
 
-  void onMarkFavorite(MarkFavoriteEvent event) {
-    PersonProxy person = event.getPerson();
-    if (person == null) {
+  private void onMarkFavorite(MarkFavoriteEvent event) {
+    EntityProxyId<PersonProxy> id = event.getId();
+    if (id == null) {
       return;
     }
 
     // EntityProxies should be compared by stable id
-    EntityProxyId<PersonProxy> lookFor = person.stableId();
     PersonProxy found = null;
     for (PersonProxy displayed : displayedList) {
-      if (displayed.stableId().equals(lookFor)) {
+      if (displayed.stableId().equals(id)) {
         found = displayed;
         break;
       }
     }
 
     if (event.isFavorite() && found == null) {
-      displayedList.add(person);
+      factory.find(id).to(new Receiver<PersonProxy>() {
+        @Override
+        public void onSuccess(PersonProxy response) {
+          displayedList.add(response);
+          sortDisplayedList();
+        }
+      }).fire();
     } else if (!event.isFavorite() && found != null) {
-      displayedList.remove(person);
-    } else {
-      // No change
-      return;
+      displayedList.remove(found);
+      sortDisplayedList();
     }
+  }
 
+  private void sortDisplayedList() {
     // Sorting the list of PersonProxies will also change the UI display
     Collections.sort(displayedList, new Comparator<PersonProxy>() {
       public int compare(PersonProxy o1, PersonProxy o2) {
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/MentorSelector.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/MentorSelector.java
index 7925ceb..b48101f 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/MentorSelector.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/MentorSelector.java
@@ -74,7 +74,7 @@
   }
 
   @UiHandler("choose")
-  void onChoose(@SuppressWarnings("unused") ClickEvent event) {
+  void onChoose(ClickEvent event) {
     setEnabled(false);
     factory.schoolCalendarRequest().getRandomPerson().to(
         new Receiver<PersonProxy>() {
@@ -87,7 +87,7 @@
   }
 
   @UiHandler("clear")
-  void onClear(@SuppressWarnings("unused") ClickEvent event) {
+  void onClear(ClickEvent event) {
     setValue(null);
   }
 
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 d7ebe00..b169461 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
@@ -31,6 +31,10 @@
  * the displayed object.
  */
 class NameLabel extends Composite implements ValueAwareEditor<PersonProxy> {
+  /**
+   * Many of the GWT UI widgets that implement TakesValue also implement
+   * IsEditor and are directly usable as sub-Editors.
+   */
   final Label nameEditor = new Label();
   private PersonProxy person;
   private HandlerRegistration subscription;
@@ -59,7 +63,9 @@
   }
 
   public void setDelegate(EditorDelegate<PersonProxy> delegate) {
-    assert subscription == null;
+    if (subscription != null) {
+      subscription.removeHandler();
+    }
     subscription = delegate.subscribe();
   }
 
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/SummaryWidget.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/SummaryWidget.java
index 348d2f2..213fd21 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/SummaryWidget.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/SummaryWidget.java
@@ -15,8 +15,12 @@
  */
 package com.google.gwt.sample.dynatablerf.client.widgets;
 
+import static com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory.SchoolCalendarRequest.ALL_DAYS;
+
 import com.google.gwt.cell.client.TextCell;
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.shared.EventBus;
 import com.google.gwt.requestfactory.shared.EntityProxyChange;
@@ -28,15 +32,15 @@
 import com.google.gwt.sample.dynatablerf.client.events.FilterChangeEvent;
 import com.google.gwt.sample.dynatablerf.shared.AddressProxy;
 import com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory;
-import com.google.gwt.sample.dynatablerf.shared.PersonProxy;
 import com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory.PersonRequest;
+import com.google.gwt.sample.dynatablerf.shared.PersonProxy;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.uibinder.client.UiHandler;
 import com.google.gwt.user.cellview.client.CellTable;
 import com.google.gwt.user.cellview.client.Column;
-import com.google.gwt.user.cellview.client.SimplePager;
 import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy;
+import com.google.gwt.user.cellview.client.SimplePager;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.DockLayoutPanel;
 import com.google.gwt.user.client.ui.Widget;
@@ -45,6 +49,7 @@
 import com.google.gwt.view.client.SelectionChangeEvent;
 import com.google.gwt.view.client.SingleSelectionModel;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
@@ -111,13 +116,13 @@
   CellTable<PersonProxy> table;
 
   private final EventBus eventBus;
+  private List<Boolean> filter = new ArrayList<Boolean>(ALL_DAYS);
+  private int lastFetch;
   private final int numRows;
+  private boolean pending;
   private final DynaTableRequestFactory requestFactory;
   private final SingleSelectionModel<PersonProxy> selectionModel = new SingleSelectionModel<PersonProxy>();
 
-  private boolean[] filter = new boolean[] {
-      true, true, true, true, true, true, true};
-
   public SummaryWidget(EventBus eventBus,
       DynaTableRequestFactory requestFactory, int numRows) {
     this.eventBus = eventBus;
@@ -148,8 +153,16 @@
 
     FilterChangeEvent.register(eventBus, new FilterChangeEvent.Handler() {
       public void onFilterChanged(FilterChangeEvent e) {
-        filter[e.getDay()] = e.isSelected();
-        fetch(0);
+        filter.set(e.getDay(), e.isSelected());
+        if (!pending) {
+          pending = true;
+          Scheduler.get().scheduleFinally(new ScheduledCommand() {
+            public void execute() {
+              pending = false;
+              fetch(0);
+            }
+          });
+        }
       }
     });
 
@@ -162,8 +175,8 @@
     fetch(0);
   }
 
-  @UiHandler("create") 
-  void onCreate(@SuppressWarnings("unused") ClickEvent event) {
+  @UiHandler("create")
+  void onCreate(ClickEvent event) {
     PersonRequest context = requestFactory.personRequest();
     AddressProxy address = context.create(AddressProxy.class);
     PersonProxy person = context.edit(context.create(PersonProxy.class));
@@ -173,6 +186,12 @@
   }
 
   void onPersonChanged(EntityProxyChange<PersonProxy> event) {
+    if (WriteOperation.PERSIST.equals(event.getWriteOperation())) {
+      // Re-fetch if we're already displaying the last page
+      if (table.isRowCountExact()) {
+        fetch(lastFetch + 1);
+      }
+    }
     if (WriteOperation.UPDATE.equals(event.getWriteOperation())) {
       EntityProxyId<PersonProxy> personId = event.getProxyId();
 
@@ -213,14 +232,15 @@
   }
 
   private void fetch(final int start) {
-    // XXX add back filter
-    requestFactory.schoolCalendarRequest().getPeople(start, numRows).fire(
+    lastFetch = start;
+    requestFactory.schoolCalendarRequest().getPeople(start, numRows, filter).fire(
         new Receiver<List<PersonProxy>>() {
           @Override
           public void onSuccess(List<PersonProxy> response) {
             int responses = response.size();
             table.setRowData(start, response);
-            if (!table.isRowCountExact()) {
+            pager.setPageStart(start);
+            if (start == 0 || !table.isRowCountExact()) {
               table.setRowCount(start + responses, responses < numRows);
             }
           }
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/ZipPlusFourBox.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/ZipPlusFourBox.java
new file mode 100644
index 0000000..7712995
--- /dev/null
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/ZipPlusFourBox.java
@@ -0,0 +1,71 @@
+/*
+ * 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.sample.dynatablerf.client.widgets;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.regexp.shared.RegExp;
+import com.google.gwt.text.shared.Parser;
+import com.google.gwt.text.shared.Renderer;
+import com.google.gwt.user.client.ui.ValueBox;
+
+import java.io.IOException;
+import java.text.ParseException;
+
+/**
+ * A simple implementation of a US zip code input field.
+ * <p>
+ * Accepted formats are <code>ddddd</code> or <code>ddddd-dddd</code>.
+ */
+public class ZipPlusFourBox extends ValueBox<String> {
+  private static final RegExp PATTERN = RegExp.compile("^\\d{5}(-\\d{4})?$");
+  private static final Renderer<String> RENDERER = new Renderer<String>() {
+    public String render(String object) {
+      if (object == null) {
+        return null;
+      }
+      StringBuilder sb = new StringBuilder(String.valueOf(object));
+      if (sb.length() == 9) {
+        sb.insert(5, '-');
+      }
+      return sb.toString();
+    }
+
+    public void render(String object, Appendable appendable) throws IOException {
+      appendable.append(render(object));
+    }
+  };
+  private static final Parser<String> PARSER = new Parser<String>() {
+    public String parse(CharSequence text) throws ParseException {
+      switch (text.length()) {
+        case 9:
+          text = text.subSequence(0, 5) + "-" + text.subSequence(5, 9);
+          // Fall-though intentional
+        case 5:
+        case 10:
+          if (PATTERN.test(text.toString())) {
+            return text.toString();
+          } else {
+            throw new ParseException("Illegal value", 0);
+          }
+      }
+      throw new ParseException("Illegal length", 0);
+    }
+  };
+
+  public ZipPlusFourBox() {
+    super(Document.get().createTextInputElement(), RENDERER, PARSER);
+  }
+}
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Address.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Address.java
index b87ee48..c7059bd 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Address.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Address.java
@@ -19,6 +19,7 @@
 
 import javax.validation.constraints.DecimalMin;
 import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
 import javax.validation.constraints.Size;
 
 /**
@@ -53,8 +54,8 @@
   private Integer version = 0;
 
   @NotNull
-  @DecimalMin("10000")
-  private Integer zip;
+  @Pattern(regexp = "\\d{5}(-\\d{4})?")
+  private String zip;
 
   public Address() {
   }
@@ -92,7 +93,7 @@
     return version;
   }
 
-  public Integer getZip() {
+  public String getZip() {
     return zip;
   }
 
@@ -129,7 +130,7 @@
     this.version = version;
   }
 
-  public void setZip(Integer zip) {
+  public void setZip(String zip) {
     this.zip = zip;
   }
 }
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Person.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Person.java
index d7eec2d..3c10fd8 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Person.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Person.java
@@ -15,8 +15,12 @@
  */
 package com.google.gwt.sample.dynatablerf.domain;
 
+import static com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory.SchoolCalendarRequest.ALL_DAYS;
+
 import com.google.gwt.sample.dynatablerf.server.SchoolCalendarService;
 
+import java.util.List;
+
 import javax.validation.constraints.DecimalMin;
 import javax.validation.constraints.NotNull;
 import javax.validation.constraints.Size;
@@ -69,8 +73,7 @@
 
   private String note;
 
-  private final boolean[] daysFilters = new boolean[] {
-      true, true, true, true, true, true, true};
+  private List<Boolean> daysFilters = ALL_DAYS;
 
   public Person() {
   }
@@ -129,7 +132,7 @@
     return getScheduleWithFilter(daysFilters);
   }
 
-  public String getScheduleWithFilter(boolean[] daysFilter) {
+  public String getScheduleWithFilter(List<Boolean> daysFilter) {
     return classSchedule.getDescription(daysFilter);
   }
 
@@ -158,10 +161,9 @@
     this.address.copyFrom(address);
   }
 
-  public void setDaysFilter(boolean[] daysFilter) {
-    assert daysFilter.length == this.daysFilters.length;
-    System.arraycopy(daysFilter, 0, this.daysFilters, 0,
-        this.daysFilters.length);
+  public void setDaysFilter(List<Boolean> daysFilter) {
+    assert daysFilter.size() == this.daysFilters.size();
+    this.daysFilters = daysFilter;
   }
 
   public void setDescription(String description) {
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Schedule.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Schedule.java
index 6a7cfad..14b6ddb 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Schedule.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Schedule.java
@@ -32,10 +32,10 @@
     timeSlots.add(timeSlot);
   }
 
-  public String getDescription(boolean[] daysFilter) {
+  public String getDescription(List<Boolean> daysFilter) {
     String s = null;
     for (TimeSlot timeSlot : timeSlots) {
-      if (daysFilter[timeSlot.getDayOfWeek()]) {
+      if (daysFilter.get(timeSlot.getDayOfWeek())) {
         if (s == null) {
           s = timeSlot.getDescription();
         } else {
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/AddressFuzzer.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/AddressFuzzer.java
index 16bcc4d..25bdb93 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/AddressFuzzer.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/AddressFuzzer.java
@@ -49,6 +49,11 @@
     a.setStreet(r.nextInt(4096) + " "
         + STREET_NAMES[r.nextInt(STREET_NAMES.length)]);
     a.setState(STATE_NAMES[r.nextInt(STATE_NAMES.length)]);
-    a.setZip(10000 + r.nextInt(89999));
+    StringBuilder zip = new StringBuilder();
+    zip.append(String.format("%05d", r.nextInt(99999)));
+    if (r.nextBoolean()) {
+      zip.append(String.format("-%04d", r.nextInt(9999)));
+    }
+    a.setZip(zip.toString());
   }
 }
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/PersonSource.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/PersonSource.java
index 08ec81c..19b06e5 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/PersonSource.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/PersonSource.java
@@ -15,17 +15,24 @@
  */
 package com.google.gwt.sample.dynatablerf.server;
 
+import static com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory.SchoolCalendarRequest.ALL_DAYS;
+import static com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory.SchoolCalendarRequest.NO_DAYS;
+
 import com.google.gwt.sample.dynatablerf.domain.Address;
 import com.google.gwt.sample.dynatablerf.domain.Person;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
 /**
- * 
+ * Provides a number of Person objects as a demonstration datasource. Many of
+ * the operations in this implementation would be much more efficient in a real
+ * database, but are implemented is a straightforward fashion because they're
+ * not really important for understanding the RequestFactory framework.
  */
 public abstract class PersonSource {
   static class Backing extends PersonSource {
@@ -43,7 +50,7 @@
 
     @Override
     public List<Person> getPeople(int startIndex, int maxCount,
-        boolean[] daysFilter) {
+        List<Boolean> daysFilter) {
       int peopleCount = countPeople();
 
       int start = startIndex;
@@ -55,8 +62,31 @@
       if (start == end) {
         return Collections.emptyList();
       }
-      // This is ugly, but a real backend would have a skip mechanism
-      return new ArrayList<Person>(people.values()).subList(start, end);
+
+      // If there's a simple filter, use a fast path
+      if (ALL_DAYS.equals(daysFilter)) {
+        return new ArrayList<Person>(people.values()).subList(start, end);
+      } else if (NO_DAYS.equals(daysFilter)) {
+        return new ArrayList<Person>();
+      }
+
+      /*
+       * Otherwise, iterate from the start position until we collect enough
+       * People or hit the end of the list.
+       */
+      Iterator<Person> it = people.values().iterator();
+      int skipped = 0;
+      List<Person> toReturn = new ArrayList<Person>(maxCount);
+      while (toReturn.size() < maxCount && it.hasNext()) {
+        Person person = it.next();
+        if (person.getScheduleWithFilter(daysFilter).length() > 0) {
+          if (skipped++ < startIndex) {
+            continue;
+          }
+          toReturn.add(person);
+        }
+      }
+      return toReturn;
     }
 
     @Override
@@ -107,13 +137,13 @@
 
     @Override
     public List<Person> getPeople(int startIndex, int maxCount,
-        boolean[] daysFilter) {
+        List<Boolean> daysFilter) {
       List<Person> toReturn = new ArrayList<Person>(maxCount);
       for (Person person : backingStore.getPeople(startIndex, maxCount,
           daysFilter)) {
         Person copy = findPerson(person.getId());
-        toReturn.add(copy);
         copy.setDaysFilter(daysFilter);
+        toReturn.add(copy);
       }
       return toReturn;
     }
@@ -155,7 +185,7 @@
   public abstract Person findPerson(String id);
 
   public abstract List<Person> getPeople(int startIndex, int maxCount,
-      boolean[] daysFilter);
+      List<Boolean> daysFilter);
 
   public abstract void persist(Address address);
 
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/SchoolCalendarService.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/SchoolCalendarService.java
index 7bcdcd3..5493cd7 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/SchoolCalendarService.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/SchoolCalendarService.java
@@ -15,6 +15,8 @@
  */
 package com.google.gwt.sample.dynatablerf.server;
 
+import static com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory.SchoolCalendarRequest.ALL_DAYS;
+
 import com.google.gwt.sample.dynatablerf.domain.Address;
 import com.google.gwt.sample.dynatablerf.domain.Person;
 
@@ -33,8 +35,6 @@
  * The server side service class.
  */
 public class SchoolCalendarService implements Filter {
-  private static final boolean[] ALL_DAYS = new boolean[] {
-      true, true, true, true, true, true, true};
 
   private static final ThreadLocal<PersonSource> PERSON_SOURCE = new ThreadLocal<PersonSource>();
 
@@ -50,8 +50,10 @@
     return PERSON_SOURCE.get().findPerson(id);
   }
 
-  public static List<Person> getPeople(int startIndex, int maxCount) {
-    return getPeople(startIndex, maxCount, ALL_DAYS);
+  public static List<Person> getPeople(int startIndex, int maxCount,
+      List<Boolean> filter) {
+    checkPersonSource();
+    return PERSON_SOURCE.get().getPeople(startIndex, maxCount, filter);
   }
 
   public static Person getRandomPerson() {
@@ -77,15 +79,6 @@
     }
   }
 
-  /**
-   * XXX private due to inability to add method overloads.
-   */
-  private static List<Person> getPeople(int startIndex, int maxCount,
-      boolean[] filter) {
-    checkPersonSource();
-    return PERSON_SOURCE.get().getPeople(startIndex, maxCount, filter);
-  }
-
   private PersonSource backingStore;
 
   public void destroy() {
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/AddressProxy.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/AddressProxy.java
index 3f59f78..f96e5c8 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/AddressProxy.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/AddressProxy.java
@@ -31,7 +31,7 @@
 
   String getStreet();
 
-  Integer getZip();
+  String getZip();
 
   void setCity(String city);
 
@@ -39,7 +39,7 @@
 
   void setStreet(String street);
 
-  void setZip(Integer zip);
+  void setZip(String zip);
 
   EntityProxyId<AddressProxy> stableId();
 }
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/DynaTableRequestFactory.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/DynaTableRequestFactory.java
index f7aa832..0a0e38a 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/DynaTableRequestFactory.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/DynaTableRequestFactory.java
@@ -25,6 +25,8 @@
 import com.google.gwt.sample.dynatablerf.domain.Person;
 import com.google.gwt.sample.dynatablerf.server.SchoolCalendarService;
 
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -54,7 +56,14 @@
    */
   @Service(SchoolCalendarService.class)
   interface SchoolCalendarRequest extends RequestContext {
-    Request<List<PersonProxy>> getPeople(int startIndex, int maxCount);
+    List<Boolean> ALL_DAYS = Collections.unmodifiableList(Arrays.asList(true,
+        true, true, true, true, true, true));
+    List<Boolean> NO_DAYS = Collections.unmodifiableList(Arrays.asList(false,
+        false, false, false, false, false, false));
+
+    Request<List<PersonProxy>> getPeople(int startIndex, int maxCount,
+        List<Boolean> dayFilter);
+
     Request<PersonProxy> getRandomPerson();
   }
 
diff --git a/user/src/com/google/gwt/editor/client/IsEditor.java b/user/src/com/google/gwt/editor/client/IsEditor.java
index 265f936..f787df2 100644
--- a/user/src/com/google/gwt/editor/client/IsEditor.java
+++ b/user/src/com/google/gwt/editor/client/IsEditor.java
@@ -17,7 +17,28 @@
 
 /**
  * Extended by view objects that wish to participate in an Editor hierarchy, but
- * that do not implement the {@link Editor} contract directly.
+ * that do not implement the {@link Editor} contract directly. The primary
+ * advantage of the IsEditor interface is that is allows composition of behavior
+ * without the need to implement delegate methods for every interface
+ * implemented by the common editor logic.
+ * <p>
+ * For example, an editor Widget that supports adding and removing elements from
+ * a list might wish to re-use the provided
+ * {@link com.google.gwt.editor.client.adapters.ListEditor ListEditor}
+ * controller. It might be roughly built as:
+ * 
+ * <pre>
+ * class MyListEditor extends Composite implements IsEditor&lt;ListEditor&lt;Foo, FooEditor>> {
+ *   private ListEditor&lt;Foo, FooEditor> controller = ListEditor.of(new FooEditorSource());
+ *   public ListEditor&lt;Foo, FooEditor> asEditor() {return controller;}
+ *   void onAddButtonClicked() { controller.getList().add(new Foo()); }
+ *   void onClearButtonClicked() { controller.getList().clear(); }
+ * }
+ * </pre>
+ * By implementing only the one <code>asEditor()</code> method, the
+ * <code>MyListEditor</code> type is able to incorporate the
+ * <code>ListEditor</code> behavior without needing to write delegate methods
+ * for every method in <code>ListEditor</code>.
  * <p>
  * It is legal for a type to implement both Editor and IsEditor. In this case,
  * the Editor returned from {@link #asEditor()} will be a co-Editor of the
diff --git a/user/test/com/google/gwt/editor/client/SimpleBeanEditorTest.java b/user/test/com/google/gwt/editor/client/SimpleBeanEditorTest.java
index b5a9beb..1f15edb 100644
--- a/user/test/com/google/gwt/editor/client/SimpleBeanEditorTest.java
+++ b/user/test/com/google/gwt/editor/client/SimpleBeanEditorTest.java
@@ -42,6 +42,14 @@
     }
   }
 
+  class AddressEditorPartOne implements Editor<Address> {
+    SimpleEditor<String> city = SimpleEditor.of(UNINITIALIZED);
+  }
+
+  class AddressEditorPartTwo implements Editor<Address> {
+    SimpleEditor<String> street = SimpleEditor.of(UNINITIALIZED);
+  }
+
   class AddressEditorView implements IsEditor<LeafAddressEditor> {
     LeafAddressEditor addressEditor = new LeafAddressEditor();
 
@@ -105,6 +113,18 @@
       SimpleBeanEditorDriver<Person, PersonEditorWithLeafAddressEditor> {
   }
 
+  class PersonEditorWithMultipleBindings implements Editor<Person> {
+    @Editor.Path("address")
+    AddressEditorPartOne one = new AddressEditorPartOne();
+
+    @Editor.Path("address")
+    AddressEditorPartTwo two = new AddressEditorPartTwo();
+  }
+
+  interface PersonEditorWithMultipleBindingsDriver extends
+      SimpleBeanEditorDriver<Person, PersonEditorWithMultipleBindings> {
+  }
+
   interface PersonEditorWithOptionalAddressDriver extends
       SimpleBeanEditorDriver<Person, PersonEditorWithOptionalAddressEditor> {
   }
@@ -361,10 +381,10 @@
 
     List<SimpleEditor<String>> editors = editor.getEditors();
     assertEquals(rawData.size(), editors.size());
-    assertEquals(rawData, Arrays.asList(editors.get(0).getValue(), editors.get(
-        1).getValue(), editors.get(2).getValue()));
-    assertEquals(editors, new ArrayList<SimpleEditor<String>>(
-        positionMap.values()));
+    assertEquals(rawData, Arrays.asList(editors.get(0).getValue(),
+        editors.get(1).getValue(), editors.get(2).getValue()));
+    assertEquals(editors,
+        new ArrayList<SimpleEditor<String>>(positionMap.values()));
 
     List<String> mutableList = editor.getList();
     assertEquals(rawData, mutableList);
@@ -383,8 +403,8 @@
     mutableList.add("quux");
     assertEquals(4, editors.size());
     assertEquals("quux", editors.get(3).getValue());
-    assertEquals(editors, new ArrayList<SimpleEditor<String>>(
-        positionMap.values()));
+    assertEquals(editors,
+        new ArrayList<SimpleEditor<String>>(positionMap.values()));
 
     // Delete an element
     SimpleEditor<String> expectedDisposed = editors.get(0);
@@ -392,8 +412,8 @@
     assertSame(expectedDisposed, disposed[0]);
     assertEquals(3, editors.size());
     assertEquals("quux", editors.get(2).getValue());
-    assertEquals(editors, new ArrayList<SimpleEditor<String>>(
-        positionMap.values()));
+    assertEquals(editors,
+        new ArrayList<SimpleEditor<String>>(positionMap.values()));
   }
 
   /**
@@ -434,6 +454,23 @@
     assertEquals("edited", person.addresses.get(1).getCity());
   }
 
+  public void testMultipleBinding() {
+    PersonEditorWithMultipleBindingsDriver driver = GWT.create(PersonEditorWithMultipleBindingsDriver.class);
+    PersonEditorWithMultipleBindings editor = new PersonEditorWithMultipleBindings();
+
+    driver.initialize(editor);
+    driver.edit(person);
+    assertEquals("City", editor.one.city.getValue());
+    assertEquals("Street", editor.two.street.getValue());
+
+    editor.one.city.setValue("Foo");
+    editor.two.street.setValue("Bar");
+    driver.flush();
+
+    assertEquals("Foo", person.getAddress().getCity());
+    assertEquals("Bar", person.getAddress().getStreet());
+  }
+
   public void testOptionalField() {
     PersonEditorWithOptionalAddressDriver driver = GWT.create(PersonEditorWithOptionalAddressDriver.class);
     person.address = null;