From 754caf060f1473f2367c421ecd70f3a8966f6f10 Mon Sep 17 00:00:00 2001 From: Henrik Paul Date: Mon, 23 Mar 2015 15:35:26 +0200 Subject: [PATCH] Adds Escalator.scrollToSpacer (#17270) Change-Id: Ib420e8da6c167fdba9d3023a73cb242643c7af67 --- .../com/vaadin/client/widgets/Escalator.java | 159 +++++++++++++----- .../EscalatorBasicClientFeaturesTest.java | 2 + .../escalator/EscalatorSpacerTest.java | 37 ++++ .../EscalatorBasicClientFeaturesWidget.java | 13 ++ 4 files changed, 171 insertions(+), 40 deletions(-) diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 75b797eb1f..87f19d2ded 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -827,8 +827,8 @@ public class Escalator extends Widget implements RequiresResize, boolean verticalScrollNeeded = scrollContentHeight > tableWrapperHeight + WidgetUtil.PIXEL_EPSILON - - header.heightOfSection - - footer.heightOfSection; + - header.getHeightOfSection() + - footer.getHeightOfSection(); boolean horizontalScrollNeeded = scrollContentWidth > tableWrapperWidth + WidgetUtil.PIXEL_EPSILON; @@ -837,8 +837,8 @@ public class Escalator extends Widget implements RequiresResize, if (!verticalScrollNeeded && horizontalScrollNeeded) { verticalScrollNeeded = scrollContentHeight > tableWrapperHeight + WidgetUtil.PIXEL_EPSILON - - header.heightOfSection - - footer.heightOfSection + - header.getHeightOfSection() + - footer.getHeightOfSection() - horizontalScrollbar.getScrollbarThickness(); } else { horizontalScrollNeeded = scrollContentWidth > tableWrapperWidth @@ -860,8 +860,10 @@ public class Escalator extends Widget implements RequiresResize, tableWrapper.getStyle().setHeight(tableWrapperHeight, Unit.PX); tableWrapper.getStyle().setWidth(tableWrapperWidth, Unit.PX); + double footerHeight = footer.getHeightOfSection(); + double headerHeight = header.getHeightOfSection(); double vScrollbarHeight = Math.max(0, tableWrapperHeight - - footer.heightOfSection - header.heightOfSection); + - footerHeight - headerHeight); verticalScrollbar.setOffsetSize(vScrollbarHeight); verticalScrollbar.setScrollSize(scrollContentHeight); @@ -1158,7 +1160,7 @@ public class Escalator extends Widget implements RequiresResize, final double viewportStartPx = getScrollTop(); final double viewportEndPx = viewportStartPx - + body.calculateHeight(); + + body.getHeightOfSection(); final double scrollTop = getScrollPos(destination, targetStartPx, targetEndPx, viewportStartPx, viewportEndPx, padding); @@ -1183,9 +1185,6 @@ public class Escalator extends Widget implements RequiresResize, */ protected final TableSectionElement root; - /** The height of the combined rows in the DOM. Never negative. */ - protected double heightOfSection = 0; - /** * The primary style name of the escalator. Most commonly provided by * Escalator as "v-escalator". @@ -2097,10 +2096,24 @@ public class Escalator extends Widget implements RequiresResize, refreshCells(rowRange, colRange); } } + + /** + * The height of this table section. + *

+ * Note that {@link Escalator#getBody() the body} will calculate its + * height, while the others will return a precomputed value. + * + * @return the height of this table section + */ + protected abstract double getHeightOfSection(); } private abstract class AbstractStaticRowContainer extends AbstractRowContainer { + + /** The height of the combined rows in the DOM. Never negative. */ + private double heightOfSection = 0; + public AbstractStaticRowContainer(final TableSectionElement headElement) { super(headElement); } @@ -2194,7 +2207,8 @@ public class Escalator extends Widget implements RequiresResize, * indices are calculated from the scrollbar position. */ verticalScrollbar.setOffsetSize(heightOfEscalator - - header.heightOfSection - footer.heightOfSection); + - header.getHeightOfSection() + - footer.getHeightOfSection()); body.verifyEscalatorCount(); } @@ -2246,6 +2260,11 @@ public class Escalator extends Widget implements RequiresResize, assert root.isOrHasChild(tr) : "Row does not belong to this table section"; return true; } + + @Override + protected double getHeightOfSection() { + return Math.max(0, heightOfSection); + } } private class HeaderRowContainer extends AbstractStaticRowContainer { @@ -2255,10 +2274,10 @@ public class Escalator extends Widget implements RequiresResize, @Override protected void sectionHeightCalculated() { - bodyElem.getStyle().setMarginTop(heightOfSection, Unit.PX); + bodyElem.getStyle().setMarginTop(getHeightOfSection(), Unit.PX); verticalScrollbar.getElement().getStyle() - .setTop(heightOfSection, Unit.PX); - headerDeco.getStyle().setHeight(heightOfSection, Unit.PX); + .setTop(getHeightOfSection(), Unit.PX); + headerDeco.getStyle().setHeight(getHeightOfSection(), Unit.PX); } @Override @@ -2291,8 +2310,10 @@ public class Escalator extends Widget implements RequiresResize, @Override protected void sectionHeightCalculated() { + double headerHeight = header.getHeightOfSection(); + double footerHeight = footer.getHeightOfSection(); int vscrollHeight = (int) Math.floor(heightOfEscalator - - header.heightOfSection - footer.heightOfSection); + - headerHeight - footerHeight); final boolean horizontalScrollbarNeeded = columnConfiguration .calculateRowWidth() > widthOfEscalator; @@ -2300,7 +2321,8 @@ public class Escalator extends Widget implements RequiresResize, vscrollHeight -= horizontalScrollbar.getScrollbarThickness(); } - footerDeco.getStyle().setHeight(footer.heightOfSection, Unit.PX); + footerDeco.getStyle().setHeight(footer.getHeightOfSection(), + Unit.PX); verticalScrollbar.setOffsetSize(vscrollHeight); } @@ -2633,7 +2655,7 @@ public class Escalator extends Widget implements RequiresResize, * getDefaultRowHeight() < getScrollTop(); final boolean addedRowsBelowCurrentViewport = index * getDefaultRowHeight() > getScrollTop() - + calculateHeight(); + + getHeightOfSection(); if (addedRowsAboveCurrentViewport) { /* @@ -2917,7 +2939,7 @@ public class Escalator extends Widget implements RequiresResize, private int getMaxEscalatorRowCapacity() { final int maxEscalatorRowCapacity = (int) Math - .ceil(calculateHeight() / getDefaultRowHeight()) + 1; + .ceil(getHeightOfSection() / getDefaultRowHeight()) + 1; /* * maxEscalatorRowCapacity can become negative if the headers and @@ -3087,7 +3109,7 @@ public class Escalator extends Widget implements RequiresResize, final double contentBottom = getRowCount() * getDefaultRowHeight(); final double viewportBottom = tBodyScrollTop - + calculateHeight(); + + getHeightOfSection(); if (viewportBottom <= contentBottom) { /* * We're in the middle of the row container, everything @@ -3219,7 +3241,7 @@ public class Escalator extends Widget implements RequiresResize, * 5 5 */ final double newScrollTop = contentBottom - - calculateHeight(); + - getHeightOfSection(); setScrollTop(newScrollTop); /* * Manually call the scroll handler, so we get immediate @@ -3406,15 +3428,14 @@ public class Escalator extends Widget implements RequiresResize, return "td"; } - /** - * Calculates the height of the {@code } as it should be rendered - * in the DOM. - */ - private double calculateHeight() { + @Override + protected double getHeightOfSection() { final int tableHeight = tableWrapper.getOffsetHeight(); - final double footerHeight = footer.heightOfSection; - final double headerHeight = header.heightOfSection; - return tableHeight - footerHeight - headerHeight; + final double footerHeight = footer.getHeightOfSection(); + final double headerHeight = header.getHeightOfSection(); + + double heightOfSection = tableHeight - footerHeight - headerHeight; + return Math.max(0, heightOfSection); } @Override @@ -3862,9 +3883,14 @@ public class Escalator extends Widget implements RequiresResize, return visualRowOrder.contains(tr); } - public void reapplySpacerWidths() { + void reapplySpacerWidths() { spacerContainer.reapplySpacerWidths(); } + + void scrollToSpacer(int spacerIndex, ScrollDestination destination, + int padding) { + spacerContainer.scrollToSpacer(spacerIndex, destination, padding); + } } private class ColumnConfigurationImpl implements ColumnConfiguration { @@ -4723,6 +4749,32 @@ public class Escalator extends Widget implements RequiresResize, } } + @SuppressWarnings("boxing") + void scrollToSpacer(int spacerIndex, ScrollDestination destination, + int padding) { + + assert !destination.equals(ScrollDestination.MIDDLE) + || padding != 0 : "destination/padding check should be done before this method"; + + if (!rowIndexToSpacer.containsKey(spacerIndex)) { + throw new IllegalArgumentException("No spacer open at index " + + spacerIndex); + } + + SpacerImpl spacer = rowIndexToSpacer.get(spacerIndex); + double targetStartPx = spacer.getTop(); + double targetEndPx = targetStartPx + spacer.getHeight(); + + Range viewportPixels = getViewportPixels(); + double viewportStartPx = viewportPixels.getStart(); + double viewportEndPx = viewportPixels.getEnd(); + + double scrollTop = getScrollPos(destination, targetStartPx, + targetEndPx, viewportStartPx, viewportEndPx, padding); + + setScrollTop(scrollTop); + } + public void reapplySpacerWidths() { for (SpacerImpl spacer : rowIndexToSpacer.values()) { spacer.getRootElement().getStyle() @@ -5812,10 +5864,7 @@ public class Escalator extends Widget implements RequiresResize, public void scrollToColumn(final int columnIndex, final ScrollDestination destination, final int padding) throws IndexOutOfBoundsException, IllegalArgumentException { - if (destination == ScrollDestination.MIDDLE && padding != 0) { - throw new IllegalArgumentException( - "You cannot have a padding with a MIDDLE destination"); - } + validateScrollDestination(destination, padding); verifyValidColumnIndex(columnIndex); if (columnIndex < columnConfiguration.frozenColumns) { @@ -5856,10 +5905,7 @@ public class Escalator extends Widget implements RequiresResize, public void scrollToRow(final int rowIndex, final ScrollDestination destination, final int padding) throws IndexOutOfBoundsException, IllegalArgumentException { - if (destination == ScrollDestination.MIDDLE && padding != 0) { - throw new IllegalArgumentException( - "You cannot have a padding with a MIDDLE destination"); - } + validateScrollDestination(destination, padding); verifyValidRowIndex(rowIndex); scroller.scrollToRow(rowIndex, destination, padding); @@ -5872,6 +5918,39 @@ public class Escalator extends Widget implements RequiresResize, } } + /** + * Scrolls the body vertically so that the spacer at the given row index is + * visible and there is at least {@literal padding} pixesl to the given + * scroll destination + * + * @since + * @param spacerIndex + * the row index of the spacer to scroll to + * @param destination + * where the spacer should be aligned visually after scrolling + * @param padding + * the number of pixels to place between the scrolled-to spacer + * and the viewport edge + * @throws IllegalArgumentException + * if {@code spacerIndex} is not an opened spacer if + * {@code destination} is {@link ScrollDestination#MIDDLE} and + * padding is nonzero + */ + public void scrollToSpacer(final int spacerIndex, + ScrollDestination destination, final int padding) + throws IllegalArgumentException { + validateScrollDestination(destination, padding); + body.scrollToSpacer(spacerIndex, destination, padding); + } + + private static void validateScrollDestination( + final ScrollDestination destination, final int padding) { + if (destination == ScrollDestination.MIDDLE && padding != 0) { + throw new IllegalArgumentException( + "You cannot have a padding with a MIDDLE destination"); + } + } + /** * Recalculates the dimensions for all elements that require manual * calculations. Also updates the dimension caches. @@ -6074,8 +6153,8 @@ public class Escalator extends Widget implements RequiresResize, return; } - double headerHeight = header.heightOfSection; - double footerHeight = footer.heightOfSection; + double headerHeight = header.getHeightOfSection(); + double footerHeight = footer.getHeightOfSection(); double bodyHeight = body.getDefaultRowHeight() * heightByRows; double scrollbar = horizontalScrollbar.showsScrollHandle() ? horizontalScrollbar .getScrollbarThickness() : 0; @@ -6271,8 +6350,8 @@ public class Escalator extends Widget implements RequiresResize, private Range getViewportPixels() { int from = (int) Math.floor(verticalScrollbar.getScrollPos()); - int to = (int) Math.ceil(body.heightOfSection); - return Range.between(from, to); + int to = (int) body.getHeightOfSection(); + return Range.withLength(from, to); } @Override diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java index 04c0933866..de254b4deb 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java @@ -77,11 +77,13 @@ public abstract class EscalatorBasicClientFeaturesTest extends MultiBrowserTest protected static final String COLSPAN_NONE = "Apply no colspan"; protected static final String SET_100PX = "Set 100px"; protected static final String SPACERS = "Spacers"; + protected static final String SCROLL_HERE_ANY_0PADDING = "Scroll here (ANY, 0)"; protected static final String REMOVE = "Remove"; protected static final String ROW_MINUS1 = "Row -1"; protected static final String ROW_1 = "Row 1"; protected static final String ROW_25 = "Row 25"; + protected static final String ROW_50 = "Row 50"; protected static final String ROW_75 = "Row 75"; protected static final String ROW_99 = "Row 99"; diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java index 1e9771ab13..a0fa961ad2 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java @@ -30,6 +30,7 @@ import org.junit.Test; import org.openqa.selenium.WebElement; import com.vaadin.client.WidgetUtil; +import com.vaadin.shared.ui.grid.Range; import com.vaadin.testbench.elements.NotificationElement; import com.vaadin.tests.components.grid.basicfeatures.EscalatorBasicClientFeaturesTest; @@ -96,6 +97,7 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { @Before public void before() { openTestURL(); + selectMenuPath(COLUMNS_AND_ROWS, BODY_ROWS, "Set 20px default height"); populate(); } @@ -299,6 +301,41 @@ public class EscalatorSpacerTest extends EscalatorBasicClientFeaturesTest { } } + @Test + public void scrollToSpacerFromAbove() throws Exception { + selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX); + selectMenuPath(FEATURES, SPACERS, ROW_50, SCROLL_HERE_ANY_0PADDING); + + // Browsers might vary with a few pixels. + Range allowableScrollRange = Range.between(765, 780); + int scrollTop = (int) getScrollTop(); + assertTrue("Scroll position was not " + allowableScrollRange + ", but " + + scrollTop, allowableScrollRange.contains(scrollTop)); + } + + @Test + public void scrollToSpacerFromBelow() throws Exception { + selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX); + scrollVerticallyTo(999999); + selectMenuPath(FEATURES, SPACERS, ROW_50, SCROLL_HERE_ANY_0PADDING); + + // Browsers might vary with a few pixels. + Range allowableScrollRange = Range.between(1015, 1025); + int scrollTop = (int) getScrollTop(); + assertTrue("Scroll position was not " + allowableScrollRange + ", but " + + scrollTop, allowableScrollRange.contains(scrollTop)); + } + + @Test + public void scrollToSpacerAlreadyInViewport() throws Exception { + selectMenuPath(FEATURES, SPACERS, ROW_50, SET_100PX); + scrollVerticallyTo(1000); + selectMenuPath(FEATURES, SPACERS, ROW_50, SCROLL_HERE_ANY_0PADDING); + + // Browsers might vary with a few pixels. + assertEquals(getScrollTop(), 1000); + } + private static double[] getElementDimensions(WebElement element) { /* * we need to parse the style attribute, since using getCssValue gets a diff --git a/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorBasicClientFeaturesWidget.java b/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorBasicClientFeaturesWidget.java index 0d4aa305d9..4cf4726c28 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorBasicClientFeaturesWidget.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorBasicClientFeaturesWidget.java @@ -585,6 +585,13 @@ public class EscalatorBasicClientFeaturesWidget extends } }, scrollToRowMenuPath); } + + addMenuCommand("Set 20px default height", new ScheduledCommand() { + @Override + public void execute() { + escalator.getBody().setDefaultRowHeight(20); + } + }, menupath); } private void createRowsMenu(final RowContainer container, String[] menupath) { @@ -685,6 +692,12 @@ public class EscalatorBasicClientFeaturesWidget extends escalator.getBody().setSpacer(rowIndex, -1); } }, menupath); + addMenuCommand("Scroll here (ANY, 0)", new ScheduledCommand() { + @Override + public void execute() { + escalator.scrollToSpacer(rowIndex, ScrollDestination.ANY, 0); + } + }, menupath); } private void insertRows(final RowContainer container, int offset, int number) { -- 2.39.5