Introduces ValueListBox, and uses it in Scaffold to pick
employees.

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

Review by: jlabanca@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8583 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/Scaffold.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/Scaffold.java
index b4552f7..430426c 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/Scaffold.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/Scaffold.java
@@ -97,7 +97,7 @@
     /* Left side lets us pick from all the types of entities */
 
     HasConstrainedValue<ProxyListPlace> listPlacePickerView = shell.getPlacesBox();
-    listPlacePickerView.setValues(getTopPlaces());
+    listPlacePickerView.setAcceptableValues(getTopPlaces());
     factory.getListPlacePicker().register(eventBus, listPlacePickerView);
 
     /*
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldMobile.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldMobile.java
index 022b688..03317ed 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldMobile.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldMobile.java
@@ -81,7 +81,7 @@
     /* Left side lets us pick from all the types of entities */
 
     HasConstrainedValue<ProxyListPlace> placePickerView = shell.getPlacesBox();
-    placePickerView.setValues(getTopPlaces());
+    placePickerView.setAcceptableValues(getTopPlaces());
     factory.getListPlacePicker().register(eventBus, placePickerView);
 
     /*
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.java
index 6bc8acb..f270976 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.java
@@ -95,7 +95,7 @@
     displayName.setInnerText(record.getDisplayName());
     userName.setInnerText(record.getUserName());
     password.setInnerText(record.getPassword());
-    supervisor.setInnerText(String.valueOf(record.getSupervisor() == null ? null : record.getSupervisor().getId()));
+    supervisor.setInnerText(EmployeeRenderer.instance().render(record.getSupervisor()));
     idSpan.setInnerText(record.getId().toString());
     versionSpan.setInnerText(record.getVersion().toString());
   }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditActivity.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditActivity.java
index 25bfc93..75b9e96 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditActivity.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditActivity.java
@@ -18,12 +18,19 @@
 import com.google.gwt.app.place.AbstractRecordEditActivity;
 import com.google.gwt.app.place.PlaceController;
 import com.google.gwt.app.place.RecordEditView;
+import com.google.gwt.event.shared.EventBus;
 import com.google.gwt.requestfactory.shared.Receiver;
 import com.google.gwt.requestfactory.shared.RequestObject;
 import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
+import com.google.gwt.valuestore.shared.SyncResult;
 import com.google.gwt.valuestore.shared.Value;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
 /**
  * An activity that requests all info on an employee, allows the user to edit
  * it, and persists the results.
@@ -56,11 +63,32 @@
   public EmployeeEditActivity(RecordEditView<EmployeeRecord> view,
       EmployeeRecord proxy, ExpensesRequestFactory requests,
       PlaceController placeController, boolean creating) {
-    super(view, proxy, EmployeeRecord.class, creating, requests, placeController);
+    super(view, proxy, EmployeeRecord.class, creating, requests,
+        placeController);
     this.requests = requests;
   }
 
   @Override
+  public void start(Display display, EventBus eventBus) {
+    getEmployeeEditView().setEmployeePickerValues(
+        Collections.<EmployeeRecord> emptyList());
+    
+    requests.employeeRequest().findEmployeeEntries(0, 50).with(
+        EmployeeRenderer.instance().getPaths()).fire(
+        new Receiver<List<EmployeeRecord>>() {
+          public void onSuccess(List<EmployeeRecord> response,
+              Set<SyncResult> syncResults) {
+            List<EmployeeRecord> values = new ArrayList<EmployeeRecord>();
+            values.add(null);
+            values.addAll(response);
+            getEmployeeEditView().setEmployeePickerValues(values);
+          }
+
+        });
+    super.start(display, eventBus);
+  }
+
+  @Override
   protected void fireFindRequest(Value<Long> id,
       Receiver<EmployeeRecord> callback) {
     requests.employeeRequest().findEmployee(id).fire(callback);
@@ -69,4 +97,8 @@
   protected RequestObject<Void> getPersistRequest(EmployeeRecord record) {
     return requests.employeeRequest().persist(record);
   }
+
+  private EmployeeEditView getEmployeeEditView() {
+    return ((EmployeeEditView) getView());
+  }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditView.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditView.java
index d85da94..729e01b 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditView.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditView.java
@@ -16,7 +16,6 @@
 package com.google.gwt.sample.expenses.gwt.ui.employee;
 
 import com.google.gwt.app.client.EditorSupport;
-import com.google.gwt.app.client.LongBox;
 import com.google.gwt.app.place.RecordEditView;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.DivElement;
@@ -32,10 +31,10 @@
 import com.google.gwt.user.client.ui.HTMLPanel;
 import com.google.gwt.user.client.ui.InlineLabel;
 import com.google.gwt.user.client.ui.TextBox;
-import com.google.gwt.valuestore.shared.Property;
+import com.google.gwt.user.client.ui.ValueListBox;
 
+import java.util.Collection;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * Edit view for employee records.
@@ -51,18 +50,31 @@
   private static final Binder BINDER = GWT.create(Binder.class);
   private static final DataBinder DATA_BINDER = GWT.create(DataBinder.class);
 
-  @UiField TextBox displayName;
-  @UiField TextBox password;
-  @UiField LongBox supervisorKey;
-  @UiField TextBox userName;
-  @UiField Button cancel;
-  @UiField Button save;
-  @UiField InlineLabel id;
-  @UiField InlineLabel version;
-  @UiField DivElement errors;
-  
-  @UiField Element editTitle;
-  @UiField Element createTitle;
+  @UiField
+  TextBox displayName;
+  @UiField
+  TextBox password;
+  @UiField(provided = true)
+  ValueListBox<EmployeeRecord> supervisor = new ValueListBox<EmployeeRecord>(
+      EmployeeRenderer.instance());
+
+  @UiField
+  TextBox userName;
+  @UiField
+  Button cancel;
+  @UiField
+  Button save;
+  @UiField
+  InlineLabel id;
+  @UiField
+  InlineLabel version;
+  @UiField
+  DivElement errors;
+
+  @UiField
+  Element editTitle;
+  @UiField
+  Element createTitle;
 
   private Delegate delegate;
   private EmployeeRecord record;
@@ -76,8 +88,8 @@
     return this;
   }
 
-  public Set<Property<?>> getProperties() {
-    return DATA_BINDER.getProperties();
+  public String[] getPaths() {
+    return DATA_BINDER.getPaths();
   }
 
   public EmployeeRecord getValue() {
@@ -102,6 +114,10 @@
     this.delegate = delegate;
   }
 
+  public void setEmployeePickerValues(Collection<EmployeeRecord> values) {
+    supervisor.setAcceptableValues(values);
+  }
+
   public void setEnabled(boolean enabled) {
     DATA_BINDER.setEnabled(this, enabled);
     save.setEnabled(enabled);
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditView.ui.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditView.ui.xml
index ccfba3e..1f836af 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditView.ui.xml
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditView.ui.xml
@@ -40,7 +40,7 @@
     <table class='{style.fields}'>
       <tr><td><div class='{style.label}'>Display Name:</div></td><td><g:TextBox ui:field='displayName'></g:TextBox></td></tr>
       <tr><td><div class='{style.label}'>User Name:</div></td><td><g:TextBox ui:field='userName'></g:TextBox></td></tr>
-      <tr><td><div class='{style.label}'>Supervisor Key:</div></td><td><a:LongBox ui:field='supervisorKey'></a:LongBox></td></tr>
+      <tr><td><div class='{style.label}'>Supervisor:</div></td><td><g:ValueListBox ui:field='supervisor'></g:ValueListBox></td></tr>
       <tr><td><div class='{style.label}'>Password:</div></td><td><g:PasswordTextBox ui:field='password'></g:PasswordTextBox></td></tr>
     </table>
 
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 e481300..523ad87 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
@@ -15,7 +15,6 @@
  */
 package com.google.gwt.sample.expenses.gwt.ui.employee;
 
