View Javadoc
1   /*
2    * Copyright 2000-2018 Vaadin Ltd.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5    * use this file except in compliance with the License. You may obtain a copy of
6    * the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations under
14   * the License.
15   */
16  package com.vaadin.v7.client.ui.table;
17  
18  import java.util.Collections;
19  import java.util.List;
20  
21  import com.google.gwt.core.client.Scheduler;
22  import com.google.gwt.core.client.Scheduler.ScheduledCommand;
23  import com.google.gwt.dom.client.Element;
24  import com.google.gwt.dom.client.EventTarget;
25  import com.google.gwt.event.shared.HandlerRegistration;
26  import com.google.gwt.user.client.ui.Widget;
27  import com.vaadin.client.ApplicationConnection;
28  import com.vaadin.client.ComponentConnector;
29  import com.vaadin.client.ConnectorHierarchyChangeEvent;
30  import com.vaadin.client.ConnectorHierarchyChangeEvent.ConnectorHierarchyChangeHandler;
31  import com.vaadin.client.DirectionalManagedLayout;
32  import com.vaadin.client.HasChildMeasurementHintConnector;
33  import com.vaadin.client.Paintable;
34  import com.vaadin.client.ServerConnector;
35  import com.vaadin.client.TooltipInfo;
36  import com.vaadin.client.UIDL;
37  import com.vaadin.client.WidgetUtil;
38  import com.vaadin.client.ui.PostLayoutListener;
39  import com.vaadin.shared.MouseEventDetails;
40  import com.vaadin.v7.client.ui.AbstractFieldConnector;
41  import com.vaadin.v7.client.ui.VScrollTable.FooterCell;
42  import com.vaadin.v7.client.ui.VScrollTable.HeaderCell;
43  import com.vaadin.v7.client.ui.VScrollTablePatched;
44  import com.vaadin.v7.client.ui.VScrollTablePatched.ContextMenuDetails;
45  import com.vaadin.v7.client.ui.VScrollTablePatched.VScrollTableBody.VScrollTableRow;
46  import com.vaadin.v7.shared.ui.table.TableConstants;
47  import com.vaadin.v7.shared.ui.table.TableConstants.Section;
48  import com.vaadin.v7.shared.ui.table.TableServerRpc;
49  import com.vaadin.v7.shared.ui.table.TableState;
50  
51  //@Connect(com.vaadin.v7.ui.Table.class)
52  public class TableConnectorPatched extends AbstractFieldConnector
53          implements ConnectorHierarchyChangeHandler,
54          Paintable, DirectionalManagedLayout, PostLayoutListener,
55          HasChildMeasurementHintConnector {
56  
57      private List<ComponentConnector> childComponents;
58  
59      public TableConnectorPatched() {
60          addConnectorHierarchyChangeHandler(this);
61      }
62  
63      @Override
64      protected void init() {
65          super.init();
66          getWidget().init(getConnection());
67      }
68  
69      /*
70       * (non-Javadoc)
71       *
72       * @see com.vaadin.client.ui.AbstractComponentConnector#onUnregister()
73       */
74      @Override
75      public void onUnregister() {
76          super.onUnregister();
77          getWidget().onUnregister();
78      }
79  
80      @Override
81      protected void sendContextClickEvent(MouseEventDetails details,
82              EventTarget eventTarget) {
83  
84          if (!Element.is(eventTarget)) {
85              return;
86          }
87          Element e = Element.as(eventTarget);
88  
89          Section section;
90          String colKey = null;
91          String rowKey = null;
92          if (getWidget().tFoot.getElement().isOrHasChild(e)) {
93              section = Section.FOOTER;
94              FooterCell w = WidgetUtil.findWidget(e, FooterCell.class);
95              colKey = w.getColKey();
96          } else if (getWidget().tHead.getElement().isOrHasChild(e)) {
97              section = Section.HEADER;
98              HeaderCell w = WidgetUtil.findWidget(e, HeaderCell.class);
99              colKey = w.getColKey();
100         } else {
101             section = Section.BODY;
102             if (getWidget().scrollBody.getElement().isOrHasChild(e)) {
103                 VScrollTableRow w = getScrollTableRow(e);
104                 /*
105                  * if w is null because we've clicked on an empty area, we will
106                  * let rowKey and colKey be null too, which will then lead to
107                  * the server side returning a null object.
108                  */
109                 if (w != null) {
110                     rowKey = w.getKey();
111                     colKey = getWidget().tHead
112                             .getHeaderCell(getElementIndex(e, w.getElement()))
113                             .getColKey();
114                 }
115             }
116         }
117 
118         getRpcProxy(TableServerRpc.class).contextClick(rowKey, colKey, section,
119                 details);
120 
121         WidgetUtil.clearTextSelection();
122     }
123 
124     protected VScrollTableRow getScrollTableRow(Element e) {
125         return WidgetUtil.findWidget(e, VScrollTableRow.class);
126     }
127 
128     private int getElementIndex(Element e,
129             com.google.gwt.user.client.Element element) {
130         int i = 0;
131         Element current = element.getFirstChildElement();
132         while (!current.isOrHasChild(e)) {
133             current = current.getNextSiblingElement();
134             ++i;
135         }
136         return i;
137     }
138 
139     /*
140      * (non-Javadoc)
141      *
142      * @see com.vaadin.client.Paintable#updateFromUIDL(com.vaadin.client.UIDL,
143      * com.vaadin.client.ApplicationConnection)
144      */
145     @Override
146     public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
147         getWidget().rendering = true;
148 
149         // If a row has an open context menu, it will be closed as the row is
150         // detached. Retain a reference here so we can restore the menu if
151         // required.
152         ContextMenuDetails contextMenuBeforeUpdate = getWidget().contextMenu;
153 
154         if (uidl.hasAttribute(TableConstants.ATTRIBUTE_PAGEBUFFER_FIRST)) {
155             getWidget().serverCacheFirst = uidl
156                     .getIntAttribute(TableConstants.ATTRIBUTE_PAGEBUFFER_FIRST);
157             getWidget().serverCacheLast = uidl
158                     .getIntAttribute(TableConstants.ATTRIBUTE_PAGEBUFFER_LAST);
159         } else {
160             getWidget().serverCacheFirst = -1;
161             getWidget().serverCacheLast = -1;
162         }
163         /*
164          * We need to do this before updateComponent since updateComponent calls
165          * this.setHeight() which will calculate a new body height depending on
166          * the space available.
167          */
168         if (uidl.hasAttribute("colfooters")) {
169             getWidget().showColFooters = uidl.getBooleanAttribute("colfooters");
170         }
171 
172         getWidget().tFoot.setVisible(getWidget().showColFooters);
173 
174         if (!isRealUpdate(uidl)) {
175             getWidget().rendering = false;
176             return;
177         }
178 
179         getWidget().paintableId = uidl.getStringAttribute("id");
180         getWidget().immediate = getState().immediate;
181 
182         int previousTotalRows = getWidget().totalRows;
183         getWidget().updateTotalRows(uidl);
184         boolean totalRowsHaveChanged = (getWidget().totalRows != previousTotalRows);
185 
186         getWidget().updateDragMode(uidl);
187 
188         // Update child measure hint
189         int childMeasureHint = uidl.hasAttribute("measurehint")
190                 ? uidl.getIntAttribute("measurehint")
191                 : 0;
192         getWidget().setChildMeasurementHint(
193                 ChildMeasurementHint.values()[childMeasureHint]);
194 
195         getWidget().updateSelectionProperties(uidl, getState(), isReadOnly());
196 
197         if (uidl.hasAttribute("alb")) {
198             getWidget().bodyActionKeys = uidl.getStringArrayAttribute("alb");
199         } else {
200             // Need to clear the actions if the action handlers have been
201             // removed
202             getWidget().bodyActionKeys = null;
203         }
204 
205         getWidget().setCacheRateFromUIDL(uidl);
206 
207         getWidget().recalcWidths = uidl.hasAttribute("recalcWidths");
208         if (getWidget().recalcWidths) {
209             getWidget().tHead.clear();
210             getWidget().tFoot.clear();
211         }
212 
213         getWidget().updatePageLength(uidl);
214 
215         getWidget().updateFirstVisibleAndScrollIfNeeded(uidl);
216 
217         getWidget().showRowHeaders = uidl.getBooleanAttribute("rowheaders");
218         getWidget().showColHeaders = uidl.getBooleanAttribute("colheaders");
219 
220         getWidget().updateSortingProperties(uidl);
221 
222         getWidget().updateActionMap(uidl);
223 
224         getWidget().updateColumnProperties(uidl);
225 
226         UIDL ac = uidl.getChildByTagName("-ac");
227         if (ac == null) {
228             if (getWidget().dropHandler != null) {
229                 // remove dropHandler if not present anymore
230                 getWidget().dropHandler = null;
231             }
232         } else {
233             if (getWidget().dropHandler == null) {
234                 getWidget().dropHandler = getWidget().new VScrollTableDropHandler();
235             }
236             getWidget().dropHandler.updateAcceptRules(ac);
237         }
238 
239         UIDL partialRowAdditions = uidl.getChildByTagName("prows");
240         UIDL partialRowUpdates = uidl.getChildByTagName("urows");
241         if (partialRowUpdates != null || partialRowAdditions != null) {
242             getWidget().postponeSanityCheckForLastRendered = true;
243             // we may have pending cache row fetch, cancel it. See #2136
244             getWidget().rowRequestHandler.cancel();
245 
246             getWidget().updateRowsInBody(partialRowUpdates);
247             getWidget().addAndRemoveRows(partialRowAdditions);
248 
249             // sanity check (in case the value has slipped beyond the total
250             // amount of rows)
251             getWidget().scrollBody
252                     .setLastRendered(getWidget().scrollBody.getLastRendered());
253             getWidget().updateMaxIndent();
254         } else {
255             getWidget().postponeSanityCheckForLastRendered = false;
256             UIDL rowData = uidl.getChildByTagName("rows");
257             if (rowData != null) {
258                 // we may have pending cache row fetch, cancel it. See #2136
259                 getWidget().rowRequestHandler.cancel();
260 
261                 if (!getWidget().recalcWidths
262                         && getWidget().initializedAndAttached) {
263                     getWidget().updateBody(rowData,
264                             uidl.getIntAttribute("firstrow"),
265                             uidl.getIntAttribute("rows"));
266                     if (getWidget().headerChangedDuringUpdate) {
267                         getWidget().triggerLazyColumnAdjustment(true);
268                     }
269                 } else {
270                     getWidget().initializeRows(uidl, rowData);
271                 }
272             }
273         }
274 
275         boolean keyboardSelectionOverRowFetchInProgress = getWidget()
276                 .selectSelectedRows(uidl);
277 
278         // If a row had an open context menu before the update, and after the
279         // update there's a row with the same key as that row, restore the
280         // context menu. See #8526.
281         showSavedContextMenu(contextMenuBeforeUpdate);
282 
283         if (!getWidget().isSelectable()) {
284             getWidget().scrollBody.addStyleName(
285                     getWidget().getStylePrimaryName() + "-body-noselection");
286         } else {
287             getWidget().scrollBody.removeStyleName(
288                     getWidget().getStylePrimaryName() + "-body-noselection");
289         }
290 
291         getWidget().hideScrollPositionAnnotation();
292 
293         // selection is no in sync with server, avoid excessive server visits by
294         // clearing to flag used during the normal operation
295         if (!keyboardSelectionOverRowFetchInProgress) {
296             getWidget().selectionChanged = false;
297         }
298 
299         /*
300          * This is called when the Home or page up button has been pressed in
301          * selectable mode and the next selected row was not yet rendered in the
302          * client
303          */
304         if (getWidget().selectFirstItemInNextRender
305                 || getWidget().focusFirstItemInNextRender) {
306             getWidget().selectFirstRenderedRowInViewPort(
307                     getWidget().focusFirstItemInNextRender);
308             getWidget().selectFirstItemInNextRender = getWidget().focusFirstItemInNextRender = false;
309         }
310 
311         /*
312          * This is called when the page down or end button has been pressed in
313          * selectable mode and the next selected row was not yet rendered in the
314          * client
315          */
316         if (getWidget().selectLastItemInNextRender
317                 || getWidget().focusLastItemInNextRender) {
318             getWidget().selectLastRenderedRowInViewPort(
319                     getWidget().focusLastItemInNextRender);
320             getWidget().selectLastItemInNextRender = getWidget().focusLastItemInNextRender = false;
321         }
322         getWidget().multiselectPending = false;
323 
324         if (getWidget().focusedRow != null) {
325             if (!getWidget().focusedRow.isAttached()
326                     && !getWidget().rowRequestHandler
327                             .isRequestHandlerRunning()) {
328                 // focused row has been orphaned, can't focus
329                 if (getWidget().selectedRowKeys
330                         .contains(getWidget().focusedRow.getKey())) {
331                     // if row cache was refreshed, focused row should be
332                     // in selection and exists with same index
333                     getWidget().setRowFocus(getWidget().getRenderedRowByKey(
334                             getWidget().focusedRow.getKey()));
335                 } else if (!getWidget().selectedRowKeys.isEmpty()) {
336                     // try to focus any row in selection
337                     getWidget().setRowFocus(getWidget().getRenderedRowByKey(
338                             getWidget().selectedRowKeys.iterator().next()));
339                 } else {
340                     // try to focus any row
341                     getWidget().focusRowFromBody();
342                 }
343             }
344         }
345 
346         /*
347          * If the server has (re)initialized the rows, our selectionRangeStart
348          * row will point to an index that the server knows nothing about,
349          * causing problems if doing multi selection with shift. The field will
350          * be cleared a little later when the row focus has been restored.
351          * (#8584)
352          */
353         if (uidl.hasAttribute(TableConstants.ATTRIBUTE_KEY_MAPPER_RESET)
354                 && uidl.getBooleanAttribute(
355                         TableConstants.ATTRIBUTE_KEY_MAPPER_RESET)
356                 && getWidget().selectionRangeStart != null) {
357             assert !getWidget().selectionRangeStart.isAttached();
358             getWidget().selectionRangeStart = getWidget().focusedRow;
359         }
360 
361         getWidget().tabIndex = getState().tabIndex;
362         getWidget().setProperTabIndex();
363 
364         Scheduler.get().scheduleFinally(new ScheduledCommand() {
365 
366             @Override
367             public void execute() {
368                 getWidget().resizeSortedColumnForSortIndicator();
369             }
370         });
371 
372         // Remember this to detect situations where overflow hack might be
373         // needed during scrolling
374         getWidget().lastRenderedHeight = getWidget().scrollBody
375                 .getOffsetHeight();
376 
377         getWidget().rendering = false;
378         getWidget().headerChangedDuringUpdate = false;
379 
380         getWidget().collapsibleMenuContent = getState().collapseMenuContent;
381     }
382 
383     @Override
384     public void updateEnabledState(boolean enabledState) {
385         super.updateEnabledState(enabledState);
386         getWidget().enabled = isEnabled();
387 
388         // IE8 is no longer supported
389     }
390 
391     @Override
392     public VScrollTablePatched getWidget() {
393         return (VScrollTablePatched) super.getWidget();
394     }
395 
396     @Override
397     public void updateCaption(ComponentConnector component) {
398         // NOP, not rendered
399     }
400 
401     @Override
402     public void layoutVertically() {
403         getWidget().updateHeight();
404     }
405 
406     @Override
407     public void layoutHorizontally() {
408         getWidget().updateWidth();
409     }
410 
411     @Override
412     public void postLayout() {
413         VScrollTablePatched table = getWidget();
414         if (table.isVisibleInHierarchy() && table.sizeNeedsInit) {
415             table.sizeInit();
416             Scheduler.get().scheduleFinally(new ScheduledCommand() {
417                 @Override
418                 public void execute() {
419                     // IE8 is no longer supported
420                     getLayoutManager().setNeedsMeasure(TableConnectorPatched.this);
421                     ServerConnector parent = getParent();
422                     if (parent instanceof ComponentConnector) {
423                         getLayoutManager()
424                                 .setNeedsMeasure((ComponentConnector) parent);
425                     }
426                     getLayoutManager()
427                             .setNeedsVerticalLayout(TableConnectorPatched.this);
428                     getLayoutManager().layoutNow();
429                 }
430             });
431         }
432     }
433 
434     @Override
435     public boolean isReadOnly() {
436         return super.isReadOnly() || getState().propertyReadOnly;
437     }
438 
439     @Override
440     public TableState getState() {
441         return (TableState) super.getState();
442     }
443 
444     /**
445      * Shows a saved row context menu if the row for the context menu is still
446      * visible. Does nothing if a context menu has not been saved.
447      *
448      * @param savedContextMenu
449      */
450     public void showSavedContextMenu(ContextMenuDetails savedContextMenu) {
451         if (isEnabled() && savedContextMenu != null) {
452             for (Widget w : getWidget().scrollBody) {
453                 VScrollTableRow row = (VScrollTableRow) w;
454                 if (row.getKey().equals(savedContextMenu.rowKey)) {
455                     row.showContextMenu(savedContextMenu.left,
456                             savedContextMenu.top);
457                 }
458             }
459         }
460     }
461 
462     @Override
463     public TooltipInfo getTooltipInfo(Element element) {
464 
465         TooltipInfo info = null;
466 
467         if (element != getWidget().getElement()) {
468             Object node = WidgetUtil.findWidget(element, getWidget().scrollBody.iterator().next().getClass());
469 
470             if (node != null) {
471                 VScrollTableRow row = (VScrollTableRow) node;
472                 info = row.getTooltip(element);
473             }
474         }
475 
476         if (info == null) {
477             info = super.getTooltipInfo(element);
478         }
479 
480         return info;
481     }
482 
483     @Override
484     public boolean hasTooltip() {
485         /*
486          * Tooltips for individual rows and cells are not processed until
487          * updateFromUIDL, so we can't be sure that there are no tooltips during
488          * onStateChange when this method is used.
489          */
490         return true;
491     }
492 
493     @Override
494     public void onConnectorHierarchyChange(
495             ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent) {
496         // TODO Move code from updateFromUIDL to this method
497     }
498 
499     @Override
500     protected void updateComponentSize(String newWidth, String newHeight) {
501         super.updateComponentSize(newWidth, newHeight);
502 
503         if ("".equals(newWidth)) {
504             getWidget().updateWidth();
505         }
506         if ("".equals(newHeight)) {
507             getWidget().updateHeight();
508         }
509     }
510 
511     @Override
512     public List<ComponentConnector> getChildComponents() {
513         if (childComponents == null) {
514             return Collections.emptyList();
515         }
516 
517         return childComponents;
518     }
519 
520     @Override
521     public void setChildComponents(List<ComponentConnector> childComponents) {
522         this.childComponents = childComponents;
523     }
524 
525     @Override
526     public HandlerRegistration addConnectorHierarchyChangeHandler(
527             ConnectorHierarchyChangeHandler handler) {
528         return ensureHandlerManager()
529                 .addHandler(ConnectorHierarchyChangeEvent.TYPE, handler);
530     }
531 
532     @Override
533     public void setChildMeasurementHint(ChildMeasurementHint hint) {
534         getWidget().setChildMeasurementHint(hint);
535     }
536 
537     @Override
538     public ChildMeasurementHint getChildMeasurementHint() {
539         return getWidget().getChildMeasurementHint();
540     }
541 
542 }