DataTable.java revision 35dbd8414c0e7022a6a4b54f7ef16b5ff51ae53b
1e3f6868dac3b4c4714637d12b93d97823011a35cshowardpackage autotest.common.table;
2e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh
3e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh
4e8819cdf80ca0e0602d22551a50f970aa68e108dmblighimport com.google.gwt.json.client.JSONArray;
5e8819cdf80ca0e0602d22551a50f970aa68e108dmblighimport com.google.gwt.json.client.JSONObject;
6e8819cdf80ca0e0602d22551a50f970aa68e108dmblighimport com.google.gwt.json.client.JSONValue;
7e8819cdf80ca0e0602d22551a50f970aa68e108dmblighimport com.google.gwt.user.client.ui.Composite;
8e8819cdf80ca0e0602d22551a50f970aa68e108dmblighimport com.google.gwt.user.client.ui.FlexTable;
994b698cacab819b42104ce0db68aa63b68f4d1d1showardimport com.google.gwt.user.client.ui.Widget;
101c8c2215e525de8813c375e796354f8ffb811a08showard
111c8c2215e525de8813c375e796354f8ffb811a08showardimport java.util.ArrayList;
121c8c2215e525de8813c375e796354f8ffb811a08showardimport java.util.List;
13e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh
14e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh/**
15e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh * A table to display data from JSONObjects.  Each row displays data from one
16e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh * JSONObject.  A header row with column titles is automatically generated, and
17e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh * support is included for adding other arbitrary header rows.
18e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh * <br><br>
19e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh * Styles:
20e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh * <ul>
21e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh * <li>.data-table - the entire table
22e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh * <li>.data-row-header - the column title row
23e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh * <li>.data-row-one/.data-row-two - data row styles.  These two are alternated.
24e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh * </ul>
25e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh */
26e8819cdf80ca0e0602d22551a50f970aa68e108dmblighpublic class DataTable extends Composite {
27e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    public static final String HEADER_STYLE = "data-row-header";
28e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    public static final String CLICKABLE_STYLE = "data-row-clickable";
291c8c2215e525de8813c375e796354f8ffb811a08showard    public static final String HIGHLIGHTED_STYLE = "data-row-highlighted";
3094b698cacab819b42104ce0db68aa63b68f4d1d1showard    public static final String WIDGET_COLUMN = "_WIDGET_COLUMN_";
3135dbd8414c0e7022a6a4b54f7ef16b5ff51ae53bshoward    // use CLICKABLE_WIDGET_COLUMN for widget that expect to receive clicks.  The table will ignore
3235dbd8414c0e7022a6a4b54f7ef16b5ff51ae53bshoward    // click events coming from these columns.
3335dbd8414c0e7022a6a4b54f7ef16b5ff51ae53bshoward    public static final String CLICKABLE_WIDGET_COLUMN = "_CLICKABLE_WIDGET_COLUMN_";
341c8c2215e525de8813c375e796354f8ffb811a08showard    // for indexing into column subarrays (i.e. columns[1][COL_NAME])
351c8c2215e525de8813c375e796354f8ffb811a08showard    public static final int COL_NAME = 0, COL_TITLE = 1;
36e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh
37e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    protected FlexTable table;
38e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh
39e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    protected String[][] columns;
40e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    protected int headerRow = 0;
41e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    protected boolean clickable = false;
421c8c2215e525de8813c375e796354f8ffb811a08showard
4394b698cacab819b42104ce0db68aa63b68f4d1d1showard    protected TableWidgetFactory widgetFactory = null;
4494b698cacab819b42104ce0db68aa63b68f4d1d1showard
451c8c2215e525de8813c375e796354f8ffb811a08showard    // keep a list of JSONObjects corresponding to rows in the table
466bc47015cce0ebc2fc255d3950bfeaf4851f36fdshoward    protected List<JSONObject> jsonObjects = new ArrayList<JSONObject>();
47e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh
4894b698cacab819b42104ce0db68aa63b68f4d1d1showard
4994b698cacab819b42104ce0db68aa63b68f4d1d1showard    public static interface TableWidgetFactory {
509d821ab7d97c677a63589e6d71ee3c9da46f7077showard        public Widget createWidget(int row, int cell, JSONObject rowObject);
5194b698cacab819b42104ce0db68aa63b68f4d1d1showard    }
5294b698cacab819b42104ce0db68aa63b68f4d1d1showard
53e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    /**
54e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     * @param columns An array specifying the name of each column and the field
55e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     * to which it corresponds.  The array should have the form
56e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     * {{'field_name1', 'Column Title 1'},
57e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     *  {'field_name2', 'Column Title 2'}, ...}.
58e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     */
59e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    public DataTable(String[][] columns) {
606bc47015cce0ebc2fc255d3950bfeaf4851f36fdshoward        int rows = columns.length;
616bc47015cce0ebc2fc255d3950bfeaf4851f36fdshoward        this.columns = new String[rows][2];
626bc47015cce0ebc2fc255d3950bfeaf4851f36fdshoward        for (int i = 0; i < rows; i++) {
636bc47015cce0ebc2fc255d3950bfeaf4851f36fdshoward            System.arraycopy(columns[i], 0, this.columns[i], 0, 2);
646bc47015cce0ebc2fc255d3950bfeaf4851f36fdshoward        }
656bc47015cce0ebc2fc255d3950bfeaf4851f36fdshoward
66e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        table = new FlexTable();
67e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        initWidget(table);
68e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh
69e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        table.setCellSpacing(0);
70e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        table.setCellPadding(0);
71e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        table.setStyleName("data-table");
72e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh
73e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        for (int i = 0; i < columns.length; i++) {
74e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh            table.setText(0, i, columns[i][1]);
75e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        }
76e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh
77e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        table.getRowFormatter().setStylePrimaryName(0, HEADER_STYLE);
78e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    }
7994b698cacab819b42104ce0db68aa63b68f4d1d1showard
8094b698cacab819b42104ce0db68aa63b68f4d1d1showard    public void setWidgetFactory(TableWidgetFactory widgetFactory) {
8194b698cacab819b42104ce0db68aa63b68f4d1d1showard        this.widgetFactory = widgetFactory;
8294b698cacab819b42104ce0db68aa63b68f4d1d1showard    }
83e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh
84e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    protected void setRowStyle(int row) {
85e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        table.getRowFormatter().setStyleName(row, "data-row");
86e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        if ((row & 1) == 0) {
87e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh            table.getRowFormatter().addStyleName(row, "data-row-alternate");
88e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        }
89e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        if (clickable) {
90e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh            table.getRowFormatter().addStyleName(row, CLICKABLE_STYLE);
91e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        }
92e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    }
93e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh
94e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    public void setClickable(boolean clickable) {
95e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        this.clickable = clickable;
96e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        for(int i = headerRow + 1; i < table.getRowCount(); i++)
97e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh            setRowStyle(i);
98e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    }
99e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh
100e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    /**
101e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     * Clear all data rows from the table.  Leaves the header rows intact.
102e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     */
103e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    public void clear() {
104e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        while (getRowCount() > 0) {
105e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh            removeRow(0);
106e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        }
1071c8c2215e525de8813c375e796354f8ffb811a08showard        jsonObjects.clear();
108e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    }
109e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh
110e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    /**
111e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     * This gets called for every JSONObject that gets added to the table using
112e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     * addRow().  This allows subclasses to customize objects before they are
113e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     * added to the table, for example to reformat fields or generate new
114e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     * fields from the existing data.
115e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     * @param row The row object about to be added to the table.
116e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     */
117e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    protected void preprocessRow(JSONObject row) {}
118e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh
119e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    protected String getTextForValue(JSONValue value) {
120b8d3424d02cfdc6c3eeda788e3527c5ec50ad8f9showard        if (value == null || value.isNull() != null)
121b8d3424d02cfdc6c3eeda788e3527c5ec50ad8f9showard            return "";
122b8d3424d02cfdc6c3eeda788e3527c5ec50ad8f9showard        else if (value.isNumber() != null)
1236bc47015cce0ebc2fc255d3950bfeaf4851f36fdshoward            return Integer.toString((int) value.isNumber().doubleValue());
124e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        else if (value.isString() != null)
125e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh            return  value.isString().stringValue();
126e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        else
127e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh            throw new IllegalArgumentException(value.toString());
128e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    }
129e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh
130e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    protected String[] getRowText(JSONObject row) {
131e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        String[] rowText = new String[columns.length];
132e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        for (int i = 0; i < columns.length; i++) {
13394b698cacab819b42104ce0db68aa63b68f4d1d1showard            if (isWidgetColumn(i))
13494b698cacab819b42104ce0db68aa63b68f4d1d1showard                continue;
13594b698cacab819b42104ce0db68aa63b68f4d1d1showard
136e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh            String columnKey = columns[i][0];
137e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh            JSONValue columnValue = row.get(columnKey);
138e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh            rowText[i] = getTextForValue(columnValue);
139e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        }
140e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        return rowText;
141e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    }
142e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh
143e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    /**
144e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     * Add a row from an array of Strings, one String for each column.
145e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     * @param rowData Data for each column, in left-to-right column order.
146e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     */
1471c8c2215e525de8813c375e796354f8ffb811a08showard    protected void addRowFromData(String[] rowData) {
148e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        int row = table.getRowCount();
14994b698cacab819b42104ce0db68aa63b68f4d1d1showard        for(int i = 0; i < columns.length; i++) {
15094b698cacab819b42104ce0db68aa63b68f4d1d1showard            if(isWidgetColumn(i)) {
1519d821ab7d97c677a63589e6d71ee3c9da46f7077showard                table.setWidget(row, i, widgetFactory.createWidget(row - 1, i,
1529d821ab7d97c677a63589e6d71ee3c9da46f7077showard                                                                   jsonObjects.get(row - 1)));
15394b698cacab819b42104ce0db68aa63b68f4d1d1showard            } else {
15494b698cacab819b42104ce0db68aa63b68f4d1d1showard                table.setHTML(row, i, rowData[i]);
15594b698cacab819b42104ce0db68aa63b68f4d1d1showard            }
15694b698cacab819b42104ce0db68aa63b68f4d1d1showard        }
157e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        setRowStyle(row);
158e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    }
159e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh
16035dbd8414c0e7022a6a4b54f7ef16b5ff51ae53bshoward    protected boolean isWidgetColumn(int column) {
16135dbd8414c0e7022a6a4b54f7ef16b5ff51ae53bshoward        return columns[column][COL_NAME].equals(WIDGET_COLUMN) || isClickableWidgetColumn(column);
16235dbd8414c0e7022a6a4b54f7ef16b5ff51ae53bshoward    }
16335dbd8414c0e7022a6a4b54f7ef16b5ff51ae53bshoward
16435dbd8414c0e7022a6a4b54f7ef16b5ff51ae53bshoward    protected boolean isClickableWidgetColumn(int column) {
16535dbd8414c0e7022a6a4b54f7ef16b5ff51ae53bshoward        return columns[column][COL_NAME].equals(CLICKABLE_WIDGET_COLUMN);
16694b698cacab819b42104ce0db68aa63b68f4d1d1showard    }
16794b698cacab819b42104ce0db68aa63b68f4d1d1showard
168e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    /**
169e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     * Add a row from a JSONObject.  Columns will be populated by pulling fields
170e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     * from the objects, as dictated by the columns information passed into the
171e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     * DataTable constructor.
172e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     */
173e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    public void addRow(JSONObject row) {
174e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        preprocessRow(row);
1751c8c2215e525de8813c375e796354f8ffb811a08showard        jsonObjects.add(row);
176e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        addRowFromData(getRowText(row));
177e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    }
178e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh
179e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    /**
180e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     * Add all objects in a JSONArray.
181e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     * @param rows An array of JSONObjects
182e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     * @throws IllegalArgumentException if any other type of JSONValue is in the
183e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     * array.
184e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     */
185e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    public void addRows(JSONArray rows) {
186e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        for (int i = 0; i < rows.size(); i++) {
187e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh            JSONObject row = rows.get(i).isObject();
188e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh            if (row == null)
189e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh                throw new IllegalArgumentException("rows must be JSONObjects");
190e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh            addRow(row);
191e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        }
192e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    }
193e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh
194e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    /**
195e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     * Remove a data row from the table.
1961c8c2215e525de8813c375e796354f8ffb811a08showard     * @param rowIndex The index of the row, where the first data row is indexed 0.
197e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     * Header rows are ignored.
198e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     */
1991c8c2215e525de8813c375e796354f8ffb811a08showard    public void removeRow(int rowIndex) {
2001c8c2215e525de8813c375e796354f8ffb811a08showard        jsonObjects.remove(rowIndex);
2011c8c2215e525de8813c375e796354f8ffb811a08showard        int realRow = rowIndex + 1; // header row
202e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        table.removeRow(realRow);
203e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh        for(int i = realRow; i < table.getRowCount(); i++)
204e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh            setRowStyle(i);
205e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    }
206e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh
207e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    /**
208e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     * Returns the number of data rows in the table.  The actual number of
2091c8c2215e525de8813c375e796354f8ffb811a08showard     * visible table rows is more than this, due to the header row.
210e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     */
211e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    public int getRowCount() {
2121c8c2215e525de8813c375e796354f8ffb811a08showard        return table.getRowCount() - 1;
213e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    }
2141c8c2215e525de8813c375e796354f8ffb811a08showard
215e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    /**
2161c8c2215e525de8813c375e796354f8ffb811a08showard     * Get the JSONObject corresponding to the indexed row.
217e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh     */
2181c8c2215e525de8813c375e796354f8ffb811a08showard    public JSONObject getRow(int rowIndex) {
2196bc47015cce0ebc2fc255d3950bfeaf4851f36fdshoward        return jsonObjects.get(rowIndex);
220e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    }
221e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh
2221c8c2215e525de8813c375e796354f8ffb811a08showard    public void highlightRow(int row) {
2231c8c2215e525de8813c375e796354f8ffb811a08showard        row++; // account for header row
2241c8c2215e525de8813c375e796354f8ffb811a08showard        table.getRowFormatter().addStyleName(row, HIGHLIGHTED_STYLE);
2251c8c2215e525de8813c375e796354f8ffb811a08showard    }
2261c8c2215e525de8813c375e796354f8ffb811a08showard
2271c8c2215e525de8813c375e796354f8ffb811a08showard    public void unhighlightRow(int row) {
2286bc47015cce0ebc2fc255d3950bfeaf4851f36fdshoward        row++; // account for header row
2291c8c2215e525de8813c375e796354f8ffb811a08showard        table.getRowFormatter().removeStyleName(row, HIGHLIGHTED_STYLE);
230e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh    }
231e8819cdf80ca0e0602d22551a50f970aa68e108dmbligh}
232