Improves expenses scaffold to make better use of PagingTableListView.
Breaks out ReportListView and EmployeeListView as their own UI
classes. Most of what they do should really be happening in a ui.xml
file, but UiBinder doesn't know about PagingTableListView yet.

Introduces IdentityColumn, DateCell and ActionCell to
com.google.gwt.bikeshed.cells.client

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


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7874 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/src/com/google/gwt/app/place/GoToPlaceCommand.java b/bikeshed/src/com/google/gwt/app/place/GoToPlaceCommand.java
deleted file mode 100644
index 9042609..0000000
--- a/bikeshed/src/com/google/gwt/app/place/GoToPlaceCommand.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.app.place;
-
-import com.google.gwt.user.client.Command;
-
-/**
- * Command to change the app location.
- * 
- * @param <P> the type of place managed by the {@link PlaceController}
- */
-public class GoToPlaceCommand<P extends Place> implements Command {
-  private final P place;
-  private final PlaceController<? super P> placeController;
-
-  /**
-   * @param place
-   * @param placeController
-   */
-  public GoToPlaceCommand(P place, PlaceController<? super P> placeController) {
-    this.place = place;
-    this.placeController = placeController;
-  }
-
-  public void execute() {
-    placeController.goTo(place);
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/ActionCell.java b/bikeshed/src/com/google/gwt/bikeshed/cells/client/ActionCell.java
new file mode 100644
index 0000000..823eaaf
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/cells/client/ActionCell.java
@@ -0,0 +1,60 @@
+/*
+ * 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.bikeshed.cells.client;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+
+/**
+ * A cell that renders a button and takes a delegate to perform actions on
+ * mouseUp.
+ * 
+ * @param <C> the type that this Cell represents
+ */
+public class ActionCell<C> extends Cell<C, Void> {
+  /**
+   * @param <C> the type that this delegate acts on
+   */
+  public interface Delegate<T> {
+    void execute(T object);
+  }
+
+  private final String message;
+  private final Delegate<C> delegate;
+
+  /**
+   * @param message
+   * @param delegate
+   */
+  public ActionCell(String message, Delegate<C> delegate) {
+    this.message = message;
+    this.delegate = delegate;
+  }
+
+  @Override
+  public Void onBrowserEvent(Element parent, C value, Void viewData,
+      NativeEvent event, ValueUpdater<C, Void> valueUpdater) {
+    if ("mouseup".equals(event.getType())) {
+      delegate.execute(value);
+    }
+    return null;
+  }
+
+  @Override
+  public void render(C value, Void viewData, StringBuilder sb) {
+    sb.append("<button>" + message + "</button>");
+  }
+}
\ No newline at end of file
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/Cell.java b/bikeshed/src/com/google/gwt/bikeshed/cells/client/Cell.java
index 8d4d795..6037da5 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/client/Cell.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/cells/client/Cell.java
@@ -45,6 +45,7 @@
    * @param valueUpdater a {@link ValueUpdater}, or null
    * @return a view data object which may be the one passed in or a new object
    */
+  @SuppressWarnings("unused") 
   public V onBrowserEvent(Element parent, C value, V viewData,
       NativeEvent event, ValueUpdater<C, V> valueUpdater) {
     return null;
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/DateCell.java b/bikeshed/src/com/google/gwt/bikeshed/cells/client/DateCell.java
new file mode 100644
index 0000000..d8c359d
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/cells/client/DateCell.java
@@ -0,0 +1,40 @@
+/*
+ * 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.bikeshed.cells.client;
+
+import com.google.gwt.i18n.client.DateTimeFormat;
+
+import java.util.Date;
+
+/**
+ * A {@link Cell} used to render {@link Date}s.
+ */
+public class DateCell extends Cell<Date, Void> {
+
+  private final DateTimeFormat format;
+  
+  public DateCell(DateTimeFormat format) {
+    this.format = format;
+  }
+
+  @Override
+  public void render(Date value, Void viewData, StringBuilder sb) {
+    if (value != null) {
+      sb.append(format.format(value));
+    }
+  }
+
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/IdentityColumn.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/IdentityColumn.java
new file mode 100644
index 0000000..116368d
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/IdentityColumn.java
@@ -0,0 +1,41 @@
+/*
+ * 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.bikeshed.list.client;
+
+import com.google.gwt.bikeshed.cells.client.Cell;
+
+
+/**
+ * A passthrough column, useful for giving cells access to the entire row
+ * object.
+ * 
+ * @param <T> the row type
+ */
+public class IdentityColumn<T> extends SimpleColumn<T, T> {
+
+  /**
+   * @param cell
+   * @param hasKey
+   */
+  public IdentityColumn(Cell<T, Void> cell) {
+    super(cell);
+  }
+
+  @Override
+  public T getValue(T object) {
+    return object;
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/Expenses.gwt.xml b/bikeshed/src/com/google/gwt/sample/expenses/Expenses.gwt.xml
index 9f46f26..10961e0 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/Expenses.gwt.xml
+++ b/bikeshed/src/com/google/gwt/sample/expenses/Expenses.gwt.xml
@@ -8,6 +8,7 @@
   <!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/>     -->
   <inherits name='com.google.gwt.app.App' />
   <inherits name='com.google.gwt.requestfactory.RequestFactory'/>
+  <inherits name="com.google.gwt.bikeshed.list.List" />
 
   <!-- Specify the app entry point class.                         -->
   <entry-point class='com.google.gwt.sample.expenses.client.Expenses'/>
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/client/CommandCell.java b/bikeshed/src/com/google/gwt/sample/expenses/client/CommandCell.java
deleted file mode 100644
index f5a72ab..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/client/CommandCell.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.client;
-
-import com.google.gwt.bikeshed.cells.client.Cell;
-import com.google.gwt.bikeshed.cells.client.ValueUpdater;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.dom.client.NativeEvent;
-import com.google.gwt.user.client.Command;
-
-/**
- * A Cell embedding a Command.
- */
-public class CommandCell extends Cell<Command, Void> {
-  
-  private String message;
-
-  public CommandCell(String message) {
-    this.message = message;
-  }
-  
-  @Override
-  public Void onBrowserEvent(Element parent, Command value, Void viewData,
-      NativeEvent event, ValueUpdater<Command, Void> valueUpdater) {
-    if ("mouseup".equals(event.getType())) {
-      if (valueUpdater != null) {
-        valueUpdater.update(value, viewData);
-      }
-      value.execute();
-    }
-
-    return viewData;
-  }
-
-  @Override
-  public void render(Command value, Void viewData, StringBuilder sb) {
-    sb.append("<button>" + message + "</button>");
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/client/EmployeeListView.java b/bikeshed/src/com/google/gwt/sample/expenses/client/EmployeeListView.java
new file mode 100644
index 0000000..6d32ccf
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/client/EmployeeListView.java
@@ -0,0 +1,102 @@
+/*
+ * 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.client;
+
+import com.google.gwt.bikeshed.cells.client.ActionCell;
+import com.google.gwt.bikeshed.list.client.Column;
+import com.google.gwt.bikeshed.list.client.Header;
+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.sample.expenses.client.place.Places;
+import com.google.gwt.sample.expenses.shared.EmployeeKey;
+import com.google.gwt.sample.expenses.shared.ExpenseRequestFactory;
+import com.google.gwt.valuestore.shared.Property;
+import com.google.gwt.valuestore.shared.Values;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@ValuesListViewTable} specialized to {@link EmployeeKey} values.
+ * <p>
+ * TODO The bulk of this should be in a <g:table> in a ui.xml file
+ */
+public class EmployeeListView extends ValuesListViewTable<EmployeeKey> {
+
+  private static List<Column<Values<EmployeeKey>, ?, ?>> getColumns(
+      final Places places) {
+    List<Column<Values<EmployeeKey>, ?, ?>> columns = new ArrayList<Column<Values<EmployeeKey>, ?, ?>>();
+
+    columns.add(new TextColumn<Values<EmployeeKey>>() {
+      @Override
+      public String getValue(Values<EmployeeKey> object) {
+        return object.get(EmployeeKey.get().getUserName());
+      }
+    });
+
+    columns.add(new TextColumn<Values<EmployeeKey>>() {
+      @Override
+      public String getValue(Values<EmployeeKey> object) {
+        return object.get(EmployeeKey.get().getDisplayName());
+      }
+    });
+
+    columns.add(new IdentityColumn<Values<EmployeeKey>>(
+        new ActionCell<Values<EmployeeKey>>("Show",
+            places.<EmployeeKey> getDetailsGofer())));
+
+//    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()) {
+      headers.add(new TextHeader(property.getName()));
+    }
+    return headers;
+  }
+
+  private static ListModel<Values<EmployeeKey>> getModel(
+      final ExpenseRequestFactory requests) {
+    return new ListModelAdapter<EmployeeKey>() {
+
+      @Override
+      protected void onRangeChanged(int start, int length) {
+        requests.employeeRequest().findAllEmployees().forProperties(
+            getProperties()).to(this).fire();
+      }
+    };
+  }
+
+  private static List<Property<EmployeeKey, ?>> getProperties() {
+    List<Property<EmployeeKey, ?>> properties = new ArrayList<Property<EmployeeKey, ?>>();
+    properties.add(EmployeeKey.get().getUserName());
+    properties.add(EmployeeKey.get().getDisplayName());
+    return properties;
+  }
+
+  public EmployeeListView(String headingMessage, Places places,
+      ExpenseRequestFactory requests) {
+    super(headingMessage, getModel(requests), getColumns(places), getHeaders());
+  }
+
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/client/EntityListPresenter.java b/bikeshed/src/com/google/gwt/sample/expenses/client/EntityListPresenter.java
deleted file mode 100644
index 842d358..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/client/EntityListPresenter.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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.client;
-
-import com.google.gwt.sample.expenses.client.place.Places;
-import com.google.gwt.sample.expenses.shared.ExpensesEntityKey;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.ui.TakesValueList;
-import com.google.gwt.valuestore.shared.Property;
-import com.google.gwt.valuestore.shared.Values;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Presenter that shows a list of entities and provides "edit" and "show"
- * commands for them.
- * 
- * @param <T> the type of entity listed
- */
-public class EntityListPresenter<T extends ExpensesEntityKey<T>> implements
-    TakesValueList<Values<T>> {
-  private final EntityListView view;
-  private final List<Property<T, ?>> properties;
-  private final Places places;
-  private List<String> columnNames;
-
-  public EntityListPresenter(String heading, EntityListView view,
-      List<Property<T, ?>> properties, Places places) {
-    this.view = view;
-    view.setHeading(heading);
-    this.properties = properties;
-    this.places = places;
-
-   columnNames = new ArrayList<String>();
-    for (Property<T, ?> property : properties) {
-      columnNames.add(property.getName());
-    }
-  }
-
-  public void setValueList(List<Values<T>> newValues) {
-    List<EntityListView.Row> rows = new ArrayList<EntityListView.Row>();
-
-    for (final Values<T> values : newValues) {
-      final List<String> strings = new ArrayList<String>();
-      for (Property<T, ?> property : properties) {
-        strings.add(values.get(property).toString());
-      }
-      EntityListView.Row row = new EntityListView.Row() {
-
-        public Command getEditCommand() {
-          return places.getGoToEditFor(values);
-        }
-
-        public Command getShowDetailsCommand() {
-          return places.getGoToDetailsFor(values);
-        }
-
-        public List<String> getValues() {
-          return strings;
-        }
-
-      };
-      rows.add(row);
-    }
-
-    view.setRowData(columnNames, rows);
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/client/ExpensesScaffold.java b/bikeshed/src/com/google/gwt/sample/expenses/client/ExpensesScaffold.java
index 6e70c09..a914a86 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/client/ExpensesScaffold.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/client/ExpensesScaffold.java
@@ -39,8 +39,8 @@
 
     // App controllers and services
     final HandlerManager eventBus = new HandlerManager(null);
-    final ExpenseRequestFactory requests = GWT.create(ExpenseRequestFactory.class);
-    requests.init(eventBus);
+    final ExpenseRequestFactory requestFactory = GWT.create(ExpenseRequestFactory.class);
+    requestFactory.init(eventBus);
 
     final PlaceController<AbstractExpensesPlace> placeController = new PlaceController<AbstractExpensesPlace>(
         eventBus);
@@ -49,22 +49,21 @@
     // Renderers
     final EntityNameRenderer entityNamer = new EntityNameRenderer();
     final ListPlaceRenderer listPlaceNamer = new ListPlaceRenderer(entityNamer);
-    
+
     // Top level UI
     final ExpensesScaffoldShell shell = new ExpensesScaffoldShell();
-    
+
     // Left side
     PlacePicker<EntityListPlace> placePicker = new PlacePicker<EntityListPlace>(
         shell.getPlacesBox(), placeController, listPlaceNamer);
     placePicker.setPlaces(places.getListPlaces());
 
-    // Shared view for entity lists. Perhaps real app would have
-    // a separate view per type?
-    final TableEntityListView entitiesView = new TableEntityListView();
+    // Shows entity lists
     eventBus.addHandler(PlaceChanged.TYPE, new ListRequester(places,
-        shell.getBody(), entitiesView, requests, listPlaceNamer));
-    
-    // Shared view for entity details. Again, perhaps real app should not share
+        shell.getBody(), requestFactory, listPlaceNamer));
+
+    // Shared view for entity details.
+    // TODO Real app should not share
     final HTML detailsView = new HTML();
     eventBus.addHandler(PlaceChanged.TYPE, new DetailsRequester(entityNamer,
         shell.getBody(), detailsView));
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/client/ListModelAdapter.java b/bikeshed/src/com/google/gwt/sample/expenses/client/ListModelAdapter.java
new file mode 100644
index 0000000..a1f03cf
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/client/ListModelAdapter.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.client;
+
+import com.google.gwt.bikeshed.list.shared.AsyncListModel;
+import com.google.gwt.user.client.ui.TakesValueList;
+import com.google.gwt.valuestore.shared.Values;
+import com.google.gwt.valuestore.shared.ValuesKey;
+
+import java.util.List;
+
+/**
+ * Simple adapter from a {@link com.google.gwt.valuestore.shared.ValueStore
+ * ValueStore} to a {@link com.google.gwt.bikeshed.list.shared.ListModel
+ * ListModel}
+ * <p>
+ * TODO: pay attention to the visible range info that subclasses receive via
+ * {@link #onRangeChanged}
+ * 
+ * @param <K> the ValuesKey of the records to display
+ */
+public abstract class ListModelAdapter<K extends ValuesKey<K>> extends
+    AsyncListModel<Values<K>> implements TakesValueList<Values<K>> {
+
+  public void setValueList(List<Values<K>> newValues) {
+    updateDataSize(newValues.size(), true);
+    updateViewData(0, newValues.size() - 1, newValues);
+  }
+}
\ No newline at end of file
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/client/ListRequester.java b/bikeshed/src/com/google/gwt/sample/expenses/client/ListRequester.java
index 3bb724c..254c732 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/client/ListRequester.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/client/ListRequester.java
@@ -25,10 +25,9 @@
 import com.google.gwt.sample.expenses.shared.ReportKey;
 import com.google.gwt.user.client.ui.Renderer;
 import com.google.gwt.user.client.ui.SimplePanel;
-import com.google.gwt.valuestore.shared.Property;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * In charge of requesting and displaying the appropriate entity lists when the
@@ -37,67 +36,54 @@
 public final class ListRequester implements PlaceChanged.Handler {
 
   private final SimplePanel panel;
-  private final TableEntityListView entitiesView;
   private final ExpenseRequestFactory requests;
-  private final Renderer<EntityListPlace> listNameFilter;
+  private final Renderer<EntityListPlace> placeRenderer;
   private final Places places;
 
+  // TODO This dependency on view classes prevents testing this class in JRE.
+  // Get a factory in here or something
+  private final Map<EntityListPlace, ValuesListViewTable<?>> viewMap = new HashMap<EntityListPlace, ValuesListViewTable<?>>();
+
   public ListRequester(Places places, SimplePanel panel,
-      TableEntityListView entitiesView, ExpenseRequestFactory requests,
-      Renderer<EntityListPlace> renderer) {
+      ExpenseRequestFactory requests, Renderer<EntityListPlace> renderer) {
     this.places = places;
     this.panel = panel;
-    this.entitiesView = entitiesView;
     this.requests = requests;
-    this.listNameFilter = renderer;
+    this.placeRenderer = renderer;
   }
 
   public void onPlaceChanged(PlaceChanged event) {
     if (!(event.getNewPlace() instanceof EntityListPlace)) {
       return;
     }
-    EntityListPlace newPlace = (EntityListPlace) event.getNewPlace();
-    final String name = listNameFilter.render(newPlace);
+    final EntityListPlace newPlace = (EntityListPlace) event.getNewPlace();
+    ExpensesEntityKey<?> key = newPlace.getKey();
 
-    final ExpensesEntityKey<?> key = newPlace.getKey();
-
-    // TODO Would be simpler if every entity key knew its find method
     key.accept(new ExpensesEntityVisitor() {
 
       public void visit(EmployeeKey employeeKey) {
-        List<Property<EmployeeKey, ?>> columns = getEmployeeColumns();
-        EntityListPresenter<EmployeeKey> presenter = new EntityListPresenter<EmployeeKey>(
-            name, entitiesView, columns, places);
-        requests.employeeRequest().findAllEmployees().forProperties(columns).to(
-            presenter).fire();
+        ValuesListViewTable<?> view = viewMap.get(newPlace);
+        if (null == view) {
+          view = new EmployeeListView(placeRenderer.render(newPlace), places,
+              requests);
+          viewMap.put(newPlace, view);
+        }
       }
 
       public void visit(ReportKey reportKey) {
-        List<Property<ReportKey, ?>> columns = getReportColumns();
-        EntityListPresenter<ReportKey> presenter = new EntityListPresenter<ReportKey>(
-            name, entitiesView, columns, places);
-        requests.reportRequest().findAllReports().forProperties(columns).to(
-            presenter).fire();
+        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) {
       panel.clear();
       panel.add(entitiesView);
     }
   }
-
-  private List<Property<EmployeeKey, ?>> getEmployeeColumns() {
-    List<Property<EmployeeKey, ?>> columns = new ArrayList<Property<EmployeeKey, ?>>();
-    columns.add(EmployeeKey.get().getUserName());
-    columns.add(EmployeeKey.get().getDisplayName());
-    return columns;
-  }
-
-  private List<Property<ReportKey, ?>> getReportColumns() {
-    List<Property<ReportKey, ?>> columns = new ArrayList<Property<ReportKey, ?>>();
-    columns.add(ReportKey.get().getCreated());
-    columns.add(ReportKey.get().getPurpose());
-    return columns;
-  }
 }
\ No newline at end of file
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/client/ReportListView.java b/bikeshed/src/com/google/gwt/sample/expenses/client/ReportListView.java
new file mode 100644
index 0000000..ba1eb64
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/client/ReportListView.java
@@ -0,0 +1,107 @@
+/*
+ * 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.client;
+
+import com.google.gwt.bikeshed.cells.client.ActionCell;
+import com.google.gwt.bikeshed.cells.client.DateCell;
+import com.google.gwt.bikeshed.list.client.Column;
+import com.google.gwt.bikeshed.list.client.Header;
+import com.google.gwt.bikeshed.list.client.IdentityColumn;
+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.i18n.client.DateTimeFormat;
+import com.google.gwt.sample.expenses.client.place.Places;
+import com.google.gwt.sample.expenses.shared.ExpenseRequestFactory;
+import com.google.gwt.sample.expenses.shared.ReportKey;
+import com.google.gwt.valuestore.shared.Property;
+import com.google.gwt.valuestore.shared.Values;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * {@ValuesListViewTable} specialized to {@link ReportKey} values.
+ * <p>
+ * TODO The bulk of this should be in a <g:table> in a ui.xml file
+ */
+public class ReportListView extends ValuesListViewTable<ReportKey> {
+
+  private static List<Column<Values<ReportKey>, ?, ?>> getColumns(
+      final Places places) {
+    List<Column<Values<ReportKey>, ?, ?>> columns = new ArrayList<Column<Values<ReportKey>, ?, ?>>();
+
+    DateCell dateCell = new DateCell(DateTimeFormat.getShortDateFormat());
+    columns.add(new SimpleColumn<Values<ReportKey>, Date>(dateCell) {
+      @Override
+      public Date getValue(Values<ReportKey> object) {
+        return object.get(ReportKey.get().getCreated());
+      }
+    });
+
+    columns.add(new TextColumn<Values<ReportKey>>() {
+      @Override
+      public String getValue(Values<ReportKey> object) {
+        return object.get(ReportKey.get().getPurpose());
+      }
+    });
+
+    columns.add(new IdentityColumn<Values<ReportKey>>(
+        new ActionCell<Values<ReportKey>>("Show",
+            places.<ReportKey> getDetailsGofer())));
+
+//    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()) {
+      headers.add(new TextHeader(property.getName()));
+    }
+    return headers;
+  }
+
+  private static ListModel<Values<ReportKey>> getModel(
+      final ExpenseRequestFactory requests) {
+    return new ListModelAdapter<ReportKey>() {
+
+      @Override
+      protected void onRangeChanged(int start, int length) {
+        requests.reportRequest().findAllReports().forProperties(getProperties()).to(
+            this).fire();
+      }
+    };
+  }
+
+  private static List<Property<ReportKey, ?>> getProperties() {
+    List<Property<ReportKey, ?>> properties = new ArrayList<Property<ReportKey, ?>>();
+    properties.add(ReportKey.get().getCreated());
+    properties.add(ReportKey.get().getPurpose());
+    return properties;
+  }
+
+  public ReportListView(String headingMessage, Places places,
+      ExpenseRequestFactory requests) {
+    super(headingMessage, getModel(requests), getColumns(places), getHeaders());
+  }
+
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/client/TableEntityListView.java b/bikeshed/src/com/google/gwt/sample/expenses/client/TableEntityListView.java
deleted file mode 100644
index 2e5088d..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/client/TableEntityListView.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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.client;
-
-import com.google.gwt.bikeshed.list.client.PagingTableListView;
-import com.google.gwt.bikeshed.list.client.SimpleColumn;
-import com.google.gwt.bikeshed.list.client.TextColumn;
-import com.google.gwt.bikeshed.list.shared.ListListModel;
-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.Command;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.HTMLPanel;
-import com.google.gwt.user.client.ui.SimplePanel;
-
-import java.util.List;
-
-/**
- * Interim table based implementation of {@link EntityListView}. Will be replaced
- * by some descendant of {@link com.google.gwt.bikeshed.list.client.PagingTableListView<}
- */
-public class TableEntityListView extends Composite implements EntityListView {
-  interface Binder extends UiBinder<HTMLPanel, TableEntityListView> {
-  }
-
-  private static final Binder BINDER = GWT.create(Binder.class);
-
-   @UiField SimplePanel body;
-   @UiField HeadingElement heading;
-
-  public TableEntityListView() {
-    initWidget(BINDER.createAndBindUi(this));
-  }
-
-  public void setHeading(String text) {
-    heading.setInnerText(text);
-  }
-
-  public void setRowData(final List<String> columnNames, List<Row> newValues) {
-    ListListModel<Row> model = new ListListModel<Row>();
-    List<Row> list = model.getList();
-    list.addAll(newValues);
-    
-    PagingTableListView<Row> table = new PagingTableListView<Row>(model, 100);
-    
-    SimpleColumn<Row, Command> showColumn =
-      new SimpleColumn<Row, Command>(new CommandCell("Show")) {
-      @Override
-      public Command getValue(Row object) {
-        return object.getShowDetailsCommand();
-      }
-    };
-    table.addColumn(showColumn, "Show");
-    
-    SimpleColumn<Row, Command> editColumn =
-      new SimpleColumn<Row, Command>(new CommandCell("Edit")) {
-      @Override
-      public Command getValue(Row object) {
-        return object.getShowDetailsCommand();
-      }
-    };
-    table.addColumn(editColumn, "Edit");
-
-    for (int i = 0; i < columnNames.size(); i++) {
-      final int index = i;
-      TextColumn<Row> column = new TextColumn<Row>() {
-        @Override
-        public String getValue(Row object) {
-          return object.getValues().get(index);
-        }
-      };
-      table.addColumn(column, columnNames.get(index));
-    }
-    
-    body.setWidget(table);
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/client/TableEntityListView.ui.xml b/bikeshed/src/com/google/gwt/sample/expenses/client/TableEntityListView.ui.xml
deleted file mode 100644
index 60c5c1e..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/client/TableEntityListView.ui.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
-<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
-  xmlns:g='urn:import:com.google.gwt.user.client.ui'
-  xmlns:b='urn:import:com.google.gwt.bikeshed.list.client'>
-  <ui:style>
-    table.reports td {
-     border-width: 1px;
-     padding: 1px;
-     border-style: solid;
-     background-color: white;
-    }
-  </ui:style>
-  <g:HTMLPanel>
-  <h3 ui:field='heading'/>
-  <g:SimplePanel ui:field='body'/>
-  </g:HTMLPanel>
-</ui:UiBinder>
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/client/ValuesListViewTable.java b/bikeshed/src/com/google/gwt/sample/expenses/client/ValuesListViewTable.java
new file mode 100644
index 0000000..4c46b46
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/client/ValuesListViewTable.java
@@ -0,0 +1,58 @@
+/*
+ * 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.client;
+
+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.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.Values;
+import com.google.gwt.valuestore.shared.ValuesKey;
+
+import java.util.Iterator;
+import java.util.List;
+
+public class ValuesListViewTable<K extends ValuesKey<K>> extends Composite {
+  interface Binder extends UiBinder<HTMLPanel, ValuesListViewTable<?>> {
+  }
+
+  private static final Binder BINDER = GWT.create(Binder.class);
+
+  @UiField(provided = true) PagingTableListView<Values<K>> table;
+  @UiField HeadingElement heading;
+
+  public ValuesListViewTable(String headingMessage, ListModel<Values<K>> model,
+      List<Column<Values<K>, ?, ?>> columns, List<Header<?>> headers) {
+    table = new PagingTableListView<Values<K>>(model, 100);
+    final Iterator<Header<?>> nextHeader = headers.iterator();
+    for (Column<Values<K>, ?, ?> column : columns) {
+      if (nextHeader.hasNext()) {
+        table.addColumn(column, nextHeader.next());
+      } else {
+        table.addColumn(column);
+      }
+    }
+    initWidget(BINDER.createAndBindUi(this));
+
+    heading.setInnerText(headingMessage);
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/client/ValuesListViewTable.ui.xml b/bikeshed/src/com/google/gwt/sample/expenses/client/ValuesListViewTable.ui.xml
new file mode 100644
index 0000000..7fab0c5
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/client/ValuesListViewTable.ui.xml
@@ -0,0 +1,9 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:g='urn:import:com.google.gwt.user.client.ui'
+  xmlns:b='urn:import:com.google.gwt.bikeshed.list.client'>
+  <g:HTMLPanel>
+    <h3 ui:field='heading'/>
+    <b:PagingTableListView ui:field='table'/>
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/client/place/Places.java b/bikeshed/src/com/google/gwt/sample/expenses/client/place/Places.java
index f4a5ff1..21f01e5 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/client/place/Places.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/client/place/Places.java
@@ -15,8 +15,8 @@
  */
 package com.google.gwt.sample.expenses.client.place;
 
-import com.google.gwt.app.place.GoToPlaceCommand;
 import com.google.gwt.app.place.PlaceController;
+import com.google.gwt.bikeshed.cells.client.ActionCell;
 import com.google.gwt.sample.expenses.shared.EmployeeKey;
 import com.google.gwt.sample.expenses.shared.ExpensesEntityKey;
 import com.google.gwt.sample.expenses.shared.ReportKey;
@@ -31,9 +31,9 @@
  */
 public class Places {
   private final PlaceController<AbstractExpensesPlace> controller;
-  
+
   private final List<EntityListPlace> listPlaces;
-  
+
   public Places(PlaceController<AbstractExpensesPlace> controller) {
     this.controller = controller;
 
@@ -42,16 +42,32 @@
     places.add(new EntityListPlace(ReportKey.get()));
     listPlaces = Collections.unmodifiableList(places);
   }
-  
-  public GoToPlaceCommand<EntityDetailsPlace> getGoToDetailsFor(Values<? extends ExpensesEntityKey<?>> e) {
-    return new GoToPlaceCommand<EntityDetailsPlace>(new EntityDetailsPlace(e), controller);
+
+  public <K extends ExpensesEntityKey<K>> ActionCell.Delegate<Values<K>> getDetailsGofer() {
+    return new ActionCell.Delegate<Values<K>>() {
+      public void execute(Values<K> object) {
+        goToDetailsFor(object);
+      }
+    };
   }
 
-  public GoToPlaceCommand<EditEntityPlace> getGoToEditFor(Values<? extends ExpensesEntityKey<?>> e) {
-    return new GoToPlaceCommand<EditEntityPlace>(new EditEntityPlace(e), controller);
+  public <K extends ExpensesEntityKey<K>> ActionCell.Delegate<Values<K>> getEditorGofer() {
+    return new ActionCell.Delegate<Values<K>>() {
+      public void execute(Values<K> object) {
+        goToEditorFor(object);
+      }
+    };
   }
 
   public List<EntityListPlace> getListPlaces() {
     return listPlaces;
   }
+
+  private void goToDetailsFor(Values<? extends ExpensesEntityKey<?>> e) {
+    controller.goTo(new EntityDetailsPlace(e));
+  }
+
+  private void goToEditorFor(Values<? extends ExpensesEntityKey<?>> e) {
+    controller.goTo(new EditEntityPlace(e));
+  }
 }