DynamicTable.java revision 4cd4763a855d8eb7d25fd4963babc432eb4d25e6
1package autotest.common.table;
2
3import autotest.common.SimpleCallback;
4import autotest.common.table.DataSource.DataCallback;
5import autotest.common.table.DataSource.SortDirection;
6import autotest.common.table.DataSource.SortSpec;
7import autotest.common.ui.Paginator;
8
9import com.google.gwt.json.client.JSONArray;
10import com.google.gwt.json.client.JSONObject;
11import com.google.gwt.user.client.ui.Composite;
12import com.google.gwt.user.client.ui.HTMLPanel;
13import com.google.gwt.user.client.ui.Image;
14
15import java.util.ArrayList;
16import java.util.Collections;
17import java.util.Iterator;
18import java.util.List;
19
20/**
21 * Extended DataTable supporting sorting, filtering and pagination.
22 */
23public class DynamicTable extends DataTable implements DataCallback {
24    public static final int NO_COLUMN = -1;
25    public static final String SORT_UP_IMAGE = "arrow_up.png",
26                               SORT_DOWN_IMAGE = "arrow_down.png";
27
28    public static interface DynamicTableListener extends DataTableListener {
29        public void onTableRefreshed();
30    }
31
32    static class SortIndicator extends Composite {
33        public int column;
34        private Image image = new Image();
35
36        public SortIndicator(int column) {
37            this.column = column;
38            initWidget(image);
39            setVisible(false);
40        }
41
42        public void sortOn(SortDirection direction) {
43            image.setUrl(direction == SortDirection.ASCENDING ? SORT_UP_IMAGE : SORT_DOWN_IMAGE);
44            setVisible(true);
45        }
46
47        public void sortOff() {
48            setVisible(false);
49        }
50    }
51
52    protected DataSource dataSource;
53
54    private boolean clientSortable = false;
55    private SortIndicator[] sortIndicators;
56    private List<SortSpec> sortColumns = new ArrayList<SortSpec>();
57
58    protected List<Filter> filters = new ArrayList<Filter>();
59    protected List<Paginator> paginators = new ArrayList<Paginator>();
60    protected Integer rowsPerPage;
61
62    protected List<DynamicTableListener> dynamicTableListeners =
63        new ArrayList<DynamicTableListener>();
64
65    public DynamicTable(String[][] columns, DataSource dataSource) {
66        super(columns);
67        setDataSource(dataSource);
68    }
69
70    // SORTING
71
72    /**
73     * Makes the table client sortable, that is, sortable by the user by
74     * clicking on column headers.
75     */
76    public void makeClientSortable() {
77        this.clientSortable = true;
78        table.getRowFormatter().addStyleName(0,
79                                         DataTable.HEADER_STYLE + "-sortable");
80
81        sortIndicators = new SortIndicator[columns.length];
82        for(int i = 0; i < columns.length; i++) {
83            sortIndicators[i] = new SortIndicator(i);
84
85            // we have to use an HTMLPanel here to preserve styles correctly and
86            // not break hover
87            // we add a <span> with a unique ID to hold the sort indicator
88            String name = columns[i][COL_TITLE];
89            String id = HTMLPanel.createUniqueId();
90            HTMLPanel panel = new HTMLPanel(name +
91                                            " <span id=\"" + id + "\"></span>");
92            panel.add(sortIndicators[i], id);
93            table.setWidget(0, i, panel);
94        }
95    }
96
97    private void updateSortIndicators() {
98        if (!clientSortable) {
99            return;
100        }
101
102        SortSpec firstSpec = getFirstSortSpec();
103        for (SortIndicator indicator : sortIndicators) {
104            if (columns[indicator.column][COL_NAME].equals(firstSpec.getField())) {
105                indicator.sortOn(firstSpec.getDirection());
106            } else {
107                indicator.sortOff();
108            }
109        }
110    }
111
112    private SortSpec getFirstSortSpec() {
113        if (sortColumns.isEmpty()) {
114            return null;
115        }
116        return sortColumns.get(0);
117    }
118
119    /**
120     * Set column on which data is sorted.  You must call <code>refresh()</code>
121     * after this to display the results.
122     * @param columnField field of the column to sort on
123     * @param sortDirection DynamicTable.ASCENDING or DynamicTable.DESCENDING
124     */
125    public void sortOnColumn(String columnField, SortDirection sortDirection) {
126        // remove any existing sort on this column
127        for (Iterator<SortSpec> i = sortColumns.iterator(); i.hasNext(); ) {
128            if (i.next().getField().equals(columnField)) {
129                i.remove();
130                break;
131            }
132        }
133
134        sortColumns.add(0, new SortSpec(columnField, sortDirection));
135        updateSortIndicators();
136    }
137
138    /**
139     * Defaults to ascending order.
140     */
141    public void sortOnColumn(String columnField) {
142        sortOnColumn(columnField, SortDirection.ASCENDING);
143    }
144
145    public void clearSorts() {
146        sortColumns.clear();
147        updateSortIndicators();
148    }
149
150    // PAGINATION
151
152    /**
153     * Attach a new paginator to this table.
154     */
155    public void attachPaginator(Paginator paginator) {
156        assert rowsPerPage != null;
157        paginators.add(paginator);
158        paginator.addCallback(new SimpleCallback() {
159            public void doCallback(Object source) {
160                setPaginatorStart(((Paginator) source).getStart());
161                refresh();
162            }
163        });
164        paginator.setResultsPerPage(rowsPerPage.intValue());
165    }
166
167    /**
168     * Set the page size of this table (only useful if you attach paginators).
169     */
170    public void setRowsPerPage(int rowsPerPage) {
171        assert rowsPerPage > 0;
172        this.rowsPerPage = Integer.valueOf(rowsPerPage);
173        for (Paginator paginator : paginators) {
174            paginator.setResultsPerPage(rowsPerPage);
175        }
176    }
177
178    /**
179     * Set start row for pagination.  You must call
180     * <code>refresh()</code> after this to display the results.
181     */
182    public void setPaginatorStart(int start) {
183        for (Paginator paginator : paginators) {
184            paginator.setStart(start);
185        }
186    }
187
188    protected void refreshPaginators() {
189        for (Paginator paginator : paginators) {
190            paginator.update();
191        }
192    }
193
194    protected void updatePaginatorTotalResults(int totalResults) {
195        for (Paginator paginator : paginators) {
196            paginator.setNumTotalResults(totalResults);
197        }
198    }
199
200
201    // FILTERING
202
203    public void addFilter(Filter filter) {
204        filters.add(filter);
205        filter.addCallback(new SimpleCallback() {
206            public void doCallback(Object source) {
207                setPaginatorStart(0);
208                refresh();
209            }
210        });
211    }
212
213    protected void addFilterParams(JSONObject params) {
214        for (Filter filter : filters) {
215            if (filter.isActive()) {
216                filter.addParams(params);
217            }
218        }
219    }
220
221
222    // DATA MANAGEMENT
223
224    public void refresh() {
225        JSONObject params = new JSONObject();
226        addFilterParams(params);
227        dataSource.updateData(params, this);
228    }
229
230    public void onGotData(int totalCount) {
231        Integer start = null, limit = null;
232        SortSpec[] sortOn = null;
233        if (!paginators.isEmpty()) {
234            updatePaginatorTotalResults(totalCount);
235            Paginator p = paginators.get(0);
236            start = Integer.valueOf(p.getStart());
237            limit = Integer.valueOf(p.getResultsPerPage());
238        }
239
240        if (!sortColumns.isEmpty()) {
241            sortOn = new SortSpec[sortColumns.size()];
242            sortColumns.toArray(sortOn);
243        }
244        dataSource.getPage(start, limit, sortOn, this);
245    }
246
247    public void handlePage(JSONArray data) {
248        clear();
249        addRows(data);
250        refreshPaginators();
251        notifyListenersRefreshed();
252    }
253
254    public String[] getRowData(int row) {
255        String[] data = new String[columns.length];
256        for (int i = 0; i < columns.length; i++) {
257            if(isWidgetColumn(i))
258                continue;
259            data[i] = table.getHTML(row, i);
260        }
261        return data;
262    }
263
264    public DataSource getDataSource() {
265        return dataSource;
266    }
267
268    public void setDataSource(DataSource dataSource) {
269        this.dataSource = dataSource;
270    }
271
272
273    // INPUT
274
275    @Override
276    protected void onCellClicked(int row, int cell, boolean isRightClick) {
277        if (row == headerRow) {
278            if (isWidgetColumn(cell)) {
279                // ignore sorting on widget columns
280                return;
281            }
282            String columnName = columns[cell][COL_NAME];
283            SortDirection newSortDirection = SortDirection.ASCENDING;
284            SortSpec firstSortSpec = getFirstSortSpec();
285            // when clicking on the last sorted field, invert the sort
286            if (firstSortSpec != null && columnName.equals(firstSortSpec.getField())) {
287                newSortDirection = invertSortDirection(firstSortSpec.getDirection());
288            }
289
290            sortOnColumn(columnName, newSortDirection);
291            refresh();
292            return;
293        }
294
295        super.onCellClicked(row, cell, isRightClick);
296    }
297
298    private SortDirection invertSortDirection(SortDirection direction) {
299        return direction == SortDirection.ASCENDING ?
300                                        SortDirection.DESCENDING : SortDirection.ASCENDING;
301    }
302
303    public void addListener(DynamicTableListener listener) {
304        super.addListener(listener);
305        dynamicTableListeners.add(listener);
306    }
307
308    public void removeListener(DynamicTableListener listener) {
309        super.removeListener(listener);
310        dynamicTableListeners.remove(listener);
311    }
312
313    protected void notifyListenersRefreshed() {
314        for (DynamicTableListener listener : dynamicTableListeners) {
315            listener.onTableRefreshed();
316        }
317    }
318
319    public List<SortSpec> getSortSpecs() {
320        return Collections.unmodifiableList(sortColumns);
321    }
322
323    public void onError(JSONObject errorObject) {
324        // nothing to do
325    }
326}
327