You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2008/01/21 02:02:20 UTC

svn commit: r613714 - 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/java/org/apache/tapestry/internal/ tapestry-core/src...

Author: hlship
Date: Sun Jan 20 17:02:17 2008
New Revision: 613714

URL: http://svn.apache.org/viewvc?rev=613714&view=rev
Log:
TAPESTRY-1847: Grid component should output additional CSS classes into TDs to identify first and last column, first and last row

Added:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/grid/GridConstants.java
Modified:
    tapestry/tapestry5/trunk/pom.xml
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Form.java
    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/GridColumns.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/internal/TapestryInternalUtils.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/corelib/components/GridColumns.tml
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/TapestryInternalUtilsTest.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/internal/util/InternalUtils.java

Modified: tapestry/tapestry5/trunk/pom.xml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/pom.xml?rev=613714&r1=613713&r2=613714&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/pom.xml (original)
+++ tapestry/tapestry5/trunk/pom.xml Sun Jan 20 17:02:17 2008
@@ -226,7 +226,7 @@
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-surefire-plugin</artifactId>
-                    <version>2.4-SNAPSHOT</version>
+                    <version>2.4</version>
                     <configuration>
                         <suiteXmlFiles>
                             <suiteXmlFile>src/test/conf/testng.xml</suiteXmlFile>
@@ -369,11 +369,6 @@
         <pluginRepository>
             <id>tapestry</id>
             <url>http://tapestry.formos.com/maven-repository</url>
-        </pluginRepository>
-        <!-- Needed to get surefire 2.4-SNAPSHOT -->
-        <pluginRepository>
-            <id>apache.snapshots</id>
-            <url>http://people.apache.org/repo/m2-snapshot-repository/</url>
         </pluginRepository>
         <!-- I believe the Cobertura plugin lives here. -->
         <pluginRepository>

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Form.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Form.java?rev=613714&r1=613713&r2=613714&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Form.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/Form.java Sun Jan 20 17:02:17 2008
@@ -28,6 +28,7 @@
 import org.apache.tapestry.internal.services.HeartbeatImpl;
 import org.apache.tapestry.internal.util.Base64ObjectInputStream;
 import org.apache.tapestry.internal.util.Base64ObjectOutputStream;
+import org.apache.tapestry.ioc.Location;
 import org.apache.tapestry.ioc.annotations.Inject;
 import org.apache.tapestry.ioc.internal.util.TapestryException;
 import org.apache.tapestry.runtime.Component;
