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