Change-Id: I8563f1e2cfc66ca89399590401fd77ec67e50e82tags/7.3.0.beta1
@@ -30,6 +30,7 @@ import com.google.gwt.core.client.Scheduler.ScheduledCommand; | |||
import com.google.gwt.dom.client.Element; | |||
import com.google.gwt.dom.client.Style; | |||
import com.google.gwt.dom.client.Style.Display; | |||
import com.google.gwt.dom.client.Style.Overflow; | |||
import com.google.gwt.dom.client.Style.Unit; | |||
import com.google.gwt.event.dom.client.BlurEvent; | |||
import com.google.gwt.event.dom.client.BlurHandler; | |||
@@ -212,6 +213,8 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, | |||
private final Element down = DOM.createDiv(); | |||
private final Element status = DOM.createDiv(); | |||
private int desiredHeight = -1; | |||
private boolean isPagingEnabled = true; | |||
private long lastAutoClosed; | |||
@@ -228,6 +231,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, | |||
debug("VFS.SP: constructor()"); | |||
setOwner(VFilterSelect.this); | |||
menu = new SuggestionMenu(); | |||
menu.getElement().getStyle().setOverflowY(Overflow.AUTO); | |||
setWidget(menu); | |||
getElement().getStyle().setZIndex(Z_INDEX); | |||
@@ -550,16 +554,12 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, | |||
public void setPosition(int offsetWidth, int offsetHeight) { | |||
debug("VFS.SP: setPosition()"); | |||
int top = -1; | |||
int left = -1; | |||
int top = getPopupTop(); | |||
int left = getPopupLeft(); | |||
// reset menu size and retrieve its "natural" size | |||
menu.setHeight(""); | |||
if (currentPage > 0) { | |||
// fix height to avoid height change when getting to last page | |||
menu.fixHeightTo(pageLength); | |||
if (desiredHeight < 0) { | |||
desiredHeight = offsetHeight; | |||
} | |||
offsetHeight = getOffsetHeight(); | |||
final int desiredWidth = getMainWidth(); | |||
Element menuFirstChild = menu.getElement().getFirstChildElement(); | |||
@@ -585,16 +585,20 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, | |||
getContainerElement().getStyle().setWidth(rootWidth, Unit.PX); | |||
} | |||
if (offsetHeight + getPopupTop() > Window.getClientHeight() | |||
+ Window.getScrollTop()) { | |||
final int spaceAvailableBelow = Window.getClientHeight() | |||
- (top - Window.getScrollTop()); | |||
final int spaceAvailableAbove = top - Window.getScrollTop() | |||
- VFilterSelect.this.getOffsetHeight(); | |||
if (spaceAvailableBelow < desiredHeight | |||
&& spaceAvailableBelow < spaceAvailableAbove) { | |||
// popup on top of input instead | |||
top = getPopupTop() - offsetHeight | |||
- VFilterSelect.this.getOffsetHeight(); | |||
top -= desiredHeight + VFilterSelect.this.getOffsetHeight(); | |||
offsetHeight = desiredHeight; | |||
if (top < 0) { | |||
offsetHeight += top; | |||
top = 0; | |||
} | |||
} else { | |||
top = getPopupTop(); | |||
/* | |||
* Take popup top margin into account. getPopupTop() returns the | |||
* top value including the margin but the value we give must not | |||
@@ -602,6 +606,19 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, | |||
*/ | |||
int topMargin = (top - topPosition); | |||
top -= topMargin; | |||
offsetHeight = Math.min(desiredHeight, spaceAvailableBelow); | |||
} | |||
/* | |||
* Resize popup and menu if calculated height doesn't match the | |||
* actual height | |||
*/ | |||
if (getOffsetHeight() != offsetHeight) { | |||
int menuHeight = offsetHeight - up.getOffsetHeight() | |||
- down.getOffsetHeight() - status.getOffsetHeight(); | |||
menu.setHeight(menuHeight + "px"); | |||
getContainerElement().getStyle().setHeight(offsetHeight, | |||
Unit.PX); | |||
} | |||
// fetch real width (mac FF bugs here due GWT popups overflow:auto ) | |||
@@ -614,8 +631,6 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, | |||
if (left < 0) { | |||
left = 0; | |||
} | |||
} else { | |||
left = getPopupLeft(); | |||
} | |||
setPopupPosition(left, top); | |||
} |
@@ -0,0 +1,64 @@ | |||
/* | |||
* 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.combobox; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.tests.components.AbstractTestUI; | |||
import com.vaadin.ui.Alignment; | |||
import com.vaadin.ui.ComboBox; | |||
import com.vaadin.ui.VerticalLayout; | |||
/** | |||
* Test UI for issue #11929 where ComboBox suggestion popup hides the ComboBox | |||
* itself obscuring the text input field. | |||
* | |||
* @author Vaadin Ltd | |||
*/ | |||
public class ComboBoxOnSmallScreen extends AbstractTestUI { | |||
private static final String PID = "captionPID"; | |||
@Override | |||
protected void setup(VaadinRequest request) { | |||
addComponents(createComboBox(), createComboBox()); | |||
VerticalLayout vl = getLayout(); | |||
vl.setHeight(300, Unit.PIXELS); | |||
vl.setComponentAlignment(vl.getComponent(1), Alignment.BOTTOM_LEFT); | |||
} | |||
@Override | |||
protected String getTestDescription() { | |||
return "Combobox hides what you are typing on small screen"; | |||
} | |||
@Override | |||
protected Integer getTicketNumber() { | |||
return 11929; | |||
} | |||
private ComboBox createComboBox() { | |||
ComboBox cb = new ComboBox(); | |||
cb.addContainerProperty(PID, String.class, ""); | |||
cb.setItemCaptionPropertyId(PID); | |||
for (int i = 1; i < 21; ++i) { | |||
final String v = "Item #" + i; | |||
cb.getItem(cb.addItem()).getItemProperty(PID).setValue(v); | |||
} | |||
return cb; | |||
} | |||
} |
@@ -0,0 +1,108 @@ | |||
/* | |||
* 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.combobox; | |||
import static org.hamcrest.MatcherAssert.assertThat; | |||
import static org.hamcrest.Matchers.is; | |||
import org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.Dimension; | |||
import org.openqa.selenium.WebDriver.Window; | |||
import org.openqa.selenium.WebElement; | |||
import com.vaadin.client.ui.VFilterSelect; | |||
import com.vaadin.testbench.elements.ComboBoxElement; | |||
import com.vaadin.tests.tb3.MultiBrowserTest; | |||
/** | |||
* ComboBox suggestion popup should not obscure the text input box. | |||
* | |||
* @author Vaadin Ltd | |||
*/ | |||
public class ComboBoxOnSmallScreenTest extends MultiBrowserTest { | |||
private static final Dimension TARGETSIZE = new Dimension(600, 300); | |||
private static final String POPUPCLASSNAME = VFilterSelect.CLASSNAME | |||
+ "-suggestpopup"; | |||
@Override | |||
public void setup() throws Exception { | |||
super.setup(); | |||
openTestURL(); | |||
getWindow().setSize(TARGETSIZE); | |||
} | |||
@Test | |||
public void testUpperSuggestionPopupOverlayPosition() { | |||
ComboBoxElement cb = getComboBoxAndOpenPopup(0); | |||
assertOverlayPosition(cb, getPopup()); | |||
} | |||
@Test | |||
public void testUpperSuggestionPopupOverlaySize() { | |||
ComboBoxElement cb = getComboBoxAndOpenPopup(0); | |||
assertOverlaySize(cb, getPopup()); | |||
} | |||
@Test | |||
public void testLowerSuggestionPopupOverlayPosition() { | |||
ComboBoxElement cb = getComboBoxAndOpenPopup(1); | |||
assertOverlayPosition(cb, getPopup()); | |||
} | |||
@Test | |||
public void testLowerSuggestionPopupOverlaySize() { | |||
ComboBoxElement cb = getComboBoxAndOpenPopup(1); | |||
assertOverlaySize(cb, getPopup()); | |||
} | |||
private void assertOverlayPosition(WebElement combobox, WebElement popup) { | |||
final int popupTop = popup.getLocation().y; | |||
final int popupBottom = popupTop + popup.getSize().getHeight(); | |||
final int cbTop = combobox.getLocation().y; | |||
final int cbBottom = cbTop + combobox.getSize().getHeight(); | |||
assertThat("Popup overlay does not overlap with the textbox", | |||
popupTop >= cbBottom || popupBottom <= cbTop, is(true)); | |||
} | |||
private void assertOverlaySize(WebElement combobox, WebElement popup) { | |||
final int popupTop = popup.getLocation().y; | |||
final int popupBottom = popupTop + popup.getSize().getHeight(); | |||
final int rootHeight = findElement(By.tagName("body")).getSize().height; | |||
assertThat("Popup overlay inside the viewport", popupTop < 0 | |||
|| popupBottom > rootHeight, is(false)); | |||
} | |||
private ComboBoxElement getComboBoxAndOpenPopup(int comboboxIndex) { | |||
ComboBoxElement cb = $(ComboBoxElement.class).get(comboboxIndex); | |||
cb.openPopup(); | |||
return cb; | |||
} | |||
private WebElement getPopup() { | |||
return findElement(By.className(POPUPCLASSNAME)); | |||
} | |||
private Window getWindow() { | |||
return getDriver().manage().window(); | |||
} | |||
} |