001: // Copyright 2007 The Apache Software Foundation
002: //
003: // Licensed under the Apache License, Version 2.0 (the "License");
004: // you may not use this file except in compliance with the License.
005: // You may obtain a copy of the License at
006: //
007: // http://www.apache.org/licenses/LICENSE-2.0
008: //
009: // Unless required by applicable law or agreed to in writing, software
010: // distributed under the License is distributed on an "AS IS" BASIS,
011: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: // See the License for the specific language governing permissions and
013: // limitations under the License.
014:
015: package org.apache.tapestry.corelib.components;
016:
017: import org.apache.tapestry.Binding;
018: import org.apache.tapestry.Block;
019: import org.apache.tapestry.ComponentResources;
020: import org.apache.tapestry.annotations.Component;
021: import org.apache.tapestry.annotations.Inject;
022: import org.apache.tapestry.annotations.Parameter;
023: import org.apache.tapestry.annotations.Persist;
024: import org.apache.tapestry.annotations.SupportsInformalParameters;
025: import org.apache.tapestry.beaneditor.BeanModel;
026: import org.apache.tapestry.beaneditor.PropertyModel;
027: import org.apache.tapestry.corelib.data.GridPagerPosition;
028: import org.apache.tapestry.grid.GridDataSource;
029: import org.apache.tapestry.grid.GridModelProvider;
030: import org.apache.tapestry.internal.bindings.AbstractBinding;
031: import org.apache.tapestry.ioc.services.TypeCoercer;
032: import org.apache.tapestry.services.BeanModelSource;
033:
034: /**
035: * A grid presents tabular data. It is a composite component, created in terms of several
036: * sub-components. The sub-components are statically wired to the Grid, as it provides access to the
037: * data and other models that they need.
038: *
039: * @see BeanModel
040: * @see BeanModelSource
041: */
042: @SupportsInformalParameters
043: public class Grid implements GridModelProvider {
044: /**
045: * The source of data for the Grid to display. This will usually be a List or array but can also
046: * be an explicit {@link GridDataSource}. For Lists and Arrays, a GridDataSource is created
047: * automatically as a wrapper around the underlying List.
048: */
049: @Parameter(required=true)
050: private Object _source;
051:
052: /**
053: * The number of rows of data displayed on each page. If there are more rows than will fit, the
054: * Grid will divide up the rows into "pages" and (normally) provide a pager to allow the user to
055: * navigate within the overall result set.
056: */
057: @Parameter("25")
058: private int _rowsPerPage;
059:
060: /**
061: * Defines where the pager (used to navigate within the "pages" of results) should be displayed:
062: * "top", "bottom", "both" or "none".
063: */
064: @Parameter(value="bottom",defaultPrefix="literal")
065: private GridPagerPosition _pagerPosition;
066:
067: @Persist
068: private int _currentPage = 1;
069:
070: @Persist
071: private String _sortColumnId;
072:
073: @Persist
074: private boolean _sortAscending = true;
075:
076: /**
077: * Used to store the current object being rendered (for the current row). This is used when
078: * parameter blocks are provided to override the default cell renderer for a particular column
079: * ... the components within the block can use the property bound to the row parameter to know
080: * what they should render.
081: */
082: @Parameter
083: private Object _row;
084:
085: /**
086: * The model used to identify the properties to be presented and the order of presentation. The
087: * model may be omitted, in which case a default model is generated from the first object in the
088: * data source (this implies that the objects provided by the source are uniform). The model may
089: * be explicitly specified to override the default behavior, say to reorder or rename columns or
090: * add additional columns.
091: */
092: @Parameter
093: private BeanModel _model;
094:
095: /**
096: * A Block to render instead of the table (and pager, etc.) when the source is empty. The
097: * default is simply the text "There is no data to display". This parameter is used to customize
098: * that message, possibly including components to allow the user to create new objects.
099: */
100: @Parameter(value="block:empty")
101: private Block _empty;
102:
103: @Inject
104: private ComponentResources _resources;
105:
106: @Inject
107: private BeanModelSource _modelSource;
108:
109: @Inject
110: private TypeCoercer _typeCoercer;
111:
112: // Transformed version of the source parameter.
113:
114: private GridDataSource _dataSource;
115:
116: /**
117: * The CSS class for the tr element for each data row. This can be used to highlight particular
118: * rows, or cycle between CSS values (for the "zebra effect"). If null or not bound, then no
119: * particular CSS class value is used.
120: */
121: @Parameter(cache=false)
122: private String _rowClass;
123:
124: @SuppressWarnings("unused")
125: @Component(parameters={"sortColumnId=sortColumnId","sortAscending=sortAscending"})
126: private GridColumns _columns;
127:
128: @SuppressWarnings("unused")
129: @Component(parameters={"rowClass=rowClass","rowsPerPage=rowsPerPage","currentPage=currentPage","row=row"})
130: private GridRows _rows;
131:
132: @Component(parameters={"source=dataSource","rowsPerPage=rowsPerPage","currentPage=currentPage"})
133: private GridPager _pager;
134:
135: @SuppressWarnings("unused")
136: @Component(parameters="to=pagerTop")
137: private Delegate _pagerTop;
138:
139: @SuppressWarnings("unused")
140: @Component(parameters="to=pagerBottom")
141: private Delegate _pagerBottom;
142:
143: Binding defaultModel() {
144: final ComponentResources containerResources = _resources
145: .getContainerResources();
146:
147: return new AbstractBinding() {
148:
149: public Object get() {
150: // Get the default row type from the data source
151:
152: Class rowType = _dataSource.getRowType();
153:
154: if (rowType == null)
155: throw new RuntimeException(
156: "xxx -- no source to determine list type from");
157:
158: // Properties do not have to be read/write
159:
160: return _modelSource.create(rowType, false,
161: containerResources);
162: }
163:
164: /**
165: * Returns false. This may be overkill, but it basically exists because the model is
166: * inherently mutable and therefore may contain client-specific state and needs to be
167: * discarded at the end of the request. If the model were immutable, then we could leave
168: * invariant as true.
169: */
170: @Override
171: public boolean isInvariant() {
172: return false;
173: }
174: };
175: }
176:
177: Object setupRender() {
178: _dataSource = _typeCoercer
179: .coerce(_source, GridDataSource.class);
180:
181: // If there's no rows, display the empty block placeholder.
182:
183: int availableRows = _dataSource.getAvailableRows();
184:
185: if (availableRows == 0)
186: return _empty;
187:
188: PropertyModel sortModel = null;
189:
190: if (_sortColumnId != null) {
191: for (String name : _model.getPropertyNames()) {
192: PropertyModel propertyModel = _model.get(name);
193:
194: if (propertyModel.getId().equals(_sortColumnId)) {
195: sortModel = propertyModel;
196: break;
197: }
198: }
199: }
200:
201: int startIndex = (_currentPage - 1) * _rowsPerPage;
202: int endIndex = Math.min(startIndex + _rowsPerPage - 1,
203: availableRows - 1);
204:
205: _dataSource.prepare(startIndex, endIndex, sortModel,
206: _sortAscending);
207:
208: return null;
209: }
210:
211: Object beginRender() {
212: // Skip rendering of component (template, body, etc.) when there's nothing to display.
213: // The empty placeholder will already have rendered.
214:
215: if (_dataSource.getAvailableRows() == 0)
216: return false;
217:
218: return null;
219: }
220:
221: public BeanModel getDataModel() {
222: return _model;
223: }
224:
225: public GridDataSource getDataSource() {
226: return _dataSource;
227: }
228:
229: public String getRowClass() {
230: return _rowClass;
231: }
232:
233: public int getCurrentPage() {
234: return _currentPage;
235: }
236:
237: public void setCurrentPage(int currentPage) {
238: _currentPage = currentPage;
239: }
240:
241: public Object getRow() {
242: return _row;
243: }
244:
245: public void setRow(Object row) {
246: _row = row;
247: }
248:
249: public int getRowsPerPage() {
250: return _rowsPerPage;
251: }
252:
253: public void setRowsPerPage(int rowsPerPage) {
254: _rowsPerPage = rowsPerPage;
255: }
256:
257: public boolean isSortAscending() {
258: return _sortAscending;
259: }
260:
261: public String getSortColumnId() {
262: return _sortColumnId;
263: }
264:
265: public void setSortAscending(boolean sortAscending) {
266: _sortAscending = sortAscending;
267: }
268:
269: public void setSortColumnId(String sortColumnId) {
270: _sortColumnId = sortColumnId;
271: }
272:
273: public Object getPagerTop() {
274: return _pagerPosition.isMatchTop() ? _pager : null;
275: }
276:
277: public Object getPagerBottom() {
278: return _pagerPosition.isMatchBottom() ? _pager : null;
279: }
280: }
|