@@ -345,7 +346,9 @@
             }
             catch (Exception ex)
             {
-                throw new TapestryException(ex.getMessage(), component, ex);
+                Location location = component == null ? null : component.getComponentResources().getLocation();
+
+                throw new TapestryException(ex.getMessage(), location, ex);
             }
             finally
             {

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=613714&r1=613713&r2=613714&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 Jan 20 17:02:17 2008
@@ -32,15 +32,13 @@
 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.
+ * 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.
+ * 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
@@ -49,24 +47,24 @@
 public class Grid implements GridModelProvider
 {
     /**
-     * The source of data for the Grid to display. This will usually be a List or array but can also
-     * be an explicit {@link GridDataSource}. For Lists and Arrays, a GridDataSource is created
-     * automatically as a wrapper around the underlying List.
+     * The source of data for the Grid to display. This will usually be a List or array but can also be an explicit
+     * {@link GridDataSource}. For Lists and Arrays, a GridDataSource is created automatically as a wrapper around the
+     * underlying List.
      */
     @Parameter(required = true)
     private Object _source;
 
     /**
-     * The number of rows of data displayed on each page. If there are more rows than will fit, the
-     * Grid will divide up the rows into "pages" and (normally) provide a pager to allow the user to
-     * navigate within the overall result set.
+     * The number of rows of data displayed on each page. If there are more rows than will fit, the Grid will divide up
+     * the rows into "pages" and (normally) provide a pager to allow the user to navigate within the overall result
+     * set.
      */
     @Parameter("25")
     private int _rowsPerPage;
 
     /**
-     * Defines where the pager (used to navigate within the "pages" of results) should be displayed:
-     * "top", "bottom", "both" or "none".
+     * Defines where the pager (used to navigate within the "pages" of results) should be displayed: "top", "bottom",
+     * "both" or "none".
      */
     @Parameter(value = "bottom", defaultPrefix = "literal")
     private GridPagerPosition _pagerPosition;
@@ -81,52 +79,50 @@
     private boolean _sortAscending = true;
 
     /**
-     * Used to store the current object being rendered (for the current row). This is used when
-     * parameter blocks are provided to override the default cell renderer for a particular column
-     * ... the components within the block can use the property bound to the row parameter to know
-     * what they should render.
+     * Used to store the current object being rendered (for the current row). This is used when parameter blocks are
+     * provided to override the default cell renderer for a particular column ... the components within the block can
+     * use the property bound to the row parameter to know what they should render.
      */
     @Parameter
     private Object _row;
 
     /**
-     * The model used to identify the properties to be presented and the order of presentation. The
-     * model may be omitted, in which case a default model is generated from the first object in the
-     * data source (this implies that the objects provided by the source are uniform). The model may
-     * be explicitly specified to override the default behavior, say to reorder or rename columns or
-     * add additional columns.
+     * The model used to identify the properties to be presented and the order of presentation. The model may be
+     * omitted, in which case a default model is generated from the first object in the data source (this implies that
+     * the objects provided by the source are uniform). The model may be explicitly specified to override the default
+     * behavior, say to reorder or rename columns or add additional columns.
      */
     @Parameter
     private BeanModel _model;
 
     /**
-     * A comma-separated list of property names to be removed from the {@link BeanModel}. The names
-     * are case-insensitive.
+     * A comma-separated list of property names to be removed from the {@link BeanModel}. The names are
+     * case-insensitive.
      */
     @Parameter(defaultPrefix = "literal")
     private String _remove;
 
     /**
-     * A comma-separated list of property names indicating the order in which the properties should
-     * be presented. The names are case insensitive. Any properties not indicated in the list will
-     * be appended to the end of the display order.
+     * A comma-separated list of property names indicating the order in which the properties should be presented. The
+     * names are case insensitive. Any properties not indicated in the list will be appended to the end of the display
+     * order.
      */
     @Parameter(defaultPrefix = "literal")
     private String _reorder;
 
     /**
-     * A Block to render instead of the table (and pager, etc.) when the source is empty. The
-     * default is simply the text "There is no data to display". This parameter is used to customize
-     * that message, possibly including components to allow the user to create new objects.
+     * A Block to render instead of the table (and pager, etc.) when the source is empty. The default is simply the text
+     * "There is no data to display". This parameter is used to customize that message, possibly including components to
+     * allow the user to create new objects.
      */
     @Parameter(value = "block:empty")
     private Block _empty;
 
 
     /**
-     * If true, then the CSS class on each &lt;TD&gt; and &lt;TH&gt; cell will be omitted, which can reduce
-     * the amount of output from the component overall by a considerable amount. Leave this as false, the
-     * default, when you are leveraging the CSS to customize the look and feel of particular columns.
+     * If true, then the CSS class on each &lt;TD&gt; and &lt;TH&gt; cell will be omitted, which can reduce the amount
+     * of output from the component overall by a considerable amount. Leave this as false, the default, when you are
+     * leveraging the CSS to customize the look and feel of particular columns.
      */
     @Parameter
     private boolean _lean;
@@ -145,9 +141,8 @@
     private GridDataSource _dataSource;
 
     /**
-     * The CSS class for the tr element for each data row. This can be used to highlight particular
-     * rows, or cycle between CSS values (for the "zebra effect"). If null or not bound, then no
-     * particular CSS class value is used.
+     * The CSS class for the tr element for each data row. This can be used to highlight particular rows, or cycle
+     * between CSS values (for the "zebra effect"). If null or not bound, then no particular CSS class value is used.
      */
     @Parameter(cache = false)
     private String _rowClass;
@@ -158,7 +153,7 @@
 
     @SuppressWarnings("unused")
     @Component(
-            parameters = {"rowClass=rowClass", "rowsPerPage=rowsPerPage", "currentPage=currentPage", "row=row", "volatile=inherit:volatile", "lean=inherit:lean"})
+            parameters = {"sortColumnId=sortColumnId", "sortAscending=sortAscending", "rowClass=rowClass", "rowsPerPage=rowsPerPage", "currentPage=currentPage", "row=row", "volatile=inherit:volatile", "lean=inherit:lean"})
     private GridRows _rows;
 
     @Component(parameters = {"source=dataSource", "rowsPerPage=rowsPerPage", "currentPage=currentPage"})
@@ -173,11 +168,10 @@
     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.
+     * 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;

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/GridColumns.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/GridColumns.java?rev=613714&r1=613713&r2=613714&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/GridColumns.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/corelib/components/GridColumns.java Sun Jan 20 17:02:17 2008
@@ -19,9 +19,12 @@
 import org.apache.tapestry.annotations.Parameter;
 import org.apache.tapestry.annotations.Path;
 import org.apache.tapestry.beaneditor.PropertyModel;
+import org.apache.tapestry.grid.GridConstants;
 import org.apache.tapestry.grid.GridModelProvider;
+import org.apache.tapestry.internal.TapestryInternalUtils;
 import org.apache.tapestry.ioc.Messages;
 import org.apache.tapestry.ioc.annotations.Inject;
+import org.apache.tapestry.ioc.internal.util.CollectionFactory;
 
 import java.util.List;
 
@@ -78,8 +81,18 @@
     @Inject
     private Messages _messages;
 
+
+    private int _columnIndex;
+
+    private int _lastColumnIndex;
+
     private PropertyModel _columnModel;
 
+    void setupRender()
+    {
+        _lastColumnIndex = _dataProvider.getDataModel().getPropertyNames().size() - 1;
+    }
+
     public boolean isSortDisabled()
     {
         return !_columnModel.isSortable();
@@ -87,16 +100,27 @@
 
     public String getSortLinkClass()
     {
-        if (isActiveSortColumn()) return _sortAscending ? "t-sort-column-ascending" : "t-sort-column-descending";
+        if (isActiveSortColumn())
+            return _sortAscending ? GridConstants.SORT_ASCENDING_CLASS : GridConstants.SORT_DESCENDING_CLASS;
 
         return null;
     }
 
     public String getHeaderClass()
     {
-        if (_lean) return null;
+        List<String> classes = CollectionFactory.newList();
+
+        if (!_lean) classes.add(_columnModel.getId() + "-header");
+
+        String sort = getSortLinkClass();
+
+        if (sort != null) classes.add(sort);
+
+        if (_columnIndex == 0) classes.add(GridConstants.FIRST_CLASS);
 
-        return _columnModel.getId() + "-header";
+        if (_columnIndex == _lastColumnIndex) classes.add(GridConstants.LAST_CLASS);
+
+        return TapestryInternalUtils.toClassAttributeValue(classes);
     }
 
     public boolean isActiveSortColumn()
@@ -144,5 +168,18 @@
     public void setColumnName(String columnName)
     {
         _columnModel = _dataProvider.getDataModel().get(columnName);
+    }
+
+    /**
+     * Set by the Loop component.
+     */
+    public void setColumnIndex(int columnIndex)
+    {
+        _columnIndex = columnIndex;
+    }
+
+    public int getColumnIndex()
+    {
+        return _columnIndex;
     }
 }

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=613714&r1=613713&r2=613714&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 Jan 20 17:02:17 2008
@@ -18,8 +18,11 @@
 import org.apache.tapestry.annotations.Environmental;
 import org.apache.tapestry.annotations.Parameter;
 import org.apache.tapestry.beaneditor.PropertyModel;
+import org.apache.tapestry.grid.GridConstants;
 import org.apache.tapestry.grid.GridDataSource;
 import org.apache.tapestry.grid.GridModelProvider;
+import org.apache.tapestry.internal.TapestryInternalUtils;
+import org.apache.tapestry.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry.services.FormSupport;
 
 import java.util.List;
@@ -27,13 +30,15 @@
 /**
  * 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.
+ * 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
 {
+    private int _startRow;
+
     static class SetupForRow implements ComponentAction<GridRows>
     {
         private static final long serialVersionUID = -3216282071752371975L;
@@ -52,8 +57,22 @@
     }
 
     /**
-     * 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.
+     * The column which is currently being sorted. This value is the column's {@link PropertyModel#getId() id}, not its
+     * {@link PropertyModel#getPropertyName() name}. This parameter may be null, in which case no column is being used
+     * for sorting.
+     */
+    @Parameter(required = true)
+    private String _sortColumnId;
+
+    /**
+     * If true, then the sort is ascending (A - Z), if false the descending (Z - A).
+     */
+    @Parameter(required = true)
+    private boolean _sortAscending;
+
+    /**
+     * 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.
      */
     @Parameter(cache = false)
     private String _rowClass;
@@ -77,23 +96,23 @@
     private int _currentPage;
 
     /**
-     * The current row being rendered, this is primarily an output parameter used to allow the Grid,
-     * and the Grid's container, to know what object is being rendered.
+     * The current row being rendered, this is primarily an output parameter used to allow the Grid, and the Grid's
+     * container, to know what object is being rendered.
      */
     @Parameter(required = true)
     private Object _row;
 
     /**
-     * If true, then the CSS class on each &lt;TD&gt; cell will be omitted, which can reduce
-     * the amount of output from the component overall by a considerable amount. Leave this as false, the
-     * default, when you are leveraging the CSS to customize the look and feel of particular columns.
+     * If true, then the CSS class on each &lt;TD&gt; cell will be omitted, which can reduce the amount of output from
+     * the component overall by a considerable amount. Leave this as false, the default, when you are leveraging the CSS
+     * to customize the look and feel of particular columns.
      */
     @Parameter
     private boolean _lean;
 
     /**
-     * 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.
+     * 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
@@ -114,16 +133,39 @@
 
     public String getRowClass()
     {
-        return _rowClass;
+        List<String> classes = CollectionFactory.newList();
+
+        // Not a cached parameter, so careful to only access it once.
+
+        String rc = _rowClass;
+
+        if (rc != null) classes.add(rc);
+
+        if (_rowIndex == _startRow) classes.add(GridConstants.FIRST_CLASS);
+
+        if (_rowIndex == _endRow) classes.add(GridConstants.LAST_CLASS);
+
+        return TapestryInternalUtils.toClassAttributeValue(classes);
     }
 
     public String getCellClass()
     {
-        if (_lean) return null;
+        List<String> classes = CollectionFactory.newList();
+
+        if (!_lean)
+        {
+            String id = _provider.getDataModel().get(_propertyName).getId();
+
+            classes.add(id + "-cell");
+        }
 
-        String id = _provider.getDataModel().get(_propertyName).getId();
+        if (_columnModel.getId().equals(_sortColumnId))
+        {
+            String sortClassName = _sortAscending ? GridConstants.SORT_ASCENDING_CLASS : GridConstants.SORT_DESCENDING_CLASS;
+            classes.add(sortClassName);
+        }
 
-        return id + "-cell";
+        return TapestryInternalUtils.toClassAttributeValue(classes);
     }
 
     void setupRender()
@@ -138,17 +180,16 @@
 
         if (_currentPage > maxPages) _currentPage = maxPages;
 
-        int startRow = (_currentPage - 1) * _rowsPerPage;
-        _endRow = Math.min(availableRows - 1, startRow + _rowsPerPage - 1);
+        _startRow = (_currentPage - 1) * _rowsPerPage;
+        _endRow = Math.min(availableRows - 1, _startRow + _rowsPerPage - 1);
 
-        _rowIndex = startRow;
+        _rowIndex = _startRow;
 
         _recordingStateInsideForm = !_volatile && _formSupport != null;
     }
 
     /**
-     * Callback method, used when recording state to a form, or called directly when not recording
-     * state.
+     * Callback method, used when recording state to a form, or called directly when not recording state.
      */
     void setupForRow(int rowIndex)
     {

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/grid/GridConstants.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/grid/GridConstants.java?rev=613714&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/grid/GridConstants.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/grid/GridConstants.java Sun Jan 20 17:02:17 2008
@@ -0,0 +1,43 @@
+// Copyright 2008 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.grid;
+
+public class GridConstants
+{
+    /**
+     * CSS class for the first column or the first row. May be applied to a &lt;th&gt; (in the &lt;thead&gt;) or a
+     * &lt;tr&gt; (in the &lt;tbody&gt;).
+     */
+    public static final String FIRST_CLASS = "t-first";
+
+    /**
+     * CSS class for the last column or the last row. May be applied to a &lt;th&gt; (in the &lt;thead&gt;) or a
+     * &lt;tr&gt; (in the &lt;tbody&gt;).
+     */
+    public static final String LAST_CLASS = "t-last";
+
+    /**
+     * Marks the column that is currently sorted for sort ascending.  May be applied to a &lt;th&gt; (in the
+     * &lt;thead&gt; or a &lt;td&gt; in the &lt;tbody&gt;).
+     */
+    public static final String SORT_ASCENDING_CLASS = "t-sort-column-ascending";
+
+    /**
+     * Marks the column that is currently sorted for sort descending.  May be applied to a &lt;th&gt; (in the
+     * &lt;thead&gt; or a &lt;td&gt; in the &lt;tbody&gt;).
+     */
+
+    public static final String SORT_DESCENDING_CLASS = "t-sort-column-descending";
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/TapestryInternalUtils.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/TapestryInternalUtils.java?rev=613714&r1=613713&r2=613714&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/TapestryInternalUtils.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/TapestryInternalUtils.java Sun Jan 20 17:02:17 2008
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007 The Apache Software Foundation
+// Copyright 2006, 2007, 2008 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.
@@ -26,6 +26,7 @@
 import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
 import org.apache.tapestry.ioc.internal.util.Defense;
 import static org.apache.tapestry.ioc.internal.util.Defense.notNull;
+import org.apache.tapestry.ioc.internal.util.InternalUtils;
 import org.apache.tapestry.ioc.internal.util.Orderer;
 import org.apache.tapestry.ioc.services.ClassFactory;
 import org.apache.tapestry.ioc.services.ClassPropertyAdapter;
@@ -81,9 +82,9 @@
     }
 
     /**
-     * Capitalizes the string, and inserts a space before each upper case character (or sequence of
-     * upper case characters). Thus "userId" becomes "User Id", etc. Also, converts underscore into
-     * space (and capitalizes the following word), thus "user_id" also becomes "User Id".
+     * Capitalizes the string, and inserts a space before each upper case character (or sequence of upper case
+     * characters). Thus "userId" becomes "User Id", etc. Also, converts underscore into space (and capitalizes the
+     * following word), thus "user_id" also becomes "User Id".
      */
     public static String toUserPresentable(String id)
     {
@@ -139,8 +140,8 @@
     }
 
     /**
-     * Converts a string to an {@link OptionModel}. The string is of the form "value=label". If the
-     * equals sign is omitted, then the same value is used for both value and label.
+     * Converts a string to an {@link OptionModel}. The string is of the form "value=label". If the equals sign is
+     * omitted, then the same value is used for both value and label.
      *
      * @param input
      * @return
@@ -160,8 +161,8 @@
     }
 
     /**
-     * Parses a string input into a series of value=label pairs compatible with
-     * {@link #toOptionModel(String)}. Splits on commas. Ignores whitespace around commas.
+     * Parses a string input into a series of value=label pairs compatible with {@link #toOptionModel(String)}. Splits
+     * on commas. Ignores whitespace around commas.
      *
      * @param input comma seperated list of terms
      * @return list of option models
@@ -179,8 +180,7 @@
     }
 
     /**
-     * Wraps the result of {@link #toOptionModels(String)} as a {@link SelectModel} (with no option
-     * groups).
+     * Wraps the result of {@link #toOptionModels(String)} as a {@link SelectModel} (with no option groups).
      *
      * @param input
      * @return
@@ -208,8 +208,7 @@
     }
 
     /**
-     * Processes a map input into a series of map entries compatible with
-     * {@link #toOptionModel(Map.Entry)}.
+     * Processes a map input into a series of map entries compatible with {@link #toOptionModel(Map.Entry)}.
      *
      * @param input map of elements
      * @return list of option models
@@ -227,8 +226,7 @@
     }
 
     /**
-     * Wraps the result of {@link #toOptionModels(Map)} as a {@link SelectModel} (with no option
-     * groups).
+     * Wraps the result of {@link #toOptionModels(Map)} as a {@link SelectModel} (with no option groups).
      *
      * @param input
      * @return
@@ -254,8 +252,7 @@
     }
 
     /**
-     * Processes a list input into a series of objects compatible with
-     * {@link #toOptionModel(Object)}.
+     * Processes a list input into a series of objects compatible with {@link #toOptionModel(Object)}.
      *
      * @param input list of elements
      * @return list of option models
@@ -273,8 +270,7 @@
     }
 
     /**
-     * Wraps the result of {@link #toOptionModels(List)} as a {@link SelectModel} (with no option
-     * groups).
+     * Wraps the result of {@link #toOptionModels(List)} as a {@link SelectModel} (with no option groups).
      *
      * @param input
      * @return
@@ -287,8 +283,8 @@
     }
 
     /**
-     * Parses a key/value pair where the key and the value are seperated by an equals sign. The key
-     * and value are trimmed of leading and trailing whitespace, and returned as a {@link KeyValue}.
+     * Parses a key/value pair where the key and the value are seperated by an equals sign. The key and value are
+     * trimmed of leading and trailing whitespace, and returned as a {@link KeyValue}.
      *
      * @param input
      * @return
@@ -306,9 +302,9 @@
     }
 
     /**
-     * Used to convert a property expression into a key that can be used to locate various resources
-     * (Blocks, messages, etc.). Strips out any punctuation characters, leaving just words
-     * characters (letters, number and the underscore).
+     * Used to convert a property expression into a key that can be used to locate various resources (Blocks, messages,
+     * etc.). Strips out any punctuation characters, leaving just words characters (letters, number and the
+     * underscore).
      *
      * @param expression
      * @return
@@ -319,8 +315,8 @@
     }
 
     /**
-     * Looks for a label within the messages based on the id. If found, it is used, otherwise the
-     * name is converted to a user presentable form.
+     * Looks for a label within the messages based on the id. If found, it is used, otherwise the name is converted to a
+     * user presentable form.
      */
     public static String defaultLabel(String id, Messages messages, String propertyExpression)
     {
@@ -332,8 +328,8 @@
     }
 
     /**
-     * Strips a dotted sequence (such as a property expression, or a qualified class name) down to
-     * the last term of that expression, by locating the last period ('.') in the string.
+     * Strips a dotted sequence (such as a property expression, or a qualified class name) down to the last term of that
+     * expression, by locating the last period ('.') in the string.
      */
     public static String lastTerm(String input)
     {
@@ -342,6 +338,20 @@
         return input.substring(dotx + 1);
     }
 
+    /**
+     * Converts an list of strings into a space-separated string combining them all, suitable for use as an HTML class
+     * attribute value.
+     *
+     * @param classes classes to combine
+     * @return the joined classes, or null if classes is empty
+     */
+    public static String toClassAttributeValue(List<String> classes)
+    {
+        if (classes.isEmpty()) return null;
+
+        return InternalUtils.join(classes, " ");
+    }
+
     private static class PropertyOrder implements Comparable<PropertyOrder>
     {
         final String _propertyName;
@@ -370,10 +380,9 @@
     }
 
     /**
-     * Sorts the property names into presentation order. Filters out any properties that have an
-     * explicit {@link OrderBefore}, leaving the remainder. Estimates each propertie's position
-     * based on the relative position of the property's getter. The code assumes that all methods
-     * are readable (have a getter method).
+     * Sorts the property names into presentation order. Filters out any properties that have an explicit {@link
+     * OrderBefore}, leaving the remainder. Estimates each propertie's position based on the relative position of the
+     * property's getter. The code assumes that all methods are readable (have a getter method).
      *
      * @param classAdapter  defines the bean that contains the properties
      * @param classFactory  used to access method line number information
@@ -511,8 +520,8 @@
     }
 
     /**
-     * Determines if the two values are equal. They are equal if they are the exact same value
-     * (including if they are both null). Otherwise standard equals() comparison is used.
+     * Determines if the two values are equal. They are equal if they are the exact same value (including if they are
+     * both null). Otherwise standard equals() comparison is used.
      *
      * @param <T>
      * @param left

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java?rev=613714&r1=613713&r2=613714&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java Sun Jan 20 17:02:17 2008
@@ -830,22 +830,28 @@
 
     public boolean handleEvent(ComponentEvent event)
     {
-        // Simple case: no mixins
+        try
+        { // Simple case: no mixins
 
-        if (_components == null) return _coreComponent.handleComponentEvent(event);
+            if (_components == null) return _coreComponent.handleComponentEvent(event);
 
-        // Otherwise, iterate over mixins + core component
+            // Otherwise, iterate over mixins + core component
 
-        boolean result = false;
+            boolean result = false;
 
-        for (Component component : _components)
-        {
-            result |= component.handleComponentEvent(event);
+            for (Component component : _components)
+            {
+                result |= component.handleComponentEvent(event);
 
-            if (event.isAborted()) break;
-        }
+                if (event.isAborted()) break;
+            }
 
-        return result;
+            return result;
+        }
+        catch (RuntimeException ex)
+        {
+            throw new TapestryException(ex.getMessage(), this, ex);
+        }
     }
 
     /**

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/corelib/components/GridColumns.tml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/corelib/components/GridColumns.tml?rev=613714&r1=613713&r2=613714&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/corelib/components/GridColumns.tml (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/corelib/components/GridColumns.tml Sun Jan 20 17:02:17 2008
@@ -1,6 +1,7 @@
 <thead xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
     <tr>
-        <th t:type="Loop" source="columnNames" value="columnName" volatile="true" class="prop:headerClass">
+        <th t:type="Loop" source="columnNames" value="columnName" volatile="true" class="prop:headerClass"
+            index="columnIndex">
             <a t:id="sort">${columnModel.label}</a>
             <t:if test="columnModel.sortable">
                 <a t:id="sort2">

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/TapestryInternalUtilsTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/TapestryInternalUtilsTest.java?rev=613714&r1=613713&r2=613714&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/TapestryInternalUtilsTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/TapestryInternalUtilsTest.java Sun Jan 20 17:02:17 2008
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007 The Apache Software Foundation
+// Copyright 2006, 2007, 2008 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.
@@ -19,6 +19,7 @@
 import org.apache.tapestry.SelectModel;
 import org.apache.tapestry.internal.test.InternalBaseTestCase;
 import org.apache.tapestry.ioc.Messages;
+import org.apache.tapestry.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry.ioc.services.ClassFactory;
 import org.apache.tapestry.ioc.services.ClassPropertyAdapter;
 import org.apache.tapestry.ioc.services.PropertyAccess;
@@ -432,7 +433,6 @@
         assertSame(actual, resources);
 
         verify();
-
     }
 
     @Test
@@ -469,5 +469,21 @@
         String input = "\u65E5\u672C\u8A9E";
         String expected = "%E6%97%A5%E6%9C%AC%E8%AA%9E";
         assertEquals(TapestryInternalUtils.encodeContext(input), expected);
+    }
+
+    @Test
+    public void to_class_attribute_value_empty()
+    {
+        List<String> classes = Collections.emptyList();
+
+        assertNull(TapestryInternalUtils.toClassAttributeValue(classes));
+    }
+
+    @Test
+    public void to_class_attribute_value_normal()
+    {
+        List<String> classes = CollectionFactory.newList("fred", "barney", "wilma");
+
+        assertEquals(TapestryInternalUtils.toClassAttributeValue(classes), "fred barney wilma");
     }
 }

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/internal/util/InternalUtils.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/internal/util/InternalUtils.java?rev=613714&r1=613713&r2=613714&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/internal/util/InternalUtils.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry/ioc/internal/util/InternalUtils.java Sun Jan 20 17:02:17 2008
@@ -33,22 +33,20 @@
 import java.util.*;
 
 /**
- * Utilities used within various internal implemenations of Tapestry IOC and the rest of the
- * tapestry-core framework.
+ * Utilities used within various internal implemenations of Tapestry IOC and the rest of the tapestry-core framework.
  */
 
 public class InternalUtils
 {
     /**
-     * Leading punctiation on member names that is stripped off to form a property name or new
-     * member name.
+     * Leading punctiation on member names that is stripped off to form a property name or new member name.
      */
     private static final String NAME_PREFIX = "_$";
 
     /**
-     * Converts a method to a user presentable string using a {@link ClassFactory} to obtain a
-     * {@link Location} (where possible). {@link #asString(Method)} is used under the covers,
-     * to present a detailed, but not excessive, description of the class, method and parameters.
+     * Converts a method to a user presentable string using a {@link ClassFactory} to obtain a {@link Location} (where
+     * possible). {@link #asString(Method)} is used under the covers, to present a detailed, but not excessive,
+     * description of the class, method and parameters.
      *
      * @param method       method to convert to a string
      * @param classFactory used to obtain the {@link Location}
@@ -62,9 +60,8 @@
     }
 
     /**
-     * Converts a method to a user presentable string consisting of the containing class name, the
-     * method name, and the short form of the parameter list (the class name of each parameter type,
-     * shorn of the package name portion).
+     * Converts a method to a user presentable string consisting of the containing class name, the method name, and the
+     * short form of the parameter list (the class name of each parameter type, shorn of the package name portion).
      *
      * @param method
      * @return short string representation
@@ -125,8 +122,7 @@
     }
 
     /**
-     * Strips leading characters defined by {@link InternalUtils#NAME_PREFIX}, then adds the prefix
-     * back in.
+     * Strips leading characters defined by {@link InternalUtils#NAME_PREFIX}, then adds the prefix back in.
      */
     public static String createMemberName(String memberName)
     {
@@ -250,26 +246,48 @@
      */
     public static String join(List elements)
     {
-        StringBuilder buffer = new StringBuilder();
-        boolean first = true;
+        return join(elements, ", ");
+    }
 
-        for (Object o : elements)
+    /**
+     * Joins together some number of elements.
+     *
+     * @param elements  objects to be joined together
+     * @param separator used between elements when joining
+     */
+    public static String join(List elements, String separator)
+    {
+        switch (elements.size())
         {
-            if (!first) buffer.append(", ");
+            case 0:
+                return "";
 
-            buffer.append(String.valueOf(o));
+            case 1:
+                return elements.get(0).toString();
 
-            first = false;
-        }
+            default:
+
+                StringBuilder buffer = new StringBuilder();
+                boolean first = true;
 
-        return buffer.toString();
+                for (Object o : elements)
+                {
+                    if (!first) buffer.append(separator);
+
+                    buffer.append(String.valueOf(o));
+
+                    first = false;
+                }
+
+                return buffer.toString();
+        }
     }
 
     /**
      * Creates a sorted copy of the provided elements, then turns that into a comma separated list.
      *
-     * @return the elements converted to strings, sorted, joined with comma ... or "(none)" if the elements
-     *         are null or empty
+     * @return the elements converted to strings, sorted, joined with comma ... or "(none)" if the elements are null or
+     *         empty
      */
     public static String joinSorted(Collection elements)
     {
@@ -286,8 +304,7 @@
     }
 
     /**
-     * Returns true if the input is null, or is a zero length string (excluding leading/trailing
-     * whitespace).
+     * Returns true if the input is null, or is a zero length string (excluding leading/trailing whitespace).
      */
 
     public static boolean isBlank(String input)
@@ -311,8 +328,8 @@
     }
 
     /**
-     * Sniffs the object to see if it is a {@link Location} or {@link Locatable}. Returns null if
-     * null or not convertable to a location.
+     * Sniffs the object to see if it is a {@link Location} or {@link Locatable}. Returns null if null or not
+     * convertable to a location.
      */
 
     public static Location locationOf(Object location)
@@ -327,8 +344,7 @@
     }
 
     /**
-     * Extracts the string keys from a map and returns them in sorted order. The keys are converted
-     * to strings.
+     * Extracts the string keys from a map and returns them in sorted order. The keys are converted to strings.
      *
      * @param map the map to extract keys from (may be null)
      * @return the sorted keys, or the empty set if map is null
@@ -407,10 +423,10 @@
     }
 
     /**
-     * Searches the string for the final period ('.') character and returns everything after that.
-     * The input string is generally a fully qualified class name, though tapestry-core also uses
-     * this method for the occasional property expression (which is also dot separated). Returns the
-     * input string unchanged if it does not contain a period character.
+     * Searches the string for the final period ('.') character and returns everything after that. The input string is
+     * generally a fully qualified class name, though tapestry-core also uses this method for the occasional property
+     * expression (which is also dot separated). Returns the input string unchanged if it does not contain a period
+     * character.
      */
     public static String lastTerm(String input)
     {
@@ -424,14 +440,12 @@
     }
 
     /**
-     * Searches a class for the "best" constructor, the public constructor with the most parameters.
-     * Returns null if there are no public constructors. If there is more than one constructor with
-     * the maximum number of parameters, it is not determined which will be returned (don't build a
-     * class like that!).
+     * Searches a class for the "best" constructor, the public constructor with the most parameters. Returns null if
+     * there are no public constructors. If there is more than one constructor with the maximum number of parameters, it
+     * is not determined which will be returned (don't build a class like that!).
      *
      * @param clazz to search for a constructor for
-     * @return the constructor to be used to instantiate the class, or null if no appropriate
-     *         constructor was found
+     * @return the constructor to be used to instantiate the class, or null if no appropriate constructor was found
      */
     public static Constructor findAutobuildConstructor(Class clazz)
     {
@@ -467,8 +481,8 @@
     }
 
     /**
-     * Adds a value to a specially organized map where the values are lists of objects. This
-     * somewhat simulates a map that allows mutiple values for the same key.
+     * Adds a value to a specially organized map where the values are lists of objects. This somewhat simulates a map
+     * that allows mutiple values for the same key.
      *
      * @param map   to store value into
      * @param key   for which a value is added