-import com.google.gwt.app.client.ProxyIdRenderer;
 import com.google.gwt.app.place.AbstractRecordListView;
 import com.google.gwt.app.place.PropertyColumn;
 import com.google.gwt.core.client.GWT;
@@ -51,10 +50,14 @@
     List<PropertyColumn<EmployeeRecord, ?>> columns = new ArrayList<PropertyColumn<EmployeeRecord, ?>>();
 
     columns.add(PropertyColumn.<EmployeeRecord> getStringPropertyColumn(EmployeeRecord.userName));
+
     columns.add(PropertyColumn.<EmployeeRecord> getStringPropertyColumn(EmployeeRecord.displayName));
+
     columns.add(PropertyColumn.<EmployeeRecord> getStringPropertyColumn(EmployeeRecord.password));
+
     columns.add(new PropertyColumn<EmployeeRecord, EmployeeRecord>(
-        EmployeeRecord.supervisor, ProxyIdRenderer.<EmployeeRecord>instance()));
+        EmployeeRecord.supervisor, EmployeeRenderer.instance()));
+
     return columns;
   }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeRenderer.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeRenderer.java
new file mode 100644
index 0000000..b20897c
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeRenderer.java
@@ -0,0 +1,46 @@
+/*
+ * 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.app.place.ProxyRenderer;
+import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
+
+/**
+ * Renders {@link EmployeeRecord}s for display to the user. Requires the
+ * displayName property to have been fetched.
+ */
+public class EmployeeRenderer extends ProxyRenderer<EmployeeRecord> {
+  private static EmployeeRenderer INSTANCE;
+
+  public static EmployeeRenderer instance() {
+    if (INSTANCE == null) {
+      INSTANCE = new EmployeeRenderer();
+    }
+
+    return INSTANCE;
+  }
+
+  protected EmployeeRenderer() {
+    super(new String[] { "displayName"} );
+  }
+
+  public String render(EmployeeRecord object) {
+    if (object == null) {
+      return "";
+    }
+    return object.getDisplayName() + " (" + object.getId() + ")";
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsView.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsView.java
index 7c2fed6..ed49bae 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsView.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsView.java
@@ -22,6 +22,7 @@
 import com.google.gwt.event.dom.client.HasClickHandlers;
 import com.google.gwt.i18n.client.DateTimeFormatRenderer;
 import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
+import com.google.gwt.sample.expenses.gwt.ui.employee.EmployeeRenderer;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.uibinder.client.UiHandler;
@@ -99,7 +100,7 @@
     created.setInnerText(new DateTimeFormatRenderer().render(record.getCreated()));
     idSpan.setInnerText(record.getId().toString());
     versionSpan.setInnerText(record.getVersion().toString());
-    reporterKey.setInnerText(String.valueOf(record.getReporter()));
-    approvedSupervisorKey.setInnerText(String.valueOf(record.getApprovedSupervisor()));
+    reporterKey.setInnerText(EmployeeRenderer.instance().render(record.getReporter()));
+    approvedSupervisorKey.setInnerText(EmployeeRenderer.instance().render(record.getApprovedSupervisor()));
   }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditActivity.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditActivity.java
index 572fe9f..91586c6 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditActivity.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditActivity.java
@@ -18,12 +18,21 @@
 import com.google.gwt.app.place.AbstractRecordEditActivity;
 import com.google.gwt.app.place.PlaceController;
 import com.google.gwt.app.place.RecordEditView;
+import com.google.gwt.event.shared.EventBus;
 import com.google.gwt.requestfactory.shared.Receiver;
 import com.google.gwt.requestfactory.shared.RequestObject;
+import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
 import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
+import com.google.gwt.sample.expenses.gwt.ui.employee.EmployeeRenderer;
+import com.google.gwt.valuestore.shared.SyncResult;
 import com.google.gwt.valuestore.shared.Value;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
 /**
  * An activity that requests all info on a report, allows the user to edit it,
  * and persists the results.
@@ -59,7 +68,26 @@
     super(view, proxy, ReportRecord.class, creating, requests, placeController);
     this.requests = requests;
   }
+  @Override
+  public void start(Display display, EventBus eventBus) {
+    getReportEditView().setEmployeePickerValues(
+        Collections.<EmployeeRecord> emptyList());
+    
+    requests.employeeRequest().findEmployeeEntries(0, 50).with(
+        EmployeeRenderer.instance().getPaths()).fire(
+        new Receiver<List<EmployeeRecord>>() {
+          public void onSuccess(List<EmployeeRecord> response,
+              Set<SyncResult> syncResults) {
+            List<EmployeeRecord> values = new ArrayList<EmployeeRecord>();
+            values.add(null);
+            values.addAll(response);
+            getReportEditView().setEmployeePickerValues(values);
+          }
 
+        });
+    super.start(display, eventBus);
+  }
+  
   @Override
   protected void fireFindRequest(Value<Long> id, Receiver<ReportRecord> callback) {
     requests.reportRequest().findReport(id).fire(callback);
@@ -68,4 +96,8 @@
   protected RequestObject<Void> getPersistRequest(ReportRecord record) {
     return requests.reportRequest().persist(record);
   }
+
+  private ReportEditView getReportEditView() {
+    return ((ReportEditView) getView());
+  }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditView.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditView.java
index c0a5dfb..1b85958 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditView.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditView.java
@@ -16,14 +16,15 @@
 package com.google.gwt.sample.expenses.gwt.ui.report;
 
 import com.google.gwt.app.client.EditorSupport;
-import com.google.gwt.app.client.LongBox;
 import com.google.gwt.app.place.RecordEditView;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.DivElement;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.Style.Display;
 import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
 import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
+import com.google.gwt.sample.expenses.gwt.ui.employee.EmployeeRenderer;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.uibinder.client.UiHandler;
@@ -32,11 +33,11 @@
 import com.google.gwt.user.client.ui.HTMLPanel;
 import com.google.gwt.user.client.ui.InlineLabel;
 import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.ValueListBox;
 import com.google.gwt.user.datepicker.client.DateBox;
-import com.google.gwt.valuestore.shared.Property;
 
+import java.util.Collection;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * Edit view for employee records.
@@ -54,8 +55,10 @@
 
   @UiField TextBox notes;
   @UiField TextBox purpose;
-  @UiField LongBox reporterKey;
-  @UiField LongBox approvedSupervisorKey;
+  @UiField(provided = true) ValueListBox<EmployeeRecord> reporter =
+    new ValueListBox<EmployeeRecord>(EmployeeRenderer.instance());
+  @UiField(provided = true) ValueListBox<EmployeeRecord> approvedSupervisor =
+    new ValueListBox<EmployeeRecord>(EmployeeRenderer.instance());
   @UiField DateBox created;
   @UiField Button cancel;
   @UiField Button save;
@@ -68,18 +71,18 @@
   private Delegate delegate;
 
   private ReportRecord record;
-  
+
   public ReportEditView() {
     initWidget(BINDER.createAndBindUi(this));
     DATA_BINDER.init(this);
   }
-  
+
   public ReportEditView asWidget() {
     return this;
   }
 
-  public Set<Property<?>> getProperties() {
-    return DATA_BINDER.getProperties();
+  public String[] getPaths() {
+    return DATA_BINDER.getPaths();
   }
 
   public ReportRecord getValue() {
@@ -104,6 +107,11 @@
     this.delegate = delegate;
   }
 
+  public void setEmployeePickerValues(Collection<EmployeeRecord> values) {
+    approvedSupervisor.setAcceptableValues(values);
+    reporter.setAcceptableValues(values);
+  }
+
   public void setEnabled(boolean enabled) {
     DATA_BINDER.setEnabled(this, enabled);
     save.setEnabled(enabled);
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditView.ui.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditView.ui.xml
index 2120839..98c8743 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditView.ui.xml
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditView.ui.xml
@@ -42,8 +42,8 @@
       <tr><td><div class='{style.label}'>Purpose:</div></td><td><g:TextBox ui:field='purpose'></g:TextBox></td></tr>
       <tr><td><div class='{style.label}'>Notes:</div></td><td><g:TextBox ui:field='notes'></g:TextBox></td></tr>
       <tr><td><div class='{style.label}'>Created:</div></td><td><d:DateBox ui:field='created'></d:DateBox></td></tr>
-      <tr><td><div class='{style.label}'>Reporter Key:</div></td><td><a:LongBox ui:field='reporterKey'></a:LongBox></td></tr>
-      <tr><td><div class='{style.label}'>Approved Supervisor Key:</div></td><td><a:LongBox ui:field='approvedSupervisorKey'></a:LongBox></td></tr>
+      <tr><td><div class='{style.label}'>Reporter:</div></td><td><g:ValueListBox ui:field='reporter'></g:ValueListBox></td></tr>
+      <tr><td><div class='{style.label}'>Approved Supervisor:</div></td><td><g:ValueListBox ui:field='approvedSupervisor'></g:ValueListBox></td></tr>
     </table>
 
     <div class='{style.bar}'>
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 0c4c0ac..302fd15 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
@@ -15,7 +15,6 @@
  */
 package com.google.gwt.sample.expenses.gwt.ui.report;
 
-import com.google.gwt.app.client.ProxyIdRenderer;
 import com.google.gwt.app.place.AbstractRecordListView;
 import com.google.gwt.app.place.PropertyColumn;
 import com.google.gwt.core.client.GWT;
@@ -23,6 +22,7 @@
 import com.google.gwt.i18n.client.DateTimeFormatRenderer;
 import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
 import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
+import com.google.gwt.sample.expenses.gwt.ui.employee.EmployeeRenderer;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.user.cellview.client.CellTable;
@@ -58,11 +58,15 @@
 
     columns.add(new PropertyColumn<ReportRecord, Date>(ReportRecord.created,
         new DateTimeFormatRenderer(DateTimeFormat.getShortDateFormat())));
+
     columns.add(PropertyColumn.<ReportRecord> getStringPropertyColumn(ReportRecord.purpose));
+
     columns.add(new PropertyColumn<ReportRecord, EmployeeRecord>(
-        ReportRecord.reporter, ProxyIdRenderer.<EmployeeRecord>instance()));
+        ReportRecord.reporter, EmployeeRenderer.instance()));
+
     columns.add(new PropertyColumn<ReportRecord, EmployeeRecord>(
-        ReportRecord.approvedSupervisor, ProxyIdRenderer.<EmployeeRecord>instance()));
+        ReportRecord.approvedSupervisor, EmployeeRenderer.instance()));
+
     return columns;
   }
 }
