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         getWidget().updateFirstVisibleAndScrollIfNeeded(uidl);
139 
140         getWidget().showRowHeaders = uidl.getBooleanAttribute("rowheaders");
141         getWidget().showColHeaders = uidl.getBooleanAttribute("colheaders");
142 
143         getWidget().updateSortingProperties(uidl);
144 
145         boolean keyboardSelectionOverRowFetchInProgress = getWidget()
146                 .selectSelectedRows(uidl);
147 
148         getWidget().updateActionMap(uidl);
149 
150         getWidget().updateColumnProperties(uidl);
151 
152         UIDL ac = uidl.getChildByTagName("-ac");
153         if (ac == null) {
154             if (getWidget().dropHandler != null) {
155                 // remove dropHandler if not present anymore
156                 getWidget().dropHandler = null;
157             }
158         } else {
159             if (getWidget().dropHandler == null) {
160                 getWidget().dropHandler = getWidget().new VScrollTableDropHandler();
161             }
162             getWidget().dropHandler.updateAcceptRules(ac);
163         }
164 
165         UIDL partialRowAdditions = uidl.getChildByTagName("prows");
166         UIDL partialRowUpdates = uidl.getChildByTagName("urows");
167         if (partialRowUpdates != null || partialRowAdditions != null) {
168             getWidget().postponeSanityCheckForLastRendered = true;
169             // we may have pending cache row fetch, cancel it. See #2136
170             getWidget().rowRequestHandler.cancel();
171 
172             getWidget().updateRowsInBody(partialRowUpdates);
173             getWidget().addAndRemoveRows(partialRowAdditions);
174 
175             // sanity check (in case the value has slipped beyond the total
176             // amount of rows)
177             getWidget().scrollBody.setLastRendered(getWidget().scrollBody
178                     .getLastRendered());
179             getWidget().updateMaxIndent();
180         } else {
181             getWidget().postponeSanityCheckForLastRendered = false;
182             UIDL rowData = uidl.getChildByTagName("rows");
183             if (rowData != null) {
184                 // we may have pending cache row fetch, cancel it. See #2136
185                 getWidget().rowRequestHandler.cancel();
186 
187                 if (!getWidget().recalcWidths
188                         && getWidget().initializedAndAttached) {
189                     getWidget().updateBody(rowData,
190                             uidl.getIntAttribute("firstrow"),
191                             uidl.getIntAttribute("rows"));
192                     if (getWidget().headerChangedDuringUpdate) {
193                         getWidget().triggerLazyColumnAdjustment(true);
194                     } else if (!getWidget().isScrollPositionVisible()
195                             || totalRowsChanged
196                             || getWidget().lastRenderedHeight != getWidget().scrollBody
197                                     .getOffsetHeight()) {
198                         // webkits may still bug with their disturbing scrollbar
199                         // bug, see #3457
200                         // Run overflow fix for the scrollable area
201                         // #6698 - If there's a scroll going on, don't abort it
202                         // by changing overflows as the length of the contents
203                         // *shouldn't* have changed (unless the number of rows
204                         // or the height of the widget has also changed)
205                         Scheduler.get().scheduleDeferred(new Command() {
206                             @Override
207                             public void execute() {
208                                 Util.runWebkitOverflowAutoFix(getWidget().scrollBodyPanel
209                                         .getElement());
210                             }
211                         });
212                     }
213                 } else {
214                     getWidget().initializeRows(uidl, rowData);
215                 }
216             }
217         }
218 
219         // If a row had an open context menu before the update, and after the
220         // update there's a row with the same key as that row, restore the
221         // context menu. See #8526.
222         showSavedContextMenu(contextMenuBeforeUpdate);
223 
224         if (!getWidget().isSelectable()) {
225             getWidget().scrollBody.addStyleName(getWidget()
226                     .getStylePrimaryName() + "-body-noselection");
227         } else {
228             getWidget().scrollBody.removeStyleName(getWidget()
229                     .getStylePrimaryName() + "-body-noselection");
230         }
231 
232         getWidget().hideScrollPositionAnnotation();
233 
234         // selection is no in sync with server, avoid excessive server visits by
235         // clearing to flag used during the normal operation
236         if (!keyboardSelectionOverRowFetchInProgress) {
237             getWidget().selectionChanged = false;
238         }
239 
240         /*
241          * This is called when the Home or page up button has been pressed in
242          * selectable mode and the next selected row was not yet rendered in the
243          * client
244          */
245         if (getWidget().selectFirstItemInNextRender
246                 || getWidget().focusFirstItemInNextRender) {
247             getWidget().selectFirstRenderedRowInViewPort(
248                     getWidget().focusFirstItemInNextRender);
249             getWidget().selectFirstItemInNextRender = getWidget().focusFirstItemInNextRender = false;
250         }
251 
252         /*
253          * This is called when the page down or end button has been pressed in
254          * selectable mode and the next selected row was not yet rendered in the
255          * client
256          */
257         if (getWidget().selectLastItemInNextRender
258                 || getWidget().focusLastItemInNextRender) {
259             getWidget().selectLastRenderedRowInViewPort(
260                     getWidget().focusLastItemInNextRender);
261             getWidget().selectLastItemInNextRender = getWidget().focusLastItemInNextRender = false;
262         }
263         getWidget().multiselectPending = false;
264 
265         if (getWidget().focusedRow != null) {
266             if (!getWidget().focusedRow.isAttached()
267                     && !getWidget().rowRequestHandler.isRunning()) {
268                 // focused row has been orphaned, can't focus
269                 if (getWidget().selectedRowKeys.contains(getWidget().focusedRow
270                         .getKey())) {
271                     // if row cache was refreshed, focused row should be
272                     // in selection and exists with same index
273                     getWidget().setRowFocus(
274                             getWidget().getRenderedRowByKey(
275                                     getWidget().focusedRow.getKey()));
276                 } else if (getWidget().selectedRowKeys.size() > 0) {
277                     // try to focus any row in selection
278                     getWidget().setRowFocus(
279                             getWidget().getRenderedRowByKey(
280                                     getWidget().selectedRowKeys.iterator()
281                                             .next()));
282                 } else {
283                     // try to focus any row
284                     getWidget().focusRowFromBody();
285                 }
286             }
287         }
288 
289         /*
290          * If the server has (re)initialized the rows, our selectionRangeStart
291          * row will point to an index that the server knows nothing about,
292          * causing problems if doing multi selection with shift. The field will
293          * be cleared a little later when the row focus has been restored.
294          * (#8584)
295          */
296         if (uidl.hasAttribute(TableConstants.ATTRIBUTE_KEY_MAPPER_RESET)
297                 && uidl.getBooleanAttribute(TableConstants.ATTRIBUTE_KEY_MAPPER_RESET)
298                 && getWidget().selectionRangeStart != null) {
299             assert !getWidget().selectionRangeStart.isAttached();
300             getWidget().selectionRangeStart = getWidget().focusedRow;
301         }
302 
303         getWidget().tabIndex = getState().tabIndex;
304         getWidget().setProperTabIndex();
305 
306         getWidget().resizeSortedColumnForSortIndicator();
307 
308         // Remember this to detect situations where overflow hack might be
309         // needed during scrolling
310         getWidget().lastRenderedHeight = getWidget().scrollBody
311                 .getOffsetHeight();
312 
313         getWidget().rendering = false;
314         getWidget().headerChangedDuringUpdate = false;
315 
316     }
317 
318     @Override
319     public VScrollTablePatched getWidget() {
320         return (VScrollTablePatched) super.getWidget();
321     }
322 
323     @Override
324     public void updateCaption(ComponentConnector component) {
325         // NOP, not rendered
326     }
327 
328     @Override
329     public void layoutVertically() {
330         getWidget().updateHeight();
331     }
332 
333     @Override
334     public void layoutHorizontally() {
335         getWidget().updateWidth();
336     }
337 
338     @Override
339     public void postLayout() {
340         VScrollTablePatched table = getWidget();
341         if (table.isVisibleInHierarchy() && table.sizeNeedsInit) {
342             table.sizeInit();
343             Scheduler.get().scheduleFinally(new ScheduledCommand() {
344                 @Override
345                 public void execute() {
346                     getLayoutManager().setNeedsMeasure(TableConnectorPatched.this);
347                     ServerConnector parent = getParent();
348                     if (parent instanceof ComponentConnector) {
349                         getLayoutManager().setNeedsMeasure(
350                                 (ComponentConnector) parent);
351                     }
352                     getLayoutManager().setNeedsVerticalLayout(
353                             TableConnectorPatched.this);
354                     getLayoutManager().layoutNow();
355                 }
356             });
357         }
358     }
359 
360     @Override
361     public boolean isReadOnly() {
362         return super.isReadOnly() || getState().propertyReadOnly;
363     }
364 
365     @Override
366     public TableState getState() {
367         return (TableState) super.getState();
368     }
369 
370     /**
371      * Shows a saved row context menu if the row for the context menu is still
372      * visible. Does nothing if a context menu has not been saved.
373      * 
374      * @param savedContextMenu
375      */
376     public void showSavedContextMenu(ContextMenuDetails savedContextMenu) {
377         if (isEnabled() && savedContextMenu != null) {
378             Iterator<Widget> iterator = getWidget().scrollBody.iterator();
379             while (iterator.hasNext()) {
380                 Widget w = iterator.next();
381                 VScrollTableRow row = (VScrollTableRow) w;
382                 if (row.getKey().equals(savedContextMenu.rowKey)) {
383                     row.showContextMenu(savedContextMenu.left,
384                             savedContextMenu.top);
385                 }
386             }
387         }
388     }
389 
390     @Override
391     public TooltipInfo getTooltipInfo(Element element) {
392 
393         TooltipInfo info = null;
394 
395         if (element != getWidget().getElement()) {
396             Object node = Util.findWidget(
397                     (com.google.gwt.user.client.Element) element,
398                     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 }