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