diff --git a/user/src/com/google/gwt/app/client/EditorSupport.java b/user/src/com/google/gwt/app/client/EditorSupport.java
index fb26b55..2180a85 100644
--- a/user/src/com/google/gwt/app/client/EditorSupport.java
+++ b/user/src/com/google/gwt/app/client/EditorSupport.java
@@ -16,11 +16,9 @@
 package com.google.gwt.app.client;
 
 import com.google.gwt.app.place.RecordEditView;
-import com.google.gwt.valuestore.shared.Property;
 import com.google.gwt.valuestore.shared.Record;
 
 import java.util.Map;
-import java.util.Set;
 
 /**
  * <p>
@@ -35,7 +33,7 @@
  * @param <V> the View type
  */
 public interface EditorSupport<R extends Record, V extends RecordEditView<R>> {
-  Set<Property<?>> getProperties();
+  String[] getPaths();
   
   void init(final V view);
 
diff --git a/user/src/com/google/gwt/app/client/ProxyBox.java b/user/src/com/google/gwt/app/client/ProxyBox.java
deleted file mode 100644
index 30410a9..0000000
--- a/user/src/com/google/gwt/app/client/ProxyBox.java
+++ /dev/null
@@ -1,43 +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.client;
-
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.user.client.ui.ValueBox;
-import com.google.gwt.valuestore.shared.Record;
-
-/**
- * <span style="color:red">Experimental API: This class is still under rapid
- * development, and is very likely to be deleted. Use it at your own risk.
- * </span>
- * <p>
- * A ValueBox that uses {@link com.google.gwt.app.client.ProxyParser} and
- * {@link com.google.gwt.app.client.ProxyRenderer}.
- *
- * @param <T> a proxy record
- */
-public class ProxyBox<T extends Record> extends ValueBox<T> {
-
-  public ProxyBox() {
-    super(Document.get().createTextInputElement(), ProxyRenderer.<T>instance(),
-        ProxyParser.<T>instance());
-  }
-
-  public T getValue() {
-    // Until a sensible ProxyParser can be written
-    return null;
-  }
-}
diff --git a/user/src/com/google/gwt/app/client/ProxyIdRenderer.java b/user/src/com/google/gwt/app/client/ProxyIdRenderer.java
deleted file mode 100644
index 239ecf8..0000000
--- a/user/src/com/google/gwt/app/client/ProxyIdRenderer.java
+++ /dev/null
@@ -1,49 +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.client;
-
-import com.google.gwt.text.shared.AbstractRenderer;
-import com.google.gwt.text.shared.Renderer;
-import com.google.gwt.valuestore.shared.Record;
-
-/**
- * <span style="color:red">Experimental API: This class is still under rapid
- * development, and is very likely to be deleted. Use it at your own risk.
- * </span>
- * <p>
- * Renderer of Record values
- * @param <T> a record type
- */
-public class ProxyIdRenderer<T extends Record> extends AbstractRenderer<T> {
-  private static ProxyIdRenderer INSTANCE;
-
-  /**
-   * @return the instance
-   */
-  public static <T extends Record> Renderer<T> instance() {
-    if (INSTANCE == null) {
-      INSTANCE = new ProxyIdRenderer<T>();
-    }
-    return INSTANCE;
-  }
-
-  protected ProxyIdRenderer() {
-  }
-
-  public String render(T object) {
-    return toString(object == null ? null : object.getId());
-  }
-}
diff --git a/user/src/com/google/gwt/app/client/ProxyParser.java b/user/src/com/google/gwt/app/client/ProxyParser.java
deleted file mode 100644
index 221a4f0..0000000
--- a/user/src/com/google/gwt/app/client/ProxyParser.java
+++ /dev/null
@@ -1,57 +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.client;
-
-import com.google.gwt.text.shared.Parser;
-import com.google.gwt.valuestore.shared.Record;
-
-import java.text.ParseException;
-
-/**
- * <span style="color:red">Experimental API: This class is still under rapid
- * development, and is very likely to be deleted. Use it at your own risk.
- * </span>
- * <p>
- * A no-op renderer.
- * @param <T> a Record type.
- */
-public class ProxyParser<T extends Record> implements Parser<T> {
-
-  private static ProxyParser INSTANCE;
-
-  /**
-   * @return the instance of the no-op renderer
-   */
-  public static <T extends Record> Parser<T> instance() {
-    if (INSTANCE == null) {
-      INSTANCE = new ProxyParser<T>();
-    }
-    return INSTANCE;
-  }
-
-  protected ProxyParser() {
-  }
-
-  public T parse(CharSequence object) throws ParseException {
-    try {
-      // TODO: how can we map an id back into a record synchronously? Use
-      // ValueStore?
-      return null;
-    } catch (NumberFormatException e) { 
-      throw new ParseException(e.getMessage(), 0);
-    }
-  }
-}
diff --git a/user/src/com/google/gwt/app/client/ProxyRenderer.java b/user/src/com/google/gwt/app/client/ProxyRenderer.java
deleted file mode 100644
index 5ea3d4f..0000000
--- a/user/src/com/google/gwt/app/client/ProxyRenderer.java
+++ /dev/null
@@ -1,49 +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.client;
-
-import com.google.gwt.text.shared.AbstractRenderer;
-import com.google.gwt.text.shared.Renderer;
-import com.google.gwt.valuestore.shared.Record;
-
-/**
- * <span style="color:red">Experimental API: This class is still under rapid
- * development, and is very likely to be deleted. Use it at your own risk.
- * </span>
- * <p>
- * Renderer of Record values
- * @param <T> a record type
- */
-public class ProxyRenderer<T extends Record> extends AbstractRenderer<T> {
-  private static ProxyRenderer INSTANCE;
-
-  /**
-   * @return the instance
-   */
-  public static <T extends Record> Renderer<T> instance() {
-    if (INSTANCE == null) {
-      INSTANCE = new ProxyRenderer<T>();
-    }
-    return INSTANCE;
-  }
-
-  protected ProxyRenderer() {
-  }
-
-  public String render(T object) {
-    return toString(object == null ? null : object.getId());
-  }
-}
diff --git a/user/src/com/google/gwt/app/client/ReadonlyProxyBox.java b/user/src/com/google/gwt/app/client/ReadonlyProxyBox.java
deleted file mode 100644
index cafe722..0000000
--- a/user/src/com/google/gwt/app/client/ReadonlyProxyBox.java
+++ /dev/null
@@ -1,51 +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.client;
-
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.user.client.ui.ValueBox;
-import com.google.gwt.valuestore.shared.Record;
-
-/**
- * <span style="color:red">Experimental API: This class is still under rapid
- * development, and is very likely to be deleted. Use it at your own risk.
- * </span>
- * <p>
- * A ValueBox that uses {@link NullParser} and
- * {@link ProxyIdRenderer}. It is a display only placeholder class for now;
- *
- * @param <T> a proxy record
- */
-public class ReadonlyProxyBox<T extends Record> extends ValueBox<T> {
-
-  private T currentValue;
-
-  public ReadonlyProxyBox() {
-    super(Document.get().createTextInputElement(), ProxyIdRenderer.<T>instance(),
-        NullParser.<T>instance());
-    setReadOnly(true);
-  }
-
-  public T getValue() {
-    // The display value cannot be modified;
-    return currentValue;
-  }
-
-  public void setValue(T value) {
-    this.currentValue = value;
-    super.setValue(value);
-  }
-}
diff --git a/user/src/com/google/gwt/app/place/AbstractRecordEditActivity.java b/user/src/com/google/gwt/app/place/AbstractRecordEditActivity.java
index 9875c1e..80f5415 100644
--- a/user/src/com/google/gwt/app/place/AbstractRecordEditActivity.java
+++ b/user/src/com/google/gwt/app/place/AbstractRecordEditActivity.java
@@ -81,6 +81,10 @@
     return record;
   }
 
