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.Iterator;
19  
20  import com.google.gwt.core.client.Scheduler;
21  import com.google.gwt.core.client.Scheduler.ScheduledCommand;
22  import com.google.gwt.dom.client.Element;
23  import com.google.gwt.dom.client.Style.Position;
24  import com.google.gwt.user.client.ui.Widget;
25  import com.vaadin.client.ApplicationConnection;
26  import com.vaadin.client.BrowserInfo;
27  import com.vaadin.client.ComponentConnector;
28  import com.vaadin.client.ConnectorHierarchyChangeEvent;
29  import com.vaadin.client.DirectionalManagedLayout;
30  import com.vaadin.client.Paintable;
31  import com.vaadin.client.ServerConnector;
32  import com.vaadin.client.TooltipInfo;
33  import com.vaadin.client.UIDL;
34  import com.vaadin.client.WidgetUtil;
35  import com.vaadin.client.ui.AbstractHasComponentsConnector;
36  import com.vaadin.client.ui.PostLayoutListener;
37  import com.vaadin.client.ui.VScrollTablePatched;
38  import com.vaadin.client.ui.VScrollTablePatched.ContextMenuDetails;
39  import com.vaadin.client.ui.VScrollTablePatched.VScrollTableBody.VScrollTableRow;
40  import com.vaadin.shared.ui.Connect;
41  import com.vaadin.shared.ui.table.TableConstants;
42  import com.vaadin.shared.ui.table.TableState;
43  
44  //@Connect(com.vaadin.ui.Table.class)
45  public class TableConnectorPatched extends AbstractHasComponentsConnector implements
46          Paintable, DirectionalManagedLayout, PostLayoutListener {
47  
48      @Override
49      protected void init() {
50          super.init();
51          getWidget().init(getConnection());
52      }
53  
54      /*
55       * (non-Javadoc)
56       * 
57       * @see com.vaadin.client.ui.AbstractComponentConnector#onUnregister()
58       */
59      @Override
60      public void onUnregister() {
61          super.onUnregister();
62          getWidget().onUnregister();
63      }
64  
65      /*
66       * (non-Javadoc)
67       * 
68       * @see com.vaadin.client.Paintable#updateFromUIDL(com.vaadin.client.UIDL,
69       * com.vaadin.client.ApplicationConnection)
70       */
71      @Override
72      public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
73          getWidget().rendering = true;
74  
75          // If a row has an open context menu, it will be closed as the row is
76          // detached. Retain a reference here so we can restore the menu if
77          // required.
78          ContextMenuDetails contextMenuBeforeUpdate = getWidget().contextMenu;
79  
80          if (uidl.hasAttribute(TableConstants.ATTRIBUTE_PAGEBUFFER_FIRST)) {
81              getWidget().serverCacheFirst = uidl
82                      .getIntAttribute(TableConstants.ATTRIBUTE_PAGEBUFFER_FIRST);
83              getWidget().serverCacheLast = uidl
84                      .getIntAttribute(TableConstants.ATTRIBUTE_PAGEBUFFER_LAST);
85          } else {
86              getWidget().serverCacheFirst = -1;
87              getWidget().serverCacheLast = -1;
88          }
89          /*
90           * We need to do this before updateComponent since updateComponent calls
91           * this.setHeight() which will calculate a new body height depending on
92           * the space available.
93           */
94          if (uidl.hasAttribute("colfooters")) {
95              getWidget().showColFooters = uidl.getBooleanAttribute("colfooters");
96          }
97  
98          getWidget().tFoot.setVisible(getWidget().showColFooters);
99  
100         if (!isRealUpdate(uidl)) {
101             getWidget().rendering = false;
102             return;
103         }
104 
105         getWidget().enabled = isEnabled();
106 
107         if (BrowserInfo.get().isIE8() && !getWidget().enabled) {
108             /*
109              * The disabled shim will not cover the table body if it is relative
110              * in IE8. See #7324
111              */
112             getWidget().scrollBodyPanel.getElement().getStyle()
113                     .setPosition(Position.STATIC);
114         } else if (BrowserInfo.get().isIE8()) {
115             getWidget().scrollBodyPanel.getElement().getStyle()
116                     .setPosition(Position.RELATIVE);
117         }
118 
119         getWidget().paintableId = uidl.getStringAttribute("id");
120         getWidget().immediate = getState().immediate;
121 
122         int previousTotalRows = getWidget().totalRows;
123         getWidget().updateTotalRows(uidl);
124         boolean totalRowsHaveChanged = (getWidget().totalRows != previousTotalRows);
125 
126         getWidget().updateDragMode(uidl);
127 
128         getWidget().updateSelectionProperties(uidl, getState(), isReadOnly());
129 
130         if (uidl.hasAttribute("alb")) {
131             getWidget().bodyActionKeys = uidl.getStringArrayAttribute("alb");
132         } else {
133             // Need to clear the actions if the action handlers have been
134             // removed
135             getWidget().bodyActionKeys = null;
136         }
137 
138         getWidget().setCacheRateFromUIDL(uidl);
139 
140         getWidget().recalcWidths = uidl.hasAttribute("recalcWidths");
141         if (getWidget().recalcWidths) {
142             getWidget().tHead.clear();
143             getWidget().tFoot.clear();
144         }
145 
146         getWidget().updatePageLength(uidl);
147 
148         // MGNLUI-960 Avoid TreeTable jump for the time it repaints
149         getWidget().updateFirstVisibleAndScrollIfNeeded(uidl);
150 
151         getWidget().showRowHeaders = uidl.getBooleanAttribute("rowheaders");
152         getWidget().showColHeaders = uidl.getBooleanAttribute("colheaders");
153 
154         getWidget().updateSortingProperties(uidl);
155 
156         getWidget().updateActionMap(uidl);
157 
158         getWidget().updateColumnProperties(uidl);
159 
160         UIDL ac = uidl.getChildByTagName("-ac");
161         if (ac == null) {
162             if (getWidget().dropHandler != null) {
163                 // remove dropHandler if not present anymore
164                 getWidget().dropHandler = null;
165             }
166         } else {
167             if (getWidget().dropHandler == null) {
168                 getWidget().dropHandler = getWidget().new VScrollTableDropHandler();
169             }
170             getWidget().dropHandler.updateAcceptRules(ac);
171         }
172 
173         UIDL partialRowAdditions = uidl.getChildByTagName("prows");
174         UIDL partialRowUpdates = uidl.getChildByTagName("urows");
175         if (partialRowUpdates != null || partialRowAdditions != null) {
176             getWidget().postponeSanityCheckForLastRendered = true;
177             // we may have pending cache row fetch, cancel it. See #2136
178             getWidget().rowRequestHandler.cancel();
179 
180             getWidget().updateRowsInBody(partialRowUpdates);
181             getWidget().addAndRemoveRows(partialRowAdditions);
182 
183             // sanity check (in case the value has slipped beyond the total
184             // amount of rows)
185             getWidget().scrollBody.setLastRendered(getWidget().scrollBody
186                     .getLastRendered());
187             getWidget().updateMaxIndent();
188         } else {
189             getWidget().postponeSanityCheckForLastRendered = false;
190             UIDL rowData = uidl.getChildByTagName("rows");
191             if (rowData != null) {
192                 // we may have pending cache row fetch, cancel it. See #2136
193                 getWidget().rowRequestHandler.cancel();
194 
195                 if (!getWidget().recalcWidths
196                         && getWidget().initializedAndAttached) {
197                     getWidget().updateBody(rowData,
198                             uidl.getIntAttribute("firstrow"),
199                             uidl.getIntAttribute("rows"));
200                     if (getWidget().headerChangedDuringUpdate) {
201                         getWidget().triggerLazyColumnAdjustment(true);
202                     }
203                 } else {
204                     getWidget().initializeRows(uidl, rowData);
205                 }
206             }
207         }
208 
209         boolean keyboardSelectionOverRowFetchInProgress = getWidget()
210                 .selectSelectedRows(uidl);
211 
212         // If a row had an open context menu before the update, and after the
213         // update there's a row with the same key as that row, restore the
214         // context menu. See #8526.
215         showSavedContextMenu(contextMenuBeforeUpdate);
216 
217         if (!getWidget().isSelectable()) {
218             getWidget().scrollBody.addStyleName(getWidget()
219                     .getStylePrimaryName() + "-body-noselection");
220         } else {
221             getWidget().scrollBody.removeStyleName(getWidget()
222                     .getStylePrimaryName() + "-body-noselection");
223         }
224 
225         getWidget().hideScrollPositionAnnotation();
226 
227         // selection is no in sync with server, avoid excessive server visits by
228         // clearing to flag used during the normal operation
229         if (!keyboardSelectionOverRowFetchInProgress) {
230             getWidget().selectionChanged = false;
231         }
232 
233         /*
234          * This is called when the Home or page up button has been pressed in
235          * selectable mode and the next selected row was not yet rendered in the
236          * client
237          */
238         if (getWidget().selectFirstItemInNextRender
239                 || getWidget().focusFirstItemInNextRender) {
240             getWidget().selectFirstRenderedRowInViewPort(
241                     getWidget().focusFirstItemInNextRender);
242             getWidget().selectFirstItemInNextRender = getWidget().focusFirstItemInNextRender = false;
243         }
244 
245         /*
246          * This is called when the page down or end button has been pressed in
247          * selectable mode and the next selected row was not yet rendered in the
248          * client
249          */
250         if (getWidget().selectLastItemInNextRender
251                 || getWidget().focusLastItemInNextRender) {
252             getWidget().selectLastRenderedRowInViewPort(
253                     getWidget().focusLastItemInNextRender);
254             getWidget().selectLastItemInNextRender = getWidget().focusLastItemInNextRender = false;
255         }
256         getWidget().multiselectPending = false;
257 
258         if (getWidget().focusedRow != null) {
259             if (!getWidget().focusedRow.isAttached()
260                     && !getWidget().rowRequestHandler.isRequestHandlerRunning()) {
261                 // focused row has been orphaned, can't focus
262                 if (getWidget().selectedRowKeys.contains(getWidget().focusedRow
263                         .getKey())) {
264                     // if row cache was refreshed, focused row should be
265                     // in selection and exists with same index
266                     getWidget().setRowFocus(
267                             getWidget().getRenderedRowByKey(
268                                     getWidget().focusedRow.getKey()));
269                 } else if (getWidget().selectedRowKeys.size() > 0) {
270                     // try to focus any row in selection
271                     getWidget().setRowFocus(
272                             getWidget().getRenderedRowByKey(
273                                     getWidget().selectedRowKeys.iterator()
274                                             .next()));
275                 } else {
276                     // try to focus any row
277                     getWidget().focusRowFromBody();
278                 }
279             }
280         }
281 
282         /*
283          * If the server has (re)initialized the rows, our selectionRangeStart
284          * row will point to an index that the server knows nothing about,
285          * causing problems if doing multi selection with shift. The field will
286          * be cleared a little later when the row focus has been restored.
287          * (#8584)
288          */
289         if (uidl.hasAttribute(TableConstants.ATTRIBUTE_KEY_MAPPER_RESET)
290                 && uidl.getBooleanAttribute(TableConstants.ATTRIBUTE_KEY_MAPPER_RESET)
291                 && getWidget().selectionRangeStart != null) {
292             assert !getWidget().selectionRangeStart.isAttached();
293             getWidget().selectionRangeStart = getWidget().focusedRow;
294         }
295 
296         getWidget().tabIndex = getState().tabIndex;
297         getWidget().setProperTabIndex();
298 
299         Scheduler.get().scheduleFinally(new ScheduledCommand() {
300 
301             @Override
302             public void execute() {
303                 getWidget().resizeSortedColumnForSortIndicator();
304             }
305         });
306 
307         // Remember this to detect situations where overflow hack might be
308         // needed during scrolling
309         getWidget().lastRenderedHeight = getWidget().scrollBody
310                 .getOffsetHeight();
311 
312         getWidget().rendering = false;
313         getWidget().headerChangedDuringUpdate = false;
314 
315     }
316 
317     @Override
318     public VScrollTablePatched getWidget() {
319         return (VScrollTablePatched) super.getWidget();
320     }
321 
322     @Override
323     public void updateCaption(ComponentConnector component) {
324         // NOP, not rendered
325     }
326 
327     @Override
328     public void layoutVertically() {
329         getWidget().updateHeight();
330     }
331 
332     @Override
333     public void layoutHorizontally() {
334         getWidget().updateWidth();
335     }
336 
337     @Override
338     public void postLayout() {
339         VScrollTablePatched table = getWidget();
340         if (table.isVisibleInHierarchy() && table.sizeNeedsInit) {
341             table.sizeInit();
342             Scheduler.get().scheduleFinally(new ScheduledCommand() {
343                 @Override
344                 public void execute() {
345                     // IE8 needs some hacks to measure sizes correctly
346                     WidgetUtil.forceIE8Redraw(getWidget().getElement());
347 
348                     getLayoutManager().setNeedsMeasure(TableConnectorPatched.this);
349                     ServerConnector parent = getParent();
350                     if (parent instanceof ComponentConnector) {
351                         getLayoutManager().setNeedsMeasure(
352                                 (ComponentConnector) parent);
353                     }
354                     getLayoutManager().setNeedsVerticalLayout(
355                             TableConnectorPatched.this);
356                     getLayoutManager().layoutNow();
357                 }
358             });
359         }
360     }
361 
362     @Override
363     public boolean isReadOnly() {
364         return super.isReadOnly() || getState().propertyReadOnly;
365     }
366 
367     @Override
368     public TableState getState() {
369         return (TableState) super.getState();
370     }
371 
372     /**
373      * Shows a saved row context menu if the row for the context menu is still
374      * visible. Does nothing if a context menu has not been saved.
375      *
376      * @param savedContextMenu
377      */
378     public void showSavedContextMenu(ContextMenuDetails savedContextMenu) {
379         if (isEnabled() && savedContextMenu != null) {
380             Iterator<Widget> iterator = getWidget().scrollBody.iterator();
381             while (iterator.hasNext()) {
382                 Widget w = iterator.next();
383                 VScrollTableRow row = (VScrollTableRow) w;
384                 if (row.getKey().equals(savedContextMenu.rowKey)) {
385                     row.showContextMenu(savedContextMenu.left,
386                             savedContextMenu.top);
387                 }
388             }
389         }
390     }
391 
392     @Override
393     public TooltipInfo getTooltipInfo(Element element) {
394 
395         TooltipInfo info = null;
396 
397         if (element != getWidget().getElement()) {
398             Object node = WidgetUtil.findWidget(element, getWidget().scrollBody.iterator().next().getClass());
399 
400             if (node != null) {
401                 VScrollTableRow row = (VScrollTableRow) node;
402                 info = row.getTooltip(element);
403             }
404         }
405 
406         if (info == null) {
407             info = super.getTooltipInfo(element);
408         }
409 
410         return info;
411     }
412 
413     @Override
414     public boolean hasTooltip() {
415         /*
416          * Tooltips for individual rows and cells are not processed until
417          * updateFromUIDL, so we can't be sure that there are no tooltips during
418          * onStateChange when this method is used.
419          */
420         return true;
421     }
422 
423     @Override
424     public void onConnectorHierarchyChange(
425             ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent) {
426         // TODO Move code from updateFromUIDL to this method
427     }
428 
429     @Override
430     protected void updateComponentSize(String newWidth, String newHeight) {
431         super.updateComponentSize(newWidth, newHeight);
432 
433         if("".equals(newWidth)) {
434             getWidget().updateWidth();
435         }
436         if("".equals(newHeight)) {
437             getWidget().updateHeight();
438         }
439     }
440 }