]> source.dussan.org Git - vaadin-framework.git/commitdiff
Added support for partial updates in Table and TreeTable (#6722)
authorJonatan Kronqvist <jonatan.kronqvist@itmill.com>
Thu, 16 Jun 2011 07:55:38 +0000 (07:55 +0000)
committerJonatan Kronqvist <jonatan.kronqvist@itmill.com>
Thu, 16 Jun 2011 07:55:38 +0000 (07:55 +0000)
svn changeset:19417/svn branch:6.7

src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java
src/com/vaadin/ui/Table.java
src/com/vaadin/ui/TreeTable.java

index fdf61b8292d81792a1eb5f83008e5e1cdbe9b739..381e9c83b491ab3881766118b4cf1faba62a7332 100644 (file)
@@ -807,39 +807,12 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
         this.client = client;
         paintableId = uidl.getStringAttribute("id");
         immediate = uidl.getBooleanAttribute("immediate");
-        final int newTotalRows = uidl.getIntAttribute("totalrows");
-        if (newTotalRows != totalRows) {
-            if (scrollBody != null) {
-                if (totalRows == 0) {
-                    tHead.clear();
-                    tFoot.clear();
-                }
-                initializedAndAttached = false;
-                initialContentReceived = false;
-                isNewBody = true;
-            }
-            totalRows = newTotalRows;
-        }
 
-        dragmode = uidl.hasAttribute("dragmode") ? uidl
-                .getIntAttribute("dragmode") : 0;
-        if (BrowserInfo.get().isIE()) {
-            if (dragmode > 0) {
-                getElement().setPropertyJSO("onselectstart",
-                        getPreventTextSelectionIEHack());
-            } else {
-                getElement().setPropertyJSO("onselectstart", null);
-            }
-        }
+        updateTotalRows(uidl);
 
-        tabIndex = uidl.hasAttribute("tabindex") ? uidl
-                .getIntAttribute("tabindex") : 0;
+        updateDragMode(uidl);
 
-        if (!BrowserInfo.get().isTouchDevice()) {
-            multiselectmode = uidl.hasAttribute("multiselectmode") ? uidl
-                    .getIntAttribute("multiselectmode")
-                    : MULTISELECT_MODE_DEFAULT;
-        }
+        updateSelectionProperties(uidl);
 
         if (uidl.hasAttribute("alb")) {
             bodyActionKeys = uidl.getStringArrayAttribute("alb");
@@ -849,8 +822,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
             bodyActionKeys = null;
         }
 
-        setCacheRate(uidl.hasAttribute("cr") ? uidl.getDoubleAttribute("cr")
-                : CACHE_RATE_DEFAULT);
+        setCacheRateFromUIDL(uidl);
 
         recalcWidths = uidl.hasAttribute("recalcWidths");
         if (recalcWidths) {
@@ -858,112 +830,20 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
             tFoot.clear();
         }
 
-        int oldPageLength = pageLength;
-        if (uidl.hasAttribute("pagelength")) {
-            pageLength = uidl.getIntAttribute("pagelength");
-        } else {
-            // pagelenght is "0" meaning scrolling is turned off
-            pageLength = totalRows;
-        }
-
-        if (oldPageLength != pageLength && initializedAndAttached) {
-            // page length changed, need to update size
-            sizeInit();
-        }
+        updatePageLength(uidl);
 
-        firstvisible = uidl.hasVariable("firstvisible") ? uidl
-                .getIntVariable("firstvisible") : 0;
-        if (firstvisible != lastRequestedFirstvisible && scrollBody != null) {
-            // received 'surprising' firstvisible from server: scroll there
-            firstRowInViewPort = firstvisible;
-            scrollBodyPanel.setScrollPosition((int) (firstvisible * scrollBody
-                    .getRowHeight()));
-        }
+        updateFirstVisibleAndScrollIfNeeded(uidl);
 
         showRowHeaders = uidl.getBooleanAttribute("rowheaders");
         showColHeaders = uidl.getBooleanAttribute("colheaders");
 
-        nullSelectionAllowed = uidl.hasAttribute("nsa") ? uidl
-                .getBooleanAttribute("nsa") : true;
+        updateSortingProperties(uidl);
 
-        String oldSortColumn = sortColumn;
-        if (uidl.hasVariable("sortascending")) {
-            sortAscending = uidl.getBooleanVariable("sortascending");
-            sortColumn = uidl.getStringVariable("sortcolumn");
-        }
+        boolean keyboardSelectionOverRowFetchInProgress = selectSelectedRows(uidl);
 
-        boolean keyboardSelectionOverRowFetchInProgress = false;
+        updateColumnProperties(uidl);
 
-        if (uidl.hasVariable("selected")) {
-            final Set<String> selectedKeys = uidl
-                    .getStringArrayVariableAsSet("selected");
-            if (scrollBody != null) {
-                Iterator<Widget> iterator = scrollBody.iterator();
-                while (iterator.hasNext()) {
-                    /*
-                     * Make the focus reflect to the server side state unless we
-                     * are currently selecting multiple rows with keyboard.
-                     */
-                    VScrollTableRow row = (VScrollTableRow) iterator.next();
-                    boolean selected = selectedKeys.contains(row.getKey());
-                    if (!selected
-                            && unSyncedselectionsBeforeRowFetch != null
-                            && unSyncedselectionsBeforeRowFetch.contains(row
-                                    .getKey())) {
-                        selected = true;
-                        keyboardSelectionOverRowFetchInProgress = true;
-                    }
-                    if (selected != row.isSelected()) {
-                        row.toggleSelection();
-                    }
-                }
-            }
-        }
-        unSyncedselectionsBeforeRowFetch = null;
-
-        if (uidl.hasAttribute("selectmode")) {
-            if (uidl.getBooleanAttribute("readonly")) {
-                selectMode = Table.SELECT_MODE_NONE;
-            } else if (uidl.getStringAttribute("selectmode").equals("multi")) {
-                selectMode = Table.SELECT_MODE_MULTI;
-            } else if (uidl.getStringAttribute("selectmode").equals("single")) {
-                selectMode = Table.SELECT_MODE_SINGLE;
-            } else {
-                selectMode = Table.SELECT_MODE_NONE;
-            }
-        }
-
-        if (uidl.hasVariable("columnorder")) {
-            columnReordering = true;
-            columnOrder = uidl.getStringArrayVariable("columnorder");
-        } else {
-            columnReordering = false;
-            columnOrder = null;
-        }
-
-        if (uidl.hasVariable("collapsedcolumns")) {
-            tHead.setColumnCollapsingAllowed(true);
-            collapsedColumns = uidl
-                    .getStringArrayVariableAsSet("collapsedcolumns");
-        } else {
-            tHead.setColumnCollapsingAllowed(false);
-        }
-
-        UIDL rowData = null;
-        UIDL ac = null;
-        for (final Iterator<Object> it = uidl.getChildIterator(); it.hasNext();) {
-            final UIDL c = (UIDL) it.next();
-            if (c.getTag().equals("rows")) {
-                rowData = c;
-            } else if (c.getTag().equals("actions")) {
-                updateActionMap(c);
-            } else if (c.getTag().equals("visiblecolumns")) {
-                tHead.updateCellsFromUIDL(c);
-                tFoot.updateCellsFromUIDL(c);
-            } else if (c.getTag().equals("-ac")) {
-                ac = c;
-            }
-        }
+        UIDL ac = uidl.getChildByTagName("-ac");
         if (ac == null) {
             if (dropHandler != null) {
                 // remove dropHandler if not present anymore
@@ -975,42 +855,34 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
             }
             dropHandler.updateAcceptRules(ac);
         }
-        updateHeader(uidl.getStringArrayAttribute("vcolorder"));
-
-        updateFooter(uidl.getStringArrayAttribute("vcolorder"));
 
-        if (!recalcWidths && initializedAndAttached) {
-            updateBody(rowData, uidl.getIntAttribute("firstrow"),
-                    uidl.getIntAttribute("rows"));
-            if (headerChangedDuringUpdate) {
-                lazyAdjustColumnWidths.schedule(1);
-            } else {
-                // webkits may still bug with their disturbing scrollbar bug,
-                // See #3457
-                // run overflow fix for scrollable area
-                Scheduler.get().scheduleDeferred(new Command() {
-                    public void execute() {
-                        Util.runWebkitOverflowAutoFix(scrollBodyPanel
-                                .getElement());
-                    }
-                });
-            }
+        UIDL partialRowAdditions = uidl.getChildByTagName("prows");
+        UIDL partialRowUpdates = uidl.getChildByTagName("urows");
+        if (partialRowUpdates != null || partialRowAdditions != null) {
+            updateRowsInBody(partialRowUpdates);
+            addRowsToBody(partialRowAdditions);
         } else {
-            if (scrollBody != null) {
-                scrollBody.removeFromParent();
-                lazyUnregistryBag.add(scrollBody);
-            }
-            scrollBody = createScrollBody();
-
-            scrollBody.renderInitialRows(rowData,
-                    uidl.getIntAttribute("firstrow"),
-                    uidl.getIntAttribute("rows"));
-            scrollBodyPanel.add(scrollBody);
-            initialContentReceived = true;
-            if (isAttached()) {
-                sizeInit();
+            UIDL rowData = uidl.getChildByTagName("rows");
+            if (!recalcWidths && initializedAndAttached) {
+                updateBody(rowData, uidl.getIntAttribute("firstrow"),
+                        uidl.getIntAttribute("rows"));
+                if (headerChangedDuringUpdate) {
+                    lazyAdjustColumnWidths.schedule(1);
+                } else {
+                    // webkits may still bug with their disturbing scrollbar
+                    // bug,
+                    // See #3457
+                    // run overflow fix for scrollable area
+                    Scheduler.get().scheduleDeferred(new Command() {
+                        public void execute() {
+                            Util.runWebkitOverflowAutoFix(scrollBodyPanel
+                                    .getElement());
+                        }
+                    });
+                }
+            } else {
+                initializeRows(uidl, rowData);
             }
-            scrollBody.restoreRowVisibility();
         }
 
         if (selectMode == Table.SELECT_MODE_NONE) {
@@ -1056,8 +928,107 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
             }
         }
 
+        tabIndex = uidl.hasAttribute("tabindex") ? uidl
+                .getIntAttribute("tabindex") : 0;
         setProperTabIndex();
 
+        rendering = false;
+        headerChangedDuringUpdate = false;
+
+    }
+
+    private void initializeRows(UIDL uidl, UIDL rowData) {
+        if (scrollBody != null) {
+            scrollBody.removeFromParent();
+            lazyUnregistryBag.add(scrollBody);
+        }
+        scrollBody = createScrollBody();
+
+        scrollBody.renderInitialRows(rowData, uidl.getIntAttribute("firstrow"),
+                uidl.getIntAttribute("rows"));
+        scrollBodyPanel.add(scrollBody);
+        initialContentReceived = true;
+        if (isAttached()) {
+            sizeInit();
+        }
+        scrollBody.restoreRowVisibility();
+    }
+
+    private void updateColumnProperties(UIDL uidl) {
+        updateColumnOrder(uidl);
+
+        updateCollapsedColumns(uidl);
+
+        UIDL vc = uidl.getChildByTagName("visiblecolumns");
+        if (vc != null) {
+            tHead.updateCellsFromUIDL(vc);
+            tFoot.updateCellsFromUIDL(vc);
+        }
+
+        updateHeader(uidl.getStringArrayAttribute("vcolorder"));
+
+        updateFooter(uidl.getStringArrayAttribute("vcolorder"));
+    }
+
+    private void updateCollapsedColumns(UIDL uidl) {
+        if (uidl.hasVariable("collapsedcolumns")) {
+            tHead.setColumnCollapsingAllowed(true);
+            collapsedColumns = uidl
+                    .getStringArrayVariableAsSet("collapsedcolumns");
+        } else {
+            tHead.setColumnCollapsingAllowed(false);
+        }
+    }
+
+    private void updateColumnOrder(UIDL uidl) {
+        if (uidl.hasVariable("columnorder")) {
+            columnReordering = true;
+            columnOrder = uidl.getStringArrayVariable("columnorder");
+        } else {
+            columnReordering = false;
+            columnOrder = null;
+        }
+    }
+
+    private boolean selectSelectedRows(UIDL uidl) {
+        boolean keyboardSelectionOverRowFetchInProgress = false;
+
+        if (uidl.hasVariable("selected")) {
+            final Set<String> selectedKeys = uidl
+                    .getStringArrayVariableAsSet("selected");
+            if (scrollBody != null) {
+                Iterator<Widget> iterator = scrollBody.iterator();
+                while (iterator.hasNext()) {
+                    /*
+                     * Make the focus reflect to the server side state unless we
+                     * are currently selecting multiple rows with keyboard.
+                     */
+                    VScrollTableRow row = (VScrollTableRow) iterator.next();
+                    boolean selected = selectedKeys.contains(row.getKey());
+                    if (!selected
+                            && unSyncedselectionsBeforeRowFetch != null
+                            && unSyncedselectionsBeforeRowFetch.contains(row
+                                    .getKey())) {
+                        selected = true;
+                        keyboardSelectionOverRowFetchInProgress = true;
+                    }
+                    if (selected != row.isSelected()) {
+                        row.toggleSelection();
+                    }
+                }
+            }
+        }
+        unSyncedselectionsBeforeRowFetch = null;
+        return keyboardSelectionOverRowFetchInProgress;
+    }
+
+    private void updateSortingProperties(UIDL uidl) {
+        String oldSortColumn = sortColumn;
+        if (uidl.hasVariable("sortascending")) {
+            sortAscending = uidl.getBooleanVariable("sortascending");
+            sortColumn = uidl.getStringVariable("sortcolumn");
+        }
+
         // Force recalculation of the captionContainer element inside the header
         // cell to accomodate for the size of the sort arrow.
         HeaderCell sortedHeader = tHead.getHeaderCell(sortColumn);
@@ -1070,10 +1041,91 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
         if (oldSortedHeader != null) {
             oldSortedHeader.resizeCaptionContainer();
         }
+    }
 
-        rendering = false;
-        headerChangedDuringUpdate = false;
+    private void updateFirstVisibleAndScrollIfNeeded(UIDL uidl) {
+        firstvisible = uidl.hasVariable("firstvisible") ? uidl
+                .getIntVariable("firstvisible") : 0;
+        if (firstvisible != lastRequestedFirstvisible && scrollBody != null) {
+            // received 'surprising' firstvisible from server: scroll there
+            firstRowInViewPort = firstvisible;
+            scrollBodyPanel.setScrollPosition((int) (firstvisible * scrollBody
+                    .getRowHeight()));
+        }
+    }
+
+    private void updatePageLength(UIDL uidl) {
+        int oldPageLength = pageLength;
+        if (uidl.hasAttribute("pagelength")) {
+            pageLength = uidl.getIntAttribute("pagelength");
+        } else {
+            // pagelenght is "0" meaning scrolling is turned off
+            pageLength = totalRows;
+        }
 
+        if (oldPageLength != pageLength && initializedAndAttached) {
+            // page length changed, need to update size
+            sizeInit();
+        }
+    }
+
+    private void updateSelectionProperties(UIDL uidl) {
+        if (!BrowserInfo.get().isTouchDevice()) {
+            multiselectmode = uidl.hasAttribute("multiselectmode") ? uidl
+                    .getIntAttribute("multiselectmode")
+                    : MULTISELECT_MODE_DEFAULT;
+        }
+        nullSelectionAllowed = uidl.hasAttribute("nsa") ? uidl
+                .getBooleanAttribute("nsa") : true;
+
+        if (uidl.hasAttribute("selectmode")) {
+            if (uidl.getBooleanAttribute("readonly")) {
+                selectMode = Table.SELECT_MODE_NONE;
+            } else if (uidl.getStringAttribute("selectmode").equals("multi")) {
+                selectMode = Table.SELECT_MODE_MULTI;
+            } else if (uidl.getStringAttribute("selectmode").equals("single")) {
+                selectMode = Table.SELECT_MODE_SINGLE;
+            } else {
+                selectMode = Table.SELECT_MODE_NONE;
+            }
+        }
+    }
+
+    private void updateDragMode(UIDL uidl) {
+        dragmode = uidl.hasAttribute("dragmode") ? uidl
+                .getIntAttribute("dragmode") : 0;
+        if (BrowserInfo.get().isIE()) {
+            if (dragmode > 0) {
+                getElement().setPropertyJSO("onselectstart",
+                        getPreventTextSelectionIEHack());
+            } else {
+                getElement().setPropertyJSO("onselectstart", null);
+            }
+        }
+    }
+
+    private void updateTotalRows(UIDL uidl) {
+        int newTotalRows = uidl.getIntAttribute("totalrows");
+        if (newTotalRows != getTotalRows()) {
+            if (scrollBody != null) {
+                if (getTotalRows() == 0) {
+                    tHead.clear();
+                    tFoot.clear();
+                }
+                initializedAndAttached = false;
+                initialContentReceived = false;
+                isNewBody = true;
+            }
+            setTotalRows(newTotalRows);
+        }
+    }
+
+    protected void setTotalRows(int newTotalRows) {
+        totalRows = newTotalRows;
+    }
+
+    protected int getTotalRows() {
+        return totalRows;
     }
 
     private void focusRowFromBody() {
@@ -1143,6 +1195,11 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
         }
     }
 
