DataTable.java revision 35dbd8414c0e7022a6a4b54f7ef16b5ff51ae53b
1package autotest.common.table;
2
3
4import com.google.gwt.json.client.JSONArray;
5import com.google.gwt.json.client.JSONObject;
6import com.google.gwt.json.client.JSONValue;
7import com.google.gwt.user.client.ui.Composite;
8import com.google.gwt.user.client.ui.FlexTable;
9import com.google.gwt.user.client.ui.Widget;
10
11import java.util.ArrayList;
12import java.util.List;
13
14/**
15 * A table to display data from JSONObjects.  Each row displays data from one
16 * JSONObject.  A header row with column titles is automatically generated, and
17 * support is included for adding other arbitrary header rows.
18 * <br><br>
19 * Styles:
20 * <ul>
21 * <li>.data-table - the entire table
22 * <li>.data-row-header - the column title row
23 * <li>.data-row-one/.data-row-two - data row styles.  These two are alternated.
24 * </ul>
25 */
26public class DataTable extends Composite {
27    public static final String HEADER_STYLE = "data-row-header";
28    public static final String CLICKABLE_STYLE = "data-row-clickable";
29    public static final String HIGHLIGHTED_STYLE = "data-row-highlighted";
30    public static final String WIDGET_COLUMN = "_WIDGET_COLUMN_";
31    // use CLICKABLE_WIDGET_COLUMN for widget that expect to receive clicks.  The table will ignore
32    // click events coming from these columns.
33    public static final String CLICKABLE_WIDGET_COLUMN = "_CLICKABLE_WIDGET_COLUMN_";
34    // for indexing into column subarrays (i.e. columns[1][COL_NAME])
35    public static final int COL_NAME = 0, COL_TITLE = 1;
36
37    protected FlexTable table;
38
39    protected String[][] columns;
40    protected int headerRow = 0;
41    protected boolean clickable = false;
42
43    protected TableWidgetFactory widgetFactory = null;
44
45    // keep a list of JSONObjects corresponding to rows in the table
46    protected List<JSONObject> jsonObjects = new ArrayList<JSONObject>();
47
48
49    public static interface TableWidgetFactory {
50        public Widget createWidget(int row, int cell, JSONObject rowObject);
51    }
52
53    /**
54     * @param columns An array specifying the name of each column and the field
55     * to which it corresponds.  The array should have the form
56     * {{'field_name1', 'Column Title 1'},
57     *  {'field_name2', 'Column Title 2'}, ...}.
58     */
59    public DataTable(String[][] columns) {
60        int rows = columns.length;
61        this.columns = new String[rows][2];
62        for (int i = 0; i < rows; i++) {
63            System.arraycopy(columns[i], 0, this.columns[i], 0, 2);
64        }
65
66        table = new FlexTable();
67        initWidget(table);
68
69        table.setCellSpacing(0);
70        table.setCellPadding(0);
71        table.setStyleName("data-table");
72
73        for (int i = 0; i < columns.length; i++) {
74            table.setText(0, i, columns[i][1]);
75        }
76
77        table.getRowFormatter().setStylePrimaryName(0, HEADER_STYLE);
78    }
79
80    public void setWidgetFactory(TableWidgetFactory widgetFactory) {
81        this.widgetFactory = widgetFactory;
82    }
83
84    protected void setRowStyle(int row) {
85        table.getRowFormatter().setStyleName(row, "data-row");
86        if ((row & 1) == 0) {
87            table.getRowFormatter().addStyleName(row, "data-row-alternate");
88        }
89        if (clickable) {
90            table.getRowFormatter().addStyleName(row, CLICKABLE_STYLE);
91        }
92    }
93
94    public void setClickable(boolean clickable) {
95        this.clickable = clickable;
96        for(int i = headerRow + 1; i < table.getRowCount(); i++)
97            setRowStyle(i);
98    }
99
100    /**
101     * Clear all data rows from the table.  Leaves the header rows intact.
102     */
103    public void clear() {
104        while (getRowCount() > 0) {
105            removeRow(0);
106        }
107        jsonObjects.clear();
108    }
109
110    /**
111     * This gets called for every JSONObject that gets added to the table using
112     * addRow().  This allows subclasses to customize objects before they are
113     * added to the table, for example to reformat fields or generate new
114     * fields from the existing data.
115     * @param row The row object about to be added to the table.
116     */
117    protected void preprocessRow(JSONObject row) {}
118
119    protected String getTextForValue(JSONValue value) {
120        if (value == null || value.isNull() != null)
121            return "";
122        else if (value.isNumber() != null)
123            return Integer.toString((int) value.isNumber().doubleValue());
124        else if (value.isString() != null)
125            return  value.isString().stringValue();
126        else
127            throw new IllegalArgumentException(value.toString());
128    }
129
130    protected String[] getRowText(JSONObject row) {
131        String[] rowText = new String[columns.length];
132        for (int i = 0; i < columns.length; i++) {
133            if (isWidgetColumn(i))
134                continue;
135
136            String columnKey = columns[i][0];
137            JSONValue columnValue = row.get(columnKey);
138            rowText[i] = getTextForValue(columnValue);
139        }
140        return rowText;
141    }
142
143    /**
144     * Add a row from an array of Strings, one String for each column.
145     * @param rowData Data for each column, in left-to-right column order.
146     */
147    protected void addRowFromData(String[] rowData) {
148        int row = table.getRowCount();
149        for(int i = 0; i < columns.length; i++) {
150            if(isWidgetColumn(i)) {
151                table.setWidget(row, i, widgetFactory.createWidget(row - 1, i,
152                                                                   jsonObjects.get(row - 1)));
153            } else {
154                table.setHTML(row, i, rowData[i]);
155            }
156        }
157        setRowStyle(row);
158    }
159
160    protected boolean isWidgetColumn(int column) {
161        return columns[column][COL_NAME].equals(WIDGET_COLUMN) || isClickableWidgetColumn(column);
162    }
163
164    protected boolean isClickableWidgetColumn(int column) {
165        return columns[column][COL_NAME].equals(CLICKABLE_WIDGET_COLUMN);
166    }
167
168    /**
169     * Add a row from a JSONObject.  Columns will be populated by pulling fields
170     * from the objects, as dictated by the columns information passed into the
171     * DataTable constructor.
172     */
173    public void addRow(JSONObject row) {
174        preprocessRow(row);
175        jsonObjects.add(row);
176        addRowFromData(getRowText(row));
177    }
178
179    /**
180     * Add all objects in a JSONArray.
181     * @param rows An array of JSONObjects
182     * @throws IllegalArgumentException if any other type of JSONValue is in the
183     * array.
184     */
185    public void addRows(JSONArray rows) {
186        for (int i = 0; i < rows.size(); i++) {
187            JSONObject row = rows.get(i).isObject();
188            if (row == null)
189                throw new IllegalArgumentException("rows must be JSONObjects");
190            addRow(row);
191        }
192    }
193
194    /**
195     * Remove a data row from the table.
196     * @param rowIndex The index of the row, where the first data row is indexed 0.
197     * Header rows are ignored.
198     */
199    public void removeRow(int rowIndex) {
200        jsonObjects.remove(rowIndex);
201        int realRow = rowIndex + 1; // header row
202        table.removeRow(realRow);
203        for(int i = realRow; i < table.getRowCount(); i++)
204            setRowStyle(i);
205    }
206
207    /**
208     * Returns the number of data rows in the table.  The actual number of
209     * visible table rows is more than this, due to the header row.
210     */
211    public int getRowCount() {
212        return table.getRowCount() - 1;
213    }
214
215    /**
216     * Get the JSONObject corresponding to the indexed row.
217     */
218    public JSONObject getRow(int rowIndex) {
219        return jsonObjects.get(rowIndex);
220    }
221
222    public void highlightRow(int row) {
223        row++; // account for header row
224        table.getRowFormatter().addStyleName(row, HIGHLIGHTED_STYLE);
225    }
226
227    public void unhighlightRow(int row) {
228        row++; // account for header row
229        table.getRowFormatter().removeStyleName(row, HIGHLIGHTED_STYLE);
230    }
231}
232