package autotest.common.table; import autotest.common.Utils; import autotest.common.ui.RightClickTable; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.ContextMenuEvent; import com.google.gwt.event.dom.client.ContextMenuHandler; import com.google.gwt.event.dom.client.DomEvent; import com.google.gwt.json.client.JSONObject; import com.google.gwt.json.client.JSONValue; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HTMLTable; import com.google.gwt.user.client.ui.HTMLTable.CellFormatter; import com.google.gwt.user.client.ui.Widget; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * A table to display data from JSONObjects. Each row displays data from one * JSONObject. A header row with column titles is automatically generated, and * support is included for adding other arbitrary header rows. *

* Styles: * */ public class DataTable extends Composite implements ClickHandler, ContextMenuHandler { public static final String HEADER_STYLE = "data-row-header"; public static final String CLICKABLE_STYLE = "data-row-clickable"; public static final String HIGHLIGHTED_STYLE = "data-row-highlighted"; public static final String WIDGET_COLUMN = "_WIDGET_COLUMN_"; // use CLICKABLE_WIDGET_COLUMN for widget that expect to receive clicks. The table will ignore // click events coming from these columns. public static final String CLICKABLE_WIDGET_COLUMN = "_CLICKABLE_WIDGET_COLUMN_"; // for indexing into column subarrays (i.e. columns[1][COL_NAME]) public static final int COL_NAME = 0, COL_TITLE = 1; public static interface DataTableListener { public void onRowClicked(int rowIndex, JSONObject row, boolean isRightClick); } protected RightClickTable table; protected String[][] columns; protected int headerRow = 0; protected boolean clickable = false; protected TableWidgetFactory widgetFactory = null; private List listeners = new ArrayList(); // keep a list of JSONObjects corresponding to rows in the table protected List jsonObjects = new ArrayList(); public static interface TableWidgetFactory { public Widget createWidget(int row, int cell, JSONObject rowObject); } /** * @param columns An array specifying the name of each column and the field * to which it corresponds. The array should have the form * {{'field_name1', 'Column Title 1'}, * {'field_name2', 'Column Title 2'}, ...}. */ public DataTable(String[][] columns) { int rows = columns.length; this.columns = new String[rows][2]; for (int i = 0; i < rows; i++) { System.arraycopy(columns[i], 0, this.columns[i], 0, 2); } table = new RightClickTable(); initWidget(table); table.setCellSpacing(0); table.setCellPadding(0); table.setStylePrimaryName("data-table"); table.addStyleDependentName("outlined"); for (int i = 0; i < columns.length; i++) { table.setText(0, i, columns[i][1]); } table.getRowFormatter().setStylePrimaryName(0, HEADER_STYLE); table.addClickHandler(this); } /** * Causes the last column of the data table to fill the remainder of the width left in the * parent widget. */ public void fillParent() { table.getColumnFormatter().setWidth(table.getCellCount(0) - 1, "100%"); } public void setWidgetFactory(TableWidgetFactory widgetFactory) { this.widgetFactory = widgetFactory; } protected void setRowStyle(int row) { table.getRowFormatter().setStyleName(row, "data-row"); if ((row & 1) == 0) { table.getRowFormatter().addStyleName(row, "data-row-alternate"); } if (clickable) { table.getRowFormatter().addStyleName(row, CLICKABLE_STYLE); } } public void setClickable(boolean clickable) { this.clickable = clickable; for(int i = headerRow + 1; i < table.getRowCount(); i++) setRowStyle(i); } /** * Clear all data rows from the table. Leaves the header rows intact. */ public void clear() { while (table.getRowCount() > 1) { table.removeRow(1); } jsonObjects.clear(); } /** * This gets called for every JSONObject that gets added to the table using * addRow(). This allows subclasses to customize objects before they are * added to the table, for example to reformat fields or generate new * fields from the existing data. * @param row The row object about to be added to the table. */ protected void preprocessRow(JSONObject row) {} protected String[] getRowText(JSONObject row) { String[] rowText = new String[columns.length]; for (int i = 0; i < columns.length; i++) { if (isWidgetColumn(i)) continue; String columnKey = columns[i][0]; JSONValue columnValue = row.get(columnKey); if (columnValue == null || columnValue.isNull() != null) { rowText[i] = ""; } else { rowText[i] = Utils.jsonToString(columnValue); } } return rowText; } /** * Add a row from an array of Strings, one String for each column. * @param rowData Data for each column, in left-to-right column order. */ protected void addRowFromData(String[] rowData) { int row = table.getRowCount(); for(int i = 0; i < columns.length; i++) { if(isWidgetColumn(i)) { table.setWidget(row, i, getWidgetForCell(row, i)); } else { table.setText(row, i, rowData[i]); } } setRowStyle(row); } protected boolean isWidgetColumn(int column) { return columns[column][COL_NAME].equals(WIDGET_COLUMN) || isClickableWidgetColumn(column); } protected boolean isClickableWidgetColumn(int column) { return columns[column][COL_NAME].equals(CLICKABLE_WIDGET_COLUMN); } /** * Add a row from a JSONObject. Columns will be populated by pulling fields * from the objects, as dictated by the columns information passed into the * DataTable constructor. */ public void addRow(JSONObject row) { preprocessRow(row); jsonObjects.add(row); addRowFromData(getRowText(row)); } /** * Add all objects in a JSONArray. * @param rows An array of JSONObjects * @throws IllegalArgumentException if any other type of JSONValue is in the * array. */ public void addRows(List rows) { for (JSONObject row : rows) { addRow(row); } } /** * Remove a data row from the table. * @param rowIndex The index of the row, where the first data row is indexed 0. * Header rows are ignored. */ public void removeRow(int rowIndex) { jsonObjects.remove(rowIndex); int realRow = rowIndex + 1; // header row table.removeRow(realRow); for(int i = realRow; i < table.getRowCount(); i++) setRowStyle(i); } /** * Returns the number of data rows in the table. The actual number of * visible table rows is more than this, due to the header row. */ public int getRowCount() { return table.getRowCount() - 1; } /** * Get the JSONObject corresponding to the indexed row. */ public JSONObject getRow(int rowIndex) { return jsonObjects.get(rowIndex); } public List getAllRows() { return Collections.unmodifiableList(jsonObjects); } public void highlightRow(int row) { row++; // account for header row table.getRowFormatter().addStyleName(row, HIGHLIGHTED_STYLE); } public void unhighlightRow(int row) { row++; // account for header row table.getRowFormatter().removeStyleName(row, HIGHLIGHTED_STYLE); } public void sinkRightClickEvents() { table.addContextMenuHandler(this); } @Override public void onClick(ClickEvent event) { onCellClicked(event, false); } @Override public void onContextMenu(ContextMenuEvent event) { onCellClicked(event, true); } private void onCellClicked(DomEvent event, boolean isRightClick) { HTMLTable.Cell tableCell = table.getCellForDomEvent(event); if (tableCell == null) { return; } int row = tableCell.getRowIndex(); int cell = tableCell.getCellIndex(); if (isClickableWidgetColumn(cell) && table.getWidget(row, cell) != null) { return; } onCellClicked(row, cell, isRightClick); } protected void onCellClicked(int row, int cell, boolean isRightClick) { if (row != headerRow) { notifyListenersClicked(row - headerRow - 1, isRightClick); } } public void addListener(DataTableListener listener) { listeners.add(listener); } public void removeListener(DataTableListener listener) { listeners.remove(listener); } protected void notifyListenersClicked(int rowIndex, boolean isRightClick) { JSONObject row = getRow(rowIndex); for (DataTableListener listener : listeners) { listener.onRowClicked(rowIndex, row, isRightClick); } } public void refreshWidgets() { for (int row = 1; row < table.getRowCount(); row++) { for (int column = 0; column < columns.length; column++) { if (!isWidgetColumn(column)) { continue; } table.clearCell(row, column); table.setWidget(row, column, getWidgetForCell(row, column)); } } } private Widget getWidgetForCell(int row, int column) { return widgetFactory.createWidget(row - 1, column, jsonObjects.get(row - 1)); } /** * Add a style name to a specific column by column name. */ public void addStyleNameByColumnName(String columnName, String styleName) { CellFormatter cellFormatter = table.getCellFormatter(); for (int column = 0; column < columns.length; column++) { if (columns[column][1].equals(columnName)) { for (int row = 1; row < table.getRowCount(); row++) { cellFormatter.addStyleName(row, column, styleName); } } } } }