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