diff options
author | Artur Signell <artur@vaadin.com> | 2016-04-17 11:31:01 +0300 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2016-04-28 15:18:45 +0000 |
commit | 0b0495b6d87b3c623b38643ff1ce27abf5a30d5c (patch) | |
tree | d0c0b29753148eac0b7c1d473d10bf8d1235ed99 | |
parent | ab30dfe29d12e57d7c00c12cf2edc9328cb47a65 (diff) | |
download | vaadin-framework-0b0495b6d87b3c623b38643ff1ce27abf5a30d5c.tar.gz vaadin-framework-0b0495b6d87b3c623b38643ff1ce27abf5a30d5c.zip |
Restrict grid sidebar size to visible viewport (#19349)
Change-Id: I75b7c662251de53b46e045d17d3cac650586acd2
6 files changed, 302 insertions, 4 deletions
diff --git a/client/src/main/java/com/vaadin/client/widgets/Grid.java b/client/src/main/java/com/vaadin/client/widgets/Grid.java index cf9456d8a8..c388dbc951 100644 --- a/client/src/main/java/com/vaadin/client/widgets/Grid.java +++ b/client/src/main/java/com/vaadin/client/widgets/Grid.java @@ -3760,6 +3760,7 @@ public class Grid<T> extends ResizeComposite implements addStyleName("closed"); } }); + overlay.setFitInWindow(true); } /** diff --git a/client/src/main/java/com/vaadin/client/widgets/Overlay.java b/client/src/main/java/com/vaadin/client/widgets/Overlay.java index a5e29c6774..6744840c8e 100644 --- a/client/src/main/java/com/vaadin/client/widgets/Overlay.java +++ b/client/src/main/java/com/vaadin/client/widgets/Overlay.java @@ -16,6 +16,9 @@ package com.vaadin.client.widgets; +import java.util.ArrayList; +import java.util.List; + import com.google.gwt.animation.client.Animation; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JavaScriptObject; @@ -29,6 +32,7 @@ import com.google.gwt.dom.client.Style.Position; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.logical.shared.CloseEvent; import com.google.gwt.event.logical.shared.CloseHandler; +import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.PopupPanel; @@ -238,6 +242,8 @@ public class Overlay extends PopupPanel implements CloseHandler<PopupPanel> { @Deprecated private boolean sinkShadowEvents = false; + private List<Command> runOnClose = new ArrayList<Command>(); + public Overlay() { super(); adjustZIndex(); @@ -362,6 +368,58 @@ public class Overlay extends PopupPanel implements CloseHandler<PopupPanel> { @Override public void setPopupPosition(int left, int top) { + + // PopupPanel tries to position the popup on screen (by + // default right, below) and will move it if there is not + // enough space right or below but only if there is + // sufficient space left or above. If the popup is too big + // to fit on either side, it will be in the original + // position. + + if (isFitInWindow()) { + int windowLeft = Window.getScrollLeft(); + int windowRight = Window.getScrollLeft() + Window.getClientWidth(); + int width = getOffsetWidth(); + int popupRight = left + width; + int popupRightOfWindow = popupRight - windowRight; + if (popupRightOfWindow > 0) { + // Popup is too large to fit + left -= popupRightOfWindow; + if (left < 0) { + // Would move left of screen, shrink to fit in window + setOuterWidthThroughWidget(windowRight - windowLeft); + runOnClose.add(new Command() { + @Override + public void execute() { + getWidget().setWidth(""); + } + }); + left = 0; + } + } + + int windowTop = Window.getScrollTop(); + int windowBottom = Window.getScrollTop() + Window.getClientHeight(); + int height = getOffsetHeight(); + int popupBottom = top + height; + int popupBelowWindow = popupBottom - windowBottom; + if (popupBelowWindow > 0) { + // Popup is too large to fit + top -= popupBelowWindow; + if (top < 0) { + // Would move above screen, shrink to fit in window + setOuterHeightThroughWidget(windowBottom - windowTop); + runOnClose.add(new Command() { + @Override + public void execute() { + getWidget().setHeight(""); + } + }); + top = 0; + } + } + } + // TODO, this should in fact be part of // Document.get().getBodyOffsetLeft/Top(). Would require overriding DOM // for all permutations. Now adding fix as margin instead of fixing @@ -373,6 +431,30 @@ public class Overlay extends PopupPanel implements CloseHandler<PopupPanel> { positionOrSizeUpdated(isAnimationEnabled() ? 0 : 1); } + private void setOuterHeightThroughWidget(int outerHeight) { + getWidget().setHeight(outerHeight + "px"); + + // Take margin/border/padding into account if needed + // (the height is for the overlay root but we set it on the + // widget) + int adjustedHeight = outerHeight - (getOffsetHeight() - outerHeight); + if (adjustedHeight != outerHeight) { + getWidget().setHeight(adjustedHeight + "px"); + } + } + + private void setOuterWidthThroughWidget(int outerWidth) { + getWidget().setWidth(outerWidth + "px"); + + // Take margin/border/padding into account if needed + // (the height is for the overlay root but we set it on the + // widget) + int adjustedWidth = outerWidth - (getOffsetWidth() - outerWidth); + if (adjustedWidth != outerWidth) { + getWidget().setWidth(adjustedWidth + "px"); + } + } + private IFrameElement getShimElement() { if (shimElement == null && needsShimElement()) { shimElement = Document.get().createIFrameElement(); @@ -465,6 +547,8 @@ public class Overlay extends PopupPanel implements CloseHandler<PopupPanel> { private JavaScriptObject animateInListener; + private boolean fitInWindow = false; + private boolean maybeShowWithAnimation() { boolean isAttached = isAttached() && isShowing(); super.show(); @@ -933,7 +1017,7 @@ public class Overlay extends PopupPanel implements CloseHandler<PopupPanel> { public void hide(final boolean autoClosed, final boolean animateIn, final boolean animateOut) { if (BrowserInfo.get().isIE8() || BrowserInfo.get().isIE9()) { - super.hide(autoClosed); + reallyHide(autoClosed); } else { if (animateIn && getStyleName().contains(ADDITIONAL_CLASSNAME_ANIMATE_IN)) { @@ -945,7 +1029,7 @@ public class Overlay extends PopupPanel implements CloseHandler<PopupPanel> { .getAnimationName(event) .contains( ADDITIONAL_CLASSNAME_ANIMATE_IN)) { - Overlay.this.hide(autoClosed); + reallyHide(autoClosed); } } }); @@ -990,7 +1074,7 @@ public class Overlay extends PopupPanel implements CloseHandler<PopupPanel> { + "-" + ADDITIONAL_CLASSNAME_ANIMATE_OUT); } - Overlay.super.hide(autoClosed); + reallyHide(autoClosed); } } }); @@ -1003,10 +1087,55 @@ public class Overlay extends PopupPanel implements CloseHandler<PopupPanel> { shadow.removeClassName(CLASSNAME_SHADOW + "-" + ADDITIONAL_CLASSNAME_ANIMATE_OUT); } - super.hide(autoClosed); + reallyHide(autoClosed); } } } } + private void reallyHide(boolean autoClosed) { + super.hide(autoClosed); + for (Command c : runOnClose) { + c.execute(); + } + runOnClose.clear(); + } + + /** + * Sets whether the overlay should be moved or shrunk to fit inside the + * window. + * <p> + * When this is <code>false</code>, the default {@link PopupPanel} behavior + * is used, which tries to position the popup primarly below and to the + * right of a reference UIObject and, if there is not enough space, above or + * to the left. + * <p> + * When this is <code>true</code>, the popup will be moved up/left in case + * it does not fit on either side. If the popup is larger than the window, + * it will be shrunk to fit and assume that scrolling e.g. using + * <code>overflow:auto</code>, is taken care of by the overlay user. + * + * @since + * @param fitInWindow + * <code>true</code> to ensure that no part of the popup is + * outside the visible view, <code>false</code> to use the + * default {@link PopupPanel} behavior + */ + public void setFitInWindow(boolean fitInWindow) { + this.fitInWindow = fitInWindow; + } + + /** + * Checks whether the overlay should be moved or shrunk to fit inside the + * window. + * + * @see #setFitInWindow(boolean) + * + * @since + * @return <code>true</code> if the popup will be moved and/or shrunk to fit + * inside the window, <code>false</code> otherwise + */ + public boolean isFitInWindow() { + return fitInWindow; + } } diff --git a/themes/src/main/themes/VAADIN/themes/base/grid/grid.scss b/themes/src/main/themes/VAADIN/themes/base/grid/grid.scss index 3e7b708557..983463381b 100644 --- a/themes/src/main/themes/VAADIN/themes/base/grid/grid.scss +++ b/themes/src/main/themes/VAADIN/themes/base/grid/grid.scss @@ -158,6 +158,8 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co .#{$primaryStyleName}-sidebar-content { padding: 4px 0; + overflow-y: auto; + overflow-x: hidden; .gwt-MenuBar { .gwt-MenuItem .column-hiding-toggle { diff --git a/themes/src/main/themes/VAADIN/themes/valo/components/_grid.scss b/themes/src/main/themes/VAADIN/themes/valo/components/_grid.scss index 4a0715b2d2..1552c9591b 100644 --- a/themes/src/main/themes/VAADIN/themes/valo/components/_grid.scss +++ b/themes/src/main/themes/VAADIN/themes/valo/components/_grid.scss @@ -228,6 +228,8 @@ $v-grid-details-border-bottom-stripe: $v-grid-cell-horizontal-border !default; .#{$primary-stylename}-sidebar-content { margin: 0 0 2px; padding: 4px 4px 2px; + overflow-y: auto; + overflow-x: hidden; } } diff --git a/uitest/src/main/java/com/vaadin/tests/components/grid/GridSidebarPosition.java b/uitest/src/main/java/com/vaadin/tests/components/grid/GridSidebarPosition.java new file mode 100644 index 0000000000..852ec7cea6 --- /dev/null +++ b/uitest/src/main/java/com/vaadin/tests/components/grid/GridSidebarPosition.java @@ -0,0 +1,69 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.grid; + +import com.vaadin.annotations.Theme; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.Alignment; +import com.vaadin.ui.Grid; +import com.vaadin.ui.HorizontalLayout; + +@Theme("valo") +public class GridSidebarPosition extends AbstractTestUI { + + static final String POPUP_ABOVE = "above"; + static final String POPUP_WINDOW_MOVED_UP = "movedup"; + static final String POPUP_WINDOW_HEIGHT = "windowheight"; + + @Override + protected void setup(VaadinRequest request) { + HorizontalLayout hl = new HorizontalLayout(); + hl.setSpacing(true); + hl.setHeight("100%"); + setContent(hl); + Grid grid = new Grid("Popup window height"); + grid.setId(POPUP_WINDOW_HEIGHT); + grid.setWidth("100px"); + for (int i = 0; i < 30; i++) { + grid.addColumn( + "This is a really really really really long column name " + + i).setHidable(true); + } + hl.addComponent(grid); + + grid = new Grid("Popup moved up"); + grid.setId(POPUP_WINDOW_MOVED_UP); + grid.setWidth("100px"); + grid.setHeight("400px"); + for (int i = 0; i < 15; i++) { + grid.addColumn("Column " + i).setHidable(true); + } + hl.addComponent(grid); + hl.setComponentAlignment(grid, Alignment.BOTTOM_LEFT); + + grid = new Grid("Popup above"); + grid.setId(POPUP_ABOVE); + grid.setWidth("100px"); + grid.setHeight("200px"); + for (int i = 0; i < 10; i++) { + grid.addColumn("Column " + i).setHidable(true); + } + hl.addComponent(grid); + hl.setComponentAlignment(grid, Alignment.BOTTOM_LEFT); + } + +} diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/GridSidebarPositionTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/GridSidebarPositionTest.java new file mode 100644 index 0000000000..88771b50ca --- /dev/null +++ b/uitest/src/test/java/com/vaadin/tests/components/grid/GridSidebarPositionTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.grid; + +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.Dimension; +import org.openqa.selenium.Point; +import org.openqa.selenium.WebElement; + +import com.vaadin.testbench.elements.GridElement; +import com.vaadin.testbench.parallel.BrowserUtil; +import com.vaadin.tests.tb3.MultiBrowserTest; + +public class GridSidebarPositionTest extends MultiBrowserTest { + + @Test + public void heightRestrictedToBrowserWindow() { + openTestURL(); + GridElement gridWithVeryManyColumns = $(GridElement.class).id( + GridSidebarPosition.POPUP_WINDOW_HEIGHT); + getSidebarOpenButton(gridWithVeryManyColumns).click(); + Dimension popupSize = getSidebarPopup().getSize(); + Dimension browserWindowSize = getDriver().manage().window().getSize(); + + Assert.assertTrue(popupSize.getHeight() <= browserWindowSize + .getHeight()); + } + + @Test + public void popupNotBelowBrowserWindow() { + openTestURL(); + GridElement gridAtBottom = $(GridElement.class).id( + GridSidebarPosition.POPUP_WINDOW_MOVED_UP); + getSidebarOpenButton(gridAtBottom).click(); + WebElement sidebarPopup = getSidebarPopup(); + Dimension popupSize = sidebarPopup.getSize(); + Point popupLocation = sidebarPopup.getLocation(); + int popupBottom = popupLocation.getY() + popupSize.getHeight(); + Dimension browserWindowSize = getDriver().manage().window().getSize(); + + Assert.assertTrue(popupBottom <= browserWindowSize.getHeight()); + } + + @Test + public void popupAbove() { + openTestURL(); + GridElement gridPopupAbove = $(GridElement.class).id( + GridSidebarPosition.POPUP_ABOVE); + WebElement sidebarOpenButton = getSidebarOpenButton(gridPopupAbove); + sidebarOpenButton.click(); + WebElement sidebarPopup = getSidebarPopup(); + Dimension popupSize = sidebarPopup.getSize(); + Point popupLocation = sidebarPopup.getLocation(); + int popupBottom = popupLocation.getY() + popupSize.getHeight(); + int sideBarButtonTop; + if (BrowserUtil.isIE8(getDesiredCapabilities())) { + // IE8 gets the top coordinate for the button completely wrong for + // some reason + sideBarButtonTop = 660; + } else { + sideBarButtonTop = sidebarOpenButton.getLocation().getY(); + } + Assert.assertTrue(popupBottom <= sideBarButtonTop); + } + + protected WebElement getSidebarOpenButton(GridElement grid) { + List<WebElement> elements = grid.findElements(By + .className("v-grid-sidebar-button")); + return elements.isEmpty() ? null : elements.get(0); + } + + protected WebElement getSidebarPopup() { + List<WebElement> elements = findElements(By + .className("v-grid-sidebar-popup")); + return elements.isEmpty() ? null : elements.get(0); + } + +} |