1 package autotest.common.table; 2 3 import autotest.common.SimpleCallback; 4 import autotest.common.table.DataSource.DataCallback; 5 import autotest.common.table.DataSource.Query; 6 import autotest.common.table.DataSource.SortDirection; 7 import autotest.common.table.DataSource.SortSpec; 8 import autotest.common.ui.Paginator; 9 10 import com.google.gwt.json.client.JSONObject; 11 import com.google.gwt.user.client.ui.Composite; 12 import com.google.gwt.user.client.ui.HTMLPanel; 13 import com.google.gwt.user.client.ui.Image; 14 15 import java.util.ArrayList; 16 import java.util.Collections; 17 import java.util.Iterator; 18 import java.util.List; 19 20 /** 21 * Extended DataTable supporting sorting, filtering and pagination. 22 */ 23 public 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 { onTableRefreshed()29 public void onTableRefreshed(); 30 } 31 32 static class SortIndicator extends Composite { 33 public int column; 34 private Image image = new Image(); 35 SortIndicator(int column)36 public SortIndicator(int column) { 37 this.column = column; 38 initWidget(image); 39 setVisible(false); 40 } 41 sortOn(SortDirection direction)42 public void sortOn(SortDirection direction) { 43 image.setUrl(direction == SortDirection.ASCENDING ? SORT_UP_IMAGE : SORT_DOWN_IMAGE); 44 setVisible(true); 45 } 46 sortOff()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 DynamicTable(String[][] columns, DataSource dataSource)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 */ makeClientSortable()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 updateSortIndicators()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 getFirstSortSpec()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 */ sortOnColumn(String columnField, SortDirection sortDirection)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 */ sortOnColumn(String columnField)142 public void sortOnColumn(String columnField) { 143 sortOnColumn(columnField, SortDirection.ASCENDING); 144 } 145 clearSorts()146 public void clearSorts() { 147 sortColumns.clear(); 148 updateSortIndicators(); 149 } 150 151 // PAGINATION 152 153 /** 154 * Attach a new paginator to this table. 155 */ attachPaginator(Paginator paginator)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 */ setRowsPerPage(int rowsPerPage)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 */ setPaginatorStart(int start)183 public void setPaginatorStart(int start) { 184 for (Paginator paginator : paginators) { 185 paginator.setStart(start); 186 } 187 } 188 refreshPaginators()189 protected void refreshPaginators() { 190 for (Paginator paginator : paginators) { 191 paginator.update(); 192 } 193 } 194 updatePaginatorTotalResults(int totalResults)195 protected void updatePaginatorTotalResults(int totalResults) { 196 for (Paginator paginator : paginators) { 197 paginator.setNumTotalResults(totalResults); 198 } 199 } 200 201 202 // FILTERING 203 addFilter(Filter filter)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 addFilterParams(JSONObject params)214 protected void addFilterParams(JSONObject params) { 215 for (Filter filter : filters) { 216 if (filter.isActive()) { 217 filter.addParams(params); 218 } 219 } 220 } 221 isAnyUserFilterActive()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 refresh()235 public void refresh() { 236 JSONObject params = new JSONObject(); 237 addFilterParams(params); 238 dataSource.query(params, this); 239 } 240 241 @Override onQueryReady(Query query)242 public void onQueryReady(Query query) { 243 currentQuery = query; 244 if (!paginators.isEmpty()) { 245 query.getTotalResultCount(this); 246 } 247 fetchPage(); 248 } 249 fetchPage()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 handleTotalResultCount(int totalCount)267 public void handleTotalResultCount(int totalCount) { 268 updatePaginatorTotalResults(totalCount); 269 refreshPaginators(); 270 notifyListenersRefreshed(); 271 } 272 handlePage(List<JSONObject> data)273 public void handlePage(List<JSONObject> data) { 274 clear(); 275 addRows(data); 276 refreshPaginators(); 277 notifyListenersRefreshed(); 278 } 279 getRowData(int row)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 getDataSource()291 public DataSource getDataSource() { 292 return dataSource; 293 } 294 setDataSource(DataSource dataSource)295 public void setDataSource(DataSource dataSource) { 296 this.dataSource = dataSource; 297 } 298 getCurrentQuery()299 public Query getCurrentQuery() { 300 return currentQuery; 301 } 302 303 304 // INPUT 305 306 @Override onCellClicked(int row, int cell, boolean isRightClick)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 invertSortDirection(SortDirection direction)329 private SortDirection invertSortDirection(SortDirection direction) { 330 return direction == SortDirection.ASCENDING ? 331 SortDirection.DESCENDING : SortDirection.ASCENDING; 332 } 333 addListener(DynamicTableListener listener)334 public void addListener(DynamicTableListener listener) { 335 super.addListener(listener); 336 dynamicTableListeners.add(listener); 337 } 338 removeListener(DynamicTableListener listener)339 public void removeListener(DynamicTableListener listener) { 340 super.removeListener(listener); 341 dynamicTableListeners.remove(listener); 342 } 343 notifyListenersRefreshed()344 protected void notifyListenersRefreshed() { 345 for (DynamicTableListener listener : dynamicTableListeners) { 346 listener.onTableRefreshed(); 347 } 348 } 349 getSortSpecs()350 public List<SortSpec> getSortSpecs() { 351 return Collections.unmodifiableList(sortColumns); 352 } 353 onError(JSONObject errorObject)354 public void onError(JSONObject errorObject) { 355 // nothing to do 356 } 357 } 358