blob: a301065c3c0ad01b214c872bba1b41277d79db6f [file] [log] [blame]
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);
}
}
}
}
}