+    private void setCacheRateFromUIDL(UIDL uidl) {
+        setCacheRate(uidl.hasAttribute("cr") ? uidl.getDoubleAttribute("cr")
+                : CACHE_RATE_DEFAULT);
+    }
+
     private void setCacheRate(double d) {
         if (cache_rate != d) {
             cache_rate = d;
@@ -1274,6 +1331,22 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
 
         scrollBody.renderRows(uidl, firstRow, reqRows);
 
+        updateCache();
+    }
+
+    private void updateRowsInBody(UIDL partialRowUpdates) {
+        if (partialRowUpdates == null) {
+            return;
+        }
+        int firstRowIx = partialRowUpdates.getIntAttribute("firsturowix");
+        int count = partialRowUpdates.getIntAttribute("numurows");
+        scrollBody.unlinkRows(firstRowIx, count);
+        scrollBody.insertRows(partialRowUpdates, firstRowIx, count);
+
+        updateCache();
+    }
+
+    private void updateCache() {
         final int optimalFirstRow = (int) (firstRowInViewPort - pageLength
                 * cache_rate);
         boolean cont = true;
@@ -1294,6 +1367,23 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
         scrollBody.restoreRowVisibility();
     }
 
+    private void addRowsToBody(UIDL partialRowAdditions) {
+        if (partialRowAdditions == null) {
+            return;
+        }
+        if (partialRowAdditions.hasAttribute("hide")) {
+            scrollBody.unlinkRows(
+                    partialRowAdditions.getIntAttribute("firstprowix"),
+                    partialRowAdditions.getIntAttribute("numprows"));
+        } else {
+            scrollBody.insertRows(partialRowAdditions,
+                    partialRowAdditions.getIntAttribute("firstprowix"),
+                    partialRowAdditions.getIntAttribute("numprows"));
+        }
+
+        updateCache();
+    }
+
     /**
      * Gives correct column index for given column key ("cid" in UIDL).
      * 
@@ -3489,7 +3579,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
 
         private double rowHeight = -1;
 
-        private final List<Widget> renderedRows = new ArrayList<Widget>();
+        private final LinkedList<Widget> renderedRows = new LinkedList<Widget>();
 
         /**
          * Due some optimizations row height measuring is deferred and initial
@@ -3610,6 +3700,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
                 }
                 fixSpacers();
             }
+
             // this may be a new set of rows due content change,
             // ensure we have proper cache rows
             int reactFirstRow = (int) (firstRowInViewPort - pageLength
@@ -3632,8 +3723,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
                  * Branch for fetching cache above visible area.
                  * 
                  * If cache needed for both before and after visible area, this
-                 * will be rendered after-cache is reveived and rendered. So in
-                 * some rare situations table may take two cache visits to
+                 * will be rendered after-cache is received and rendered. So in
+                 * some rare situations the table may make two cache visits to
                  * server.
                  */
                 rowRequestHandler.setReqFirstRow(reactFirstRow);
@@ -3642,6 +3733,40 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
             }
         }
 
