Don't use Strings to hold primary key values.
[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.event.shared.EventBus;
38 import com.google.gwt.i18n.client.NumberFormat;
39 import com.google.gwt.safehtml.shared.SafeHtmlUtils;
40 import com.google.gwt.user.cellview.client.CellTable;
41 import com.google.gwt.user.cellview.client.Column;
42 import com.google.gwt.user.cellview.client.ColumnSortEvent.AsyncHandler;
43 import com.google.gwt.user.cellview.client.SafeHtmlHeader;
44 import com.google.gwt.user.cellview.client.SimplePager;
45 import com.google.gwt.user.client.ui.Composite;
46 import com.google.gwt.user.client.ui.FlowPanel;
47 import com.google.gwt.user.client.ui.HasHorizontalAlignment;
48 import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant;
49 import com.google.gwt.view.client.AbstractDataProvider;
50 import com.google.gwt.view.client.ProvidesKey;
51
52 /**
53  * @author Ben Konrath <ben@bagu.org>
54  * 
55  */
56 public abstract class ListTable extends Composite {
57
58         private class ListTablePager extends SimplePager {
59                 public ListTablePager() {
60                         super(SimplePager.TextLocation.CENTER);
61                 }
62
63                 /*
64                  * A custom version of createText to display the correct number of data row when empty rows have been added to
65                  * the CellTable. This method is needed because the row count change event handler
66                  * (AbstractPager.handleRowCountChange()) doesn't use the row count that is sent along with the
67                  * RowCountChangeEvent.
68                  * 
69                  * @see com.google.gwt.user.cellview.client.AbstractPager#handleRowCountChange(int, boolean)
70                  * 
71                  * @see com.google.gwt.user.cellview.client.SimplePager#createText()
72                  */
73                 @Override
74                 protected String createText() {
75                         int numNonEmptyRows = getNumNonEmptyRows();
76                         if (numNonEmptyRows < getMinNumVisibleRows()) {
77                                 NumberFormat formatter = NumberFormat.getFormat("#,###");
78                                 return formatter.format(1) + "-" + formatter.format(numNonEmptyRows) + " of "
79                                                 + formatter.format(numNonEmptyRows);
80                         } else {
81                                 return super.createText();
82                         }
83                 }
84         }
85
86         final private FlowPanel mainPanel = new FlowPanel();
87         final private ListTablePager pager = new ListTablePager();
88         protected String documentID;
89         protected String tableName;
90         protected CellTable<DataItem[]> cellTable;
91         protected EventBus eventBus;
92
93         abstract protected AbstractDataProvider<DataItem[]> getDataProvider();
94
95         @SuppressWarnings("unused")
96         private ListTable() {
97                 // disable default constructor
98         }
99
100         public ListTable(String documentID) {
101                 this.documentID = documentID;
102         }
103
104         public void createCellTable(LayoutGroup layoutGroup, int numVisibleRows) {
105
106                 tableName = layoutGroup.getTableName();
107                 ArrayList<LayoutItem> layoutItems = layoutGroup.getItems();
108
109                 final int primaryKeyIndex = layoutGroup.getPrimaryKeyIndex();
110                 LayoutItemField primaryKeyLayoutItem = (LayoutItemField) layoutItems.get(primaryKeyIndex);
111                 final GlomFieldType primaryKeyFieldType = primaryKeyLayoutItem.getType();
112                 ProvidesKey<DataItem[]> keyProvider = new ProvidesKey<DataItem[]>() {
113                         @Override
114                         public Object getKey(DataItem[] row) {
115                                 if (row.length == 1 && row[0] == null)
116                                         // an empty row
117                                         return null;
118                                 return Utils.getPrimaryKeyItem(primaryKeyFieldType, row[primaryKeyIndex]);
119                         }
120                 };
121
122                 // create the CellTable with the requested number of rows and the key provider
123                 cellTable = new CellTable<DataItem[]>(numVisibleRows, keyProvider);
124
125                 // set some style
126                 cellTable.setStyleName("data-list");
127                 cellTable.getElement().getStyle().setProperty("whiteSpace", "nowrap"); // this prevents the header and row text
128                                                                                                                                                                 // from wrapping
129
130                 // add columns to the CellTable and deal with the case of the hidden primary key
131                 int numItems = layoutGroup.hasHiddenPrimaryKey() ? layoutItems.size() - 1 : layoutItems.size();
132                 for (int i = 0; i < numItems; i++) {
133                         LayoutItem layoutItem = layoutItems.get(i);
134
135                         // only add columns for LayoutItemField types
136                         if (layoutItem instanceof LayoutItemField) {
137                                 addColumn((LayoutItemField) layoutItem);
138                         }
139
140                 }
141
142                 // create and set the data provider
143                 AbstractDataProvider<DataItem[]> dataProvider = getDataProvider();
144                 dataProvider.addDataDisplay(cellTable);
145
146                 // add an AsyncHandler to activate sorting for the data provider
147                 cellTable.addColumnSortHandler(new AsyncHandler(cellTable));
148
149                 // pack the widgets into the container
150                 pager.setDisplay(cellTable);
151                 mainPanel.add(cellTable);
152                 mainPanel.add(pager);
153
154                 // initialize composite widget
155                 initWidget(mainPanel);
156         }
157
158         public void addColumn(final LayoutItemField layoutItemField) {
159                 // Setup the default alignment of the column.
160                 HorizontalAlignmentConstant columnAlignment;
161                 Formatting formatting = layoutItemField.getFormatting();
162                 switch (formatting.getHorizontalAlignment()) {
163                 case HORIZONTAL_ALIGNMENT_LEFT:
164                         columnAlignment = HasHorizontalAlignment.ALIGN_LEFT;
165                         break;
166                 case HORIZONTAL_ALIGNMENT_RIGHT:
167                         columnAlignment = HasHorizontalAlignment.ALIGN_RIGHT;
168                         break;
169                 case HORIZONTAL_ALIGNMENT_AUTO:
170                 default:
171                         columnAlignment = HasHorizontalAlignment.ALIGN_DEFAULT;
172                         break;
173                 }
174
175                 // create a new column
176                 Column<DataItem[], ?> column = null;
177                 final int j = cellTable.getColumnCount();
178                 switch (layoutItemField.getType()) {
179
180                 case TYPE_BOOLEAN:
181                         column = new Column<DataItem[], Boolean>(new BooleanCell()) {
182                                 @Override
183                                 public Boolean getValue(DataItem[] row) {
184                                         if (row.length == 1 && row[0] == null)
185                                                 // an empty row
186                                                 return null;
187                                         return row[j].getBoolean();
188                                 }
189                         };
190                         // override the configured horizontal alignment
191                         columnAlignment = HasHorizontalAlignment.ALIGN_CENTER;
192                         break;
193
194                 case TYPE_NUMERIC:
195                         // create a GWT NumberFormat for the column
196                         GlomNumericFormat glomNumericFormat = formatting.getGlomNumericFormat();
197                         NumberFormat gwtNumberFormat = Utils.getNumberFormat(glomNumericFormat);
198
199                         // create the actual column
200                         column = new Column<DataItem[], Double>(new NumericCell(formatting.getTextFormatColourForeground(),
201                                         formatting.getTextFormatColourBackground(), gwtNumberFormat,
202                                         glomNumericFormat.getUseAltForegroundColourForNegatives(), glomNumericFormat.getCurrencyCode())) {
203                                 @Override
204                                 public Double getValue(DataItem[] row) {
205                                         if (row.length == 1 && row[0] == null)
206                                                 // an empty row
207                                                 return null;
208                                         return row[j].getNumber();
209                                 }
210                         };
211                         break;
212
213                 default:
214                         // use a text rendering cell for types we don't know about but log an error
215                         // TODO log error here
216                 case TYPE_DATE:
217                 case TYPE_IMAGE:
218                 case TYPE_INVALID:
219                 case TYPE_TIME:
220                 case TYPE_TEXT:
221                         column = new Column<DataItem[], String>(new TextCell(formatting.getTextFormatColourForeground(),
222                                         formatting.getTextFormatColourBackground())) {
223                                 @Override
224                                 public String getValue(DataItem[] row) {
225                                         if (row.length == 1 && row[0] == null)
226                                                 // an empty row
227                                                 return null;
228                                         return row[j].getText();
229                                 }
230                         };
231                         break;
232                 }
233
234                 // set column properties and add to cell cellTable
235                 column.setHorizontalAlignment(columnAlignment);
236                 column.setSortable(true);
237                 cellTable.addColumn(column, new SafeHtmlHeader(SafeHtmlUtils.fromString(layoutItemField.getTitle())));
238         }
239
240         public void addOpenButtonColumn(final String openButtonLabel, OpenButtonCell openButtonCell) {
241
242                 Column<DataItem[], String> openButtonColumn = new Column<DataItem[], String>(openButtonCell) {
243                         @Override
244                         public String getValue(DataItem[] row) {
245                                 if (row.length == 1 && row[0] == null)
246                                         // an empty row
247                                         return null;
248                                 return openButtonLabel;
249                         }
250                 };
251
252                 // the style name for the details column is set on the col element
253                 cellTable.addColumnStyleName(cellTable.getColumnCount() - 1, "details");
254
255                 // Firefox, Chrome, and Safari only support the span and width attributes of the col element so we need to set
256                 // the alignment with code
257                 openButtonColumn.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
258
259                 cellTable.addColumn(openButtonColumn, "");
260
261         }
262
263         /**
264          * Sets the row count for the pager.
265          */
266         public void setRowCount(int rowCount) {
267                 cellTable.setRowCount(rowCount);
268         }
269
270         /**
271          * Gets the minimum number of rows the should be displayed. Empty rows will be added when the query returns fewer
272          * rows than this minimum.
273          * 
274          * @return The minimum number of rows that should be displayed.
275          */
276         public abstract int getMinNumVisibleRows();
277
278         public abstract int getNumNonEmptyRows();
279
280 }