View Javadoc

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   * A table with UiBinder.
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                  // see 6th comment in KSLAB-1790; possibly not created yet?
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         // changeFocus(FocusType.BODY);
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, "&nbsp;");
389             }
390             return;
391         }
392         final TableCellWidget widget = new TableCellWidget(v);
393         final StringBuilder debugId = new StringBuilder();
394         if ("RowHeader".equals(columnId)) {
395             //Setup debug id
396             //Skip the first 'row selection' column
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      * @see org.kuali.student.common.ui.client.widgets.list.HasSelectionChangeHandlers#addSelectionChangeHandler(org.kuali.student.common.ui.client.widgets.list.SelectionChangeHandler)
483      */
484     @Override
485     public HandlerRegistration addSelectionChangeHandler(SelectionChangeHandler handler) {
486         return addHandler(handler, SelectionChangeEvent.getType());
487     }
488 }