From 26bda727fc72dfece8bc93b346777e94d52c65d6 Mon Sep 17 00:00:00 2001 From: Jonatan Kronqvist Date: Thu, 16 Jun 2011 07:55:38 +0000 Subject: [PATCH] Added support for partial updates in Table and TreeTable (#6722) svn changeset:19417/svn branch:6.7 --- .../terminal/gwt/client/ui/VScrollTable.java | 541 ++++++---- src/com/vaadin/ui/Table.java | 966 +++++++++++------- src/com/vaadin/ui/TreeTable.java | 52 +- 3 files changed, 1001 insertions(+), 558 deletions(-) diff --git a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java index fdf61b8292..381e9c83b4 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java @@ -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 selectedKeys = uidl - .getStringArrayVariableAsSet("selected"); - if (scrollBody != null) { - Iterator 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 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 selectedKeys = uidl + .getStringArrayVariableAsSet("selected"); + if (scrollBody != null) { + Iterator 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 renderedRows = new ArrayList(); + private final LinkedList renderedRows = new LinkedList(); /** * 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 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 { diff --git a/src/com/vaadin/ui/Table.java b/src/com/vaadin/ui/Table.java index 738f9c53c5..907b3a98d9 100644 --- a/src/com/vaadin/ui/Table.java +++ b/src/com/vaadin/ui/Table.java @@ -1422,216 +1422,207 @@ public class Table extends AbstractSelect implements Action.Container, return; } - if (isContentRefreshesEnabled) { - - HashSet oldListenedProperties = listenedProperties; - HashSet oldVisibleComponents = visibleComponents; - - // initialize the listener collections - listenedProperties = new HashSet(); - visibleComponents = new HashSet(); - - // 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(); + visibleComponents = new HashSet(); + + // 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 oldListenedProperties = listenedProperties; + HashSet 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 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 actionSet) + throws PaintException { + paintPartialRowUpdates(target, actionSet); + paintPartialRowAdditions(target, actionSet); + } + + private void paintPartialRowUpdates(PaintTarget target, + Set 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 selectedKeys = new LinkedList(); - if (isMultiSelect()) { - HashSet sel = new HashSet((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 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 actionSet = new LinkedHashSet(); - if (actionHandlers != null) { - final ArrayList keys = new ArrayList(); - 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 visibleColOrder = new ArrayList(); - for (final Iterator 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 collapsedCols = new HashSet(); + 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 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 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 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 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 visibleColOrder = new ArrayList(); + for (final Iterator 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 findAndPaintBodyActions(PaintTarget target) { + Set actionSet = new LinkedHashSet(); + if (actionHandlers != null) { + final ArrayList keys = new ArrayList(); + 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 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 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 ccs = new HashSet(); - for (final Iterator 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 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 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 selectedKeys = new LinkedList(); + if (isMultiSelect()) { + HashSet sel = new HashSet((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()); } } diff --git a/src/com/vaadin/ui/TreeTable.java b/src/com/vaadin/ui/TreeTable.java index 20fc8e44d7..55b80a8e90 100644 --- a/src/com/vaadin/ui/TreeTable.java +++ b/src/com/vaadin/ui/TreeTable.java @@ -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(); } -- 2.39.5