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