From 4f1446b48c4fd77a9d1ca274c0baf977f9f662d4 Mon Sep 17 00:00:00 2001
From: Matti Tahvonen
Date: Wed, 26 Aug 2009 09:27:10 +0000
Subject: [PATCH] Multiple scrolltable improvements - fixes #3209 - moved to
GWT event handler (instead of deprecated listener) - fixed regressions since
variable pagelength implementation - fixed some other small issues
svn changeset:8547/svn branch:6.1
---
.../terminal/gwt/client/ui/VScrollTable.java | 270 ++++++++++--------
src/com/vaadin/ui/Table.java | 57 +++-
2 files changed, 210 insertions(+), 117 deletions(-)
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java
index f548b4be01..32808e36c6 100644
--- a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java
+++ b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java
@@ -17,6 +17,8 @@ import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.TableCellElement;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.dom.client.TableSectionElement;
+import com.google.gwt.event.dom.client.ScrollEvent;
+import com.google.gwt.event.dom.client.ScrollHandler;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.DeferredCommand;
@@ -27,7 +29,6 @@ import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.RootPanel;
-import com.google.gwt.user.client.ui.ScrollListener;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.terminal.gwt.client.ApplicationConnection;
@@ -63,18 +64,20 @@ import com.vaadin.terminal.gwt.client.ui.VScrollTable.VScrollTableBody.VScrollTa
*
* TODO implement unregistering for child components in Cells
*/
-public class VScrollTable extends FlowPanel implements Table, ScrollListener {
+public class VScrollTable extends FlowPanel implements Table, ScrollHandler {
public static final String CLASSNAME = "v-table";
+ private static final double CACHE_RATE_DEFAULT = 2;
+
/**
* multiple of pagelength which component will cache when requesting more
* rows
*/
- private static final double CACHE_RATE = 2;
+ private double cache_rate = CACHE_RATE_DEFAULT;
/**
* fraction of pageLenght which can be scrolled without making new request
*/
- private static final double CACHE_REACT_RATE = 1.5;
+ private double cache_react_rate = 0.75 * cache_rate;
public static final char ALIGN_CENTER = 'c';
public static final char ALIGN_LEFT = 'b';
@@ -147,7 +150,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollListener {
private boolean rendering = false;
public VScrollTable() {
- bodyContainer.addScrollListener(this);
+ bodyContainer.addScrollHandler(this);
bodyContainer.setStyleName(CLASSNAME + "-body");
setStyleName(CLASSNAME);
@@ -188,6 +191,9 @@ public class VScrollTable extends FlowPanel implements Table, ScrollListener {
totalRows = newTotalRows;
}
+ setCacheRate(uidl.hasAttribute("cr") ? uidl.getDoubleAttribute("cr")
+ : CACHE_RATE_DEFAULT);
+
recalcWidths = uidl.hasAttribute("recalcWidths");
pageLength = uidl.getIntAttribute("pagelength");
@@ -292,6 +298,13 @@ public class VScrollTable extends FlowPanel implements Table, ScrollListener {
headerChangedDuringUpdate = false;
}
+ private void setCacheRate(double d) {
+ if (cache_rate != d) {
+ cache_rate = d;
+ cache_react_rate = 0.75 * d;
+ }
+ }
+
/**
* Unregisters Paintables in "trashed" HasWidgets (IScrollTableBodys or
* IScrollTableRows). This is done lazily as Table must survive from
@@ -382,7 +395,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollListener {
tBody.renderRows(uidl, firstRow, reqRows);
final int optimalFirstRow = (int) (firstRowInViewPort - pageLength
- * CACHE_RATE);
+ * cache_rate);
boolean cont = true;
while (cont && tBody.getLastRendered() > optimalFirstRow
&& tBody.getFirstRendered() < optimalFirstRow) {
@@ -390,7 +403,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollListener {
cont = tBody.unlinkRow(true);
}
final int optimalLastRow = (int) (firstRowInViewPort + pageLength + pageLength
- * CACHE_RATE);
+ * cache_rate);
cont = true;
while (cont && tBody.getLastRendered() > optimalLastRow) {
// client.console.log("removing row from the end");
@@ -669,6 +682,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollListener {
tBody.reLayoutComponents();
}
+ updatePageLength();
+
/*
* Fix "natural" height if height is not set. This must be after width
* fixing so the components' widths have been adjusted.
@@ -711,13 +726,17 @@ public class VScrollTable extends FlowPanel implements Table, ScrollListener {
if (enabled) {
// Do we need cache rows
if (tBody.getLastRendered() + 1 < firstRowInViewPort + pageLength
- + CACHE_REACT_RATE * pageLength) {
+ + (int) cache_react_rate * pageLength) {
if (totalRows - 1 > tBody.getLastRendered()) {
// fetch cache rows
- rowRequestHandler
- .setReqFirstRow(tBody.getLastRendered() + 1);
- rowRequestHandler
- .setReqRows((int) (pageLength * CACHE_RATE));
+ int firstInNewSet = tBody.getLastRendered() + 1;
+ rowRequestHandler.setReqFirstRow(firstInNewSet);
+ int lastInNewSet = (int) (firstRowInViewPort + pageLength + cache_rate
+ * pageLength);
+ if (lastInNewSet > totalRows - 1) {
+ lastInNewSet = totalRows - 1;
+ }
+ rowRequestHandler.setReqRows(lastInNewSet - firstInNewSet);
rowRequestHandler.deferRowFetch(1);
}
}
@@ -741,87 +760,6 @@ public class VScrollTable extends FlowPanel implements Table, ScrollListener {
return false;
}
- /**
- * This method has logic which rows needs to be requested from server when
- * user scrolls
- */
- public void onScroll(Widget widget, int scrollLeft, int scrollTop) {
- if (!initializedAndAttached) {
- return;
- }
- if (!enabled) {
- bodyContainer.setScrollPosition(firstRowInViewPort
- * tBody.getRowHeight());
- return;
- }
-
- rowRequestHandler.cancel();
-
- // fix headers horizontal scrolling
- tHead.setHorizontalScrollPosition(scrollLeft);
-
- firstRowInViewPort = (int) Math.ceil(scrollTop
- / (double) tBody.getRowHeight());
-
- int postLimit = (int) (firstRowInViewPort + pageLength + pageLength
- * CACHE_REACT_RATE);
- if (postLimit > totalRows - 1) {
- postLimit = totalRows - 1;
- }
- int preLimit = (int) (firstRowInViewPort - pageLength
- * CACHE_REACT_RATE);
- if (preLimit < 0) {
- preLimit = 0;
- }
- final int lastRendered = tBody.getLastRendered();
- final int firstRendered = tBody.getFirstRendered();
-
- if (postLimit <= lastRendered && preLimit >= firstRendered) {
- // remember which firstvisible we requested, in case the server has
- // a differing opinion
- lastRequestedFirstvisible = firstRowInViewPort;
- client.updateVariable(paintableId, "firstvisible",
- firstRowInViewPort, false);
- return; // scrolled withing "non-react area"
- }
-
- if (firstRowInViewPort - pageLength * CACHE_RATE > lastRendered
- || firstRowInViewPort + pageLength + pageLength * CACHE_RATE < firstRendered) {
- // need a totally new set
- rowRequestHandler
- .setReqFirstRow((int) (firstRowInViewPort - pageLength
- * CACHE_RATE));
- int last = firstRowInViewPort + (int) CACHE_RATE * pageLength
- + pageLength;
- if (last > totalRows) {
- last = totalRows - 1;
- }
- rowRequestHandler.setReqRows(last
- - rowRequestHandler.getReqFirstRow() + 1);
- rowRequestHandler.deferRowFetch();
- return;
- }
- if (preLimit < firstRendered) {
- // need some rows to the beginning of the rendered area
- rowRequestHandler
- .setReqFirstRow((int) (firstRowInViewPort - pageLength
- * CACHE_RATE));
- rowRequestHandler.setReqRows(firstRendered
- - rowRequestHandler.getReqFirstRow());
- rowRequestHandler.deferRowFetch();
-
- return;
- }
- if (postLimit > lastRendered) {
- // need some rows to the end of the rendered area
- rowRequestHandler.setReqFirstRow(lastRendered + 1);
- rowRequestHandler.setReqRows((int) ((firstRowInViewPort
- + pageLength + pageLength * CACHE_RATE) - lastRendered));
- rowRequestHandler.deferRowFetch();
- }
-
- }
-
private void announceScrollPosition() {
if (scrollPositionElement == null) {
scrollPositionElement = DOM.createDiv();
@@ -842,13 +780,12 @@ public class VScrollTable extends FlowPanel implements Table, ScrollListener {
+ "px");
// indexes go from 1-totalRows, as rowheaders in index-mode indicate
- int last = (firstRowInViewPort + (bodyContainer.getOffsetHeight() / tBody
- .getRowHeight()));
+ int last = (firstRowInViewPort + pageLength);
if (last > totalRows) {
last = totalRows;
}
DOM.setInnerHTML(scrollPositionElement, ""
- + (firstRowInViewPort + 1) + " – " + last + "..."
+ + (firstRowInViewPort + 1) + " – " + (last) + "..."
+ "");
DOM.setStyleAttribute(scrollPositionElement, "display", "block");
}
@@ -907,9 +844,9 @@ public class VScrollTable extends FlowPanel implements Table, ScrollListener {
int firstToBeRendered = tBody.firstRendered;
if (reqFirstRow < firstToBeRendered) {
firstToBeRendered = reqFirstRow;
- } else if (firstRowInViewPort - (int) (CACHE_RATE * pageLength) > firstToBeRendered) {
+ } else if (firstRowInViewPort - (int) (cache_rate * pageLength) > firstToBeRendered) {
firstToBeRendered = firstRowInViewPort
- - (int) (CACHE_RATE * pageLength);
+ - (int) (cache_rate * pageLength);
if (firstToBeRendered < 0) {
firstToBeRendered = 0;
}
@@ -920,8 +857,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollListener {
if (reqFirstRow + reqRows - 1 > lastToBeRendered) {
lastToBeRendered = reqFirstRow + reqRows - 1;
} else if (firstRowInViewPort + pageLength + pageLength
- * CACHE_RATE < lastToBeRendered) {
- lastToBeRendered = (firstRowInViewPort + pageLength + (int) (pageLength * CACHE_RATE));
+ * cache_rate < lastToBeRendered) {
+ lastToBeRendered = (firstRowInViewPort + pageLength + (int) (pageLength * cache_rate));
if (lastToBeRendered >= totalRows) {
lastToBeRendered = totalRows - 1;
}
@@ -962,8 +899,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollListener {
* Sends request to refresh content at this position.
*/
public void refreshContent() {
- int first = (int) (firstRowInViewPort - pageLength * CACHE_RATE);
- int reqRows = (int) (2 * pageLength * CACHE_RATE + pageLength);
+ int first = (int) (firstRowInViewPort - pageLength * cache_rate);
+ int reqRows = (int) (2 * pageLength * cache_rate + pageLength);
if (first < 0) {
reqRows = reqRows + first;
first = 0;
@@ -1213,7 +1150,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollListener {
firstvisible = 0;
rowRequestHandler.setReqFirstRow(0);
rowRequestHandler.setReqRows((int) (2 * pageLength
- * CACHE_RATE + pageLength));
+ * cache_rate + pageLength));
rowRequestHandler.deferRowFetch();
}
break;
@@ -1897,13 +1834,13 @@ public class VScrollTable extends FlowPanel implements Table, ScrollListener {
// this may be a new set of rows due content change,
// ensure we have proper cache rows
int reactFirstRow = (int) (firstRowInViewPort - pageLength
- * CACHE_REACT_RATE);
+ * cache_react_rate);
int reactLastRow = (int) (firstRowInViewPort + pageLength + pageLength
- * CACHE_REACT_RATE);
+ * cache_react_rate);
if (reactFirstRow < 0) {
reactFirstRow = 0;
}
- if (reactLastRow > totalRows) {
+ if (reactLastRow >= totalRows) {
reactLastRow = totalRows - 1;
}
if (lastRendered < reactLastRow) {
@@ -2066,8 +2003,9 @@ public class VScrollTable extends FlowPanel implements Table, ScrollListener {
} else {
if (tBodyElement.getRows().getLength() > 0) {
- rowHeight = getTableHeight()
- / tBodyElement.getRows().getLength();
+ int tableHeight = getTableHeight();
+ int rowCount = tBodyElement.getRows().getLength();
+ rowHeight = tableHeight / rowCount;
} else {
if (isAttached()) {
// measure row height by adding a dummy row
@@ -2081,9 +2019,6 @@ public class VScrollTable extends FlowPanel implements Table, ScrollListener {
}
}
tBodyMeasurementsDone = true;
-
- updatePageLength();
-
return rowHeight;
}
}
@@ -2599,7 +2534,12 @@ public class VScrollTable extends FlowPanel implements Table, ScrollListener {
w = headerCell.getOffsetWidth() - getCellExtraWidth();
}
}
- return new RenderSpace(w, getRowHeight());
+ return new RenderSpace(w, 0) {
+ @Override
+ public int getHeight() {
+ return getRowHeight();
+ }
+ };
}
private int getColIndexOf(Widget child) {
@@ -2681,10 +2621,22 @@ public class VScrollTable extends FlowPanel implements Table, ScrollListener {
if (anotherPartlyVisible) {
rowsAtOnce++;
}
+ if (pageLength != rowsAtOnce) {
+ pageLength = rowsAtOnce;
+ client.updateVariable(paintableId, "pagelength", pageLength, false);
- pageLength = rowsAtOnce;
-
- client.updateVariable(paintableId, "pagelength", pageLength, false);
+ if (!rendering) {
+ int currentlyVisible = tBody.lastRendered - tBody.firstRendered;
+ if (currentlyVisible < pageLength
+ && currentlyVisible < totalRows) {
+ // shake scrollpanel to fill empty space
+ bodyContainer.setScrollPosition(bodyContainer
+ .getScrollPosition() + 1);
+ bodyContainer.setScrollPosition(bodyContainer
+ .getScrollPosition() - 1);
+ }
+ }
+ }
}
@@ -2852,7 +2804,9 @@ public class VScrollTable extends FlowPanel implements Table, ScrollListener {
this.height = height;
super.setHeight(height);
setContainerHeight();
- updatePageLength();
+ if (initializedAndAttached) {
+ updatePageLength();
+ }
}
/*
@@ -2895,4 +2849,88 @@ public class VScrollTable extends FlowPanel implements Table, ScrollListener {
return s;
}
+ /**
+ * This method has logic which rows needs to be requested from server when
+ * user scrolls
+ */
+ public void onScroll(ScrollEvent event) {
+ int scrollLeft = bodyContainer.getElement().getScrollLeft();
+ int scrollTop = bodyContainer.getScrollPosition();
+ if (!initializedAndAttached) {
+ return;
+ }
+ if (!enabled) {
+ bodyContainer.setScrollPosition(firstRowInViewPort
+ * tBody.getRowHeight());
+ return;
+ }
+
+ rowRequestHandler.cancel();
+
+ // fix headers horizontal scrolling
+ tHead.setHorizontalScrollPosition(scrollLeft);
+
+ firstRowInViewPort = (int) Math.ceil(scrollTop
+ / (double) tBody.getRowHeight());
+ if (firstRowInViewPort > totalRows - pageLength) {
+ firstRowInViewPort = totalRows - pageLength;
+ }
+
+ int postLimit = (int) (firstRowInViewPort + (pageLength - 1) + pageLength
+ * cache_react_rate);
+ if (postLimit > totalRows - 1) {
+ postLimit = totalRows - 1;
+ }
+ int preLimit = (int) (firstRowInViewPort - pageLength
+ * cache_react_rate);
+ if (preLimit < 0) {
+ preLimit = 0;
+ }
+ final int lastRendered = tBody.getLastRendered();
+ final int firstRendered = tBody.getFirstRendered();
+
+ if (postLimit <= lastRendered && preLimit >= firstRendered) {
+ // remember which firstvisible we requested, in case the server has
+ // a differing opinion
+ lastRequestedFirstvisible = firstRowInViewPort;
+ client.updateVariable(paintableId, "firstvisible",
+ firstRowInViewPort, false);
+ return; // scrolled withing "non-react area"
+ }
+
+ if (firstRowInViewPort - pageLength * cache_rate > lastRendered
+ || firstRowInViewPort + pageLength + pageLength * cache_rate < firstRendered) {
+ // need a totally new set
+ rowRequestHandler
+ .setReqFirstRow((firstRowInViewPort - (int) (pageLength * cache_rate)));
+ int last = firstRowInViewPort + (int) (cache_rate * pageLength)
+ + pageLength - 1;
+ if (last >= totalRows) {
+ last = totalRows - 1;
+ }
+ rowRequestHandler.setReqRows(last
+ - rowRequestHandler.getReqFirstRow() + 1);
+ rowRequestHandler.deferRowFetch();
+ return;
+ }
+ if (preLimit < firstRendered) {
+ // need some rows to the beginning of the rendered area
+ rowRequestHandler
+ .setReqFirstRow((int) (firstRowInViewPort - pageLength
+ * cache_rate));
+ rowRequestHandler.setReqRows(firstRendered
+ - rowRequestHandler.getReqFirstRow());
+ rowRequestHandler.deferRowFetch();
+
+ return;
+ }
+ if (postLimit > lastRendered) {
+ // need some rows to the end of the rendered area
+ rowRequestHandler.setReqFirstRow(lastRendered + 1);
+ rowRequestHandler.setReqRows((int) ((firstRowInViewPort
+ + pageLength + pageLength * cache_rate) - lastRendered));
+ rowRequestHandler.deferRowFetch();
+ }
+ }
+
}
diff --git a/src/com/vaadin/ui/Table.java b/src/com/vaadin/ui/Table.java
index 215bfedee3..0be727fa6f 100644
--- a/src/com/vaadin/ui/Table.java
+++ b/src/com/vaadin/ui/Table.java
@@ -148,6 +148,11 @@ public class Table extends AbstractSelect implements Action.Container,
*/
public static final int ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID = AbstractSelect.ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID;
+ /**
+ * The default rate that table caches rows for smooth scrolling.
+ */
+ private static final double CACHE_RATE_DEFAULT = 2;
+
/* Private table extensions to Select */
/**
@@ -320,6 +325,8 @@ public class Table extends AbstractSelect implements Action.Container,
*/
protected boolean alwaysRecalculateColumnWidths = false;
+ private double cacheRate = CACHE_RATE_DEFAULT;
+
/* Table constructors */
/**
@@ -754,8 +761,13 @@ public class Table extends AbstractSelect implements Action.Container,
* Setting page length 0 disables paging. The page length defaults to 15.
*
*
+ *
+ * If Table has width set ({@link #setWidth(float, int)} ) the client side
+ * may update the page length automatically the correct value.
+ *
+ *
* @param pageLength
- * the Length of one page.
+ * the length of one page.
*/
public void setPageLength(int pageLength) {
if (pageLength >= 0 && this.pageLength != pageLength) {
@@ -766,6 +778,45 @@ public class Table extends AbstractSelect implements Action.Container,
}
}
+ /**
+ * This method adjusts a possible caching mechanism of table implementation.
+ *
+ *
+ * Table component may fetch and render some rows outside visible area. With
+ * complex tables (for example containing layouts and components), the
+ * client side may become unresponsive. Setting the value lower, UI will
+ * become more responsive. With higher values scrolling in client will hit
+ * server less frequently.
+ *
+ *
+ * The amount of cached rows will be cacheRate multiplied with pageLength (
+ * {@link #setPageLength(int)} both below and above visible area..
+ *
+ * @param cacheRate
+ * a value over 0 (fastest rendering time). Higher value will
+ * cache more rows on server (smoother scrolling). Default value
+ * is 2.
+ */
+ public void setCacheRate(double cacheRate) {
+ if (cacheRate < 0) {
+ throw new IllegalArgumentException(
+ "cacheRate cannot be less than zero");
+ }
+ if (this.cacheRate != cacheRate) {
+ this.cacheRate = cacheRate;
+ requestRepaint();
+ }
+ }
+
+ /**
+ * @see #setCacheRate(double)
+ *
+ * @return the current cache rate value
+ */
+ public double getCacheRate() {
+ return cacheRate;
+ }
+
/**
* Getter for property currentPageFirstItem.
*
@@ -2018,6 +2069,10 @@ public class Table extends AbstractSelect implements Action.Container,
target.addAttribute("listenClicks", true);
}
+ if (cacheRate != CACHE_RATE_DEFAULT) {
+ target.addAttribute("cr", cacheRate);
+ }
+
target.addAttribute("cols", cols);
target.addAttribute("rows", rows);
--
2.39.5