+        public void insertRows(UIDL rowData, int firstIndex, int rows) {
+            aligns = tHead.getColumnAlignments();
+            final Iterator<?> it = rowData.getChildIterator();
+
+            if (firstIndex == lastRendered + 1) {
+                while (it.hasNext()) {
+                    final VScrollTableRow row = prepareRow((UIDL) it.next());
+                    addRow(row);
+                    lastRendered++;
+                }
+                fixSpacers();
+            } else if (firstIndex + rows == firstRendered) {
+                final VScrollTableRow[] rowArray = new VScrollTableRow[rows];
+                int i = rows;
+                while (it.hasNext()) {
+                    i--;
+                    rowArray[i] = prepareRow((UIDL) it.next());
+                }
+                for (i = 0; i < rows; i++) {
+                    addRowBeforeFirstRendered(rowArray[i]);
+                    firstRendered--;
+                }
+            } else {
+                // insert in the middle
+                int realIx = firstIndex - firstRendered;
+                while (it.hasNext()) {
+                    insertRowAt(prepareRow((UIDL) it.next()), realIx);
+                    lastRendered++;
+                    realIx++;
+                }
+                fixSpacers();
+            }
+        }
+
         /**
          * This method is used to instantiate new rows for this table. It
          * automatically sets correct widths to rows cells and assigns correct
@@ -3692,6 +3817,31 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
             renderedRows.add(row);
         }
 
+        private void insertRowAt(VScrollTableRow row, int index) {
+            row.setIndex(index);
+            if (row.isSelected()) {
+                row.addStyleName("v-selected");
+            }
+            if (index > 0) {
+                VScrollTableRow sibling = getRowByRowIndex(index - 1);
+                tBodyElement
+                        .insertAfter(row.getElement(), sibling.getElement());
+            } else {
+                VScrollTableRow sibling = getRowByRowIndex(index);
+                tBodyElement.insertBefore(row.getElement(),
+                        sibling.getElement());
+            }
+            adopt(row);
+            renderedRows.add(index, row);
+
+            // TODO: could this be made more efficient? like looping only once
+            // after all rows have been inserted
+            for (int ix = index + 1; ix < renderedRows.size(); ix++) {
+                VScrollTableRow r = (VScrollTableRow) renderedRows.get(ix);
+                r.setIndex(r.getIndex() + 1);
+            }
+        }
+
         public Iterator<Widget> iterator() {
             return renderedRows.iterator();
         }
@@ -3712,12 +3862,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
                 lastRendered--;
             }
             if (index >= 0) {
-                final VScrollTableRow toBeRemoved = (VScrollTableRow) renderedRows
-                        .get(index);
-                lazyUnregistryBag.add(toBeRemoved);
-                tBodyElement.removeChild(toBeRemoved.getElement());
-                orphan(toBeRemoved);
-                renderedRows.remove(index);
+                unlinkRowAtIndex(index);
                 fixSpacers();
                 return true;
             } else {
@@ -3725,6 +3870,49 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
             }
         }
 
+        public void unlinkRows(int firstIndex, int count) {
+            if (count < 1) {
+                return;
+            }
+            if (firstRendered > firstIndex
+                    && firstRendered < firstIndex + count) {
+                firstIndex = firstRendered;
+            }
+            int lastIndex = firstIndex + count - 1;
+            if (lastRendered < lastIndex) {
+                lastIndex = lastRendered;
+            }
+            for (int ix = lastIndex; ix >= firstIndex; ix--) {
+                unlinkRowAtIndex(ix);
+                lastRendered--;
+            }
+            fixSpacers();
+        }
+
+        public void unlinkAllRowsAfter(int index) {
+            if (firstRendered > index) {
+                index = firstRendered;
+            }
+            for (int ix = renderedRows.size() - 1; ix > index; ix--) {
+                unlinkRowAtIndex(ix);
+                lastRendered--;
+            }
+            fixSpacers();
+        }
+
+        private void unlinkRowAtIndex(int index) {
+            final VScrollTableRow toBeRemoved = (VScrollTableRow) renderedRows
+                    .get(index);
+            lazyUnregistryBag.add(toBeRemoved);
+            tBodyElement.removeChild(toBeRemoved.getElement());
+            orphan(toBeRemoved);
+            renderedRows.remove(index);
+            for (int ix = index; ix < renderedRows.size(); ix++) {
+                VScrollTableRow r = (VScrollTableRow) renderedRows.get(ix);
+                r.setIndex(r.getIndex() - 1);
+            }
+        }
+
         @Override
         public boolean remove(Widget w) {
             throw new UnsupportedOperationException();
@@ -4001,6 +4189,11 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
                 // Inverted logic to be backwards compatible with earlier 6.4.
                 // It is very strange because rows 1,3,5 are considered "even"
                 // and 2,4,6 "odd".
+                //
+                // First remove any old styles so that both styles aren't
+                // applied when indexes are updated.
+                removeStyleName(ROW_CLASSNAME_ODD);
+                removeStyleName(ROW_CLASSNAME_EVEN);
                 if (!isOdd) {
                     addStyleName(ROW_CLASSNAME_ODD);
                 } else {
index 738f9c53c501699f6fd0b8cdbbf6df2425d7b6e3..907b3a98d9ded96e5cf49d4e6c921a86c391d761 100644 (file)
@@ -1422,216 +1422,207 @@ public class Table extends AbstractSelect implements Action.Container,
             return;
         }
 
-        if (isContentRefreshesEnabled) {
-
-            HashSet<Property> oldListenedProperties = listenedProperties;
-            HashSet<Component> oldVisibleComponents = visibleComponents;
-
-            // initialize the listener collections
-            listenedProperties = new HashSet<Property>();
-            visibleComponents = new HashSet<Component>();
-
-            // Collects the basic facts about the table page
-            final Object[] colids = getVisibleColumns();
-            final int cols = colids.length;
-            final int pagelen = getPageLength();
-            int firstIndex = getCurrentPageFirstItemIndex();
-            int rows, totalRows;
-            rows = totalRows = size();
-            if (rows > 0 && firstIndex >= 0) {
-                rows -= firstIndex;
-            }
-            if (pagelen > 0 && pagelen < rows) {
-                rows = pagelen;
-            }
+        if (!isContentRefreshesEnabled) {
+            return;
+        }
 
-            // If "to be painted next" variables are set, use them
-            if (lastToBeRenderedInClient - firstToBeRenderedInClient > 0) {
-                rows = lastToBeRenderedInClient - firstToBeRenderedInClient + 1;
-            }
-            Object id;
-            if (firstToBeRenderedInClient >= 0) {
-                if (firstToBeRenderedInClient < totalRows) {
-                    firstIndex = firstToBeRenderedInClient;
-                } else {
-                    firstIndex = totalRows - 1;
-                }
+        // initialize the listener collections
+        listenedProperties = new HashSet<Property>();
+        visibleComponents = new HashSet<Component>();
+
+        // Collects the basic facts about the table page
+        final int pagelen = getPageLength();
+        int firstIndex = getCurrentPageFirstItemIndex();
+        int rows, totalRows;
+        rows = totalRows = size();
+        if (rows > 0 && firstIndex >= 0) {
+            rows -= firstIndex;
+        }
+        if (pagelen > 0 && pagelen < rows) {
+            rows = pagelen;
+        }
+
+        // If "to be painted next" variables are set, use them
+        if (lastToBeRenderedInClient - firstToBeRenderedInClient > 0) {
+            rows = lastToBeRenderedInClient - firstToBeRenderedInClient + 1;
+        }
+        if (firstToBeRenderedInClient >= 0) {
+            if (firstToBeRenderedInClient < totalRows) {
+                firstIndex = firstToBeRenderedInClient;
             } else {
-                // initial load
-                firstToBeRenderedInClient = firstIndex;
+                firstIndex = totalRows - 1;
             }
-            if (totalRows > 0) {
-                if (rows + firstIndex > totalRows) {
-                    rows = totalRows - firstIndex;
-                }
-            } else {
-                rows = 0;
+        } else {
+            // initial load
+            firstToBeRenderedInClient = firstIndex;
+        }
+        if (totalRows > 0) {
+            if (rows + firstIndex > totalRows) {
+                rows = totalRows - firstIndex;
             }
+        } else {
+            rows = 0;
+        }
 
-            Object[][] cells = new Object[cols + CELL_FIRSTCOL][rows];
-            if (rows == 0) {
-                pageBuffer = cells;
-                unregisterPropertiesAndComponents(oldListenedProperties,
-                        oldVisibleComponents);
+        // Saves the results to internal buffer
+        pageBuffer = getVisibleCellsNoCache(firstIndex, rows);
 
-                /*
-                 * We need to repaint so possible header or footer changes are
-                 * sent to the server
-                 */
-                requestRepaint();
+        if (rows > 0) {
+            pageBufferFirstIndex = firstIndex;
+        }
 
