Home | History | Annotate | Download | only in table
      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 {
     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 {
     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      */
     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      */
    100     public void fillParent() {
    101         table.getColumnFormatter().setWidth(table.getCellCount(0) - 1, "100%");
    102     }
    103 
    104     public void setWidgetFactory(TableWidgetFactory widgetFactory) {
    105         this.widgetFactory = widgetFactory;
    106     }
    107 
    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 
    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      */
    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      */
    141     protected void preprocessRow(JSONObject row) {}
    142 
    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      */
    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 
    176     protected boolean isWidgetColumn(int column) {
    177         return columns[column][COL_NAME].equals(WIDGET_COLUMN) || isClickableWidgetColumn(column);
    178     }
    179 
    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      */
    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      */
    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      */
    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      */
    224     public int getRowCount() {
    225         return table.getRowCount() - 1;
    226     }
    227 
    228     /**
    229      * Get the JSONObject corresponding to the indexed row.
    230      */
    231     public JSONObject getRow(int rowIndex) {
    232         return jsonObjects.get(rowIndex);
    233     }
    234 
    235     public List<JSONObject> getAllRows() {
    236         return Collections.unmodifiableList(jsonObjects);
    237     }
    238 
    239     public void highlightRow(int row) {
    240         row++; // account for header row
    241         table.getRowFormatter().addStyleName(row, HIGHLIGHTED_STYLE);
    242     }
    243 
    244     public void unhighlightRow(int row) {
    245         row++; // account for header row
    246         table.getRowFormatter().removeStyleName(row, HIGHLIGHTED_STYLE);
    247     }
    248 
    249     public void sinkRightClickEvents() {
    250         table.addContextMenuHandler(this);
    251     }
    252 
    253     @Override
    254     public void onClick(ClickEvent event) {
    255         onCellClicked(event, false);
    256     }
    257 
    258     @Override
    259     public void onContextMenu(ContextMenuEvent event) {
    260         onCellClicked(event, true);
    261     }
    262 
    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 
    279     protected void onCellClicked(int row, int cell, boolean isRightClick) {
    280         if (row != headerRow) {
    281             notifyListenersClicked(row - headerRow - 1, isRightClick);
    282         }
    283     }
    284 
    285     public void addListener(DataTableListener listener) {
    286         listeners.add(listener);
    287     }
    288 
    289     public void removeListener(DataTableListener listener) {
    290         listeners.remove(listener);
    291     }
    292 
    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 
    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 
    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      */
    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