Make ListViewTable and RelatedListTable a consistent height.
[online-glom:gwt-glom.git] / src / main / java / org / glom / web / client / ui / list / ListTable.java
1 /*
2  * Copyright (C) 2011 Openismus GmbH
3  *
4  * This file is part of GWT-Glom.
5  *
6  * GWT-Glom is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by the
8  * Free Software Foundation, either version 3 of the License, or (at your
9  * option) any later version.
10  *
11  * GWT-Glom is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
14  * for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with GWT-Glom.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package org.glom.web.client.ui.list;
21
22 import java.util.ArrayList;
23
24 import org.glom.web.client.Utils;
25 import org.glom.web.client.ui.cell.BooleanCell;
26 import org.glom.web.client.ui.cell.NumericCell;
27 import org.glom.web.client.ui.cell.OpenButtonCell;
28 import org.glom.web.client.ui.cell.TextCell;
29 import org.glom.web.shared.DataItem;
30 import org.glom.web.shared.GlomNumericFormat;
31 import org.glom.web.shared.layout.Formatting;
32 import org.glom.web.shared.layout.LayoutGroup;
33 import org.glom.web.shared.layout.LayoutItem;
34 import org.glom.web.shared.layout.LayoutItemField;
35 import org.glom.web.shared.layout.LayoutItemField.GlomFieldType;
36
37 import com.google.gwt.dom.client.Style.Unit;
38 import com.google.gwt.event.shared.EventBus;
39 import com.google.gwt.i18n.client.NumberFormat;
40 import com.google.gwt.safehtml.shared.SafeHtmlUtils;
41 import com.google.gwt.user.cellview.client.CellTable;
42 import com.google.gwt.user.cellview.client.Column;
43 import com.google.gwt.user.cellview.client.ColumnSortEvent.AsyncHandler;
44 import com.google.gwt.user.cellview.client.SafeHtmlHeader;
45 import com.google.gwt.user.cellview.client.SimplePager;
46 import com.google.gwt.user.client.ui.Composite;
47 import com.google.gwt.user.client.ui.FlowPanel;
48 import com.google.gwt.user.client.ui.HasHorizontalAlignment;
49 import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant;
50 import com.google.gwt.view.client.AbstractDataProvider;
51 import com.google.gwt.view.client.ProvidesKey;
52
53 /**
54  * @author Ben Konrath <ben@bagu.org>
55  * 
56  */
57 public abstract class ListTable extends Composite {
58
59         private class ListTablePager extends SimplePager {
60                 public ListTablePager() {
61                         super(SimplePager.TextLocation.CENTER);
62                 }
63
64                 /*
65                  * A custom version of createText to display the correct number of data row when empty rows have been added to
66                  * the CellTable. This method is needed because the row count change event handler
67                  * (AbstractPager.handleRowCountChange()) doesn't use the row count that is sent along with the
68                  * RowCountChangeEvent.
69                  * 
70                  * @see com.google.gwt.user.cellview.client.AbstractPager#handleRowCountChange(int, boolean)
71                  * 
72                  * @see com.google.gwt.user.cellview.client.SimplePager#createText()
73                  */
74                 @Override
75                 protected String createText() {
76                         int numNonEmptyRows = getNumNonEmptyRows();
77                         if (numNonEmptyRows < getMinNumVisibleRows()) {
78                                 NumberFormat formatter = NumberFormat.getFormat("#,###");
79                                 return formatter.format(1) + "-" + formatter.format(numNonEmptyRows) + " of "
80                                                 + formatter.format(numNonEmptyRows);
81                         } else {
82                                 return super.createText();
83                         }
84                 }
85         }
86
87         final private FlowPanel mainPanel = new FlowPanel();
88         final private ListTablePager pager = new ListTablePager();
89         protected String documentID;
90         protected String tableName;
91         protected CellTable<DataItem[]> cellTable;
92         protected EventBus eventBus;
93         Column<DataItem[], String> openButtonColumn;
94
95         abstract protected AbstractDataProvider<DataItem[]> getDataProvider();
96
97         @SuppressWarnings("unused")
98         private ListTable() {
99                 // disable default constructor
100         }
101
102         public ListTable(String documentID) {
103                 this.documentID = documentID;
104         }
105
106         public void createCellTable(LayoutGroup layoutGroup, int numVisibleRows, String openButtonLabel,
107                         OpenButtonCell openButtonCell) {
108                 tableName = layoutGroup.getTableName();
109                 ArrayList<LayoutItem> layoutItems = layoutGroup.getItems();
110
111                 final int primaryKeyIndex = layoutGroup.getPrimaryKeyIndex();
112                 LayoutItemField primaryKeyLayoutItem = (LayoutItemField) layoutItems.get(primaryKeyIndex);
113                 final GlomFieldType primaryKeyFieldType = primaryKeyLayoutItem.getType();
114                 ProvidesKey<DataItem[]> keyProvider = new ProvidesKey<DataItem[]>() {
115                         @Override
116                         public Object getKey(DataItem[] row) {
117                                 if (row.length == 1 && row[0] == null)
118                                         // an empty row
119                                         return null;
120                                 return Utils.getTypedDataItem(primaryKeyFieldType, row[primaryKeyIndex]);
121                         }
122                 };
123
124                 // create the CellTable with the requested number of rows and the key provider
125                 cellTable = new CellTable<DataItem[]>(numVisibleRows, keyProvider);
126
127                 // set some style
128                 cellTable.setStyleName("data-list");
129                 cellTable.getElement().getStyle().setProperty("whiteSpace", "nowrap"); // this prevents the header and row text
130                                                                                                                                                                 // from wrapping
131
132                 // add columns to the CellTable and deal with the case of the hidden primary key
133                 int numItems = layoutGroup.hasHiddenPrimaryKey() ? layoutItems.size() - 1 : layoutItems.size();
134                 for (int i = 0; i < numItems; i++) {
135                         LayoutItem layoutItem = layoutItems.get(i);
136
137                         // only add columns for LayoutItemField types
138                         if (layoutItem instanceof LayoutItemField) {
139                                 addColumn((LayoutItemField) layoutItem);
140                         }
141
142                 }
143
144                 // add the navigation buttons as the last column
145                 addOpenButtonColumn(openButtonLabel, openButtonCell);
146
147                 // create and set the data provider
148                 AbstractDataProvider<DataItem[]> dataProvider = getDataProvider();
149                 dataProvider.addDataDisplay(cellTable);
150
151                 // add an AsyncHandler to activate sorting for the data provider
152                 cellTable.addColumnSortHandler(new AsyncHandler(cellTable));
153
154                 // pack the widgets into the container
155                 pager.setDisplay(cellTable);
156                 mainPanel.add(cellTable);
157                 mainPanel.add(pager);
158
159                 // initialize composite widget
160                 initWidget(mainPanel);
161         }
162
163         private void addColumn(final LayoutItemField layoutItemField) {
164                 // Setup the default alignment of the column.
165                 HorizontalAlignmentConstant columnAlignment;
166                 Formatting formatting = layoutItemField.getFormatting();
167                 switch (formatting.getHorizontalAlignment()) {
168                 case HORIZONTAL_ALIGNMENT_LEFT:
169                         columnAlignment = HasHorizontalAlignment.ALIGN_LEFT;
170                         break;
171                 case HORIZONTAL_ALIGNMENT_RIGHT:
172                         columnAlignment = HasHorizontalAlignment.ALIGN_RIGHT;
173                         break;
174                 case HORIZONTAL_ALIGNMENT_AUTO:
175                 default:
176                         columnAlignment = HasHorizontalAlignment.ALIGN_DEFAULT;
177                         break;
178                 }
179
180                 // create a new column
181                 Column<DataItem[], ?> column = null;
182                 final int j = cellTable.getColumnCount();
183                 switch (layoutItemField.getType()) {
184
185                 case TYPE_BOOLEAN:
186                         column = new Column<DataItem[], Boolean>(new BooleanCell()) {
187                                 @Override
188                                 public Boolean getValue(DataItem[] row) {
189                                         if (row.length == 1 && row[0] == null)
190                                                 // an empty row
191                                                 return null;
192                                         return row[j].getBoolean();
193                                 }
194                         };
195                         // override the configured horizontal alignment
196                         columnAlignment = HasHorizontalAlignment.ALIGN_CENTER;
197                         break;
198
199                 case TYPE_NUMERIC:
200                         // create a GWT NumberFormat for the column
201                         GlomNumericFormat glomNumericFormat = formatting.getGlomNumericFormat();
202                         NumberFormat gwtNumberFormat = Utils.getNumberFormat(glomNumericFormat);
203
204                         // create the actual column
205                         column = new Column<DataItem[], Double>(new NumericCell(formatting.getTextFormatColourForeground(),
206                                         formatting.getTextFormatColourBackground(), gwtNumberFormat,
207                                         glomNumericFormat.getUseAltForegroundColourForNegatives(), glomNumericFormat.getCurrencyCode())) {
208                                 @Override
209                                 public Double getValue(DataItem[] row) {
210                                         if (row.length == 1 && row[0] == null)
211                                                 // an empty row
212                                                 return null;
213                                         return row[j].getNumber();
214                                 }
215                         };
216                         break;
217
218                 default:
219                         // use a text rendering cell for types we don't know about but log an error
220                         // TODO log error here
221                 case TYPE_DATE:
222                 case TYPE_IMAGE:
223                 case TYPE_INVALID:
224                 case TYPE_TIME:
225                 case TYPE_TEXT:
226                         column = new Column<DataItem[], String>(new TextCell(formatting.getTextFormatColourForeground(),
227                                         formatting.getTextFormatColourBackground())) {
228                                 @Override
229                                 public String getValue(DataItem[] row) {
230                                         if (row.length == 1 && row[0] == null)
231                                                 // an empty row
232                                                 return null;
233                                         return row[j].getText();
234                                 }
235                         };
236                         break;
237                 }
238
239                 // set column properties and add to cell cellTable
240                 column.setHorizontalAlignment(columnAlignment);
241                 column.setSortable(true);
242                 cellTable.addColumn(column, new SafeHtmlHeader(SafeHtmlUtils.fromString(layoutItemField.getTitle())));
243         }
244
245         private void addOpenButtonColumn(final String openButtonLabel, OpenButtonCell openButtonCell) {
246
247                 openButtonColumn = new Column<DataItem[], String>(openButtonCell) {
248                         @Override
249                         public String getValue(DataItem[] row) {
250                                 if (row.length == 1 && row[0] == null)
251                                         // an empty row
252                                         return null;
253                                 return openButtonLabel;
254                         }
255                 };
256
257                 // Firefox, Chrome, and Safari only support the span and width attributes of the col element so we need to set
258                 // the alignment with code
259                 openButtonColumn.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
260
261                 cellTable.addColumn(openButtonColumn, "");
262
263                 // the style name for the details column is set on the col element
264                 cellTable.addColumnStyleName(cellTable.getColumnCount() - 1, "details");
265
266         }
267
268         /**
269          * Sets the row count for the pager.
270          */
271         public void setRowCount(int rowCount) {
272                 cellTable.setRowCount(rowCount);
273         }
274
275         public void hideNavigationButtons() {
276                 if (openButtonColumn != null) {
277                         cellTable.setColumnWidth(openButtonColumn, 0, Unit.PX);
278                 }
279         }
280
281         /**
282          * Gets the minimum number of rows the should be displayed. Empty rows will be added when the query returns fewer
283          * rows than this minimum.
284          * 
285          * @return The minimum number of rows that should be displayed.
286          */
287         public abstract int getMinNumVisibleRows();
288
289         public abstract int getNumNonEmptyRows();
290
291 }