• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package autotest.common.table;
2 
3 
4 import autotest.common.Utils;
5 import autotest.common.ui.RightClickTable;
6 
7 import com.google.gwt.event.dom.client.ClickEvent;
8 import com.google.gwt.event.dom.client.ClickHandler;
9 import com.google.gwt.event.dom.client.ContextMenuEvent;
10 import com.google.gwt.event.dom.client.ContextMenuHandler;
11 import com.google.gwt.event.dom.client.DomEvent;
12 import com.google.gwt.json.client.JSONObject;
13 import com.google.gwt.json.client.JSONValue;
14 import com.google.gwt.user.client.ui.Composite;
15 import com.google.gwt.user.client.ui.HTMLTable;
16 import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
17 import com.google.gwt.user.client.ui.Widget;
18 
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.List;
22 
23 /**
24  * A table to display data from JSONObjects.  Each row displays data from one
25  * JSONObject.  A header row with column titles is automatically generated, and
26  * support is included for adding other arbitrary header rows.
27  * <br><br>
28  * Styles:
29  * <ul>
30  * <li>.data-table - the entire table
31  * <li>.data-row-header - the column title row
32  * <li>.data-row-one/.data-row-two - data row styles.  These two are alternated.
33  * </ul>
34  */
35 public class DataTable extends Composite implements ClickHandler, ContextMenuHandler {
36     public static final String HEADER_STYLE = "data-row-header";
37     public static final String CLICKABLE_STYLE = "data-row-clickable";
38     public static final String HIGHLIGHTED_STYLE = "data-row-highlighted";
39     public static final String WIDGET_COLUMN = "_WIDGET_COLUMN_";
40     // use CLICKABLE_WIDGET_COLUMN for widget that expect to receive clicks.  The table will ignore
41     // click events coming from these columns.
42     public static final String CLICKABLE_WIDGET_COLUMN = "_CLICKABLE_WIDGET_COLUMN_";
43     // for indexing into column subarrays (i.e. columns[1][COL_NAME])
44     public static final int COL_NAME = 0, COL_TITLE = 1;
45 
46     public static interface DataTableListener {
onRowClicked(int rowIndex, JSONObject row, boolean isRightClick)47         public void onRowClicked(int rowIndex, JSONObject row, boolean isRightClick);
48     }
49 
50     protected RightClickTable table;
51 
52     protected String[][] columns;
53     protected int headerRow = 0;
54     protected boolean clickable = false;
55 
56     protected TableWidgetFactory widgetFactory = null;
57     private List<DataTableListener> listeners = new ArrayList<DataTableListener>();
58 
59     // keep a list of JSONObjects corresponding to rows in the table
60     protected List<JSONObject> jsonObjects = new ArrayList<JSONObject>();
61 
62 
63     public static interface TableWidgetFactory {
createWidget(int row, int cell, JSONObject rowObject)64         public Widget createWidget(int row, int cell, JSONObject rowObject);
65     }
66 
67     /**
68      * @param columns An array specifying the name of each column and the field
69      * to which it corresponds.  The array should have the form
70      * {{'field_name1', 'Column Title 1'},
71      *  {'field_name2', 'Column Title 2'}, ...}.
72      */
DataTable(String[][] columns)73     public DataTable(String[][] columns) {
74         int rows = columns.length;
75         this.columns = new String[rows][2];
76         for (int i = 0; i < rows; i++) {
77             System.arraycopy(columns[i], 0, this.columns[i], 0, 2);
78         }
79 
80         table = new RightClickTable();
81         initWidget(table);
82 
83         table.setCellSpacing(0);
84         table.setCellPadding(0);
85         table.setStylePrimaryName("data-table");
86         table.addStyleDependentName("outlined");
87 
88         for (int i = 0; i < columns.length; i++) {
89             table.setText(0, i, columns[i][1]);
90         }
91 
92         table.getRowFormatter().setStylePrimaryName(0, HEADER_STYLE);
93         table.addClickHandler(this);
94     }
95 
96     /**
97      * Causes the last column of the data table to fill the remainder of the width left in the
98      * parent widget.
99      */
fillParent()100     public void fillParent() {
101         table.getColumnFormatter().setWidth(table.getCellCount(0) - 1, "100%");
102     }
103 
setWidgetFactory(TableWidgetFactory widgetFactory)104     public void setWidgetFactory(TableWidgetFactory widgetFactory) {
105         this.widgetFactory = widgetFactory;
106     }
107 
setRowStyle(int row)108     protected void setRowStyle(int row) {
109         table.getRowFormatter().setStyleName(row, "data-row");
110         if ((row & 1) == 0) {
111             table.getRowFormatter().addStyleName(row, "data-row-alternate");
112         }
113         if (clickable) {
114             table.getRowFormatter().addStyleName(row, CLICKABLE_STYLE);
115         }
116     }
117 
setClickable(boolean clickable)118     public void setClickable(boolean clickable) {
119         this.clickable = clickable;
120         for(int i = headerRow + 1; i < table.getRowCount(); i++)
121             setRowStyle(i);
122     }
123 
124     /**
125      * Clear all data rows from the table.  Leaves the header rows intact.
126      */
clear()127     public void clear() {
128         while (table.getRowCount() > 1) {
129             table.removeRow(1);
130         }
131         jsonObjects.clear();
132     }
133 
134     /**
135      * This gets called for every JSONObject that gets added to the table using
136      * addRow().  This allows subclasses to customize objects before they are
137      * added to the table, for example to reformat fields or generate new
138      * fields from the existing data.
139      * @param row The row object about to be added to the table.
140      */
preprocessRow(JSONObject row)141     protected void preprocessRow(JSONObject row) {}
142 
getRowText(JSONObject row)143     protected String[] getRowText(JSONObject row) {
144         String[] rowText = new String[columns.length];
145         for (int i = 0; i < columns.length; i++) {
146             if (isWidgetColumn(i))
147                 continue;
148 
149             String columnKey = columns[i][0];
150             JSONValue columnValue = row.get(columnKey);
151             if (columnValue == null || columnValue.isNull() != null) {
152                 rowText[i] = "";
153             } else {
154                 rowText[i] = Utils.jsonToString(columnValue);
155             }
156         }
157         return rowText;
158     }
159 
160     /**
161      * Add a row from an array of Strings, one String for each column.
162      * @param rowData Data for each column, in left-to-right column order.
163      */
addRowFromData(String[] rowData)164     protected void addRowFromData(String[] rowData) {
165         int row = table.getRowCount();
166         for(int i = 0; i < columns.length; i++) {
167             if(isWidgetColumn(i)) {
168                 table.setWidget(row, i, getWidgetForCell(row, i));
169             } else {
170                 table.setText(row, i, rowData[i]);
171             }
172         }
173         setRowStyle(row);
174     }
175 
isWidgetColumn(int column)176     protected boolean isWidgetColumn(int column) {
177         return columns[column][COL_NAME].equals(WIDGET_COLUMN) || isClickableWidgetColumn(column);
178     }
179 
isClickableWidgetColumn(int column)180     protected boolean isClickableWidgetColumn(int column) {
181         return columns[column][COL_NAME].equals(CLICKABLE_WIDGET_COLUMN);
182     }
183 
184     /**
185      * Add a row from a JSONObject.  Columns will be populated by pulling fields
186      * from the objects, as dictated by the columns information passed into the
187      * DataTable constructor.
188      */
addRow(JSONObject row)189     public void addRow(JSONObject row) {
190         preprocessRow(row);
191         jsonObjects.add(row);
192         addRowFromData(getRowText(row));
193     }
194 
195     /**
196      * Add all objects in a JSONArray.
197      * @param rows An array of JSONObjects
198      * @throws IllegalArgumentException if any other type of JSONValue is in the
199      * array.
200      */
addRows(List<JSONObject> rows)201     public void addRows(List<JSONObject> rows) {
202         for (JSONObject row : rows) {
203             addRow(row);
204         }
205     }
206 
207     /**
208      * Remove a data row from the table.
209      * @param rowIndex The index of the row, where the first data row is indexed 0.
210      * Header rows are ignored.
211      */
removeRow(int rowIndex)212     public void removeRow(int rowIndex) {
213         jsonObjects.remove(rowIndex);
214         int realRow = rowIndex + 1; // header row
215         table.removeRow(realRow);
216         for(int i = realRow; i < table.getRowCount(); i++)
217             setRowStyle(i);
218     }
219 
220     /**
221      * Returns the number of data rows in the table.  The actual number of
222      * visible table rows is more than this, due to the header row.
223      */
getRowCount()224     public int getRowCount() {
225         return table.getRowCount() - 1;
226     }
227 
228     /**
229      * Get the JSONObject corresponding to the indexed row.
230      */
getRow(int rowIndex)231     public JSONObject getRow(int rowIndex) {
232         return jsonObjects.get(rowIndex);
233     }
234 
getAllRows()235     public List<JSONObject> getAllRows() {
236         return Collections.unmodifiableList(jsonObjects);
237     }
238 
highlightRow(int row)239     public void highlightRow(int row) {
240         row++; // account for header row
241         table.getRowFormatter().addStyleName(row, HIGHLIGHTED_STYLE);
242     }
243 
unhighlightRow(int row)244     public void unhighlightRow(int row) {
245         row++; // account for header row
246         table.getRowFormatter().removeStyleName(row, HIGHLIGHTED_STYLE);
247     }
248 
sinkRightClickEvents()249     public void sinkRightClickEvents() {
250         table.addContextMenuHandler(this);
251     }
252 
253     @Override
onClick(ClickEvent event)254     public void onClick(ClickEvent event) {
255         onCellClicked(event, false);
256     }
257 
258     @Override
onContextMenu(ContextMenuEvent event)259     public void onContextMenu(ContextMenuEvent event) {
260         onCellClicked(event, true);
261     }
262 
onCellClicked(DomEvent<?> event, boolean isRightClick)263     private void onCellClicked(DomEvent<?> event, boolean isRightClick) {
264         HTMLTable.Cell tableCell = table.getCellForDomEvent(event);
265         if (tableCell == null) {
266             return;
267         }
268 
269         int row = tableCell.getRowIndex();
270         int cell = tableCell.getCellIndex();
271 
272         if (isClickableWidgetColumn(cell) && table.getWidget(row, cell) != null) {
273             return;
274         }
275 
276         onCellClicked(row, cell, isRightClick);
277     }
278 
onCellClicked(int row, int cell, boolean isRightClick)279     protected void onCellClicked(int row, int cell, boolean isRightClick) {
280         if (row != headerRow) {
281             notifyListenersClicked(row - headerRow - 1, isRightClick);
282         }
283     }
284 
addListener(DataTableListener listener)285     public void addListener(DataTableListener listener) {
286         listeners.add(listener);
287     }
288 
removeListener(DataTableListener listener)289     public void removeListener(DataTableListener listener) {
290         listeners.remove(listener);
291     }
292 
notifyListenersClicked(int rowIndex, boolean isRightClick)293     protected void notifyListenersClicked(int rowIndex, boolean isRightClick) {
294         JSONObject row = getRow(rowIndex);
295         for (DataTableListener listener : listeners) {
296             listener.onRowClicked(rowIndex, row, isRightClick);
297         }
298     }
299 
refreshWidgets()300     public void refreshWidgets() {
301         for (int row = 1; row < table.getRowCount(); row++) {
302             for (int column = 0; column < columns.length; column++) {
303                 if (!isWidgetColumn(column)) {
304                     continue;
305                 }
306                 table.clearCell(row, column);
307                 table.setWidget(row, column, getWidgetForCell(row, column));
308             }
309         }
310     }
311 
getWidgetForCell(int row, int column)312     private Widget getWidgetForCell(int row, int column) {
313         return widgetFactory.createWidget(row - 1, column, jsonObjects.get(row - 1));
314     }
315 
316     /**
317      * Add a style name to a specific column by column name.
318      */
addStyleNameByColumnName(String columnName, String styleName)319     public void addStyleNameByColumnName(String columnName, String styleName) {
320         CellFormatter cellFormatter = table.getCellFormatter();
321         for (int column = 0; column < columns.length; column++) {
322             if (columns[column][1].equals(columnName)) {
323                 for (int row = 1; row < table.getRowCount(); row++) {
324                     cellFormatter.addStyleName(row, column, styleName);
325                 }
326             }
327         }
328     }
329 }
330