Makes the various views a bit more modular, isolating them from the
entry point and paving the way to give them more real implementations.

Extracts ValuesListView interface from the ValuesListViewTable widget.
Also extracts the requests that populate them out of the various list
view implementations.

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

Review by: amitmanjhi@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7894 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/src/com/google/gwt/requestfactory/shared/RequestFactory.java b/bikeshed/src/com/google/gwt/requestfactory/shared/RequestFactory.java
index 3fa6ad1..acd8944 100644
--- a/bikeshed/src/com/google/gwt/requestfactory/shared/RequestFactory.java
+++ b/bikeshed/src/com/google/gwt/requestfactory/shared/RequestFactory.java
@@ -29,7 +29,8 @@
 
   /**
    * Implemented by the configuration class used by
-   * {@link RequestFactoryServlet}.
+   * {@link com.google.gwt.requestfactory.server.RequestFactoryServlet
+   * RequestFactoryServlet}.
    */
   interface Config {
     Map<String, RequestDefinition> requestDefinitions();
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRequest.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRequest.java
index 6f6636d..2a9074d 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRequest.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRequest.java
@@ -24,9 +24,9 @@
  * Request selector.
  */
 public interface EmployeeRequest {
-  
+
   /**
-   * Enum for server operations.
+   * Defines the server operations that handle these requests.
    */
   public enum ServerOperations implements RequestFactory.RequestDefinition {
     FIND_ALL_EMPLOYEES {
@@ -53,4 +53,4 @@
    */
   @ServerOperation("FIND_ALL_EMPLOYEES")
   EntityListRequest<EmployeeKey> findAllEmployees();
-}
\ No newline at end of file
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpensesKeyProcessor.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpensesKeyProcessor.java
new file mode 100644
index 0000000..4994c29
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpensesKeyProcessor.java
@@ -0,0 +1,50 @@
+/*
+ * 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.expenses.gwt.request;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Gives implementors of {@link KeyProcessor} access to all instances of
+ * {@link ExpensesKey} without hardcoding them.
+ */
+public class ExpensesKeyProcessor {
+
+  /**
+   * Implemented by objects interested in all {@link ExpensesKey} instances.
+   */
+  public interface KeyProcessor {
+    void processKey(ExpensesKey<?> key);
+  }
+
+  private static Set<ExpensesKey<?>> instance;
+
+  public static void processAll(KeyProcessor processor) {
+    for (ExpensesKey<?> key : get()) {
+      processor.processKey(key);
+    }
+  }
+
+  private static Set<ExpensesKey<?>> get() {
+    if (instance == null) {
+      instance = new HashSet<ExpensesKey<?>>();
+      instance.add(ReportKey.get());
+      instance.add(EmployeeKey.get());
+    }
+    return instance;
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRequest.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRequest.java
index 70d3683..303b89c 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRequest.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRequest.java
@@ -28,7 +28,7 @@
 public interface ReportRequest {
 
   /**
-   * Enum for server operations.
+   * Defines the server operations that handle these requests.
    */
   public enum ServerOperations implements RequestFactory.RequestDefinition {
     FIND_REPORTS_BY_EMPLOYEE {
@@ -76,4 +76,4 @@
    */
   @ServerOperation("FIND_ALL_REPORTS")
   EntityListRequest<ReportKey> findAllReports();
-}
\ No newline at end of file
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/Scaffold.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/Scaffold.java
index d39c091..8f223d7 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/Scaffold.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/Scaffold.java
@@ -26,9 +26,9 @@
 import com.google.gwt.sample.expenses.gwt.place.ExpensesListPlace;
 import com.google.gwt.sample.expenses.gwt.place.ExpensesPlace;
 import com.google.gwt.sample.expenses.gwt.place.ExpensesPlaces;
-import com.google.gwt.sample.expenses.gwt.request.EmployeeKey;
+import com.google.gwt.sample.expenses.gwt.request.ExpensesKey;
+import com.google.gwt.sample.expenses.gwt.request.ExpensesKeyProcessor;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
-import com.google.gwt.sample.expenses.gwt.request.ReportKey;
 import com.google.gwt.sample.expenses.gwt.ui.ExpensesKeyNameRenderer;
 import com.google.gwt.sample.expenses.gwt.ui.ListPlaceRenderer;
 import com.google.gwt.user.client.ui.HTML;
@@ -64,21 +64,21 @@
     // Left side
     PlacePicker<ExpensesListPlace> placePicker = new PlacePicker<ExpensesListPlace>(
         shell.getPlacesBox(), placeController, listPlaceNamer);
-    List<ExpensesListPlace> topPlaces = new ArrayList<ExpensesListPlace>();
-    topPlaces.add(new ExpensesListPlace(EmployeeKey.get()));
-    topPlaces.add(new ExpensesListPlace(ReportKey.get()));
-    topPlaces = Collections.unmodifiableList(topPlaces);
+    List<ExpensesListPlace> topPlaces = getTopPlaces();
     placePicker.setPlaces(topPlaces);
 
     // Shows entity lists
-    eventBus.addHandler(PlaceChanged.TYPE, new ScaffoldListRequester(places,
-        shell.getBody(), requestFactory, listPlaceNamer));
+    eventBus.addHandler(PlaceChanged.TYPE, new ScaffoldListRequester(
+        shell.getBody(), new ScaffoldListViewBuilder(places, requestFactory,
+            listPlaceNamer)));
 
     // Shared view for entity details.
-    // TODO Real app should not share
-    final HTML detailsView = new HTML();
-    eventBus.addHandler(PlaceChanged.TYPE, new ScaffoldDetailsRequester(entityNamer,
-        shell.getBody(), detailsView));
+    final HTML detailsView = new HTML(); // TODO Real app should not share?
+
+    // Shows entity details
+    eventBus.addHandler(PlaceChanged.TYPE, new ScaffoldDetailsRequester(
+        entityNamer, shell.getBody(), detailsView,
+        new ScaffoldDetailsViewBuilder()));
 
     // Hide the loading message
     Element loading = Document.get().getElementById("loading");
@@ -86,4 +86,14 @@
 
     RootLayoutPanel.get().add(shell);
   }
+
+  private List<ExpensesListPlace> getTopPlaces() {
+    final List<ExpensesListPlace> rtn = new ArrayList<ExpensesListPlace>();
+    ExpensesKeyProcessor.processAll(new ExpensesKeyProcessor.KeyProcessor() {
+      public void processKey(ExpensesKey<?> key) {
+        rtn.add(new ExpensesListPlace(key));
+      }
+    });
+    return Collections.unmodifiableList(rtn);
+  }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldDetailsRequester.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldDetailsRequester.java
index 1b75f13..751c1a0 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldDetailsRequester.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldDetailsRequester.java
@@ -17,10 +17,7 @@
 
 import com.google.gwt.app.place.PlaceChanged;
 import com.google.gwt.sample.expenses.gwt.place.ExpensesDetailsPlace;
-import com.google.gwt.sample.expenses.gwt.request.EmployeeKey;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesKey;
-import com.google.gwt.sample.expenses.gwt.request.ExpensesKeyVisitor;
-import com.google.gwt.sample.expenses.gwt.request.ReportKey;
 import com.google.gwt.sample.expenses.gwt.ui.ExpensesKeyNameRenderer;
 import com.google.gwt.user.client.ui.HTML;
 import com.google.gwt.user.client.ui.SimplePanel;
@@ -36,71 +33,35 @@
   private final ExpensesKeyNameRenderer entityNamer;
   private final SimplePanel panel;
   private final HTML detailsView;
+  private final ScaffoldDetailsViewBuilder detailsBuilder;
 
   public ScaffoldDetailsRequester(ExpensesKeyNameRenderer entityNamer,
-      SimplePanel simplePanel, HTML detailsView) {
+      SimplePanel simplePanel, HTML detailsView,
+      ScaffoldDetailsViewBuilder detailsBuilder) {
     this.entityNamer = entityNamer;
     this.panel = simplePanel;
     this.detailsView = detailsView;
+    this.detailsBuilder = detailsBuilder;
   }
 
   public void onPlaceChanged(PlaceChanged event) {
     if (!(event.getNewPlace() instanceof ExpensesDetailsPlace)) {
       return;
     }
-    ExpensesDetailsPlace newPlace = (ExpensesDetailsPlace) event.getNewPlace();
-    final Values<? extends ExpensesKey<?>> entity = newPlace.getEntity();
-    ExpensesKey<?> key = entity.getKey();
 
-    // TODO make a pretty uibinder page, not least because we're rendering
-    // user strings here, which is dangerous
+    ExpensesDetailsPlace newPlace = (ExpensesDetailsPlace) event.getNewPlace();
+    final Values<? extends ExpensesKey<?>> values = newPlace.getEntity();
+    ExpensesKey<?> key = values.getKey();
 
     final String title = new StringBuilder("<h1>").append(
         entityNamer.render(key)).append("</h1>").toString();
 
-    final StringBuilder list = new StringBuilder();
-
     // TODO would actually issue request to get details here, but
     // at the moment we know we already have them, such as they are.
     // And we haven't implemented the fake findEmployee call yet, ahem.
 
-    key.accept(new ExpensesKeyVisitor() {
-
-      @SuppressWarnings("unchecked")
-      public void visit(EmployeeKey employeeKey) {
-        // wow, this cast is nasty. Perhaps visitor should actually
-        // be on the Values themselves?
-        Values<EmployeeKey> eValues = (Values<EmployeeKey>) entity;
-        String user = eValues.get(EmployeeKey.get().getUserName());
-        list.append("<div>");
-        list.append("<label>").append("User Name: ").append("</label>");
-        list.append("<span>").append(user).append("</span>");
-        list.append("</div>");
-
-        list.append("<div>");
-        String display = eValues.get(EmployeeKey.get().getDisplayName());
-        list.append("<label>").append("Display Name: ").append("</label>");
-        list.append("<span>").append(display).append("</span>");
-        list.append("</div>");
-      }
-
-      @SuppressWarnings("unchecked")
-      public void visit(ReportKey reportKey) {
-        Values<ReportKey> rValues = (Values<ReportKey>) entity;
-        String purpose = rValues.get(ReportKey.get().getPurpose());
-        list.append("<div>");
-        list.append("<label>").append("Purpose: ").append("</label>");
-        list.append("<span>").append(purpose).append("</span>");
-        list.append("</div>");
-
-        list.append("<div>");
-        String created = rValues.get(ReportKey.get().getCreated()).toString();
-        list.append("<label>").append("Created: ").append("</label>");
-        list.append("<span>").append(created).append("</span>");
-        list.append("</div>");
-      }
-    });
-
+    StringBuilder list = new StringBuilder();
+    detailsBuilder.appendHtmlDescription(list, values);
     detailsView.setHTML(title + list.toString());
 
     if (detailsView.getParent() == null) {
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldDetailsViewBuilder.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldDetailsViewBuilder.java
new file mode 100644
index 0000000..eb6785e
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldDetailsViewBuilder.java
@@ -0,0 +1,53 @@
+/*
+ * 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.expenses.gwt.scaffold;
+
+import com.google.gwt.sample.expenses.gwt.request.EmployeeKey;
+import com.google.gwt.sample.expenses.gwt.request.ExpensesKey;
+import com.google.gwt.sample.expenses.gwt.request.ExpensesKeyVisitor;
+import com.google.gwt.sample.expenses.gwt.request.ReportKey;
+import com.google.gwt.sample.expenses.gwt.ui.employee.EmployeeDetailsBuilder;
+import com.google.gwt.sample.expenses.gwt.ui.report.ReportDetailsBuilder;
+import com.google.gwt.valuestore.shared.Values;
+
+/**
+ * Builds the details view for a record.
+ * <p>
+ * TODO should instead be a finder of pretty uibinder-based widgets, not least
+ * because we're rendering user strings here, which is dangerous
+ */
+public class ScaffoldDetailsViewBuilder {
+  private final EmployeeDetailsBuilder employeeBuilder = new EmployeeDetailsBuilder();
+  private final ReportDetailsBuilder reportBuilder = new ReportDetailsBuilder();
+
+  @SuppressWarnings("unchecked")
+  public void appendHtmlDescription(final StringBuilder list,
+      final Values<? extends ExpensesKey<?>> entity) {
+
+    // TODO These casts are nasty, but they probably wouldn't be necessary
+    // if we were listening for request reponses they way we're supposed to
+
+    entity.getKey().accept(new ExpensesKeyVisitor() {
+      public void visit(EmployeeKey employeeKey) {
+        employeeBuilder.append(list, (Values<EmployeeKey>) entity);
+      }
+
+      public void visit(ReportKey reportKey) {
+        reportBuilder.append(list, (Values<ReportKey>) entity);
+      }
+    });
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldListRequester.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldListRequester.java
index 6920a39..e5f1ccf 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldListRequester.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldListRequester.java
@@ -17,20 +17,8 @@
 
 import com.google.gwt.app.place.PlaceChanged;
 import com.google.gwt.sample.expenses.gwt.place.ExpensesListPlace;
-import com.google.gwt.sample.expenses.gwt.place.ExpensesPlaces;
-import com.google.gwt.sample.expenses.gwt.request.EmployeeKey;
-import com.google.gwt.sample.expenses.gwt.request.ExpensesKey;
-import com.google.gwt.sample.expenses.gwt.request.ExpensesKeyVisitor;
-import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
-import com.google.gwt.sample.expenses.gwt.request.ReportKey;
-import com.google.gwt.sample.expenses.gwt.ui.employee.EmployeeListView;
-import com.google.gwt.sample.expenses.gwt.ui.report.ReportListView;
-import com.google.gwt.user.client.ui.Renderer;
 import com.google.gwt.user.client.ui.SimplePanel;
-import com.google.gwt.valuestore.client.ValuesListViewTable;
-
-import java.util.HashMap;
-import java.util.Map;
+import com.google.gwt.user.client.ui.Widget;
 
 /**
  * In charge of requesting and displaying the appropriate record lists in the
@@ -40,20 +28,12 @@
 public final class ScaffoldListRequester implements PlaceChanged.Handler {
 
   private final SimplePanel panel;
-  private final ExpensesRequestFactory requests;
-  private final Renderer<ExpensesListPlace> placeRenderer;
-  private final ExpensesPlaces places;
+  private final ScaffoldListViewBuilder builder;
 
-  // TODO This dependency on view classes prevents testing this class in JRE.
-  // Get a factory in here or something
-  private final Map<ExpensesListPlace, ValuesListViewTable<?>> viewMap = new HashMap<ExpensesListPlace, ValuesListViewTable<?>>();
-
-  public ScaffoldListRequester(ExpensesPlaces places, SimplePanel panel,
-      ExpensesRequestFactory requests, Renderer<ExpensesListPlace> renderer) {
-    this.places = places;
+  public ScaffoldListRequester(SimplePanel panel,
+      ScaffoldListViewBuilder builder) {
     this.panel = panel;
-    this.requests = requests;
-    this.placeRenderer = renderer;
+    this.builder = builder;
   }
 
   public void onPlaceChanged(PlaceChanged event) {
@@ -61,33 +41,16 @@
       return;
     }
     final ExpensesListPlace newPlace = (ExpensesListPlace) event.getNewPlace();
-    ExpensesKey<?> key = newPlace.getKey();
 
-    key.accept(new ExpensesKeyVisitor() {
+    Widget view = builder.getListView(newPlace).asWidget();
 
-      public void visit(EmployeeKey employeeKey) {
-        ValuesListViewTable<?> view = viewMap.get(newPlace);
-        if (null == view) {
-          view = new EmployeeListView(placeRenderer.render(newPlace), places,
-              requests);
-          viewMap.put(newPlace, view);
-        }
-      }
+    if (null == view) {
+      throw new RuntimeException("Unable to locate a view for " + newPlace);
+    }
 
-      public void visit(ReportKey reportKey) {
-        ValuesListViewTable<?> view = viewMap.get(newPlace);
-        if (null == view) {
-          view = new ReportListView(placeRenderer.render(newPlace), places,
-              requests);
-          viewMap.put(newPlace, view);
-        }
-      }
-    });
-
-    ValuesListViewTable<?> entitiesView = viewMap.get(newPlace);
-    if (entitiesView.getParent() == null) {
+    if (view.getParent() == null) {
       panel.clear();
-      panel.add(entitiesView);
+      panel.add(view);
     }
   }
 }
\ No newline at end of file
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldListViewBuilder.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldListViewBuilder.java
new file mode 100644
index 0000000..4644ea8
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldListViewBuilder.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.expenses.gwt.scaffold;
+
+import com.google.gwt.sample.expenses.gwt.place.ExpensesListPlace;
+import com.google.gwt.sample.expenses.gwt.place.ExpensesPlaces;
+import com.google.gwt.sample.expenses.gwt.request.EmployeeKey;
+import com.google.gwt.sample.expenses.gwt.request.ExpensesKeyVisitor;
+import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
+import com.google.gwt.sample.expenses.gwt.request.ReportKey;
+import com.google.gwt.sample.expenses.gwt.ui.employee.AllEmployeesRequester;
+import com.google.gwt.sample.expenses.gwt.ui.employee.EmployeeListView;
+import com.google.gwt.sample.expenses.gwt.ui.report.AllReportsRequester;
+import com.google.gwt.sample.expenses.gwt.ui.report.ReportListView;
+import com.google.gwt.user.client.ui.Renderer;
+import com.google.gwt.valuestore.client.ValuesListViewTable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Returns the view instances for {@link ExpensesListPlace}s, paired with
+ * objects to request all records of the appropriate type.
+ */
+public class ScaffoldListViewBuilder {
+  private final ExpensesRequestFactory requests;
+  private final Renderer<ExpensesListPlace> placeRenderer;
+  private final ExpensesPlaces places;
+  private final Map<ExpensesListPlace, ValuesListViewTable<?>> viewMap = new HashMap<ExpensesListPlace, ValuesListViewTable<?>>();
+
+  public ScaffoldListViewBuilder(ExpensesPlaces places,
+      ExpensesRequestFactory requests, Renderer<ExpensesListPlace> renderer) {
+    this.places = places;
+    this.requests = requests;
+    this.placeRenderer = renderer;
+  }
+
+  public ValuesListViewTable<?> getListView(final ExpensesListPlace newPlace) {
+    if (!viewMap.containsKey(newPlace)) {
+      newPlace.getKey().accept(new ExpensesKeyVisitor() {
+        public void visit(EmployeeKey key) {
+          EmployeeListView newView = new EmployeeListView(
+              placeRenderer.render(newPlace), places);
+          newView.setDelegate(new AllEmployeesRequester(requests, newView));
+          viewMap.put(newPlace, newView);
+        }
+
+        public void visit(ReportKey key) {
+          ReportListView newView = new ReportListView(
+              placeRenderer.render(newPlace), places);
+          newView.setDelegate(new AllReportsRequester(requests, newView));
+          viewMap.put(newPlace, newView);
+        }
+      });
+    }
+    return viewMap.get(newPlace);
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/AllEmployeesRequester.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/AllEmployeesRequester.java
new file mode 100644
index 0000000..2eebbe9
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/AllEmployeesRequester.java
@@ -0,0 +1,42 @@
+/*
+ * 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.expenses.gwt.ui.employee;
+
+import com.google.gwt.sample.expenses.gwt.request.EmployeeKey;
+import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
+import com.google.gwt.valuestore.client.ValuesListViewTable;
+import com.google.gwt.valuestore.shared.ValuesListView;
+
+/**
+ * An implementation of {@link ValuesListView.Delegate} that requests all
+ * {@link EmployeeKey} records.
+ */
+public final class AllEmployeesRequester implements ValuesListView.Delegate {
+  private final ValuesListViewTable<EmployeeKey> view;
+  private final ExpensesRequestFactory requests;
+
+  public AllEmployeesRequester(ExpensesRequestFactory requests,
+      ValuesListViewTable<EmployeeKey> newView) {
+    this.view = newView;
+    this.requests = requests;
+  }
+
+  public void onRangeChanged(int start, int length) {
+    // TODO use start and length
+    requests.employeeRequest().findAllEmployees().forProperties(
+        view.getProperties()).to(view).fire();
+  }
+}
\ No newline at end of file
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsBuilder.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsBuilder.java
new file mode 100644
index 0000000..3971a4d
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsBuilder.java
@@ -0,0 +1,39 @@
+/*
+ * 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.expenses.gwt.ui.employee;
+
+import com.google.gwt.sample.expenses.gwt.request.EmployeeKey;
+import com.google.gwt.valuestore.shared.Values;
+
+/**
+ * Renders report details to HTML.
+ */
+public class EmployeeDetailsBuilder {
+
+  public void append(StringBuilder list, Values<EmployeeKey> values) {
+    String user = values.get(EmployeeKey.get().getUserName());
+    list.append("<div>");
+    list.append("<label>").append("User Name: ").append("</label>");
+    list.append("<span>").append(user).append("</span>");
+    list.append("</div>");
+
+    list.append("<div>");
+    String display = values.get(EmployeeKey.get().getDisplayName());
+    list.append("<label>").append("Display Name: ").append("</label>");
+    list.append("<span>").append(display).append("</span>");
+    list.append("</div>");
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListView.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListView.java
index b241e97..cdefa6e 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListView.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListView.java
@@ -21,17 +21,14 @@
 import com.google.gwt.bikeshed.list.client.IdentityColumn;
 import com.google.gwt.bikeshed.list.client.TextColumn;
 import com.google.gwt.bikeshed.list.client.TextHeader;
-import com.google.gwt.bikeshed.list.shared.ListModel;
-import com.google.gwt.bikeshed.list.shared.ListRegistration;
 import com.google.gwt.sample.expenses.gwt.place.ExpensesPlaces;
-import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
 import com.google.gwt.sample.expenses.gwt.request.EmployeeKey;
-import com.google.gwt.valuestore.client.ListModelAdapter;
 import com.google.gwt.valuestore.client.ValuesListViewTable;
 import com.google.gwt.valuestore.shared.Property;
 import com.google.gwt.valuestore.shared.Values;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -40,6 +37,13 @@
  * TODO The bulk of this should be in a <g:table> in a ui.xml file
  */
 public class EmployeeListView extends ValuesListViewTable<EmployeeKey> {
+  private static final List<Property<EmployeeKey, ?>> properties;
+  static {
+    List<Property<EmployeeKey, ?>> p = new ArrayList<Property<EmployeeKey, ?>>();
+    p.add(EmployeeKey.get().getUserName());
+    p.add(EmployeeKey.get().getDisplayName());
+    properties = Collections.unmodifiableList(p);
+  }
 
   private static List<Column<Values<EmployeeKey>, ?, ?>> getColumns(
       final ExpensesPlaces places) {
@@ -63,42 +67,26 @@
         new ActionCell<Values<EmployeeKey>>("Show",
             places.<EmployeeKey> getDetailsGofer())));
 
-//    columns.add(new IdentityColumn<Values<EmployeeKey>>(
-//        new ActionCell<Values<EmployeeKey>>("Edit",
-//            places.<EmployeeKey> getEditorGofer())));
+    // columns.add(new IdentityColumn<Values<EmployeeKey>>(
+    // new ActionCell<Values<EmployeeKey>>("Edit",
+    // places.<EmployeeKey> getEditorGofer())));
 
     return columns;
   }
 
   private static List<Header<?>> getHeaders() {
     List<Header<?>> headers = new ArrayList<Header<?>>();
-    for (final Property<EmployeeKey, ?> property : getProperties()) {
+    for (final Property<EmployeeKey, ?> property : properties) {
       headers.add(new TextHeader(property.getName()));
     }
     return headers;
   }
 
-  private static ListModel<Values<EmployeeKey>> getModel(
-      final ExpensesRequestFactory requests) {
-    return new ListModelAdapter<EmployeeKey>() {
-      @Override
-      protected void onRangeChanged(ListRegistration reg, int start, int length) {
-        requests.employeeRequest().findAllEmployees().forProperties(
-            getProperties()).to(this).fire();
-      }
-    };
+  public EmployeeListView(String headingMessage, ExpensesPlaces places) {
+    super(headingMessage, getColumns(places), getHeaders());
   }
 
-  private static List<Property<EmployeeKey, ?>> getProperties() {
-    List<Property<EmployeeKey, ?>> properties = new ArrayList<Property<EmployeeKey, ?>>();
-    properties.add(EmployeeKey.get().getUserName());
-    properties.add(EmployeeKey.get().getDisplayName());
+  public List<Property<EmployeeKey, ?>> getProperties() {
     return properties;
   }
-
-  public EmployeeListView(String headingMessage, ExpensesPlaces places,
-      ExpensesRequestFactory requests) {
-    super(headingMessage, getModel(requests), getColumns(places), getHeaders());
-  }
-
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/AllReportsRequester.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/AllReportsRequester.java
new file mode 100644
index 0000000..0306996
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/AllReportsRequester.java
@@ -0,0 +1,42 @@
+/*
+ * 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.expenses.gwt.ui.report;
+
+import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
+import com.google.gwt.sample.expenses.gwt.request.ReportKey;
+import com.google.gwt.valuestore.client.ValuesListViewTable;
+import com.google.gwt.valuestore.shared.ValuesListView;
+
+/**
+ * An implementation of {@link ValuesListView.Delegate} that requests all
+ * {@link ReportKey} records.
+ */
+public final class AllReportsRequester implements ValuesListView.Delegate {
+  private final ValuesListViewTable<ReportKey> view;
+  private final ExpensesRequestFactory requests;
+
+  public AllReportsRequester(ExpensesRequestFactory requests,
+      ValuesListViewTable<ReportKey> newView) {
+    this.view = newView;
+    this.requests = requests;
+  }
+
+  public void onRangeChanged(int start, int length) {
+    // TODO use start and length
+    requests.reportRequest().findAllReports().forProperties(
+        view.getProperties()).to(view).fire();
+  }
+}
\ No newline at end of file
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsBuilder.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsBuilder.java
new file mode 100644
index 0000000..d9e6051
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsBuilder.java
@@ -0,0 +1,39 @@
+/*
+ * 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.expenses.gwt.ui.report;
+
+import com.google.gwt.sample.expenses.gwt.request.ReportKey;
+import com.google.gwt.valuestore.shared.Values;
+
+/**
+ * Renders report details to HTML.
+ */
+public class ReportDetailsBuilder {
+
+  public void append(StringBuilder list, Values<ReportKey> values) {
+    String purpose = values.get(ReportKey.get().getPurpose());
+    list.append("<div>");
+    list.append("<label>").append("Purpose: ").append("</label>");
+    list.append("<span>").append(purpose).append("</span>");
+    list.append("</div>");
+
+    list.append("<div>");
+    String created = values.get(ReportKey.get().getCreated()).toString();
+    list.append("<label>").append("Created: ").append("</label>");
+    list.append("<span>").append(created).append("</span>");
+    list.append("</div>");
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListView.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListView.java
index a2838b0..7dfe967 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListView.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListView.java
@@ -23,18 +23,15 @@
 import com.google.gwt.bikeshed.list.client.SimpleColumn;
 import com.google.gwt.bikeshed.list.client.TextColumn;
 import com.google.gwt.bikeshed.list.client.TextHeader;
-import com.google.gwt.bikeshed.list.shared.ListModel;
-import com.google.gwt.bikeshed.list.shared.ListRegistration;
 import com.google.gwt.i18n.client.DateTimeFormat;
 import com.google.gwt.sample.expenses.gwt.place.ExpensesPlaces;
-import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
 import com.google.gwt.sample.expenses.gwt.request.ReportKey;
-import com.google.gwt.valuestore.client.ListModelAdapter;
 import com.google.gwt.valuestore.client.ValuesListViewTable;
 import com.google.gwt.valuestore.shared.Property;
 import com.google.gwt.valuestore.shared.Values;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 
@@ -44,6 +41,13 @@
  * TODO The bulk of this should be in a <g:table> in a ui.xml file
  */
 public class ReportListView extends ValuesListViewTable<ReportKey> {
+  private static final List<Property<ReportKey, ?>> properties;
+  static {
+    List<Property<ReportKey, ?>> p = new ArrayList<Property<ReportKey, ?>>();
+    p.add(ReportKey.get().getCreated());
+    p.add(ReportKey.get().getPurpose());
+    properties = Collections.unmodifiableList(p);
+  }
 
   private static List<Column<Values<ReportKey>, ?, ?>> getColumns(
       final ExpensesPlaces places) {
@@ -68,42 +72,27 @@
         new ActionCell<Values<ReportKey>>("Show",
             places.<ReportKey> getDetailsGofer())));
 
-//    columns.add(new IdentityColumn<Values<ReportKey>>(
-//        new ActionCell<Values<ReportKey>>("Edit",
-//            places.<ReportKey> getEditorGofer())));
+    // columns.add(new IdentityColumn<Values<ReportKey>>(
+    // new ActionCell<Values<ReportKey>>("Edit",
+    // places.<ReportKey> getEditorGofer())));
 
     return columns;
   }
 
   private static List<Header<?>> getHeaders() {
     List<Header<?>> headers = new ArrayList<Header<?>>();
-    for (final Property<ReportKey, ?> property : getProperties()) {
+    for (final Property<ReportKey, ?> property : properties) {
       headers.add(new TextHeader(property.getName()));
     }
     return headers;
   }
 
-  private static ListModel<Values<ReportKey>> getModel(
-      final ExpensesRequestFactory requests) {
-    return new ListModelAdapter<ReportKey>() {
-      @Override
-      protected void onRangeChanged(ListRegistration reg, int start, int length) {
-        requests.reportRequest().findAllReports().forProperties(getProperties()).to(
-            this).fire();
-      }
-    };
+  public ReportListView(String headingMessage, ExpensesPlaces places) {
+    super(headingMessage, getColumns(places), getHeaders());
   }
 
-  private static List<Property<ReportKey, ?>> getProperties() {
-    List<Property<ReportKey, ?>> properties = new ArrayList<Property<ReportKey, ?>>();
-    properties.add(ReportKey.get().getCreated());
-    properties.add(ReportKey.get().getPurpose());
+  public List<Property<ReportKey, ?>> getProperties() {
     return properties;
   }
 
-  public ReportListView(String headingMessage, ExpensesPlaces places,
-      ExpensesRequestFactory requests) {
-    super(headingMessage, getModel(requests), getColumns(places), getHeaders());
-  }
-
 }
diff --git a/bikeshed/src/com/google/gwt/valuestore/client/ListModelAdapter.java b/bikeshed/src/com/google/gwt/valuestore/client/ListModelAdapter.java
index dabae59..d52b9a8 100644
--- a/bikeshed/src/com/google/gwt/valuestore/client/ListModelAdapter.java
+++ b/bikeshed/src/com/google/gwt/valuestore/client/ListModelAdapter.java
@@ -37,6 +37,6 @@
 
   public void setValueList(List<Values<K>> newValues) {
     updateDataSize(newValues.size(), true);
-    updateViewData(0, newValues.size() - 1, newValues);
+    updateViewData(0, newValues.size(), newValues);
   }
 }
\ No newline at end of file
diff --git a/bikeshed/src/com/google/gwt/valuestore/client/ValuesListViewTable.java b/bikeshed/src/com/google/gwt/valuestore/client/ValuesListViewTable.java
index 596cf3b..7255d0f 100644
--- a/bikeshed/src/com/google/gwt/valuestore/client/ValuesListViewTable.java
+++ b/bikeshed/src/com/google/gwt/valuestore/client/ValuesListViewTable.java
@@ -18,41 +18,47 @@
 import com.google.gwt.bikeshed.list.client.Column;
 import com.google.gwt.bikeshed.list.client.Header;
 import com.google.gwt.bikeshed.list.client.PagingTableListView;
-import com.google.gwt.bikeshed.list.shared.ListModel;
+import com.google.gwt.bikeshed.list.shared.ListRegistration;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.HeadingElement;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.valuestore.shared.Property;
 import com.google.gwt.valuestore.shared.Values;
 import com.google.gwt.valuestore.shared.ValuesKey;
+import com.google.gwt.valuestore.shared.ValuesListView;
 
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 
 /**
  * A widget that displays lists of {@link com.google.gwt.valuestore.ValueStore
- * ValueStore} records in a {@link PagingTableListView}.
- * <p>
- * TODO needs MVP separation
- * 
+ * ValueStore} records in a {@link PagingTableListView}
+ *
  * @param <K> the type of the ValuesKey shared by these records
  */
-public class ValuesListViewTable<K extends ValuesKey<K>> extends Composite {
+public abstract class ValuesListViewTable<K extends ValuesKey<K>> extends
+    Composite implements ValuesListView<K> {
   interface Binder extends UiBinder<HTMLPanel, ValuesListViewTable<?>> {
   }
 
   private static final Binder BINDER = GWT.create(Binder.class);
 
+  public ValuesListView.Delegate delegate;
+  public ListModelAdapter<K> listModel;
+
   @UiField(provided = true)
   PagingTableListView<Values<K>> table;
   @UiField
   HeadingElement heading;
 
-  public ValuesListViewTable(String headingMessage, ListModel<Values<K>> model,
+  public ValuesListViewTable(String headingMessage,
       List<Column<Values<K>, ?, ?>> columns, List<Header<?>> headers) {
-    table = new PagingTableListView<Values<K>>(model, 100);
+    listModel = createModel();
+    table = new PagingTableListView<Values<K>>(listModel, 100);
     final Iterator<Header<?>> nextHeader = headers.iterator();
     for (Column<Values<K>, ?, ?> column : columns) {
       if (nextHeader.hasNext()) {
@@ -65,4 +71,37 @@
 
     heading.setInnerText(headingMessage);
   }
+
+  public ValuesListViewTable<K> asWidget() {
+    return this;
+  }
+
+  /**
+   * @return the delegate for this view, or null if there is none
+   */
+  public ValuesListView.Delegate getDelegate() {
+    return delegate;
+  }
+
+  public abstract Collection<Property<K, ?>> getProperties();
+
+  /**
+   * @param delegate the new delegate for this view. May be null
+   */
+  public void setDelegate(ValuesListView.Delegate delegate) {
+    this.delegate = delegate;
+  }
+  
+  public void setValueList(List<Values<K>> newValues) {
+    listModel.setValueList(newValues);
+  }
+
+  private ListModelAdapter<K> createModel() {
+    return new ListModelAdapter<K>() {
+      @Override
+      protected void onRangeChanged(ListRegistration<Values<K>> reg, int start, int length) {
+        getDelegate().onRangeChanged(start, length);
+      }
+    };
+  }
 }
diff --git a/bikeshed/src/com/google/gwt/valuestore/shared/ValuesListView.java b/bikeshed/src/com/google/gwt/valuestore/shared/ValuesListView.java
new file mode 100644
index 0000000..8ec5189
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/valuestore/shared/ValuesListView.java
@@ -0,0 +1,50 @@
+/*
+ * 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.valuestore.shared;
+
+import com.google.gwt.user.client.ui.TakesValueList;
+import com.google.gwt.user.client.ui.Widget;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Implemented by widgets that display lists of records.
+ * 
+ * @param <K> the type of the key of the records
+ */
+public interface ValuesListView<K extends ValuesKey<K>> extends
+    TakesValueList<Values<K>> {
+
+  /**
+   * Implemented by ValuesListView delegates, to keep informed when the visible
+   * range changes.
+   */
+  public interface Delegate {
+    void onRangeChanged(int start, int length);
+  }
+
+  Widget asWidget();
+
+  Collection<Property<K, ?>> getProperties();
+
+  /**
+   * @param delegate the new delegate for this view. May be null
+   */
+  void setDelegate(ValuesListView.Delegate delegate);
+
+  void setValueList(List<Values<K>> newValues);
+}