You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2007/09/30 21:14:41 UTC

svn commit: r580771 - in /tapestry/tapestry5/trunk: tapestry-core/src/main/java/org/apache/tapestry/corelib/components/ tapestry-core/src/main/java/org/apache/tapestry/grid/ tapestry-core/src/main/resources/org/apache/tapestry/corelib/components/ tapes...

Author: hlship
Date: Sun Sep 30 12:14:40 2007
New Revision: 580771

URL: http://svn.apache.org/viewvc?rev=580771&view=rev
Log:
TAPESTRY-1328: Support for form elements inside a Grid

Added:
    tapestry/tapestry5/trunk/tapestry-core/src/test/app1/GridFormDemo.tml
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/NewIntegrationTests.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/GridFormDemo.java
Modified:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Grid.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/GridRows.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/grid/GridDataSource.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/corelib/components/GridRows.tml
    tapestry/tapestry5/trunk/tapestry-core/src/site/apt/index.apt
    tapestry/tapestry5/trunk/tapestry-core/src/test/app1/Start.tml
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/data/ToDoItem.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/services/ToDoDatabase.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/services/ToDoDatabaseImpl.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/provider.apt
    tapestry/tapestry5/trunk/tapestry-test/src/main/java/org/apache/tapestry/test/AbstractIntegrationTestSuite.java

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Grid.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Grid.java?rev=580771&r1=580770&r2=580771&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Grid.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Grid.java Sun Sep 30 12:14:40 2007
@@ -16,8 +16,10 @@
 
 import org.apache.tapestry.Binding;
 import org.apache.tapestry.Block;
+import org.apache.tapestry.ComponentAction;
 import org.apache.tapestry.ComponentResources;
 import org.apache.tapestry.annotations.Component;
+import org.apache.tapestry.annotations.Environmental;
 import org.apache.tapestry.annotations.Inject;
 import org.apache.tapestry.annotations.Parameter;
 import org.apache.tapestry.annotations.Persist;
@@ -31,11 +33,18 @@
 import org.apache.tapestry.internal.bindings.AbstractBinding;
 import org.apache.tapestry.ioc.services.TypeCoercer;
 import org.apache.tapestry.services.BeanModelSource;
+import org.apache.tapestry.services.FormSupport;
 
 /**
  * A grid presents tabular data. It is a composite component, created in terms of several
  * sub-components. The sub-components are statically wired to the Grid, as it provides access to the
  * data and other models that they need.
+ * <p>
+ * A Grid may operate inside a {@link Form}. By overriding the cell renderers of properties, the
+ * default output only behavior can be changed to produce a complex form with individual control for
+ * editing properties of each row. This is currently workable but less than ideal -- if the order of
+ * rows provided by the {@link GridDataSource} changes between render and form submission, then
+ * there's the possibility that data will be applied to the wrong server-side objects.
  * 
  * @see BeanModel
  * @see BeanModelSource
@@ -145,7 +154,8 @@
 
     @SuppressWarnings("unused")
     @Component(parameters =
-    { "rowClass=rowClass", "rowsPerPage=rowsPerPage", "currentPage=currentPage", "row=row" })
+    { "rowClass=rowClass", "rowsPerPage=rowsPerPage", "currentPage=currentPage", "row=row",
+            "volatile=inherit:volatile" })
     private GridRows _rows;
 
     @Component(parameters =
@@ -160,6 +170,19 @@
     @Component(parameters = "to=pagerBottom")
     private Delegate _pagerBottom;
 
+    /**
+     * If true and the Loop is enclosed by a Form, then the normal state persisting logic is turned
+     * off. Defaults to false, enabling state saving persisting within Forms. If a Grid is present
+     * for some reason within a Form, but does not contain any form control components (such as
+     * {@link TextField}), then binding volatile to false will reduce the amount of client-side
+     * state that must be persisted.
+     */
+    @Parameter
+    private boolean _volatile;
+
+    @Environmental(false)
+    private FormSupport _formSupport;
+
     Binding defaultModel()
     {
         final ComponentResources containerResources = _resources.getContainerResources();
@@ -195,8 +218,27 @@
         };
     }
 
