1 package org.kuali.student.common.ui.client.widgets.table.scroll;
2
3 import java.util.ArrayList;
4 import java.util.List;
5
6 import org.kuali.student.common.ui.client.util.BrowserUtils;
7 import org.kuali.student.common.ui.client.util.DebugIdUtils;
8 import org.kuali.student.common.ui.client.widgets.list.HasSelectionChangeHandlers;
9 import org.kuali.student.common.ui.client.widgets.list.SelectionChangeEvent;
10 import org.kuali.student.common.ui.client.widgets.list.SelectionChangeHandler;
11 import org.kuali.student.common.ui.client.widgets.notification.LoadingDiv;
12
13 import com.google.gwt.core.client.GWT;
14 import com.google.gwt.event.dom.client.ChangeEvent;
15 import com.google.gwt.event.dom.client.ChangeHandler;
16 import com.google.gwt.event.dom.client.ClickEvent;
17 import com.google.gwt.event.dom.client.ClickHandler;
18 import com.google.gwt.event.dom.client.HasChangeHandlers;
19 import com.google.gwt.event.dom.client.HasClickHandlers;
20 import com.google.gwt.event.dom.client.KeyCodes;
21 import com.google.gwt.event.dom.client.KeyDownEvent;
22 import com.google.gwt.event.dom.client.KeyDownHandler;
23 import com.google.gwt.event.dom.client.ScrollEvent;
24 import com.google.gwt.event.dom.client.ScrollHandler;
25 import com.google.gwt.event.shared.HandlerRegistration;
26 import com.google.gwt.resources.client.CssResource;
27 import com.google.gwt.uibinder.client.UiBinder;
28 import com.google.gwt.uibinder.client.UiField;
29 import com.google.gwt.uibinder.client.UiHandler;
30 import com.google.gwt.user.client.Element;
31 import com.google.gwt.user.client.ui.CheckBox;
32 import com.google.gwt.user.client.ui.Composite;
33 import com.google.gwt.user.client.ui.FlexTable;
34 import com.google.gwt.user.client.ui.FocusPanel;
35 import com.google.gwt.user.client.ui.HTMLPanel;
36 import com.google.gwt.user.client.ui.Label;
37 import com.google.gwt.user.client.ui.ScrollPanel;
38 import com.google.gwt.user.client.ui.Widget;
39 import com.google.gwt.user.client.ui.HTMLTable.Cell;
40 import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
41
42
43
44
45 public class Table extends Composite implements HasRetrieveAdditionalDataHandlers, HasSelectionChangeHandlers {
46
47 private int headerSelectedCellIndex = -1;
48
49 private FocusType focusType = FocusType.NONE;
50
51 private static TableUiBinder uiBinder = GWT.create(TableUiBinder.class);
52 private final List<RetrieveAdditionalDataHandler> retrieveDataHandlers = new ArrayList<RetrieveAdditionalDataHandler>();
53
54 interface TableUiBinder extends UiBinder<Widget, Table> {}
55
56 interface SelectionStyle extends CssResource {
57 String selectedRow();
58
59 String columnAscending();
60
61 String columnDescending();
62
63 String selectedHeaderCell();
64 }
65
66 private static enum FocusType {
67 HEADER, BODY, NONE
68 }
69
70 @UiField
71 FlexTable header;
72 @UiField
73 MouseHoverFlexTable table;
74 @UiField
75 SelectionStyle selectionStyle;
76 @UiField
77 ScrollPanel scrollPanel;
78 @UiField
79 FocusPanel focusPanel;
80 @UiField
81 FocusPanel headerFocusPanel;
82 @UiField
83 HTMLPanel panel;
84
85 private TableModel tableModel;
86 private final LoadingDiv loading = new LoadingDiv();
87
88 public Table() {
89 initWidget(uiBinder.createAndBindUi(this));
90
91 scrollPanel.addScrollHandler(new ScrollHandler() {
92 @Override
93 public void onScroll(ScrollEvent event) {
94 int position = scrollPanel.getScrollPosition();
95
96 if (null != scrollPanel.getWidget()) {
97 int size = scrollPanel.getWidget().getOffsetHeight();
98 int diff = size - scrollPanel.getOffsetHeight();
99 if (position == diff) {
100 for (int i = 0; i < retrieveDataHandlers.size(); i++) {
101 retrieveDataHandlers.get(i).onAdditionalDataRequest();
102 }
103 }
104 }
105 }
106 });
107 addHandlers();
108 }
109
110 public void removeAllRows() {
111 table.removeAllRows();
112 }
113
114 public void removeContent() {
115 getScrollPanel().clear();
116 }
117
118 public void addContent() {
119 getScrollPanel().setWidget(getContent());
120 }
121
122 private void addHandlers() {
123 focusPanel.addKeyDownHandler(new KeyDownHandler() {
124
125 @Override
126 public void onKeyDown(KeyDownEvent event) {
127 int code = event.getNativeKeyCode();
128 if (code == KeyCodes.KEY_DOWN) {
129 processKeyUpAndDownEvent(TablePredicateFactory.DOWN_RIGHT_PREDICATE);
130 } else if (code == KeyCodes.KEY_UP) {
131 processKeyUpAndDownEvent(TablePredicateFactory.UP_LEFT_PREDICATE);
132 } else if (code == ' ') {
133 processSpaceClick();
134 }
135 }
136 });
137 headerFocusPanel.addKeyDownHandler(new KeyDownHandler() {
138 @Override
139 public void onKeyDown(KeyDownEvent event) {
140 int code = event.getNativeKeyCode();
141 if (code == KeyCodes.KEY_RIGHT) {
142 processKeyLeftRight(TablePredicateFactory.DOWN_RIGHT_PREDICATE);
143 } else if (code == KeyCodes.KEY_LEFT) {
144 processKeyLeftRight(TablePredicateFactory.UP_LEFT_PREDICATE);
145 } else if (code == KeyCodes.KEY_DOWN) {
146 processKeyDownOnHeader();
147 } else if (code == ' ') {
148 onTableHeaderClicked(headerSelectedCellIndex, true);
149 }
150 }
151 });
152 }
153
154 private void processKeyDownOnHeader() {
155 changeFocus(FocusType.BODY);
156 Row currentRow = tableModel.getRow(0);
157 tableModel.setCurrentIndex(0);
158 currentRow.setHighlighted(true);
159 updateTableSelection();
160 removeHeaderSelection();
161 }
162
163 private void removeHeaderSelection() {
164 if (headerSelectedCellIndex >= 0) {
165 header.getCellFormatter().removeStyleName(0, headerSelectedCellIndex, selectionStyle.selectedHeaderCell());
166 headerSelectedCellIndex = -1;
167 }
168 }
169
170 private void processKeyLeftRight(TablePredicateFactory.Predicate tablePredicate) {
171 if (tablePredicate.indexCondition(headerSelectedCellIndex, tableModel.getColumnCount())) {
172 header.getCellFormatter().removeStyleName(0, headerSelectedCellIndex, selectionStyle.selectedHeaderCell());
173 headerSelectedCellIndex = tablePredicate.nextIndex(headerSelectedCellIndex);
174 header.getCellFormatter().addStyleName(0, headerSelectedCellIndex, selectionStyle.selectedHeaderCell());
175
176 }
177 }
178
179 private void processSpaceClick() {
180 int index = tableModel.getCurrentIndex();
181 if (index >= 0) {
182 Row currentRow = tableModel.getRow(index);
183 boolean selected = currentRow.isSelected();
184 if (selected) {
185 currentRow.setSelected(false);
186 } else {
187 tableModel.setSelectedRow(index);
188 }
189 updateTableSelection();
190 }
191 }
192
193 private void processKeyUpAndDownEvent(TablePredicateFactory.Predicate tablePredicate) {
194 int currentIndex = tableModel.getCurrentIndex();
195 Row currentRow = tableModel.getRow(currentIndex);
196 if (currentIndex == 0 && tablePredicate.moveType() == TablePredicateFactory.MoveType.UP_LEFT) {
197 tableModel.getRow(currentIndex).setHighlighted(false);
198 tableModel.setCurrentIndex(-1);
199 headerSelectedCellIndex = 0;
200 changeFocus(FocusType.HEADER);
201 header.getCellFormatter().addStyleName(0, 0, selectionStyle.selectedHeaderCell());
202 } else {
203 if (currentRow.isHighlighted() && tablePredicate.indexCondition(currentIndex, tableModel.getRowCount())) {
204 currentRow.setHighlighted(false);
205 currentIndex = tablePredicate.nextIndex(currentIndex);
206 tableModel.getRow(currentIndex).setHighlighted(true);
207 tableModel.setCurrentIndex(currentIndex);
208 scrollPanel.ensureVisible(table.getWidget(currentIndex, 0));
209 }
210 }
211 updateTableSelection();
212 }
213
214 public FlexTable getHeader() {
215 return header;
216 }
217
218 public FlexTable getContent() {
219 return table;
220 }
221
222 public ScrollPanel getScrollPanel() {
223 return scrollPanel;
224 }
225
226 public void setTableModel(TableModel m) {
227 tableModel = m;
228 table.setModel(tableModel);
229 if (m instanceof AbstractTableModel) {
230 ((AbstractTableModel) tableModel).addTableModelListener(new TableModelListener() {
231 @Override
232 public void tableChanged(TableModelEvent e) {
233 updateTable(e);
234 }
235 });
236 ((AbstractTableModel) tableModel).fireTableStructureChanged();
237 }
238 }
239
240 public TableModel getTableModel() {
241 return tableModel;
242 }
243
244 @UiHandler("table")
245 void onTableClicked(ClickEvent event) {
246 removeHeaderSelection();
247
248 Cell cell = table.getCellForEvent(event);
249
250 if (cell == null) {
251 return;
252 }
253 int cellIndex = cell.getCellIndex();
254 int rowIndex = cell.getRowIndex();
255 tableModel.setCurrentIndex(rowIndex);
256 Row row = tableModel.getRow(rowIndex);
257
258 if (tableModel.isMultipleSelectable() == false) {
259 for (int r = 0; r < tableModel.getRowCount(); r++) {
260 if (r != rowIndex) {
261 tableModel.getRow(r).setSelected(false);
262 }
263 }
264 row.setSelected(!row.isSelected());
265 updateTableSelection();
266 for (int r = 0; r < tableModel.getRowCount(); r++) {
267 updateTableCell(r, 0);
268 }
269 } else {
270 if (cellIndex == 0) {
271 return;
272 }
273 row.setSelected(!row.isSelected());
274 updateTableSelection();
275 updateTableCell(rowIndex, 0);
276 }
277 }
278
279 @UiHandler("header")
280 void onTableHeaderClicked(ClickEvent event) {
281 Cell cell = header.getCellForEvent(event);
282 if (cell != null) {
283 onTableHeaderClicked(cell.getCellIndex(), false);
284 }
285 }
286
287 private void onTableHeaderClicked(int cellIndex, boolean propagateEventIfNotSortingColumn) {
288 if (cellIndex == 0 && tableModel.isMultipleSelectable() && propagateEventIfNotSortingColumn) {
289 Widget widget = header.getWidget(0, 0);
290 if (widget instanceof CheckBox) {
291 CheckBox checkBox = (CheckBox) widget;
292 boolean correctValue = !checkBox.getValue();
293 checkBox.setValue(correctValue);
294 for (int row = 0; row < tableModel.getRowCount(); row++) {
295 tableModel.getRow(row).setSelected(correctValue);
296 updateTableSelection();
297 }
298 }
299 } else {
300 removeHeaderSelection();
301 }
302 headerSelectedCellIndex = cellIndex;
303 Column col = tableModel.getColumn(cellIndex);
304 tableModel.sort(col);
305 }
306
307 private void onTableClicked(int row, String columnId, TableCellWidget cellWidget) {
308 onTableCellChanged(row, columnId, cellWidget);
309 }
310
311 private void onTableCellChanged(int rowIndex, String columnId, TableCellWidget cellWidget) {
312 Row row = tableModel.getRow(rowIndex);
313 if ("RowHeader".equals(columnId)) {
314 row.setSelected(!row.isSelected());
315 updateTableSelection();
316 }
317 row.setCellData(columnId, cellWidget.getCellEditorValue());
318 }
319
320 private void updateTableSelection() {
321 int count = tableModel.getRowCount();
322 String attrName = BrowserUtils.getClassAttr();
323 for (int i = 0; i < count; i++) {
324 Element tr = table.getRowFormatter().getElement(i);
325 if (tableModel.getRow(i).isSelected()) {
326 tr.setAttribute(attrName, "table-row-selected");
327 } else {
328 tr.setAttribute(attrName, "table-row");
329 }
330 if (tableModel.getRow(i).isHighlighted()) {
331 tr.setAttribute(attrName, "table-row-hover");
332 }
333 if (tableModel.isMultipleSelectable()) {
334 updateTableCell(i, 0);
335 }
336 }
337 SelectionChangeEvent.fire(this);
338 }
339
340 private void changeFocus(FocusType focusType) {
341 this.focusType = focusType;
342 if (focusType == FocusType.HEADER) {
343 headerFocusPanel.setFocus(true);
344 } else if (focusType == FocusType.BODY) {
345 focusPanel.setFocus(true);
346 }
347 }
348
349 public void updateTable(TableModelEvent event) {
350 if (event.getType() == TableModelEvent.TableStructure) {
351 updateTableStructure();
352 updateTableData();
353 } else if (event.getType() == TableModelEvent.TableData) {
354 updateTableData();
355 } else if (event.getType() == TableModelEvent.CellUpdate) {
356 updateTableCell(event.getFirstRow(), event.getColumn());
357 }
358 }
359
360 private void updateTableData() {
361 for (int r = 0; r < tableModel.getRowCount(); r++) {
362 int columnCount = tableModel.getColumnCount();
363 for (int c = 0; c < columnCount; c++) {
364 updateTableCell(r, c);
365 }
366 }
367 updateTableSelection();
368 }
369
370 private void updateTableCell(final int r, final int c) {
371 int columnCount = tableModel.getColumnCount();
372 for (int i = 0; i < columnCount - 1; i++) {
373 Column col = tableModel.getColumn(i);
374 table.getColumnFormatter().setWidth(i, col.getWidth());
375 }
376 final String columnId = tableModel.getColumn(c).getId();
377 Row row = tableModel.getRow(r);
378 Object v = null;
379 if ("RowHeader".equals(columnId)) {
380 v = row.isSelected();
381 } else {
382 v = row.getCellData(columnId);
383 }
384 if ("RowHeader".equals(columnId) == false) {
385 if (v != null) {
386 table.setWidget(r, c, new Label(v.toString()));
387 } else {
388 table.setHTML(r, c, " ");
389 }
390 return;
391 }
392 final TableCellWidget widget = new TableCellWidget(v);
393 final StringBuilder debugId = new StringBuilder();
394 if ("RowHeader".equals(columnId)) {
395
396
397 for (int i = 1; i < columnCount; i++) {
398 Column column = tableModel.getColumn(i);
399 Object value = row.getCellData(column.getId());
400 debugId.append(value);
401 if (i != columnCount - 1) {
402 debugId.append("-");
403 }
404 }
405 widget.getDefaultTableEditor().ensureDebugId(DebugIdUtils.createWebDriverSafeDebugId(debugId.toString()));
406 }
407 widget.setCellEditorValue(v);
408 if (widget instanceof HasClickHandlers) {
409 ((HasClickHandlers) widget).addClickHandler(new ClickHandler() {
410 @Override
411 public void onClick(ClickEvent event) {
412 onTableClicked(r, columnId, widget);
413 }
414 });
415 }
416 if (widget instanceof HasChangeHandlers) {
417 ((HasChangeHandlers) widget).addChangeHandler(new ChangeHandler() {
418 @Override
419 public void onChange(ChangeEvent event) {
420 onTableCellChanged(r, columnId, widget);
421
422 }
423 });
424 }
425 table.setWidget(r, c, widget);
426 }
427
428 private void updateTableStructure() {
429 int columnCount = tableModel.getColumnCount();
430 for (int i = 0; i < columnCount; i++) {
431 Column col = tableModel.getColumn(i);
432 header.setWidget(0, i, col.getColumnTitleWidget());
433 }
434 for (int i = 0; i < columnCount - 1; i++) {
435 Column col = tableModel.getColumn(i);
436 header.getColumnFormatter().setWidth(i, col.getWidth());
437 }
438 for (int i = 0; i < columnCount; i++) {
439 Column col = tableModel.getColumn(i);
440
441 header.getCellFormatter().removeStyleName(0, i, selectionStyle.columnAscending());
442 header.getCellFormatter().removeStyleName(0, i, selectionStyle.columnDescending());
443
444 if (col.getSortDirection() == Column.Ascending) {
445 header.getCellFormatter().addStyleName(0, i, selectionStyle.columnAscending());
446 } else if (col.getSortDirection() == Column.Descending) {
447 header.getCellFormatter().addStyleName(0, i, selectionStyle.columnDescending());
448 }
449 }
450 }
451
452 @Override
453 public HandlerRegistration addRetrieveAdditionalDataHandler(final RetrieveAdditionalDataHandler handler) {
454 retrieveDataHandlers.add(handler);
455 HandlerRegistration result = new HandlerRegistration() {
456 @Override
457 public void removeHandler() {
458 retrieveDataHandlers.remove(handler);
459 }
460 };
461 return result;
462 }
463
464 public void displayLoading(boolean isLoading) {
465 changeFocus(FocusType.BODY);
466 final int x = scrollPanel.getAbsoluteLeft() + scrollPanel.getOffsetWidth();
467 final int y = scrollPanel.getAbsoluteTop() + scrollPanel.getOffsetHeight();
468 if (isLoading) {
469 loading.setPopupPositionAndShow(new PositionCallback() {
470
471 @Override
472 public void setPosition(int offsetWidth, int offsetHeight) {
473 loading.setPopupPosition(x - offsetWidth, y + 1);
474 }
475 });
476 } else {
477 loading.hide();
478 }
479 }
480
481
482
483
484 @Override
485 public HandlerRegistration addSelectionChangeHandler(SelectionChangeHandler handler) {
486 return addHandler(handler, SelectionChangeEvent.getType());
487 }
488 }