]> source.dussan.org Git - vaadin-framework.git/commitdiff
Adds Escalator.scrollToSpacer (#17270)
authorHenrik Paul <henrik@vaadin.com>
Mon, 23 Mar 2015 13:35:26 +0000 (15:35 +0200)
committerVaadin Code Review <review@vaadin.com>
Wed, 25 Mar 2015 06:14:39 +0000 (06:14 +0000)
Change-Id: Ib420e8da6c167fdba9d3023a73cb242643c7af67

client/src/com/vaadin/client/widgets/Escalator.java
uitest/src/com/vaadin/tests/components/grid/basicfeatures/EscalatorBasicClientFeaturesTest.java
uitest/src/com/vaadin/tests/components/grid/basicfeatures/escalator/EscalatorSpacerTest.java
uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorBasicClientFeaturesWidget.java

index 75b797eb1fd2abba9a50e1eb050d650ebef18652..87f19d2ded0966bce8bf75051d06c4ace41c55f4 100644 (file)
@@ -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.
+         * <p>
+         * 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 <tbody>} 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
index 04c0933866fc21c04c344f8c113e124277f0c46c..de254b4deb4f8375f6a11da2405ca9c330669324 100644 (file)
@@ -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";
 
index 1e9771ab13170431d82c74ad0546e5559700c8f9..a0fa961ad2e7419285496e3db498a327f45352cc 100644 (file)
@@ -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
index 0d4aa305d923fee7412d3121c07c75c3ac883fe0..4cf4726c28e8b8307d99978957ee129fc753f37e 100644 (file)
@@ -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) {