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