+    static final ComponentAction<Grid> SETUP_DATA_SOURCE = new ComponentAction<Grid>()
+    {
+        private static final long serialVersionUID = 8545187927995722789L;
+
+        public void execute(Grid component)
+        {
+            component.setupDataSource();
+        }
+    };
+
     Object setupRender()
     {
+        if (!_volatile && _formSupport != null) _formSupport.store(this, SETUP_DATA_SOURCE);
+
+        setupDataSource();
+
+        return _dataSource.getAvailableRows() == 0 ? _empty : null;
+    }
+
+    void setupDataSource()
+    {
         _dataSource = _typeCoercer.coerce(_source, GridDataSource.class);
 
         if (_remove != null) BeanModelUtils.remove(_model, _remove);
@@ -207,7 +249,7 @@
 
         int availableRows = _dataSource.getAvailableRows();
 
-        if (availableRows == 0) return _empty;
+        if (availableRows == 0) return;
 
         PropertyModel sortModel = null;
 
@@ -229,8 +271,6 @@
         int endIndex = Math.min(startIndex + _rowsPerPage - 1, availableRows - 1);
 
         _dataSource.prepare(startIndex, endIndex, sortModel, _sortAscending);
-
-        return null;
     }
 
     Object beginRender()

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/GridRows.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/GridRows.java?rev=580771&r1=580770&r2=580771&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/GridRows.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/GridRows.java Sun Sep 30 12:14:40 2007
@@ -16,16 +16,41 @@
 
 import java.util.List;
 
+import org.apache.tapestry.ComponentAction;
+import org.apache.tapestry.annotations.Environmental;
 import org.apache.tapestry.annotations.Parameter;
 import org.apache.tapestry.beaneditor.PropertyModel;
 import org.apache.tapestry.grid.GridDataSource;
 import org.apache.tapestry.grid.GridModelProvider;
+import org.apache.tapestry.services.FormSupport;
 
 /**
  * Renders out a series of rows within the table.
+ * <p>
+ * Inside a {@link Form}, a series of row index numbers are stored into the form (
+ * {@linkplain FormSupport#store(Object, ComponentAction) as ComponentActions}). This is not ideal
+ * ... in a situation where the data set can shift between the form render and the form submission,
+ * this can cause unexpected results, including applying changes to the wrong objects.
  */
 public class GridRows
 {
+    static class SetupForRow implements ComponentAction<GridRows>
+    {
+        private static final long serialVersionUID = -3216282071752371975L;
+
+        private final int _rowIndex;
+
+        public SetupForRow(int rowIndex)
+        {
+            _rowIndex = rowIndex;
+        }
+
+        public void execute(GridRows component)
+        {
+            component.setupForRow(_rowIndex);
+        }
+    };
+
     /**
      * Parameter used to set the CSS class for each row (each &lt;tr&gt; element) within the
      * &lt;tbody&gt;). This is not cached, so it will be recomputed for each row.
@@ -52,6 +77,19 @@
     @Parameter(required = true)
     private Object _row;
 
+    /**
+     * If true and the Loop is enclosed by a Form, then the normal state saving logic is turned off.
+     * Defaults to false, enabling state saving logic within Forms.
+     */
+    @SuppressWarnings("unused")
+    @Parameter
+    private boolean _volatile;
+
+    @Environmental(false)
+    private FormSupport _formSupport;
+
+    private boolean _recordingStateInsideForm;
+
     private int _startRow;
 
     private int _endRow;
@@ -84,18 +122,35 @@
 
         // This can sometimes happen when the number of items shifts between requests.
 
-        if (_currentPage > maxPages)
-            _currentPage = maxPages;
+        if (_currentPage > maxPages) _currentPage = maxPages;
 
         _startRow = (_currentPage - 1) * _rowsPerPage;
         _endRow = Math.min(availableRows - 1, _startRow + _rowsPerPage - 1);
 
         _rowIndex = _startRow;
+
+        _recordingStateInsideForm = !_volatile && _formSupport != null;
+    }
+
+    /**
+     * Callback method, used when recording state to a form, or called directly when not recording
+     * state.
+     */
+    void setupForRow(int rowIndex)
+    {
+        _row = _provider.getDataSource().getRowValue(rowIndex);
+
     }
 
     void beginRender()
     {
-        _row = _provider.getDataSource().getRowValue(_rowIndex);
+        // When needed, store a callback used when the form is submitted.
+
+        if (_recordingStateInsideForm) _formSupport.store(this, new SetupForRow(_rowIndex));
+
+        // And do it now for the render.
+
+        setupForRow(_rowIndex);
     }
 
     boolean afterRender()

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/grid/GridDataSource.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/grid/GridDataSource.java?rev=580771&r1=580770&r2=580771&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/grid/GridDataSource.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/grid/GridDataSource.java Sun Sep 30 12:14:40 2007
@@ -15,12 +15,16 @@
 package org.apache.tapestry.grid;
 
 import org.apache.tapestry.beaneditor.PropertyModel;
