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