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