+  public RecordEditView<R> getView() {
+    return view;
+  }
+
   public String mayStop() {
     if (requestObject != null && requestObject.isChanged()) {
       return "Are you sure you want to abandon your changes?";
@@ -88,7 +92,7 @@
 
     return null;
   }
-
+  
   public void onCancel() {
     onStop();
   }
diff --git a/user/src/com/google/gwt/app/place/AbstractRecordListActivity.java b/user/src/com/google/gwt/app/place/AbstractRecordListActivity.java
index d13b57f..cef8237 100644
--- a/user/src/com/google/gwt/app/place/AbstractRecordListActivity.java
+++ b/user/src/com/google/gwt/app/place/AbstractRecordListActivity.java
@@ -225,7 +225,7 @@
   /**
    * Called when the user chooses a record to view. This default implementation
    * sends the {@link PlaceController} to an appropriate {@link ProxyPlace}.
-   * 
+   *
    * @param record the chosen record
    */
   protected void showDetails(R record) {
@@ -234,7 +234,7 @@
 
   private void fireRangeRequest(final Range range,
       final Receiver<List<R>> callback) {
-    createRangeRequest(range).forProperties(getView().getProperties()).fire(
+    createRangeRequest(range).with(getView().getPaths()).fire(
         callback);
   }
 
diff --git a/user/src/com/google/gwt/app/place/AbstractRecordListView.java b/user/src/com/google/gwt/app/place/AbstractRecordListView.java
index fd33ea8..34590fd 100644
--- a/user/src/com/google/gwt/app/place/AbstractRecordListView.java
+++ b/user/src/com/google/gwt/app/place/AbstractRecordListView.java
@@ -21,10 +21,10 @@
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.Widget;
-import com.google.gwt.valuestore.shared.Property;
 import com.google.gwt.valuestore.shared.Record;
 import com.google.gwt.view.client.HasData;
 
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -45,7 +45,7 @@
     Composite implements RecordListView<R> {
 
   private CellTable<R> table;
-  private Set<Property<?>> properties = new HashSet<Property<?>>();
+  private Set<String> paths = new HashSet<String>();
   private Delegate<R> delegate;
 
   public HasData<R> asHasData() {
@@ -56,8 +56,8 @@
     return this;
   }
 
-  public Set<Property<?>> getProperties() {
-    return properties;
+  public String[] getPaths() {
+    return paths.toArray(new String[paths.size()]);
   }
 
   public void setDelegate(final Delegate<R> delegate) {
@@ -70,8 +70,8 @@
     this.table = table;
 
     for (PropertyColumn<R, ?> column : columns) {
-      table.addColumn(column, column.getProperty().getDisplayName());
-      properties.add(column.getProperty());
+      table.addColumn(column, column.getDisplayName());
+      paths.addAll(Arrays.asList(column.getPaths()));
     }
 
     newButton.addClickHandler(new ClickHandler() {
diff --git a/user/src/com/google/gwt/app/place/PropertyColumn.java b/user/src/com/google/gwt/app/place/PropertyColumn.java
index 1cc44d4..66d498e 100644
--- a/user/src/com/google/gwt/app/place/PropertyColumn.java
+++ b/user/src/com/google/gwt/app/place/PropertyColumn.java
@@ -27,7 +27,9 @@
  * development, and is very likely to be deleted. Use it at your own risk.
  * </span>
  * </p>
- * A column that displays a record property as a string.
+ * A column that displays a record property as a string. NB: Property objects
+ * will soon go away, and this column class will hopefully replaced by a (much
+ * simpler to use) code generated system.
  * 
  * @param <R> the type of record in this table
  * @param <T> value type of the property
@@ -38,21 +40,43 @@
     return new PropertyColumn<R, String>(property,
         PassthroughRenderer.instance());
   }
-  private final Renderer<T> renderer;
 
+  private final Renderer<T> renderer;
   private final Property<T> property;
+  private final String[] paths;
+
+  public PropertyColumn(Property<T> property, ProxyRenderer<T> renderer) {
+    this.property = property;
+    this.renderer = renderer;
+    this.paths = pathinate(property, renderer);
+  }
 
   public PropertyColumn(Property<T> property, Renderer<T> renderer) {
     this.property = property;
     this.renderer = renderer;
+    this.paths = new String[] {property.getName()};
   }
 
-  public Property<T> getProperty() {
-    return property;
+  public String getDisplayName() {
+    return property.getDisplayName();
+  }
+
+  public String[] getPaths() {
+    return paths;
   }
 
   @Override
   public String getValue(R object) {
     return renderer.render(object.get(property));
   }
+
+  private String[] pathinate(Property<T> property, ProxyRenderer<T> renderer) {
+    String[] rtn = new String[renderer.getPaths().length];
+    int i = 0;
+    for (String rendererPath : renderer.getPaths()) {
+      rtn[i++] = property.getName() + "." + rendererPath;
+    }
+
+    return rtn;
+  }
 }
diff --git a/user/src/com/google/gwt/app/place/PropertyView.java b/user/src/com/google/gwt/app/place/PropertyView.java
index 81f34da..6e150f2 100644
--- a/user/src/com/google/gwt/app/place/PropertyView.java
+++ b/user/src/com/google/gwt/app/place/PropertyView.java
@@ -15,27 +15,22 @@
  */
 package com.google.gwt.app.place;
 
-import com.google.gwt.valuestore.shared.Property;
 import com.google.gwt.valuestore.shared.Record;
 
-import java.util.Set;
-
 /**
  * <p>
  * <span style="color:red">Experimental API: This class is still under rapid
  * development, and is very likely to be deleted. Use it at your own risk.
  * </span>
  * </p>
- * A view that displays a set of {@link Property} values for a type of
- * {@link Record}.
+ * A view that displays a set of property values for a type of {@link Record}.
  * 
  * @param <R> the type of the record
  */
 public interface PropertyView<R extends Record> {
 
   /**
-   * @return the set of properties this view displays, which are guaranteed to
-   *         be properties of R
+   * @return the set of properties this view displays
    */
-  Set<Property<?>> getProperties();
+  String[] getPaths();
 }
diff --git a/user/src/com/google/gwt/app/place/ProxyRenderer.java b/user/src/com/google/gwt/app/place/ProxyRenderer.java
new file mode 100644
index 0000000..3b7ce42
--- /dev/null
+++ b/user/src/com/google/gwt/app/place/ProxyRenderer.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.app.place;
+
+import com.google.gwt.text.shared.AbstractRenderer;
+
+/**
+ * Renders a proxy object, and reports the properties it requires to do that
+ * rendering.
+ * 
+ * @param <R> the type to render
+ */
+public abstract class ProxyRenderer<R> extends
+    AbstractRenderer<R> {
+
+  private final String[] paths;
+
+  public ProxyRenderer(String[] strings) {
+    this.paths = strings;
+  }
+
+  /**
+   * The properties required by this renderer.
+   */
+  public String[] getPaths() {
+    return paths;
+  }
+}
diff --git a/user/src/com/google/gwt/app/rebind/EditorSupportGenerator.java b/user/src/com/google/gwt/app/rebind/EditorSupportGenerator.java
index d487d5d..67cba53 100644
--- a/user/src/com/google/gwt/app/rebind/EditorSupportGenerator.java
+++ b/user/src/com/google/gwt/app/rebind/EditorSupportGenerator.java
@@ -183,8 +183,8 @@
         String.class.getName());
     jrecordType = generatorContext.getTypeOracle().findType(
         Record.class.getName());
-    writeGetPropertiesMethod(sw, recordType);
-    writeInit(sw, viewType, recordType);
+    writeGetPathsMethod(sw, recordType);
+    writeInit(sw, viewType);
     writeIsChangedMethod(sw, recordType, viewType);
     writeSetEnabledMethod(sw, viewType);
     writeSetValueMethod(sw, recordType, viewType, logger);
@@ -417,28 +417,27 @@
   }
 
   /**
-   * Write the implementation for the getProperties() method.
+   * Write the implementation for the getPaths() method.
    */
-  private void writeGetPropertiesMethod(SourceWriter sw, JClassType recordType) {
+  private void writeGetPathsMethod(SourceWriter sw, JClassType recordType) {
     sw.indent();
-    sw.println("public Set<Property<?>> getProperties() {");
+    sw.println("public String[] getPaths() {");
     sw.indent();
-    sw.println("Set<Property<?>> rtn = new HashSet<Property<?>>();");
+    sw.println("Set<String> rtn = new HashSet<String>();");
     for (JField field : recordType.getFields()) {
       if (field.getType().getQualifiedSourceName().equals(
           Property.class.getName())) {
         sw.println("rtn.add(" + recordType.getName() + "." + field.getName()
-            + ");");
+            + ".getName());");
       }
     }
-    sw.println("return rtn;");
+    sw.println("return rtn.toArray(new String[rtn.size()]);");
     sw.outdent();
     sw.println("}");
     sw.outdent();
   }
 
-  private void writeInit(SourceWriter sw, JClassType viewType,
-      JClassType recordType) {
+  private void writeInit(SourceWriter sw, JClassType viewType) {
     sw.indent();
     sw.println("public void init(final " + viewType.getName() + " view) {");
     sw.indent();
diff --git a/user/src/com/google/gwt/user/client/ui/HasConstrainedValue.java b/user/src/com/google/gwt/user/client/ui/HasConstrainedValue.java
index 235aa26..a725855 100644
--- a/user/src/com/google/gwt/user/client/ui/HasConstrainedValue.java
+++ b/user/src/com/google/gwt/user/client/ui/HasConstrainedValue.java
@@ -19,15 +19,20 @@
 
 /**
  * Implemented by widgets that pick from a set of values.
+ * <p>
+ * It is up to the implementation to decide (and document) how to behave when
+ * {@link #setValue(Object)} is called with a value that is not in the
+ * acceptable set. For example, throwing an {@link IllegalArgumentException}, or
+ * quietly adding the value to the acceptable set, are both reasonable choices.
  * 
  * @param <T> the type of value
  */
 public interface HasConstrainedValue<T> extends HasValue<T> {
 
   /**
-   * Set the acceptible values.
+   * Set the acceptable values.
    * 
    * @param values the acceptible values
    */
-  void setValues(Collection<T> values);
+  void setAcceptableValues(Collection<T> values);
 }
diff --git a/user/src/com/google/gwt/user/client/ui/ValueListBox.java b/user/src/com/google/gwt/user/client/ui/ValueListBox.java
new file mode 100644
index 0000000..23ea523
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/ValueListBox.java
@@ -0,0 +1,134 @@
+/*
+ * 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.user.client.ui;
+
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.text.shared.Renderer;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementation of {@link HasConstrainedValue} based on a
+ * {@link com.google.gwt.dom.client.SelectElement}.
+ * <p>
+ * A {@link Renderer<T>} is used to get user presentable strings to display in
+ * the select element. It is an error for two values to render to the same
+ * string.
+ * 
+ * @param <T> the value type
+ */
+public class ValueListBox<T> extends Composite implements
+    HasConstrainedValue<T> {
+
+  private final List<T> values = new ArrayList<T>();
+  private final Map<T, Integer> valueToIndex = new HashMap<T, Integer>();
+  private final Renderer<T> renderer;
+
+  private T value;
+
+  public ValueListBox(Renderer<T> renderer) {
+    this.renderer = renderer;
+    initWidget(new ListBox());
+
+    getListBox().addChangeHandler(new ChangeHandler() {
+      public void onChange(ChangeEvent event) {
+        int selectedIndex = getListBox().getSelectedIndex();
+
+        if (selectedIndex < 0) {
+          return; // Not sure why this happens during addValue
+        }
+        T newValue = values.get(selectedIndex);
+        setValue(newValue, true);
+      }
+    });
+  }
+
+  public HandlerRegistration addValueChangeHandler(ValueChangeHandler<T> handler) {
+    return addHandler(handler, ValueChangeEvent.getType());
+  }
+
+  public T getValue() {
+    return value;
+  }
+
+  public void setAcceptableValues(Collection<T> newValues) {
+    values.clear();
+    valueToIndex.clear();
+    ListBox listBox = getListBox();
+    listBox.clear();
+
+    for (T nextNewValue : newValues) {
+      addValue(nextNewValue);
+    }
+
+    updateListBox();
+  }
+
+  /**
+   * Set the value and display it in the select element. Add the value to the
+   * acceptable set if it is not already there.
+   */
+  public void setValue(T value) {
+    setValue(value, false);
+  }
+
+  public void setValue(T value, boolean fireEvents) {
+    if (value == this.value || (this.value != null && this.value.equals(value))) {
+      return;
+    }
+
+    T before = this.value;
+    this.value = value;
+    updateListBox();
+
+    if (fireEvents) {
+      ValueChangeEvent.fireIfNotEqual(this, before, value);
+    }
+  }
+
+  private void addValue(T value) {
+    if (valueToIndex.containsKey(value)) {
+      throw new IllegalArgumentException("Duplicate value: " + value);
+    }
+
+    valueToIndex.put(value, values.size());
+    values.add(value);
+    getListBox().addItem(renderer.render(value));
+    assert values.size() == getListBox().getItemCount();
+  }
+
+  private ListBox getListBox() {
+    return (ListBox) getWidget();
+  }
+
+  private void updateListBox() {
+    Integer index = valueToIndex.get(value);
+    if (index == null) {
+      addValue(value);
+    }
+
+    index = valueToIndex.get(value);
+    getListBox().setSelectedIndex(index);
+  }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/ValuePicker.java b/user/src/com/google/gwt/user/client/ui/ValuePicker.java
index 1a92b38..a433144 100644
--- a/user/src/com/google/gwt/user/client/ui/ValuePicker.java
+++ b/user/src/com/google/gwt/user/client/ui/ValuePicker.java
@@ -92,6 +92,10 @@
     return value;
   }
   
+  public void setAcceptableValues(Collection<T> places) {
+    cellList.setRowData(0, new ArrayList<T>(places));
+  }
+
   public void setPageSize(int size) {
     cellList.setPageSize(size);
   }
@@ -111,8 +115,4 @@
       ValueChangeEvent.fire(this, value);
     }
   }
-
-  public void setValues(Collection<T> places) {
-    cellList.setRowData(0, new ArrayList<T>(places));
-  }
 }
diff --git a/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java b/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
index 7a6c504..4792ee9 100644
--- a/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
+++ b/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
@@ -29,7 +29,7 @@
     return null;
   }
 
-  public static SimpleFoo findSimpleFooById(Long id) {
+  public static SimpleFoo findSimpleFooById(@SuppressWarnings("unused") Long id) {
     return null;
   }
 
diff --git a/user/test/com/google/gwt/user/UISuite.java b/user/test/com/google/gwt/user/UISuite.java
index 06960cc..e31de10 100644
--- a/user/test/com/google/gwt/user/UISuite.java
+++ b/user/test/com/google/gwt/user/UISuite.java
@@ -88,6 +88,8 @@
 import com.google.gwt.user.client.ui.TreeItemTest;
 import com.google.gwt.user.client.ui.TreeTest;
 import com.google.gwt.user.client.ui.UIObjectTest;
+import com.google.gwt.user.client.ui.ValueBoxBaseTest;
+import com.google.gwt.user.client.ui.ValueListBoxTest;
 import com.google.gwt.user.client.ui.VerticalPanelTest;
 import com.google.gwt.user.client.ui.VerticalSplitPanelTest;
 import com.google.gwt.user.client.ui.WidgetCollectionTest;
@@ -182,6 +184,8 @@
     suite.addTestSuite(TreeTest.class);
     suite.addTestSuite(TreeItemTest.class);
     suite.addTestSuite(UIObjectTest.class);
+    suite.addTestSuite(ValueBoxBaseTest.class);
+    suite.addTestSuite(ValueListBoxTest.class);
     suite.addTestSuite(VerticalPanelTest.class);
     suite.addTestSuite(VerticalSplitPanelTest.class);
     suite.addTestSuite(WidgetCollectionTest.class);
diff --git a/user/test/com/google/gwt/user/client/ui/ValueBoxBaseTest.java b/user/test/com/google/gwt/user/client/ui/ValueBoxBaseTest.java
index 0face04..da8d0ff 100644
--- a/user/test/com/google/gwt/user/client/ui/ValueBoxBaseTest.java
+++ b/user/test/com/google/gwt/user/client/ui/ValueBoxBaseTest.java
@@ -40,7 +40,7 @@
     parser.throwException = true;
     valueBoxBase.setText("");
     try {
-      String string = valueBoxBase.getValueOrThrow();
+      valueBoxBase.getValueOrThrow();
       fail("Should have thrown ParseException");
     } catch (ParseException e) {
       // exception was correctly thrown
@@ -62,7 +62,7 @@
     parser.throwException = true;
     valueBoxBase.setText("simple string");
     try {
-      String string = valueBoxBase.getValueOrThrow();
+      valueBoxBase.getValueOrThrow();
       fail("Should have thrown ParseException");
     } catch (ParseException e) {
       // exception was correctly thrown
@@ -73,7 +73,7 @@
   }
   
   // Test that a string with padding spaces correctly passes through
-  public void testSpaces() {
+  public void testSpaces() throws ParseException {
     Element elm = Document.get().createTextInputElement();
     Renderer<String> renderer = PassthroughRenderer.instance();
     MockParser parser = new MockParser();
@@ -81,12 +81,9 @@
     ValueBoxBase<String> valueBoxBase = 
       new ValueBoxBase<String>(elm, renderer, parser);
     
-    valueBoxBase.setText("  two space padding test  ");
-    try {
-      assertEquals("  two space padding  ", valueBoxBase.getValueOrThrow());
-    } catch (ParseException e) {
-      fail("Should not have thrown ParseException");
-    }
+    String text = "  two space padding test  ";
+    valueBoxBase.setText(text);
+    assertEquals(text, valueBoxBase.getValueOrThrow());
     if (!parser.parseCalled) {
       fail("Parser was not run");
     }
diff --git a/user/test/com/google/gwt/user/client/ui/ValueListBoxTest.java b/user/test/com/google/gwt/user/client/ui/ValueListBoxTest.java
new file mode 100644
index 0000000..741aada
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/ui/ValueListBoxTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.user.client.ui;
+
+import com.google.gwt.dom.client.SelectElement;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.text.shared.AbstractRenderer;
+
+import java.util.Arrays;
+
+/**
+ * Eponymous unit test.
+ */
+public class ValueListBoxTest extends GWTTestCase {
+  static class Foo {
+    final String value;
+
+    Foo(String value) {
+      this.value = value;
+    }
+
+    @Override
+    public String toString() {
+      return "Foo [value=" + value + "]";
+    }
+  }
+
+  static class FooRenderer extends AbstractRenderer<Foo> {
+    public String render(Foo object) {
+      if (object == null) {
+        return "";
+      }
+      return "Foo: " + object.value;
+    }
+  }
+
+  private static final FooRenderer renderer = new FooRenderer();
+
+  ValueListBox<Foo> subject;
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.user.User";
+  }
+
+  public void testExtraValueSet() {
+    Foo[] values = new Foo[] {new Foo("able"), new Foo("baker")};
+    Foo baz = new Foo("baz");
+    
+    subject.setAcceptableValues(Arrays.asList(values));
+    assertEquals(2, getSelect().getLength());
+
+    subject.setValue(baz);
+    assertEquals(baz, subject.getValue());
+    assertEquals(3, getSelect().getLength());
+  }
+
+  public void testNakedSet() {
+    assertNull(subject.getValue());
+    
+    SelectElement elm = getSelect();
+    assertEquals(0, elm.getLength());
+
+    Foo barFoo = new Foo("bar");
+
+    setAndCheck(barFoo);
+    
+    assertEquals(1, elm.getLength());
+    assertEquals(renderer.render(barFoo), elm.getValue());
+  }
+
+  public void testNormalSet() {
+    Foo[] values = new Foo[] {new Foo("able"), new Foo("baker")};
+    subject.setAcceptableValues(Arrays.asList(values));
+
+    assertEquals(2, getSelect().getLength());
+
+    setAndCheck(values[0]);
+    setAndCheck(values[1]);
+    setAndCheck(values[0]);
+
+    assertEquals(2, getSelect().getLength());
+  }
+
+  @Override
+  protected void gwtSetUp() {
+    subject = new ValueListBox<Foo>(renderer);
+    RootPanel.get().add(subject);
+  }
+
+  @Override
+  protected void gwtTearDown() {
+    RootPanel.get().remove(subject);
+  }
+  
+  private SelectElement getSelect() {
+    return subject.getWidget().getElement().cast();
+  }
+
+  private void setAndCheck(Foo value) {
+    subject.setValue(value);
+    assertEquals(value, subject.getValue());
+    assertEquals(renderer.render(value), getSelect().getValue());
+  }
+}