+import org.apache.tapestry.corelib.components.Form;
 import org.apache.tapestry.corelib.components.Grid;
 
 /**
  * Defines how a {@link Grid} components (and its sub-components) gain access to the row data that
  * is displayed on the page. In many cases, this is just a wrapper around a simple List, but the
  * abstractions exist to support access to a large data set that is accessible in sections.
+ * <p>
+ * This interface is still under development, as we work out the best approach to handling a
+ * {@link Grid} inside a {@link Form} using a large dataset.
  */
 public interface GridDataSource
 {
@@ -40,7 +44,7 @@
      *            sorting is required (in which case, whatever natural order is provided by the
      *            underlying data source will be used)
      * @param ascending
-     *            if true, then sort ascending, else decending
+     *            if true, then sort ascending, else descending
      */
     void prepare(int startIndex, int endIndex, PropertyModel sortModel, boolean ascending);
 

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/corelib/components/GridRows.tml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/corelib/components/GridRows.tml?rev=580771&r1=580770&r2=580771&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/corelib/components/GridRows.tml (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/corelib/components/GridRows.tml Sun Sep 30 12:14:40 2007
@@ -1,5 +1,5 @@
 <tr class="${rowClass}" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
-    <t:loop source="propertyNames" value="propertyName" volatile="true">
+    <t:loop source="propertyNames" value="propertyName" volatile="inherit:volatile">
         <td class="${cellClass}">
              <t:gridcell model="columnModel" row="row" resources="componentResources.containerResources"/>
         </td>        

Modified: tapestry/tapestry5/trunk/tapestry-core/src/site/apt/index.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/site/apt/index.apt?rev=580771&r1=580770&r2=580771&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/site/apt/index.apt (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/site/apt/index.apt Sun Sep 30 12:14:40 2007
@@ -13,6 +13,8 @@
   Progress on Tapestry 5 is really taking off. This space lists some cool new features that have been added
   recently.
   
+  * The Grid component may now be used inside a Form.
+  
   * Tapestry templates now use the extension .tml (not .html). Page templates in the context are now stored at the root, not under WEB-INF.
   
   * The core of BeanEditForm has been factored out as a new component, BeanEditor (this allows you to edit multiple beans within the same form).

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/app1/GridFormDemo.tml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/app1/GridFormDemo.tml?rev=580771&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/app1/GridFormDemo.tml (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/app1/GridFormDemo.tml Sun Sep 30 12:14:40 2007
@@ -0,0 +1,35 @@
+<html t:type="Border"
+  xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
+
+  <h1>Grid Form Demo</h1>
+
+
+  <t:form>
+
+    <t:errors />
+
+    <table t:type="Grid" source="items" row="item" pagerposition="top"
+      rowsperpage="5">
+
+      <t:parameter name="titleCell">
+        <t:textfield t:id="title" value="item.title" />
+      </t:parameter>
+
+      <t:parameter name="urgencyCell">
+        <t:select t:id="urgency" value="item.urgency" />
+      </t:parameter>
+
+    </table>
+
+    <p>
+      <input type="submit" value="Update Items" />
+    </p>
+  </t:form>
+
+
+  <p>
+    [
+    <a t:type="ActionLink" t:id="reset">reset</a>
+    ]
+  </p>
+</html>

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/app1/Start.tml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/app1/Start.tml?rev=580771&r1=580770&r2=580771&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/app1/Start.tml (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/app1/Start.tml Sun Sep 30 12:14:40 2007
@@ -250,6 +250,10 @@
             </t:pagelink>
             -- Multiple BeanEditor components in a single form
           </li>
+          <li>
+            <t:pagelink page="GridFormDemo">Grid Form Demo</t:pagelink>
+            -- Grid operating inside a Form
+          </li>
         </ul>
       </td>
     </tr>

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java?rev=580771&r1=580770&r2=580771&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java Sun Sep 30 12:14:40 2007
@@ -700,9 +700,9 @@
     {
         start("Grid Enum Demo", "reset");
 
-        assertTextSeries("//tr[1]/td[%d]", 2, "End World Hunger", "Medium");
-        assertTextSeries("//tr[2]/td[%d]", 2, "Develop Faster-Than-Light Travel", "High");
-        assertTextSeries("//tr[3]/td[%d]", 2, "Cure Common Cold", "Low");
+        assertTextSeries("//tr[1]/td[%d]", 1, "End World Hunger", "Medium");
+        assertTextSeries("//tr[2]/td[%d]", 1, "Develop Faster-Than-Light Travel", "High");
+        assertTextSeries("//tr[3]/td[%d]", 1, "Cure Common Cold", "Low");
     }
 
     @Test
@@ -1066,20 +1066,10 @@
         assertTextPresent("Zip code: [12345-9876]");
     }
 
-    private void start(String... linkText)
-    {
-        open(BASE_URL);
-
-        for (String s : linkText)
-            clickAndWait(String.format("link=%s", s));
-    }
-
     @Test
     public void multiple_beaneditor_components()
     {
-        start("MultiBeanEdit Demo");
-
-        clickAndWait("link=Clear Data");
+        start("MultiBeanEdit Demo", "Clear Data");
 
         type("firstName", "Howard");
         type("lastName", "Lewis Ship");
@@ -1097,4 +1087,25 @@
                 "Role: [GRANT]");
     }
 
+    @Test
+    public void grid_inside_form()
+    {
+        start("Grid Form Demo", "reset", "2");
+
+        // The first input field is the form's hidden field.
+
+        assertFieldValue("title", "ToDo # 6");
+        assertFieldValueSeries("title_%d", 0, "ToDo # 7", "ToDo # 8", "ToDo # 9", "ToDo # 10");
+
+        type("title_0", "Cure Cancer");
+        select("urgency_0", "High");
+
+        type("title_1", "Pay Phone Bill");
+        select("urgency_1", "Low");
+
+        clickAndWait("//input[@type='submit']");
+
+        assertFieldValueSeries("title_%d", 0, "Cure Cancer", "Pay Phone Bill");
+        assertFieldValueSeries("urgency_%d", 0, "HIGH", "LOW");
+    }
 }

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/NewIntegrationTests.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/NewIntegrationTests.java?rev=580771&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/NewIntegrationTests.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/NewIntegrationTests.java Sun Sep 30 12:14:40 2007
@@ -0,0 +1,35 @@
+// Copyright 2007 The Apache Software Foundation
+//
+// 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 org.apache.tapestry.integration;
+
+import org.apache.tapestry.test.AbstractIntegrationTestSuite;
+import org.testng.annotations.Test;
+
+/**
+ * A kind of staging space for new tests that will be added to {@link IntegrationTests}. The
+ * problem with IntegrationTests is that there's now about 35 tests that run. Rather than have to go
+ * disable all of those when adding a new test, the new test is added and debugged here. Once it is
+ * totally ready, it is moved up to IntegrationTests.
+ */
+@Test(timeOut = 50000, sequential = true, enabled = false, groups =
+{ "integration" })
+public class NewIntegrationTests extends AbstractIntegrationTestSuite
+{
+    public NewIntegrationTests()
+    {
+        super("src/test/app1");
+    }
+
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/data/ToDoItem.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/data/ToDoItem.java?rev=580771&r1=580770&r2=580771&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/data/ToDoItem.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/data/ToDoItem.java Sun Sep 30 12:14:40 2007
@@ -16,6 +16,9 @@
 
 import java.io.Serializable;
 
+import org.apache.tapestry.beaneditor.NonVisual;
+import org.apache.tapestry.beaneditor.Validate;
+
 public class ToDoItem implements Serializable, Cloneable
 {
     private static final long serialVersionUID = 329624498668043734L;
@@ -47,6 +50,7 @@
         }
     }
 
+    @NonVisual
     public long getId()
     {
         return _id;
@@ -57,6 +61,7 @@
         _id = id;
     }
 
+    @Validate("required")
     public String getTitle()
     {
         return _title;
@@ -77,6 +82,7 @@
         _urgency = urgency;
     }
 
+    @NonVisual
     public int getOrder()
     {
         return _order;

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/GridFormDemo.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/GridFormDemo.java?rev=580771&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/GridFormDemo.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/GridFormDemo.java Sun Sep 30 12:14:40 2007
@@ -0,0 +1,76 @@
+// Copyright 2007 The Apache Software Foundation
+//
+// 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 org.apache.tapestry.integration.app1.pages;
+
+import java.util.List;
+
+import org.apache.tapestry.annotations.Inject;
+import org.apache.tapestry.integration.app1.data.ToDoItem;
+import org.apache.tapestry.integration.app1.services.ToDoDatabase;
+
+public class GridFormDemo
+{
+    @Inject
+    private ToDoDatabase _database;
+
+    private ToDoItem _item;
+
+    private List<ToDoItem> _items;
+
+    void onPrepare()
+    {
+        _items = _database.findAll();
+    }
+
+    void onSuccess()
+    {
+        // Here's the down side: we don't have a good way of identifying just what changed.
+        // If we provided our own GridDataSource, we would be able to update just the items
+        // currently visible. But as is, we have to update each one!
+
+        for (ToDoItem item : _items)
+            _database.update(item);
+    }
+
+    public List<ToDoItem> getItems()
+    {
+        return _items;
+    }
+
+    public ToDoItem getItem()
+    {
+        return _item;
+    }
+
+    public void setItem(ToDoItem item)
+    {
+        _item = item;
+    }
+
+    void onActionFromReset()
+    {
+        _database.clear();
+
+        for (int i = 0; i < 20; i++)
+        {
+            ToDoItem item = new ToDoItem();
+            item.setTitle("ToDo # " + (i + 1));
+            item.setOrder(i);
+
+            _database.add(item);
+        }
+    }
+
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/services/ToDoDatabase.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/services/ToDoDatabase.java?rev=580771&r1=580770&r2=580771&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/services/ToDoDatabase.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/services/ToDoDatabase.java Sun Sep 30 12:14:40 2007
@@ -37,4 +37,7 @@
     
     /** Resets the database, clearing out all data, re-adding base data. */
     void reset();
+
+    /** Deletes all items from the database. */
+    void clear();
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/services/ToDoDatabaseImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/services/ToDoDatabaseImpl.java?rev=580771&r1=580770&r2=580771&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/services/ToDoDatabaseImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/services/ToDoDatabaseImpl.java Sun Sep 30 12:14:40 2007
@@ -43,6 +43,11 @@
         reset();
     }
 
+    public void clear()
+    {
+        _items.clear();
+    }
+
     public void reset()
     {
         _items.clear();

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/provider.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/provider.apt?rev=580771&r1=580770&r2=580771&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/provider.apt (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/provider.apt Sun Sep 30 12:14:40 2007
@@ -9,7 +9,7 @@
   resolve the parameter automatically.
   
   This is called <object injection>, rather than <service injection>, because the value that will ultimately
-  be injected is not necessarilly a service; it may be some aribrary object.
+  be injected is not necessarily a service; it may be some arbitrary object.
   
   If this sounds vague, its because there is not just one
   {{{../apidocs/org/apache/tapestry/ioc/ObjectProvider.html}ObjectProvider}}; there's a whole set of them,
@@ -18,7 +18,7 @@
   
   There are two built-in object providers:
   
-  * Check for {{{apidocs/org/apache/tapestry/ioc/annotations/Value.html}Value}} annotation
+  * Check for {{{../apidocs/org/apache/tapestry/ioc/annotations/Value.html}Value}} annotation
   
   * Check for a <unique> service in the Registry whose service interface matches the parameter type
   

Modified: tapestry/tapestry5/trunk/tapestry-test/src/main/java/org/apache/tapestry/test/AbstractIntegrationTestSuite.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-test/src/main/java/org/apache/tapestry/test/AbstractIntegrationTestSuite.java?rev=580771&r1=580770&r2=580771&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-test/src/main/java/org/apache/tapestry/test/AbstractIntegrationTestSuite.java (original)
+++ tapestry/tapestry5/trunk/tapestry-test/src/main/java/org/apache/tapestry/test/AbstractIntegrationTestSuite.java Sun Sep 30 12:14:40 2007
@@ -161,6 +161,16 @@
         }
     }
 
+    protected final void assertFieldValueSeries(String idFormat, int startIndex, String... values)
+    {
+        for (int i = 0; i < values.length; i++)
+        {
+            String id = String.format(idFormat, startIndex + i);
+
+            assertFieldValue(id, values[i]);
+        }
+    }
+
     @AfterClass(alwaysRun = true)
     public void cleanup() throws Exception
     {
@@ -519,6 +529,18 @@
     public void waitForPopUp(String windowID, String timeout)
     {
         _selenium.waitForPopUp(windowID, timeout);
+    }
+
+    /**
+     * Used to start a typical test, by opening to the base URL and clicking through a series of
+     * links.
+     */
+    protected final void start(String... linkText)
+    {
+        open(BASE_URL);
+    
+        for (String s : linkText)
+            clickAndWait(String.format("link=%s", s));
     }
 
 }