Home » tapestry-src-5.0.19 » org.apache.tapestry5.corelib.components » [javadoc | source]

    1   // Copyright 2007, 2008 The Apache Software Foundation
    2   //
    3   // Licensed under the Apache License, Version 2.0 (the "License");
    4   // you may not use this file except in compliance with the License.
    5   // You may obtain a copy of the License at
    6   //
    7   //     http://www.apache.org/licenses/LICENSE-2.0
    8   //
    9   // Unless required by applicable law or agreed to in writing, software
   10   // distributed under the License is distributed on an "AS IS" BASIS,
   11   // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   12   // See the License for the specific language governing permissions and
   13   // limitations under the License.
   14   // Copyright 2007, 2008 The Apache Software Foundation
   15   //
   16   // Licensed under the Apache License, Version 2.0 (the "License");
   17   // you may not use this file except in compliance with the License.
   18   // You may obtain a copy of the License at
   19   //
   20   //     http://www.apache.org/licenses/LICENSE-2.0
   21   //
   22   // Unless required by applicable law or agreed to in writing, software
   23   // distributed under the License is distributed on an "AS IS" BASIS,
   24   // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   25   // See the License for the specific language governing permissions and
   26   // limitations under the License.
   27   
   28   package org.apache.tapestry5.corelib.components;
   29   
   30   import org.apache.tapestry5.ComponentAction;
   31   import org.apache.tapestry5.PrimaryKeyEncoder;
   32   import org.apache.tapestry5.PropertyOverrides;
   33   import org.apache.tapestry5.annotations.Environmental;
   34   import org.apache.tapestry5.annotations.Parameter;
   35   import org.apache.tapestry5.annotations.Property;
   36   import org.apache.tapestry5.beaneditor.PropertyModel;
   37   import org.apache.tapestry5.grid.GridConstants;
   38   import org.apache.tapestry5.grid.GridDataSource;
   39   import org.apache.tapestry5.grid.GridModel;
   40   import org.apache.tapestry5.internal.TapestryInternalUtils;
   41   import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
   42   import org.apache.tapestry5.services.FormSupport;
   43   
   44   import java.io.Serializable;
   45   import java.util.List;
   46   
   47   /**
   48    * Renders out a series of rows within the table.
   49    * <p/>
   50    * Inside a {@link Form}, a series of row index numbers are stored into the form ( {@linkplain FormSupport#store(Object,
   51    * ComponentAction) as ComponentActions}). This is not ideal ... in a situation where the data set can shift between the
   52    * form render and the form submission, this can cause unexpected results, including applying changes to the wrong
   53    * objects.
   54    */
   55   @SuppressWarnings({"unchecked"})
   56   public class GridRows
   57   {
   58       private int startRow;
   59   
   60       private boolean recordStateByIndex;
   61       private boolean recordStateByEncoder;
   62   
   63       /**
   64        * This action is used when a {@link org.apache.tapestry5.PrimaryKeyEncoder} is not.
   65        */
   66       static class SetupForRowByIndex implements ComponentAction<GridRows>
   67       {
   68           private static final long serialVersionUID = -3216282071752371975L;
   69   
   70           private final int rowIndex;
   71   
   72           public SetupForRowByIndex(int rowIndex)
   73           {
   74               this.rowIndex = rowIndex;
   75           }
   76   
   77           public void execute(GridRows component)
   78           {
   79               component.setupForRow(rowIndex);
   80           }
   81   
   82           @Override
   83           public String toString()
   84           {
   85               return String.format("GridRows.SetupForRowByIndex[%d]", rowIndex);
   86           }
   87       }
   88   
   89       /**
   90        * This action is used when a {@link org.apache.tapestry5.PrimaryKeyEncoder} is provided.
   91        */
   92       static class SetupForRowByKey implements ComponentAction<GridRows>
   93       {
   94           private final Serializable rowKey;
   95   
   96           SetupForRowByKey(Serializable rowKey)
   97           {
   98               this.rowKey = rowKey;
   99           }
  100   
  101           public void execute(GridRows component)
  102           {
  103               component.setupForRowByKey(rowKey);
  104           }
  105   
  106           @Override
  107           public String toString()
  108           {
  109               return String.format("GridRows.SetupForRowByKey[%s]", rowKey);
  110           }
  111       }
  112   
  113   
  114       /**
  115        * This action is also associated with the {@link org.apache.tapestry5.PrimaryKeyEncoder}; it allows the PKE to be
  116        * informed of the series of keys to expect with the form submission.
  117        */
  118       static class PrepareForKeys implements ComponentAction<GridRows>
  119       {
  120           private List<Serializable> storedKeys;
  121   
  122           public PrepareForKeys(List<Serializable> storedKeys)
  123           {
  124               this.storedKeys = storedKeys;
  125           }
  126   
  127           public void execute(GridRows component)
  128           {
  129               component.prepareForKeys(storedKeys);
  130           }
  131   
  132           @Override
  133           public String toString()
  134           {
  135               return "GridRows.PrepareForKeys" + storedKeys.toString();
  136           }
  137       }
  138   
  139       /**
  140        * Parameter used to set the CSS class for each row (each &lt;tr&gt; element) within the &lt;tbody&gt;). This is not
  141        * cached, so it will be recomputed for each row.
  142        */
  143       @Parameter(cache = false)
  144       private String rowClass;
  145   
  146       /**
  147        * Object that provides access to the bean and data models used to render the Grid.
  148        */
  149       @Parameter(value = "componentResources.container")
  150       private GridModel gridModel;
  151   
  152       /**
  153        * Where to search for property override blocks.
  154        */
  155       @Parameter(required = true, allowNull = false)
  156       @Property
  157       private PropertyOverrides overrides;
  158   
  159       /**
  160        * Number of rows displayed on each page. Long result sets are split across multiple pages.
  161        */
  162       @Parameter(required = true)
  163       private int rowsPerPage;
  164   
  165       /**
  166        * The current page number within the available pages (indexed from 1).
  167        */
  168       @Parameter(required = true)
  169       private int currentPage;
  170   
  171       /**
  172        * The current row being rendered, this is primarily an output parameter used to allow the Grid, and the Grid's
  173        * container, to know what object is being rendered.
  174        */
  175       @Parameter(required = true)
  176       @Property(write = false)
  177       private Object row;
  178   
  179       /**
  180        * If true, then the CSS class on each &lt;TD&gt; cell will be omitted, which can reduce the amount of output from
  181        * the component overall by a considerable amount. Leave this as false, the default, when you are leveraging the CSS
  182        * to customize the look and feel of particular columns.
  183        */
  184       @Parameter
  185       private boolean lean;
  186   
  187       /**
  188        * If true and the Loop is enclosed by a Form, then the normal state saving logic is turned off. Defaults to false,
  189        * enabling state saving logic within Forms.
  190        */
  191       @Parameter(name = "volatile")
  192       private boolean volatileState;
  193   
  194       /**
  195        * Changes how state is recorded into the form to store the {@linkplain org.apache.tapestry5.PrimaryKeyEncoder#toKey(Object)
  196        * primary key} for each row (rather than the index), and restore the {@linkplain
  197        * org.apache.tapestry5.PrimaryKeyEncoder#toValue(java.io.Serializable) row values} from the primary keys.
  198        */
  199       @Parameter
  200       private PrimaryKeyEncoder encoder;
  201   
  202       /**
  203        * Optional output parameter (only set during rendering) that identifies the current row index. This is the index on
  204        * the page (i.e., always numbered from zero) as opposed to the row index inside the {@link
  205        * org.apache.tapestry5.grid.GridDataSource}.
  206        */
  207       @Parameter
  208       private int rowIndex;
  209   
  210       /**
  211        * Optional output parameter that stores the current column index.
  212        */
  213       @Parameter
  214       @Property
  215       private int columnIndex;
  216   
  217       @Environmental(false)
  218       private FormSupport formSupport;
  219   
  220   
  221       private int endRow;
  222   
  223       /**
  224        * Index into the {@link org.apache.tapestry5.grid.GridDataSource}.
  225        */
  226       private int dataRowIndex;
  227   
  228       private String propertyName;
  229   
  230       @Property(write = false)
  231       private PropertyModel columnModel;
  232   
  233       private List<Serializable> encodedPrimaryKeys;
  234   
  235       public String getRowClass()
  236       {
  237           List<String> classes = CollectionFactory.newList();
  238   
  239           // Not a cached parameter, so careful to only access it once.
  240   
  241           String rc = rowClass;
  242   
  243           if (rc != null) classes.add(rc);
  244   
  245           if (dataRowIndex == startRow) classes.add(GridConstants.FIRST_CLASS);
  246   
  247           if (dataRowIndex == endRow) classes.add(GridConstants.LAST_CLASS);
  248   
  249           return TapestryInternalUtils.toClassAttributeValue(classes);
  250       }
  251   
  252       public String getCellClass()
  253       {
  254           List<String> classes = CollectionFactory.newList();
  255   
  256           String id = gridModel.getDataModel().get(propertyName).getId();
  257   
  258           if (!lean)
  259           {
  260               classes.add(id);
  261   
  262               switch (gridModel.getSortModel().getColumnSort(id))
  263               {
  264                   case ASCENDING:
  265                       classes.add(GridConstants.SORT_ASCENDING_CLASS);
  266                       break;
  267   
  268                   case DESCENDING:
  269                       classes.add(GridConstants.SORT_DESCENDING_CLASS);
  270                       break;
  271   
  272                   default:
  273               }
  274           }
  275   
  276   
  277           return TapestryInternalUtils.toClassAttributeValue(classes);
  278       }
  279   
  280       void setupRender()
  281       {
  282           GridDataSource dataSource = gridModel.getDataSource();
  283   
  284           int availableRows = dataSource.getAvailableRows();
  285   
  286           int maxPages = ((availableRows - 1) / rowsPerPage) + 1;
  287   
  288           // This can sometimes happen when the number of items shifts between requests.
  289   
  290           if (currentPage > maxPages) currentPage = maxPages;
  291   
  292           startRow = (currentPage - 1) * rowsPerPage;
  293           endRow = Math.min(availableRows - 1, startRow + rowsPerPage - 1);
  294   
  295           dataRowIndex = startRow;
  296   
  297           boolean recordingStateInsideForm = !volatileState && formSupport != null;
  298   
  299           recordStateByIndex = recordingStateInsideForm && (encoder == null);
  300           recordStateByEncoder = recordingStateInsideForm && (encoder != null);
  301   
  302           if (recordStateByEncoder)
  303           {
  304               encodedPrimaryKeys = CollectionFactory.newList();
  305   
  306               // As we render, we'll fill in encodedPrimaryKeys.  That's ok, because nothing is serialized
  307               // until later.  When the form is submitted, this will give us a chance to inform
  308               // the PKE about the keys to expect.
  309               formSupport.store(this, new PrepareForKeys(encodedPrimaryKeys));
  310           }
  311       }
  312   
  313       /**
  314        * Callback method, used when recording state to a form, or called directly when not recording state.
  315        */
  316       void setupForRow(int rowIndex)
  317       {
  318           row = gridModel.getDataSource().getRowValue(rowIndex);
  319       }
  320   
  321       /**
  322        * Callback method that bypasses the data source and converts a primary key back into a row value (via {@link
  323        * org.apache.tapestry5.PrimaryKeyEncoder#toValue(java.io.Serializable)}).
  324        */
  325       void setupForRowByKey(Serializable rowKey)
  326       {
  327           row = encoder.toValue(rowKey);
  328   
  329           if (row == null)
  330               throw new IllegalArgumentException(
  331                       String.format("%s returned null for key %s.", encoder, rowKey));
  332       }
  333   
  334       /**
  335        * Callback method that allows the primary key encoder to prepare for the keys that will be resolved to row values
  336        * in this request.
  337        */
  338       private void prepareForKeys(List<Serializable> storedKeys)
  339       {
  340           encoder.prepareForKeys(storedKeys);
  341       }
  342   
  343   
  344       boolean beginRender()
  345       {
  346           // Setup for this row.
  347   
  348           setupForRow(dataRowIndex);
  349   
  350           // Update the index parameter (which starts from zero).
  351           rowIndex = dataRowIndex - startRow;
  352   
  353   
  354           if (row != null)
  355           {
  356               // When needed, store a callback used when the form is submitted.
  357   
  358               if (recordStateByIndex)
  359                   formSupport.store(this, new SetupForRowByIndex(dataRowIndex));
  360   
  361               if (recordStateByEncoder)
  362               {
  363                   Serializable key = encoder.toKey(row);
  364                   encodedPrimaryKeys.add(key);
  365   
  366                   formSupport.store(this, new SetupForRowByKey(key));
  367               }
  368           }
  369   
  370           // If the row is null, it's because the rowIndex is too large (see the notes
  371           // on GridDataSource).  When row is null, return false to not render anything for this iteration
  372           // of the loop.
  373   
  374           return row != null;
  375       }
  376   
  377       boolean afterRender()
  378       {
  379           dataRowIndex++;
  380   
  381           // Abort the loop when we hit a null row, or when we've exhausted the range we need to
  382           // display.
  383   
  384           return row == null || dataRowIndex > endRow;
  385       }
  386   
  387       public List<String> getPropertyNames()
  388       {
  389           return gridModel.getDataModel().getPropertyNames();
  390       }
  391   
  392       public String getPropertyName()
  393       {
  394           return propertyName;
  395       }
  396   
  397       public void setPropertyName(String propertyName)
  398       {
  399           this.propertyName = propertyName;
  400   
  401           columnModel = gridModel.getDataModel().get(propertyName);
  402       }
  403   }

Home » tapestry-src-5.0.19 » org.apache.tapestry5.corelib.components » [javadoc | source]