-                return;
-            }
+        requestRepaint();
+    }
 
-            // Gets the first item id
-            if (items instanceof Container.Indexed) {
-                id = getIdByIndex(firstIndex);
-            } else {
-                id = firstItemId();
-                for (int i = 0; i < firstIndex; i++) {
-                    id = nextItemId(id);
-                }
-            }
+    private Object[][] getVisibleCellsNoCache(int firstIndex, int rows) {
+        final Object[] colids = getVisibleColumns();
+        final int cols = colids.length;
 
-            final int headmode = getRowHeaderMode();
-            final boolean[] iscomponent = new boolean[cols];
-            for (int i = 0; i < cols; i++) {
-                iscomponent[i] = columnGenerators.containsKey(colids[i])
-                        || Component.class.isAssignableFrom(getType(colids[i]));
-            }
-            int firstIndexNotInCache;
-            if (pageBuffer != null && pageBuffer[CELL_ITEMID].length > 0) {
-                firstIndexNotInCache = pageBufferFirstIndex
-                        + pageBuffer[CELL_ITEMID].length;
-            } else {
-                firstIndexNotInCache = -1;
+        HashSet<Property> oldListenedProperties = listenedProperties;
+        HashSet<Component> oldVisibleComponents = visibleComponents;
+
+        Object[][] cells = new Object[cols + CELL_FIRSTCOL][rows];
+        if (rows == 0) {
+            unregisterPropertiesAndComponents(oldListenedProperties,
+                    oldVisibleComponents);
+            return cells;
+        }
+
+        // Gets the first item id
+        Object id;
+        if (items instanceof Container.Indexed) {
+            id = getIdByIndex(firstIndex);
+        } else {
+            id = firstItemId();
+            for (int i = 0; i < firstIndex; i++) {
+                id = nextItemId(id);
             }
+        }
 
-            // Creates the page contents
-            int filledRows = 0;
-            for (int i = 0; i < rows && id != null; i++) {
-                cells[CELL_ITEMID][i] = id;
-                cells[CELL_KEY][i] = itemIdMapper.key(id);
-                if (headmode != ROW_HEADER_MODE_HIDDEN) {
-                    switch (headmode) {
-                    case ROW_HEADER_MODE_INDEX:
-                        cells[CELL_HEADER][i] = String.valueOf(i + firstIndex
-                                + 1);
-                        break;
-                    default:
-                        cells[CELL_HEADER][i] = getItemCaption(id);
-                    }
-                    cells[CELL_ICON][i] = getItemIcon(id);
+        final int headmode = getRowHeaderMode();
+        final boolean[] iscomponent = new boolean[cols];
+        for (int i = 0; i < cols; i++) {
+            iscomponent[i] = columnGenerators.containsKey(colids[i])
+                    || Component.class.isAssignableFrom(getType(colids[i]));
+        }
+        int firstIndexNotInCache;
+        if (pageBuffer != null && pageBuffer[CELL_ITEMID].length > 0) {
+            firstIndexNotInCache = pageBufferFirstIndex
+                    + pageBuffer[CELL_ITEMID].length;
+        } else {
+            firstIndexNotInCache = -1;
+        }
+
+        // Creates the page contents
+        int filledRows = 0;
+        for (int i = 0; i < rows && id != null; i++) {
+            cells[CELL_ITEMID][i] = id;
+            cells[CELL_KEY][i] = itemIdMapper.key(id);
+            if (headmode != ROW_HEADER_MODE_HIDDEN) {
+                switch (headmode) {
+                case ROW_HEADER_MODE_INDEX:
+                    cells[CELL_HEADER][i] = String.valueOf(i + firstIndex + 1);
+                    break;
+                default:
+                    cells[CELL_HEADER][i] = getItemCaption(id);
                 }
+                cells[CELL_ICON][i] = getItemIcon(id);
+            }
 
-                if (cols > 0) {
-                    for (int j = 0; j < cols; j++) {
-                        if (isColumnCollapsed(colids[j])) {
-                            continue;
-                        }
-                        Property p = null;
-                        Object value = "";
-                        boolean isGenerated = columnGenerators
-                                .containsKey(colids[j]);
+            for (int j = 0; j < cols; j++) {
+                if (isColumnCollapsed(colids[j])) {
+                    continue;
+                }
+                Property p = null;
+                Object value = "";
+                boolean isGenerated = columnGenerators.containsKey(colids[j]);
 
-                        if (!isGenerated) {
-                            p = getContainerProperty(id, colids[j]);
-                        }
+                if (!isGenerated) {
+                    p = getContainerProperty(id, colids[j]);
+                }
 
-                        // check in current pageBuffer already has row
-                        int index = firstIndex + i;
-                        if (p != null || isGenerated) {
-                            if (index < firstIndexNotInCache
-                                    && index >= pageBufferFirstIndex) {
-                                // we have data already in our cache,
-                                // recycle it instead of fetching it via
-                                // getValue/getPropertyValue
-                                int indexInOldBuffer = index
-                                        - pageBufferFirstIndex;
-                                value = pageBuffer[CELL_FIRSTCOL + j][indexInOldBuffer];
-                                if (!isGenerated && iscomponent[j]
-                                        || !(value instanceof Component)) {
-                                    listenProperty(p, oldListenedProperties);
-                                }
-                            } else {
-                                if (isGenerated) {
-                                    ColumnGenerator cg = columnGenerators
-                                            .get(colids[j]);
-                                    value = cg
-                                            .generateCell(this, id, colids[j]);
-
-                                } else if (iscomponent[j]) {
-                                    value = p.getValue();
-                                    listenProperty(p, oldListenedProperties);
-                                } else if (p != null) {
-                                    value = getPropertyValue(id, colids[j], p);
-                                    /*
-                                     * If returned value is Component (via
-                                     * fieldfactory or overridden
-                                     * getPropertyValue) we excpect it to listen
-                                     * property value changes. Otherwise if
-                                     * property emits value change events, table
-                                     * will start to listen them and refresh
-                                     * content when needed.
-                                     */
-                                    if (!(value instanceof Component)) {
-                                        listenProperty(p, oldListenedProperties);
-                                    }
-                                } else {
-                                    value = getPropertyValue(id, colids[j],
-                                            null);
-                                }
-                            }
+                // check in current pageBuffer already has row
+                int index = firstIndex + i;
+                if (p != null || isGenerated) {
+                    if (index < firstIndexNotInCache
+                            && index >= pageBufferFirstIndex) {
+                        // we have data already in our cache,
+                        // recycle it instead of fetching it via
+                        // getValue/getPropertyValue
+                        int indexInOldBuffer = index - pageBufferFirstIndex;
+                        value = pageBuffer[CELL_FIRSTCOL + j][indexInOldBuffer];
+                        if (!isGenerated && iscomponent[j]
+                                || !(value instanceof Component)) {
+                            listenProperty(p, oldListenedProperties);
                         }
-
-                        if (value instanceof Component) {
-                            if (oldVisibleComponents == null
-                                    || !oldVisibleComponents.contains(value)) {
-                                ((Component) value).setParent(this);
+                    } else {
+                        if (isGenerated) {
+                            ColumnGenerator cg = columnGenerators
+                                    .get(colids[j]);
+                            value = cg.generateCell(this, id, colids[j]);
+
+                        } else if (iscomponent[j]) {
+                            value = p.getValue();
+                            listenProperty(p, oldListenedProperties);
+                        } else if (p != null) {
+                            value = getPropertyValue(id, colids[j], p);
+                            /*
+                             * If returned value is Component (via fieldfactory
+                             * or overridden getPropertyValue) we excpect it to
+                             * listen property value changes. Otherwise if
+                             * property emits value change events, table will
+                             * start to listen them and refresh content when
+                             * needed.
+                             */
+                            if (!(value instanceof Component)) {
+                                listenProperty(p, oldListenedProperties);
                             }
-                            visibleComponents.add((Component) value);
+                        } else {
+                            value = getPropertyValue(id, colids[j], null);
                         }
-                        cells[CELL_FIRSTCOL + j][i] = value;
                     }
                 }
 
-                // Gets the next item id
-                if (items instanceof Container.Indexed) {
-                    int index = firstIndex + i + 1;
-                    if (index < totalRows) {
-                        id = getIdByIndex(index);
-                    } else {
-                        id = null;
+                if (value instanceof Component) {
+                    if (oldVisibleComponents == null
+                            || !oldVisibleComponents.contains(value)) {
+                        ((Component) value).setParent(this);
                     }
-                } else {
-                    id = nextItemId(id);
+                    visibleComponents.add((Component) value);
                 }
-
-                filledRows++;
+                cells[CELL_FIRSTCOL + j][i] = value;
             }
 
-            // Assures that all the rows of the cell-buffer are valid
-            if (filledRows != cells[0].length) {
-                final Object[][] temp = new Object[cells.length][filledRows];
-                for (int i = 0; i < cells.length; i++) {
-                    for (int j = 0; j < filledRows; j++) {
-                        temp[i][j] = cells[i][j];
-                    }
+            // Gets the next item id
+            if (items instanceof Container.Indexed) {
+                int index = firstIndex + i + 1;
+                if (index < size()) {
+                    id = getIdByIndex(index);
+                } else {
+                    id = null;
                 }
-                cells = temp;
+            } else {
+                id = nextItemId(id);
             }
 
-            pageBufferFirstIndex = firstIndex;
-
-            // Saves the results to internal buffer
-            pageBuffer = cells;
-
-            unregisterPropertiesAndComponents(oldListenedProperties,
-                    oldVisibleComponents);
+            filledRows++;
+        }
 
-            requestRepaint();
+        // Assures that all the rows of the cell-buffer are valid
+        if (filledRows != cells[0].length) {
+            final Object[][] temp = new Object[cells.length][filledRows];
+            for (int i = 0; i < cells.length; i++) {
+                for (int j = 0; j < filledRows; j++) {
+                    temp[i][j] = cells[i][j];
+                }
+            }
+            cells = temp;
         }
 
+        unregisterPropertiesAndComponents(oldListenedProperties,
+                oldVisibleComponents);
+
+        return cells;
     }
 
     private void listenProperty(Property p,
@@ -2312,98 +2303,218 @@ public class Table extends AbstractSelect implements Action.Container,
      */
     @Override
     public void paintContent(PaintTarget target) throws PaintException {
+        /*
+         * Body actions - Actions which has the target null and can be invoked
+         * by right clicking on the table body.
+         */
+        final Set<Action> actionSet = findAndPaintBodyActions(target);
 
-        // The tab ordering number
-        if (getTabIndex() > 0) {
-            target.addAttribute("tabindex", getTabIndex());
+        final Object[][] cells = getVisibleCells();
+        int rows = findNumRowsToPaint(target, cells);
+
+        int total = size();
+        if (shouldHideNullSelectionItem()) {
+            total--;
+            rows--;
         }
 
-        if (dragMode != TableDragMode.NONE) {
-            target.addAttribute("dragmode", dragMode.ordinal());
+        // Table attributes
+        paintTableAttributes(target, rows, total);
+
+        paintVisibleColumnOrder(target);
+
+        // Rows
+        if (isPartialRowUpdate()) {
+            paintPartialRowUpdate(target, actionSet);
+        } else {
+            paintRows(target, cells, actionSet);
         }
 
-        if (multiSelectMode != MultiSelectMode.DEFAULT) {
-            target.addAttribute("multiselectmode", multiSelectMode.ordinal());
+        paintSorting(target);
+
+        resetVariablesAndPageBuffer(target);
+
+        // Actions
+        paintActions(target, actionSet);
+
+        paintColumnOrder(target);
+
+        // Available columns
+        paintAvailableColumns(target);
+
+        paintVisibleColumns(target);
+
+        if (dropHandler != null) {
+            dropHandler.getAcceptCriterion().paint(target);
         }
+    }
 
-        // Initialize temps
-        final Object[] colids = getVisibleColumns();
-        final int cols = colids.length;
-        final int first = getCurrentPageFirstItemIndex();
-        int total = size();
-        final int pagelen = getPageLength();
-        final int colHeadMode = getColumnHeaderMode();
-        final boolean colheads = colHeadMode != COLUMN_HEADER_MODE_HIDDEN;
-        final Object[][] cells = getVisibleCells();
-        final boolean iseditable = isEditable();
-        int rows;
-        if (reqRowsToPaint >= 0) {
-            rows = reqRowsToPaint;
-        } else {
-            rows = cells[0].length;
-            if (alwaysRecalculateColumnWidths) {
-                // TODO experimental feature for now: tell the client to
-                // recalculate column widths.
-                // We'll only do this for paints that do not originate from
-                // table scroll/cache requests (i.e when reqRowsToPaint<0)
-                target.addAttribute("recalcWidths", true);
+    private void paintPartialRowUpdate(PaintTarget target, Set<Action> actionSet)
+            throws PaintException {
+        paintPartialRowUpdates(target, actionSet);
+        paintPartialRowAdditions(target, actionSet);
+    }
+
+    private void paintPartialRowUpdates(PaintTarget target,
+            Set<Action> actionSet) throws PaintException {
+        final boolean[] iscomponent = findCellsWithComponents();
+
+        int firstIx = getFirstUpdatedItemIndex();
+        int count = getUpdatedRowCount();
+
+        target.startTag("urows");
+        target.addAttribute("firsturowix", firstIx);
+        target.addAttribute("numurows", count);
+
+        // Partial row updates bypass the normal caching mechanism.
+        Object[][] cells = getVisibleCellsNoCache(firstIx, count);
+        for (int indexInRowbuffer = 0; indexInRowbuffer < count; indexInRowbuffer++) {
+            final Object itemId = cells[CELL_ITEMID][indexInRowbuffer];
+
+            if (shouldHideNullSelectionItem()) {
+                // Remove null selection item if null selection is not allowed
+                continue;
             }
-        }
 
-        if (!isNullSelectionAllowed() && getNullSelectionItemId() != null
-                && containsId(getNullSelectionItemId())) {
-            total--;
-            rows--;
+            paintRow(target, cells, isEditable(), actionSet, iscomponent,
+                    indexInRowbuffer, itemId);
         }
+        target.endTag("urows");
+    }
 
-        // selection support
-        LinkedList<String> selectedKeys = new LinkedList<String>();
-        if (isMultiSelect()) {
-            HashSet<?> sel = new HashSet<Object>((Set<?>) getValue());
-            Collection<?> vids = getVisibleItemIds();
-            for (Iterator<?> it = vids.iterator(); it.hasNext();) {
-                Object id = it.next();
-                if (sel.contains(id)) {
-                    selectedKeys.add(itemIdMapper.key(id));
+    private void paintPartialRowAdditions(PaintTarget target,
+            Set<Action> actionSet) throws PaintException {
+        final boolean[] iscomponent = findCellsWithComponents();
+
+        int firstIx = getFirstAddedItemIndex();
+        int count = getAddedRowCount();
+
+        target.startTag("prows");
+        target.addAttribute("firstprowix", firstIx);
+        target.addAttribute("numprows", count);
+
+        if (!shouldHideAddedRows()) {
+            // Partial row additions bypass the normal caching mechanism.
+            Object[][] cells = getVisibleCellsNoCache(firstIx, count);
+            for (int indexInRowbuffer = 0; indexInRowbuffer < count; indexInRowbuffer++) {
+                final Object itemId = cells[CELL_ITEMID][indexInRowbuffer];
+                if (shouldHideNullSelectionItem()) {
+                    // Remove null selection item if null selection is not
+                    // allowed
+                    continue;
                 }
+
+                paintRow(target, cells, isEditable(), actionSet, iscomponent,
+                        indexInRowbuffer, itemId);
             }
         } else {
-            Object value = getValue();
-            if (value == null) {
-                value = getNullSelectionItemId();
-            }
-            if (value != null) {
-                selectedKeys.add(itemIdMapper.key(value));
-            }
+            target.addAttribute("hide", true);
         }
+        target.endTag("prows");
+    }
 
-        // Table attributes
-        if (isSelectable()) {
-            target.addAttribute("selectmode", (isMultiSelect() ? "multi"
-                    : "single"));
-        } else {
-            target.addAttribute("selectmode", "none");
-        }
+    /**
+     * Subclass and override this to enable partial row updates and additions,
+     * which bypass the normal caching mechanism. This is useful for e.g.
+     * TreeTable.
+     * 
+     * @return true if this update is a partial row update, false if not. For
+     *         plain Table it is always false.
+     */
+    protected boolean isPartialRowUpdate() {
+        return false;
+    }
+
+    /**
+     * Subclass and override this to enable partial row additions, bypassing the
+     * normal caching mechanism. This is useful for e.g. TreeTable, where
+     * expanding a node should only fetch and add the items inside of that node.
+     * 
+     * @return The index of the first added item. For plain Table it is always
+     *         0.
+     */
+    protected int getFirstAddedItemIndex() {
+        return 0;
+    }
+
+    /**
+     * Subclass and override this to enable partial row additions, bypassing the
+     * normal caching mechanism. This is useful for e.g. TreeTable, where
+     * expanding a node should only fetch and add the items inside of that node.
+     * 
+     * @return the number of rows to be added, starting at the index returned by
+     *         {@link #getFirstAddedItemIndex()}. For plain Table it is always
+     *         0.
+     */
+    protected int getAddedRowCount() {
+        return 0;
+    }
+
+    /**
+     * Subclass and override this to enable removing of rows, bypassing the
+     * normal caching and lazy loading mechanism. This is useful for e.g.
+     * TreeTable, when you need to hide certain rows as a node is collapsed.
+     * 
+     * This should return true if the rows pointed to by
+     * {@link #getFirstAddedItemIndex()} and {@link #getAddedRowCount()} should
+     * be hidden instead of added.
+     * 
+     * @return whether the rows to add (see {@link #getFirstAddedItemIndex()}
+     *         and {@link #getAddedRowCount()}) should be added or hidden. For
+     *         plain Table it is always false.
+     */
+    protected boolean shouldHideAddedRows() {
+        return false;
+    }
+
+    /**
+     * Subclass and override this to enable partial row updates, bypassing the
+     * normal caching and lazy loading mechanism. This is useful for updating
+     * the state of certain rows, e.g. in the TreeTable the collapsed state of a
+     * single node is updated using this mechanism.
+     * 
+     * @return the index of the first item to be updated. For plain Table it is
+     *         always 0.
+     */
+    protected int getFirstUpdatedItemIndex() {
+        return 0;
+    }
+
+    /**
+     * Subclass and override this to enable partial row updates, bypassing the
+     * normal caching and lazy loading mechanism. This is useful for updating
+     * the state of certain rows, e.g. in the TreeTable the collapsed state of a
+     * single node is updated using this mechanism.
+     * 
+     * @return the number of rows to update, starting at the index returned by
+     *         {@link #getFirstUpdatedItemIndex()}. For plain table it is always
+     *         0.
+     */
+    protected int getUpdatedRowCount() {
+        return 0;
+    }
+
+    private void paintTableAttributes(PaintTarget target, int rows, int total)
+            throws PaintException {
+        paintTabIndex(target);
+        paintDragMode(target);
+        paintSelectMode(target);
 
         if (cacheRate != CACHE_RATE_DEFAULT) {
             target.addAttribute("cr", cacheRate);
         }
 
-        target.addAttribute("cols", cols);
+        target.addAttribute("cols", getVisibleColumns().length);
         target.addAttribute("rows", rows);
 
-        if (!isNullSelectionAllowed()) {
-            target.addAttribute("nsa", false);
-        }
-
         target.addAttribute("firstrow",
                 (reqFirstRowToPaint >= 0 ? reqFirstRowToPaint
                         : firstToBeRenderedInClient));
         target.addAttribute("totalrows", total);
-        if (pagelen != 0) {
-            target.addAttribute("pagelength", pagelen);
+        if (getPageLength() != 0) {
+            target.addAttribute("pagelength", getPageLength());
         }
-        if (colheads) {
+        if (areColumnHeadersEnabled()) {
             target.addAttribute("colheaders", true);
         }
         if (rowHeadersAreEnabled()) {
@@ -2412,55 +2523,131 @@ public class Table extends AbstractSelect implements Action.Container,
 
         target.addAttribute("colfooters", columnFootersVisible);
 
-        /*
-         * Body actions - Actions which has the target null and can be invoked
-         * by right clicking on the table body.
-         */
-        final Set<Action> actionSet = new LinkedHashSet<Action>();
-        if (actionHandlers != null) {
-            final ArrayList<String> keys = new ArrayList<String>();
-            for (Handler ah : actionHandlers) {
-                // Getting actions for the null item, which in this case means
-                // the body item
-                final Action[] aa = ah.getActions(null, this);
-                if (aa != null) {
-                    for (int ai = 0; ai < aa.length; ai++) {
-                        final String key = actionMapper.key(aa[ai]);
-                        actionSet.add(aa[ai]);
-                        keys.add(key);
+        // The cursors are only shown on pageable table
+        if (getCurrentPageFirstItemIndex() != 0 || getPageLength() > 0) {
+            target.addVariable(this, "firstvisible",
+                    getCurrentPageFirstItemIndex());
+        }
+    }
+
+    /**
+     * Resets and paints "to be painted next" variables. Also reset pageBuffer
+     */
+    private void resetVariablesAndPageBuffer(PaintTarget target)
+            throws PaintException {
+        reqFirstRowToPaint = -1;
+        reqRowsToPaint = -1;
+        containerChangeToBeRendered = false;
+        target.addVariable(this, "reqrows", reqRowsToPaint);
+        target.addVariable(this, "reqfirstrow", reqFirstRowToPaint);
+    }
+
+    private boolean areColumnHeadersEnabled() {
+        return getColumnHeaderMode() != COLUMN_HEADER_MODE_HIDDEN;
+    }
+
+    private void paintVisibleColumns(PaintTarget target) throws PaintException {
+        target.startTag("visiblecolumns");
+        if (rowHeadersAreEnabled()) {
+            target.startTag("column");
+            target.addAttribute("cid", ROW_HEADER_COLUMN_KEY);
+            paintColumnWidth(target, ROW_HEADER_FAKE_PROPERTY_ID);
+            target.endTag("column");
+        }
+        final Collection<?> sortables = getSortableContainerPropertyIds();
+        for (Object colId : visibleColumns) {
+            if (colId != null) {
+                target.startTag("column");
+                target.addAttribute("cid", columnIdMap.key(colId));
+                final String head = getColumnHeader(colId);
+                target.addAttribute("caption", (head != null ? head : ""));
+                final String foot = getColumnFooter(colId);
+                target.addAttribute("fcaption", (foot != null ? foot : ""));
+                if (isColumnCollapsed(colId)) {
+                    target.addAttribute("collapsed", true);
+                }
+                if (areColumnHeadersEnabled()) {
+                    if (getColumnIcon(colId) != null) {
+                        target.addAttribute("icon", getColumnIcon(colId));
+                    }
+                    if (sortables.contains(colId)) {
+                        target.addAttribute("sortable", true);
                     }
                 }
+                if (!ALIGN_LEFT.equals(getColumnAlignment(colId))) {
+                    target.addAttribute("align", getColumnAlignment(colId));
+                }
+                paintColumnWidth(target, colId);
+                target.endTag("column");
             }
-            target.addAttribute("alb", keys.toArray());
         }
+        target.endTag("visiblecolumns");
+    }
 
-        // Visible column order
-        final Collection<?> sortables = getSortableContainerPropertyIds();
-        final ArrayList<String> visibleColOrder = new ArrayList<String>();
-        for (final Iterator<Object> it = visibleColumns.iterator(); it
-                .hasNext();) {
-            final Object columnId = it.next();
-            if (!isColumnCollapsed(columnId)) {
-                visibleColOrder.add(columnIdMap.key(columnId));
+    private void paintAvailableColumns(PaintTarget target)
+            throws PaintException {
+        if (columnCollapsingAllowed) {
+            final HashSet<Object> collapsedCols = new HashSet<Object>();
+            for (Object colId : visibleColumns) {
+                if (isColumnCollapsed(colId)) {
+                    collapsedCols.add(colId);
+                }
+            }
+            final String[] collapsedKeys = new String[collapsedCols.size()];
+            int nextColumn = 0;
+            for (Object colId : visibleColumns) {
+                if (isColumnCollapsed(colId)) {
+                    collapsedKeys[nextColumn++] = columnIdMap.key(colId);
+                }
             }
+            target.addVariable(this, "collapsedcolumns", collapsedKeys);
         }
-        target.addAttribute("vcolorder", visibleColOrder.toArray());
+    }
 
-        // Rows
-        final boolean selectable = isSelectable();
-        final boolean[] iscomponent = new boolean[visibleColumns.size()];
-        int iscomponentIndex = 0;
-        for (final Iterator<Object> it = visibleColumns.iterator(); it
-                .hasNext() && iscomponentIndex < iscomponent.length;) {
-            final Object columnId = it.next();
-            if (columnGenerators.containsKey(columnId)) {
-                iscomponent[iscomponentIndex++] = true;
-            } else {
-                final Class<?> colType = getType(columnId);
-                iscomponent[iscomponentIndex++] = colType != null
-                        && Component.class.isAssignableFrom(colType);
+    private void paintActions(PaintTarget target, final Set<Action> actionSet)
+            throws PaintException {
+        if (!actionSet.isEmpty()) {
+            target.addVariable(this, "action", "");
+            target.startTag("actions");
+            for (Action a : actionSet) {
+                target.startTag("action");
+                if (a.getCaption() != null) {
+                    target.addAttribute("caption", a.getCaption());
+                }
+                if (a.getIcon() != null) {
+                    target.addAttribute("icon", a.getIcon());
+                }
+                target.addAttribute("key", actionMapper.key(a));
+                target.endTag("action");
+            }
+            target.endTag("actions");
+        }
+    }
+
+    private void paintColumnOrder(PaintTarget target) throws PaintException {
+        if (columnReorderingAllowed) {
+            final String[] colorder = new String[visibleColumns.size()];
+            int i = 0;
+            for (Object colId : visibleColumns) {
+                colorder[i++] = columnIdMap.key(colId);
             }
+            target.addVariable(this, "columnorder", colorder);
+        }
+    }
+
+    private void paintSorting(PaintTarget target) throws PaintException {
+        // Sorting
+        if (getContainerDataSource() instanceof Container.Sortable) {
+            target.addVariable(this, "sortcolumn",
+                    columnIdMap.key(sortContainerPropertyId));
+            target.addVariable(this, "sortascending", sortAscending);
         }
+    }
+
+    private void paintRows(PaintTarget target, final Object[][] cells,
+            final Set<Action> actionSet) throws PaintException {
+        final boolean[] iscomponent = findCellsWithComponents();
+
         target.startTag("rows");
         // cells array contains all that are supposed to be visible on client,
         // but we'll start from the one requested by client
@@ -2483,131 +2670,144 @@ public class Table extends AbstractSelect implements Action.Container,
         for (int indexInRowbuffer = start; indexInRowbuffer < end; indexInRowbuffer++) {
             final Object itemId = cells[CELL_ITEMID][indexInRowbuffer];
 
-            if (!isNullSelectionAllowed() && getNullSelectionItemId() != null
-                    && itemId == getNullSelectionItemId()) {
+            if (shouldHideNullSelectionItem()) {
                 // Remove null selection item if null selection is not allowed
                 continue;
             }
 
-            paintRow(target, cells, iseditable, actionSet, iscomponent,
+            paintRow(target, cells, isEditable(), actionSet, iscomponent,
                     indexInRowbuffer, itemId);
         }
         target.endTag("rows");
+    }
 
-        // The select variable is only enabled if selectable
-        if (selectable) {
-            target.addVariable(this, "selected",
-                    selectedKeys.toArray(new String[selectedKeys.size()]));
+    private boolean[] findCellsWithComponents() {
+        final boolean[] isComponent = new boolean[visibleColumns.size()];
+        int isComponentIndex = 0;
+        for (final Iterator<Object> it = visibleColumns.iterator(); it
+                .hasNext() && isComponentIndex < isComponent.length;) {
+            final Object columnId = it.next();
+            if (columnGenerators.containsKey(columnId)) {
+                isComponent[isComponentIndex++] = true;
+            } else {
+                final Class<?> colType = getType(columnId);
+                isComponent[isComponentIndex++] = colType != null
+                        && Component.class.isAssignableFrom(colType);
+            }
         }
+        return isComponent;
+    }
 
-        // The cursors are only shown on pageable table
-        if (first != 0 || getPageLength() > 0) {
-            target.addVariable(this, "firstvisible", first);
+    private void paintVisibleColumnOrder(PaintTarget target) {
+        // Visible column order
+        final ArrayList<String> visibleColOrder = new ArrayList<String>();
+        for (final Iterator<Object> it = visibleColumns.iterator(); it
+                .hasNext();) {
+            final Object columnId = it.next();
+            if (!isColumnCollapsed(columnId)) {
+                visibleColOrder.add(columnIdMap.key(columnId));
+            }
         }
+        target.addAttribute("vcolorder", visibleColOrder.toArray());
+    }
 
-        // Sorting
-        if (getContainerDataSource() instanceof Container.Sortable) {
-            target.addVariable(this, "sortcolumn",
-                    columnIdMap.key(sortContainerPropertyId));
-            target.addVariable(this, "sortascending", sortAscending);
+    private Set<Action> findAndPaintBodyActions(PaintTarget target) {
+        Set<Action> actionSet = new LinkedHashSet<Action>();
+        if (actionHandlers != null) {
+            final ArrayList<String> keys = new ArrayList<String>();
+            for (Handler ah : actionHandlers) {
+                // Getting actions for the null item, which in this case means
+                // the body item
+                final Action[] actions = ah.getActions(null, this);
+                if (actions != null) {
+                    for (Action action : actions) {
+                        actionSet.add(action);
+                        keys.add(actionMapper.key(action));
+                    }
+                }
+            }
+            target.addAttribute("alb", keys.toArray());
         }
+        return actionSet;
+    }
 
-        // Resets and paints "to be painted next" variables. Also reset
-        // pageBuffer
-        reqFirstRowToPaint = -1;
-        reqRowsToPaint = -1;
-        containerChangeToBeRendered = false;
-        target.addVariable(this, "reqrows", reqRowsToPaint);
-        target.addVariable(this, "reqfirstrow", reqFirstRowToPaint);
+    private boolean shouldHideNullSelectionItem() {
+        return !isNullSelectionAllowed() && getNullSelectionItemId() != null
+                && containsId(getNullSelectionItemId());
+    }
 
-        // Actions
-        if (!actionSet.isEmpty()) {
-            target.addVariable(this, "action", "");
-            target.startTag("actions");
-            for (final Iterator<Action> it = actionSet.iterator(); it.hasNext();) {
-                final Action a = it.next();
-                target.startTag("action");
-                if (a.getCaption() != null) {
-                    target.addAttribute("caption", a.getCaption());
-                }
-                if (a.getIcon() != null) {
-                    target.addAttribute("icon", a.getIcon());
-                }
-                target.addAttribute("key", actionMapper.key(a));
-                target.endTag("action");
+    private int findNumRowsToPaint(PaintTarget target, final Object[][] cells)
+            throws PaintException {
+        int rows;
+        if (reqRowsToPaint >= 0) {
+            rows = reqRowsToPaint;
+        } else {
+            rows = cells[0].length;
+            if (alwaysRecalculateColumnWidths) {
+                // TODO experimental feature for now: tell the client to
+                // recalculate column widths.
+                // We'll only do this for paints that do not originate from
+                // table scroll/cache requests (i.e when reqRowsToPaint<0)
+                target.addAttribute("recalcWidths", true);
             }
-            target.endTag("actions");
         }
-        if (columnReorderingAllowed) {
-            final String[] colorder = new String[visibleColumns.size()];
-            int i = 0;
-            for (final Iterator<Object> it = visibleColumns.iterator(); it
-                    .hasNext() && i < colorder.length;) {
-                colorder[i++] = columnIdMap.key(it.next());
-            }
-            target.addVariable(this, "columnorder", colorder);
+        return rows;
+    }
+
+    private void paintSelectMode(PaintTarget target) throws PaintException {
+        if (multiSelectMode != MultiSelectMode.DEFAULT) {
+            target.addAttribute("multiselectmode", multiSelectMode.ordinal());
         }
-        // Available columns
-        if (columnCollapsingAllowed) {
-            final HashSet<Object> ccs = new HashSet<Object>();
-            for (final Iterator<Object> i = visibleColumns.iterator(); i
-                    .hasNext();) {
-                final Object o = i.next();
-                if (isColumnCollapsed(o)) {
-                    ccs.add(o);
-                }
-            }
-            final String[] collapsedkeys = new String[ccs.size()];
-            int nextColumn = 0;
-            for (final Iterator<Object> it = visibleColumns.iterator(); it
-                    .hasNext() && nextColumn < collapsedkeys.length;) {
-                final Object columnId = it.next();
-                if (isColumnCollapsed(columnId)) {
-                    collapsedkeys[nextColumn++] = columnIdMap.key(columnId);
-                }
-            }
-            target.addVariable(this, "collapsedcolumns", collapsedkeys);
+        if (isSelectable()) {
+            target.addAttribute("selectmode", (isMultiSelect() ? "multi"
+                    : "single"));
+        } else {
+            target.addAttribute("selectmode", "none");
         }
-        target.startTag("visiblecolumns");
-        if (rowHeadersAreEnabled()) {
-            target.startTag("column");
-            target.addAttribute("cid", ROW_HEADER_COLUMN_KEY);
-            paintColumnWidth(target, ROW_HEADER_FAKE_PROPERTY_ID);
-            target.endTag("column");
+        if (!isNullSelectionAllowed()) {
+            target.addAttribute("nsa", false);
         }
-        int i = 0;
-        for (final Iterator<Object> it = visibleColumns.iterator(); it
-                .hasNext(); i++) {
-            final Object columnId = it.next();
-            if (columnId != null) {
-                target.startTag("column");
-                target.addAttribute("cid", columnIdMap.key(columnId));
-                final String head = getColumnHeader(columnId);
-                target.addAttribute("caption", (head != null ? head : ""));
-                final String foot = getColumnFooter(columnId);
-                target.addAttribute("fcaption", (foot != null ? foot : ""));
-                if (isColumnCollapsed(columnId)) {
-                    target.addAttribute("collapsed", true);
-                }
-                if (colheads) {
-                    if (getColumnIcon(columnId) != null) {
-                        target.addAttribute("icon", getColumnIcon(columnId));
-                    }
-                    if (sortables.contains(columnId)) {
-                        target.addAttribute("sortable", true);
-                    }
-                }
-                if (!ALIGN_LEFT.equals(getColumnAlignment(columnId))) {
-                    target.addAttribute("align", getColumnAlignment(columnId));
+
+        // selection support
+        // The select variable is only enabled if selectable
+        if (isSelectable()) {
+            target.addVariable(this, "selected", findSelectedKeys());
+        }
+    }
+
+    private String[] findSelectedKeys() {
+        LinkedList<String> selectedKeys = new LinkedList<String>();
+        if (isMultiSelect()) {
+            HashSet<?> sel = new HashSet<Object>((Set<?>) getValue());
+            Collection<?> vids = getVisibleItemIds();
+            for (Iterator<?> it = vids.iterator(); it.hasNext();) {
+                Object id = it.next();
+                if (sel.contains(id)) {
+                    selectedKeys.add(itemIdMapper.key(id));
                 }
-                paintColumnWidth(target, columnId);
-                target.endTag("column");
+            }
+        } else {
+            Object value = getValue();
+            if (value == null) {
+                value = getNullSelectionItemId();
+            }
+            if (value != null) {
+                selectedKeys.add(itemIdMapper.key(value));
             }
         }
-        target.endTag("visiblecolumns");
+        return selectedKeys.toArray(new String[selectedKeys.size()]);
+    }
 
-        if (dropHandler != null) {
-            dropHandler.getAcceptCriterion().paint(target);
+    private void paintDragMode(PaintTarget target) throws PaintException {
+        if (dragMode != TableDragMode.NONE) {
+            target.addAttribute("dragmode", dragMode.ordinal());
+        }
+    }
+
+    private void paintTabIndex(PaintTarget target) throws PaintException {
+        // The tab ordering number
+        if (getTabIndex() > 0) {
+            target.addAttribute("tabindex", getTabIndex());
         }
     }
 
index 20fc8e44d7b57067257c6319934bae20b580dd1a..55b80a8e90257f489bd409bfbf4c1fbe0537d608 100644 (file)
@@ -304,6 +304,7 @@ public class TreeTable extends Table implements Hierarchical {
     private ContainerStrategy cStrategy;
     private Object focusedRowId = null;
     private Object hierarchyColumnId;
+    private Object toggledItemId;
 
     private ContainerStrategy getContainerStrategy() {
         if (cStrategy == null) {
@@ -415,6 +416,55 @@ public class TreeTable extends Table implements Hierarchical {
             }
         }
         super.paintContent(target);
+        toggledItemId = null;
+    }
+
+    @Override
+    protected boolean isPartialRowUpdate() {
+        return toggledItemId != null;
+    }
+
+    @Override
+    protected int getFirstAddedItemIndex() {
+        return indexOfId(toggledItemId) + 1;
+    }
+
+    @Override
+    protected int getAddedRowCount() {
+        return countSubNodesRecursively(getContainerDataSource(), toggledItemId);
+    }
+
+    private int countSubNodesRecursively(Hierarchical hc, Object itemId) {
+        int count = 0;
+        // we need the number of children for toggledItemId no matter if its
+        // collapsed or expanded. Other items' children are only counted if the
+        // item is expanded.
+        if (getContainerStrategy().isNodeOpen(itemId)
+                || itemId == toggledItemId) {
+            Collection<?> children = hc.getChildren(itemId);
+            if (children != null) {
+                count += children != null ? children.size() : 0;
+                for (Object id : children) {
+                    count += countSubNodesRecursively(hc, id);
+                }
+            }
+        }
+        return count;
+    }
+
+    @Override
+    protected int getFirstUpdatedItemIndex() {
+        return indexOfId(toggledItemId);
+    }
+
+    @Override
+    protected int getUpdatedRowCount() {
+        return 1;
+    }
+
+    @Override
+    protected boolean shouldHideAddedRows() {
+        return !getContainerStrategy().isNodeOpen(toggledItemId);
     }
 
     private void toggleChildVisibility(Object itemId) {
@@ -422,7 +472,7 @@ public class TreeTable extends Table implements Hierarchical {
         // ensure that page still has first item in page, ignore buffer refresh
         // (forced in this method)
         setCurrentPageFirstItemIndex(getCurrentPageFirstItemIndex());
-
+        toggledItemId = itemId;
         requestRepaint();
     }