| 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. |
| * <br><br> |
| * Styles: |
| * <ul> |
| * <li>.data-table - the entire table |
| * <li>.data-row-header - the column title row |
| * <li>.data-row-one/.data-row-two - data row styles. These two are alternated. |
| * </ul> |
| */ |
| 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<DataTableListener> listeners = new ArrayList<DataTableListener>(); |
| |
| // keep a list of JSONObjects corresponding to rows in the table |
| protected List<JSONObject> jsonObjects = new ArrayList<JSONObject>(); |
| |
| |
| 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<JSONObject> 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<JSONObject> 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); |
| } |
| } |
| } |
| } |
| } |