aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAntti Tanhuanpää <antti@vaadin.com>2014-06-30 17:07:50 +0300
committerVaadin Code Review <review@vaadin.com>2014-07-28 11:53:51 +0000
commit28702c006fe124988c03c99eea4e2d609407e47e (patch)
tree5232db1537cb4b049dcbe27b39ef0d5970251a2b
parent9b19675dffec603bc7e8fe6d973ed4edafaff136 (diff)
downloadvaadin-framework-28702c006fe124988c03c99eea4e2d609407e47e.tar.gz
vaadin-framework-28702c006fe124988c03c99eea4e2d609407e47e.zip
Add scrollbars to ComboBox suggestion popup if low on screen estate (#11929)
Change-Id: Idfeb20a385fc68c6527f1947bdbf238d9d4af918
-rw-r--r--client/src/com/vaadin/client/ui/VFilterSelect.java381
-rw-r--r--client/src/com/vaadin/client/ui/combobox/ComboBoxConnector.java39
-rw-r--r--client/src/com/vaadin/client/ui/menubar/MenuBar.java108
-rw-r--r--uitest/src/com/vaadin/tests/components/combobox/ComboBoxOnSmallScreen.java76
-rw-r--r--uitest/src/com/vaadin/tests/components/combobox/ComboBoxOnSmallScreenTest.java84
-rw-r--r--uitest/src/com/vaadin/tests/components/combobox/fi.pngbin0 -> 25094 bytes
-rw-r--r--uitest/src/com/vaadin/tests/components/combobox/fi_small.pngbin0 -> 3576 bytes
7 files changed, 552 insertions, 136 deletions
diff --git a/client/src/com/vaadin/client/ui/VFilterSelect.java b/client/src/com/vaadin/client/ui/VFilterSelect.java
index 7f67c39500..46f90c07fa 100644
--- a/client/src/com/vaadin/client/ui/VFilterSelect.java
+++ b/client/src/com/vaadin/client/ui/VFilterSelect.java
@@ -62,6 +62,7 @@ import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.BrowserInfo;
import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ComputedStyle;
import com.vaadin.client.ConnectorMap;
import com.vaadin.client.Focusable;
import com.vaadin.client.UIDL;
@@ -252,7 +253,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
/**
* Shows the popup where the user can see the filtered options
- *
+ *
* @param currentSuggestions
* The filtered suggestions
* @param currentPage
@@ -264,10 +265,8 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
final Collection<FilterSelectSuggestion> currentSuggestions,
final int currentPage, final int totalSuggestions) {
- if (enableDebug) {
- debug("VFS.SP: showSuggestions(" + currentSuggestions + ", "
- + currentPage + ", " + totalSuggestions + ")");
- }
+ debug("VFS.SP: showSuggestions(" + currentSuggestions + ", "
+ + currentPage + ", " + totalSuggestions + ")");
/*
* We need to defer the opening of the popup so that the parent DOM
@@ -316,8 +315,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
status.setInnerText("");
}
// We don't need to show arrows or statusbar if there is
- // only one
- // page
+ // only one page
if (totalSuggestions <= pageLength || pageLength == 0) {
setPagingEnabled(false);
} else {
@@ -346,7 +344,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
/**
* Should the next page button be visible to the user?
- *
+ *
* @param active
*/
private void setNextButtonActive(boolean active) {
@@ -366,7 +364,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
/**
* Should the previous page button be visible to the user
- *
+ *
* @param active
*/
private void setPrevButtonActive(boolean active) {
@@ -391,18 +389,13 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
*/
public void selectNextItem() {
debug("VFS.SP: selectNextItem()");
- final MenuItem cur = menu.getSelectedItem();
- final int index = 1 + menu.getItems().indexOf(cur);
+
+ final int index = menu.getSelectedIndex() + 1;
if (menu.getItems().size() > index) {
- final MenuItem newSelectedItem = menu.getItems().get(index);
- menu.selectItem(newSelectedItem);
- tb.setText(newSelectedItem.getText());
- tb.setSelectionRange(lastFilter.length(), newSelectedItem
- .getText().length() - lastFilter.length());
-
- } else if (hasNextPage()) {
- selectPopupItemWhenResponseIsReceived = Select.FIRST;
- filterOptions(currentPage + 1, lastFilter);
+ selectItem(menu.getItems().get(index));
+
+ } else {
+ selectNextPage();
}
}
@@ -411,29 +404,61 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
*/
public void selectPrevItem() {
debug("VFS.SP: selectPrevItem()");
- final MenuItem cur = menu.getSelectedItem();
- final int index = -1 + menu.getItems().indexOf(cur);
+
+ final int index = menu.getSelectedIndex() - 1;
if (index > -1) {
- final MenuItem newSelectedItem = menu.getItems().get(index);
- menu.selectItem(newSelectedItem);
- tb.setText(newSelectedItem.getText());
- tb.setSelectionRange(lastFilter.length(), newSelectedItem
- .getText().length() - lastFilter.length());
+ selectItem(menu.getItems().get(index));
+
} else if (index == -1) {
- if (currentPage > 0) {
- selectPopupItemWhenResponseIsReceived = Select.LAST;
- filterOptions(currentPage - 1, lastFilter);
- }
+ selectPrevPage();
+
} else {
- final MenuItem newSelectedItem = menu.getItems().get(
- menu.getItems().size() - 1);
- menu.selectItem(newSelectedItem);
- tb.setText(newSelectedItem.getText());
- tb.setSelectionRange(lastFilter.length(), newSelectedItem
- .getText().length() - lastFilter.length());
+ selectItem(menu.getItems().get(menu.getItems().size() - 1));
}
}
+ /**
+ * Select the first item of the suggestions list popup.
+ *
+ * @since
+ */
+ public void selectFirstItem() {
+ debug("VFS.SP: selectFirstItem()");
+ selectItem(menu.getFirstItem());
+ }
+
+ /**
+ * Select the last item of the suggestions list popup.
+ *
+ * @since
+ */
+ public void selectLastItem() {
+ debug("VFS.SP: selectLastItem()");
+ selectItem(menu.getLastItem());
+ }
+
+ /*
+ * Sets the selected item in the popup menu.
+ */
+ private void selectItem(final MenuItem newSelectedItem) {
+ menu.selectItem(newSelectedItem);
+
+ String text = newSelectedItem != null ? newSelectedItem.getText()
+ : "";
+
+ // Set the icon.
+ FilterSelectSuggestion suggestion = (FilterSelectSuggestion) newSelectedItem
+ .getCommand();
+ setSelectedItemIcon(suggestion.getIconUri());
+
+ // Set the text.
+ tb.setText(text);
+ tb.setSelectionRange(lastFilter.length(), text.length()
+ - lastFilter.length());
+
+ menu.updateKeyboardSelectedItem();
+ }
+
/*
* Using a timer to scroll up or down the pages so when we receive lots
* of consecutive mouse wheel events the pages does not flicker.
@@ -486,17 +511,10 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
}
}
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt
- * .user.client.Event)
- */
-
@Override
public void onBrowserEvent(Event event) {
debug("VFS.SP: onBrowserEvent()");
+
if (event.getTypeInt() == Event.ONCLICK) {
final Element target = DOM.eventGetTarget(event);
if (target == up || target == DOM.getChild(up, 0)) {
@@ -504,12 +522,24 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
} else if (target == down || target == DOM.getChild(down, 0)) {
lazyPageScroller.scrollDown();
}
+
} else if (event.getTypeInt() == Event.ONMOUSEWHEEL) {
- int velocity = event.getMouseWheelVelocityY();
- if (velocity > 0) {
- lazyPageScroller.scrollDown();
- } else {
- lazyPageScroller.scrollUp();
+
+ boolean scrollNotActive = !menu.isScrollActive();
+
+ debug("VFS.SP: onBrowserEvent() scrollNotActive: "
+ + scrollNotActive);
+
+ if (scrollNotActive) {
+ int velocity = event.getMouseWheelVelocityY();
+
+ debug("VFS.SP: onBrowserEvent() velocity: " + velocity);
+
+ if (velocity > 0) {
+ lazyPageScroller.scrollDown();
+ } else {
+ lazyPageScroller.scrollUp();
+ }
}
}
@@ -525,7 +555,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
* amount of items are visible at a time and a scrollbar or buttons are
* visible to change page. If paging is turned of then all options are
* rendered into the popup menu.
- *
+ *
* @param paging
* Should the paging be turned on?
*/
@@ -546,32 +576,29 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
isPagingEnabled = paging;
}
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.user.client.ui.PopupPanel$PositionCallback#setPosition
- * (int, int)
- */
-
@Override
public void setPosition(int offsetWidth, int offsetHeight) {
- debug("VFS.SP: setPosition()");
+ debug("VFS.SP: setPosition(" + offsetWidth + ", " + offsetHeight
+ + ")");
- int top = -1;
- int left = -1;
+ int top = topPosition;
+ int left = getPopupLeft();
// reset menu size and retrieve its "natural" size
menu.setHeight("");
- if (currentPage > 0) {
+ if (currentPage > 0 && !hasNextPage()) {
// fix height to avoid height change when getting to last page
menu.fixHeightTo(pageLength);
}
- offsetHeight = getOffsetHeight();
+ final int desiredHeight = offsetHeight = getOffsetHeight();
final int desiredWidth = getMainWidth();
+
+ debug("VFS.SP: desired[" + desiredWidth + ", " + desiredHeight
+ + "]");
+
Element menuFirstChild = menu.getElement().getFirstChildElement();
- int naturalMenuWidth = menuFirstChild.getOffsetWidth();
+ final int naturalMenuWidth = menuFirstChild.getOffsetWidth();
if (popupOuterPadding == -1) {
popupOuterPadding = Util.measureHorizontalPaddingAndBorder(
@@ -581,7 +608,6 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
if (naturalMenuWidth < desiredWidth) {
menu.setWidth((desiredWidth - popupOuterPadding) + "px");
menuFirstChild.getStyle().setWidth(100, Unit.PCT);
- naturalMenuWidth = desiredWidth;
}
if (BrowserInfo.get().isIE()) {
@@ -589,48 +615,72 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
* IE requires us to specify the width for the container
* element. Otherwise it will be 100% wide
*/
- int rootWidth = naturalMenuWidth - popupOuterPadding;
+ int rootWidth = Math.max(desiredWidth, naturalMenuWidth)
+ - popupOuterPadding;
getContainerElement().getStyle().setWidth(rootWidth, Unit.PX);
}
- if (offsetHeight + getPopupTop() > Window.getClientHeight()
- + Window.getScrollTop()) {
+ final int vfsHeight = VFilterSelect.this.getOffsetHeight();
+ final int spaceAvailableAbove = top - vfsHeight;
+ final int spaceAvailableBelow = Window.getClientHeight() - top;
+ if (spaceAvailableBelow < offsetHeight
+ && spaceAvailableBelow < spaceAvailableAbove) {
// popup on top of input instead
- top = getPopupTop() - offsetHeight
- - VFilterSelect.this.getOffsetHeight();
+ top -= offsetHeight + vfsHeight;
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
- * include the margin.
- */
- int topMargin = (top - topPosition);
- top -= topMargin;
+ offsetHeight = Math.min(offsetHeight, spaceAvailableBelow);
}
// fetch real width (mac FF bugs here due GWT popups overflow:auto )
offsetWidth = menuFirstChild.getOffsetWidth();
- if (offsetWidth + getPopupLeft() > Window.getClientWidth()
- + Window.getScrollLeft()) {
+
+ if (offsetHeight < desiredHeight) {
+ int menuHeight = offsetHeight;
+ if (isPagingEnabled) {
+ menuHeight -= up.getOffsetHeight() + down.getOffsetHeight()
+ + status.getOffsetHeight();
+ } else {
+ final ComputedStyle s = new ComputedStyle(menu.getElement());
+ menuHeight -= s.getIntProperty("marginBottom")
+ + s.getIntProperty("marginTop");
+ }
+
+ // If the available page height is really tiny then this will be
+ // negative and an exception will be thrown on setHeight.
+ int menuElementHeight = menu.getItemOffsetHeight();
+ if (menuHeight < menuElementHeight) {
+ menuHeight = menuElementHeight;
+ }
+
+ menu.setHeight(menuHeight + "px");
+
+ final int naturalMenuWidthPlusScrollBar = naturalMenuWidth
+ + Util.getNativeScrollbarSize();
+ if (offsetWidth < naturalMenuWidthPlusScrollBar) {
+ menu.setWidth(naturalMenuWidthPlusScrollBar + "px");
+ }
+ }
+
+ if (offsetWidth + left > Window.getClientWidth()) {
left = VFilterSelect.this.getAbsoluteLeft()
- + VFilterSelect.this.getOffsetWidth()
- + Window.getScrollLeft() - offsetWidth;
+ + VFilterSelect.this.getOffsetWidth() - offsetWidth;
if (left < 0) {
left = 0;
+ menu.setWidth(Window.getClientWidth() + "px");
}
- } else {
- left = getPopupLeft();
}
+
setPopupPosition(left, top);
+ menu.scrollSelectionIntoView();
}
/**
* Was the popup just closed?
- *
+ *
* @return true if popup was just closed
*/
public boolean isJustClosed() {
@@ -659,7 +709,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
/**
* Updates style names in suggestion popup to help theme building.
- *
+ *
* @param uidl
* UIDL for the whole combo box
* @param componentState
@@ -723,23 +773,34 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
super(true);
debug("VFS.SM: constructor()");
addDomHandler(this, LoadEvent.getType());
+
+ setScrollEnabled(true);
}
/**
* Fixes menus height to use same space as full page would use. Needed
- * to avoid height changes when quickly "scrolling" to last page
+ * to avoid height changes when quickly "scrolling" to last page.
+ */
+ public void fixHeightTo(int pageItemsCount) {
+ setHeight(getPreferredHeight(pageItemsCount));
+ }
+
+ /*
+ * Gets the preferred height of the menu including pageItemsCount items.
*/
- public void fixHeightTo(int pagelenth) {
+ String getPreferredHeight(int pageItemsCount) {
if (currentSuggestions.size() > 0) {
- final int pixels = pagelenth * (getOffsetHeight() - 2)
- / currentSuggestions.size();
- setHeight((pixels + 2) + "px");
+ final int pixels = (getPreferredHeight() / currentSuggestions
+ .size()) * pageItemsCount;
+ return pixels + "px";
+ } else {
+ return "";
}
}
/**
* Sets the suggestions rendered in the menu
- *
+ *
* @param suggestions
* The suggestions to be rendered in the menu
*/
@@ -789,6 +850,8 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
client.updateVariable(paintableId, "page", 0, false);
client.updateVariable(paintableId, "selected", new String[] {},
immediate);
+ afterUpdateClientVariables();
+
suggestionPopup.hide();
return;
}
@@ -837,6 +900,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
lastNewItemString = enteredItemValue;
client.updateVariable(paintableId, "newitem",
enteredItemValue, immediate);
+ afterUpdateClientVariables();
}
} else if (item != null
&& !"".equals(lastFilter)
@@ -909,26 +973,75 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
}
- public void selectFirstItem() {
- debug("VFS.SM: selectFirstItem()");
- MenuItem firstItem = getItems().get(0);
- selectItem(firstItem);
- }
-
private MenuItem getKeyboardSelectedItem() {
return keyboardSelectedItem;
}
- public void setKeyboardSelectedItem(MenuItem firstItem) {
- keyboardSelectedItem = firstItem;
+ public void setKeyboardSelectedItem(MenuItem menuItem) {
+ keyboardSelectedItem = menuItem;
+ }
+
+ /**
+ * @deprecated use {@link SuggestionPopup#selectFirstItem()} instead.
+ */
+ @Deprecated
+ public void selectFirstItem() {
+ debug("VFS.SM: selectFirstItem()");
+ MenuItem firstItem = getItems().get(0);
+ selectItem(firstItem);
}
+ /**
+ * @deprecated use {@link SuggestionPopup#selectLastItem()} instead.
+ */
+ @Deprecated
public void selectLastItem() {
debug("VFS.SM: selectLastItem()");
List<MenuItem> items = getItems();
MenuItem lastItem = items.get(items.size() - 1);
selectItem(lastItem);
}
+
+ /*
+ * Sets the keyboard item as the current selected one.
+ */
+ void updateKeyboardSelectedItem() {
+ setKeyboardSelectedItem(getSelectedItem());
+ }
+
+ /*
+ * Gets the height of one menu item.
+ */
+ int getItemOffsetHeight() {
+ List<MenuItem> items = getItems();
+ return items != null && items.size() > 0 ? items.get(0)
+ .getOffsetHeight() : 0;
+ }
+
+ /*
+ * Gets the width of one menu item.
+ */
+ int getItemOffsetWidth() {
+ List<MenuItem> items = getItems();
+ return items != null && items.size() > 0 ? items.get(0)
+ .getOffsetWidth() : 0;
+ }
+
+ /**
+ * Returns true if the scroll is active on the menu element or if the
+ * menu currently displays the last page with less items then the
+ * maximum visibility (in which case the scroll is not active, but the
+ * scroll is active for any other page in general).
+ */
+ @Override
+ public boolean isScrollActive() {
+ String height = getElement().getStyle().getHeight();
+ String preferredHeight = getPreferredHeight(pageLength);
+
+ return !(height == null || height.length() == 0 || height
+ .equals(preferredHeight));
+ }
+
}
/**
@@ -1273,10 +1386,9 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
* Whether to send the options request immediately
*/
private void filterOptions(int page, String filter, boolean immediate) {
- if (enableDebug) {
- debug("VFS: filterOptions(" + page + ", " + filter + ", "
- + immediate + ")");
- }
+ debug("VFS: filterOptions(" + page + ", " + filter + ", " + immediate
+ + ")");
+
if (filter.equals(lastFilter) && currentPage == page) {
if (!suggestionPopup.isAttached()) {
suggestionPopup.showSuggestions(currentSuggestions,
@@ -1297,8 +1409,11 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
waitingForFilteringResponse = true;
client.updateVariable(paintableId, "filter", filter, false);
client.updateVariable(paintableId, "page", page, immediate);
+ afterUpdateClientVariables();
+
lastFilter = filter;
currentPage = page;
+
}
/** For internal use only. May be removed or replaced in the future. */
@@ -1401,10 +1516,13 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
setPromptingOff(text);
}
setSelectedItemIcon(suggestion.getIconUri());
+
if (!(newKey.equals(selectedOptionKey) || ("".equals(newKey) && selectedOptionKey == null))) {
selectedOptionKey = newKey;
client.updateVariable(paintableId, "selected",
new String[] { selectedOptionKey }, immediate);
+ afterUpdateClientVariables();
+
// currentPage = -1; // forget the page
}
suggestionPopup.hide();
@@ -1597,28 +1715,22 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
switch (event.getNativeKeyCode()) {
case KeyCodes.KEY_DOWN:
suggestionPopup.selectNextItem();
- suggestionPopup.menu.setKeyboardSelectedItem(suggestionPopup.menu
- .getSelectedItem());
+
DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
event.stopPropagation();
break;
case KeyCodes.KEY_UP:
suggestionPopup.selectPrevItem();
- suggestionPopup.menu.setKeyboardSelectedItem(suggestionPopup.menu
- .getSelectedItem());
+
DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
event.stopPropagation();
break;
case KeyCodes.KEY_PAGEDOWN:
- if (hasNextPage()) {
- filterOptions(currentPage + 1, lastFilter);
- }
+ selectNextPage();
event.stopPropagation();
break;
case KeyCodes.KEY_PAGEUP:
- if (currentPage > 0) {
- filterOptions(currentPage - 1, lastFilter);
- }
+ selectPrevPage();
event.stopPropagation();
break;
case KeyCodes.KEY_TAB:
@@ -1664,6 +1776,26 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
}
+ /*
+ * Show the prev page.
+ */
+ private void selectPrevPage() {
+ if (currentPage > 0) {
+ filterOptions(currentPage - 1, lastFilter);
+ selectPopupItemWhenResponseIsReceived = Select.LAST;
+ }
+ }
+
+ /*
+ * Show the next page.
+ */
+ private void selectNextPage() {
+ if (hasNextPage()) {
+ filterOptions(currentPage + 1, lastFilter);
+ selectPopupItemWhenResponseIsReceived = Select.FIRST;
+ }
+ }
+
/**
* Triggered when a key was depressed
*
@@ -1707,15 +1839,21 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
if (currentSuggestion != null) {
String text = currentSuggestion.getReplacementString();
setPromptingOff(text);
+ setSelectedItemIcon(currentSuggestion.getIconUri());
+
selectedOptionKey = currentSuggestion.key;
+
} else {
if (focused || readonly || !enabled) {
setPromptingOff("");
} else {
setPromptingOn();
}
+ setSelectedItemIcon(null);
+
selectedOptionKey = null;
}
+
lastFilter = "";
suggestionPopup.hide();
}
@@ -1837,6 +1975,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
if (client.hasEventListeners(this, EventId.FOCUS)) {
client.updateVariable(paintableId, EventId.FOCUS, "", true);
+ afterUpdateClientVariables();
}
ComponentConnector connector = ConnectorMap.get(client).getConnector(
@@ -1913,6 +2052,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
if (client.hasEventListeners(this, EventId.BLUR)) {
client.updateVariable(paintableId, EventId.BLUR, "", true);
+ afterUpdateClientVariables();
}
}
@@ -2091,4 +2231,15 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
com.google.gwt.user.client.Element captionElement) {
AriaHelper.bindCaption(tb, captionElement);
}
+
+ /*
+ * Anything that should be set after the client updates the server.
+ */
+ private void afterUpdateClientVariables() {
+ // We need this here to be consistent with the all the calls.
+ // Then set your specific selection type only after
+ // client.updateVariable() method call.
+ selectPopupItemWhenResponseIsReceived = Select.NONE;
+ }
+
}
diff --git a/client/src/com/vaadin/client/ui/combobox/ComboBoxConnector.java b/client/src/com/vaadin/client/ui/combobox/ComboBoxConnector.java
index 78505d034c..8feb349a9e 100644
--- a/client/src/com/vaadin/client/ui/combobox/ComboBoxConnector.java
+++ b/client/src/com/vaadin/client/ui/combobox/ComboBoxConnector.java
@@ -28,7 +28,6 @@ import com.vaadin.client.ui.AbstractFieldConnector;
import com.vaadin.client.ui.SimpleManagedLayout;
import com.vaadin.client.ui.VFilterSelect;
import com.vaadin.client.ui.VFilterSelect.FilterSelectSuggestion;
-import com.vaadin.client.ui.menubar.MenuItem;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.combobox.ComboBoxConstants;
import com.vaadin.shared.ui.combobox.ComboBoxState;
@@ -157,7 +156,15 @@ public class ComboBoxConnector extends AbstractFieldConnector implements
}
// handle selection (null or a single value)
- if (uidl.hasVariable("selected")) {
+ if (uidl.hasVariable("selected")
+
+ // In case we're switching page no need to update the selection as the
+ // selection process didn't finish.
+ // && getWidget().selectPopupItemWhenResponseIsReceived ==
+ // VFilterSelect.Select.NONE
+ //
+ ) {
+
String[] selectedKeys = uidl.getStringArrayVariable("selected");
if (selectedKeys.length > 0) {
performSelection(selectedKeys[0]);
@@ -169,12 +176,16 @@ public class ComboBoxConnector extends AbstractFieldConnector implements
if ((getWidget().waitingForFilteringResponse && getWidget().lastFilter
.toLowerCase().equals(uidl.getStringVariable("filter")))
|| popupOpenAndCleared) {
+
getWidget().suggestionPopup.showSuggestions(
getWidget().currentSuggestions, getWidget().currentPage,
getWidget().totalMatches);
+
getWidget().waitingForFilteringResponse = false;
+
if (!getWidget().popupOpenerClicked
&& getWidget().selectPopupItemWhenResponseIsReceived != VFilterSelect.Select.NONE) {
+
// we're paging w/ arrows
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
@@ -222,26 +233,16 @@ public class ComboBoxConnector extends AbstractFieldConnector implements
*/
private void navigateItemAfterPageChange() {
if (getWidget().selectPopupItemWhenResponseIsReceived == VFilterSelect.Select.LAST) {
- getWidget().suggestionPopup.menu.selectLastItem();
+ getWidget().suggestionPopup.selectLastItem();
} else {
- getWidget().suggestionPopup.menu.selectFirstItem();
+ getWidget().suggestionPopup.selectFirstItem();
}
- // This is used for paging so we update the keyboard selection
- // variable as well.
- MenuItem activeMenuItem = getWidget().suggestionPopup.menu
- .getSelectedItem();
- getWidget().suggestionPopup.menu
- .setKeyboardSelectedItem(activeMenuItem);
-
- // Update text field to contain the correct text
- getWidget().setTextboxText(activeMenuItem.getText());
- getWidget().tb.setSelectionRange(
- getWidget().lastFilter.length(),
- activeMenuItem.getText().length()
- - getWidget().lastFilter.length());
-
- getWidget().selectPopupItemWhenResponseIsReceived = VFilterSelect.Select.NONE; // reset
+ // If you're in between 2 requests both changing the page back and
+ // forth, you don't want this here, instead you need it before any
+ // other request.
+ // getWidget().selectPopupItemWhenResponseIsReceived =
+ // VFilterSelect.Select.NONE; // reset
}
private void performSelection(String selectedKey) {
diff --git a/client/src/com/vaadin/client/ui/menubar/MenuBar.java b/client/src/com/vaadin/client/ui/menubar/MenuBar.java
index b00665e766..726defafd5 100644
--- a/client/src/com/vaadin/client/ui/menubar/MenuBar.java
+++ b/client/src/com/vaadin/client/ui/menubar/MenuBar.java
@@ -38,6 +38,7 @@ import java.util.List;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
@@ -74,6 +75,9 @@ import com.vaadin.client.ui.VOverlay;
public class MenuBar extends Widget implements PopupListener {
private final Element body;
+ private final Element table;
+ private final Element outer;
+
private final ArrayList<MenuItem> items = new ArrayList<MenuItem>();
private MenuBar parentMenu;
private PopupPanel popup;
@@ -98,7 +102,7 @@ public class MenuBar extends Widget implements PopupListener {
public MenuBar(boolean vertical) {
super();
- final Element table = DOM.createTable();
+ table = DOM.createTable();
body = DOM.createTBody();
DOM.appendChild(table, body);
@@ -109,7 +113,7 @@ public class MenuBar extends Widget implements PopupListener {
this.vertical = vertical;
- final Element outer = DOM.createDiv();
+ outer = DOM.createDiv();
DOM.appendChild(outer, table);
setElement(outer);
@@ -324,6 +328,37 @@ public class MenuBar extends Widget implements PopupListener {
return selectedItem;
}
+ /**
+ * Gets the first item from the menu or null if no items.
+ *
+ * @since
+ * @return the first item from the menu or null if no items.
+ */
+ public MenuItem getFirstItem() {
+ return items != null && items.size() > 0 ? items.get(0) : null;
+ }
+
+ /**
+ * Gest the last item from the menu or null if no items.
+ *
+ * @since
+ * @return the last item from the menu or null if no items.
+ */
+ public MenuItem getLastItem() {
+ return items != null && items.size() > 0 ? items.get(items.size() - 1)
+ : null;
+ }
+
+ /**
+ * Gets the index of the selected item.
+ *
+ * @since
+ * @return the index of the selected item.
+ */
+ public int getSelectedIndex() {
+ return items != null ? items.indexOf(getSelectedItem()) : -1;
+ }
+
@Override
protected void onDetach() {
// When the menu is detached, make sure to close all of its children.
@@ -468,6 +503,7 @@ public class MenuBar extends Widget implements PopupListener {
public void selectItem(MenuItem item) {
if (item == selectedItem) {
+ scrollItemIntoView(item);
return;
}
@@ -480,6 +516,74 @@ public class MenuBar extends Widget implements PopupListener {
}
selectedItem = item;
+
+ scrollItemIntoView(item);
+ }
+
+ /*
+ * Scroll the specified item into view.
+ */
+ private void scrollItemIntoView(MenuItem item) {
+ if (item != null) {
+ item.getElement().scrollIntoView();
+ }
+ }
+
+ /**
+ * Scroll the selected item into view.
+ *
+ * @since
+ */
+ public void scrollSelectionIntoView() {
+ scrollItemIntoView(selectedItem);
+ }
+
+ /**
+ * Sets the menu scroll enabled or disabled.
+ *
+ * @since
+ * @param enabled
+ * the enabled state of the scroll.
+ */
+ public void setScrollEnabled(boolean enabled) {
+ if (enabled) {
+ if (vertical) {
+ outer.getStyle().setOverflowY(Overflow.AUTO);
+ } else {
+ outer.getStyle().setOverflowX(Overflow.AUTO);
+ }
+
+ } else {
+ if (vertical) {
+ outer.getStyle().clearOverflowY();
+ } else {
+ outer.getStyle().clearOverflowX();
+ }
+ }
+ }
+
+ /**
+ * Gets whether the scroll is activate for this menu.
+ *
+ * @since
+ * @return true if the scroll is active, otherwise false.
+ */
+ public boolean isScrollActive() {
+ // Element element = getElement();
+ // return element.getOffsetHeight() > DOM.getChild(element, 0)
+ // .getOffsetHeight();
+ int outerHeight = outer.getOffsetHeight();
+ int tableHeight = table.getOffsetHeight();
+ return outerHeight < tableHeight;
+ }
+
+ /**
+ * Gets the preferred height of the menu.
+ *
+ * @since
+ */
+ protected int getPreferredHeight() {
+ return table.getOffsetHeight();
}
/**
diff --git a/uitest/src/com/vaadin/tests/components/combobox/ComboBoxOnSmallScreen.java b/uitest/src/com/vaadin/tests/components/combobox/ComboBoxOnSmallScreen.java
new file mode 100644
index 0000000000..044214cecf
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/components/combobox/ComboBoxOnSmallScreen.java
@@ -0,0 +1,76 @@
+/*
+ * 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.data.Item;
+import com.vaadin.server.ClassResource;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractTestUI;
+import com.vaadin.ui.ComboBox;
+
+/**
+ * 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());
+ }
+
+ @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);
+
+ Object selectId = null;
+
+ for (int i = 1; i < 22; ++i) {
+ final String v = "Item #" + i;
+ Object itemId = cb.addItem();
+
+ if (i == 9) {
+ selectId = itemId;
+ }
+
+ Item item = cb.getItem(itemId);
+ item.getItemProperty(PID).setValue(v);
+ int flagIndex = i % 3;
+ cb.setItemIcon(itemId, new ClassResource(
+ flagIndex == 0 ? "fi_small.png" : flagIndex == 1 ? "fi.gif"
+ : "se.gif"));
+ }
+
+ cb.select(selectId);
+
+ return cb;
+ }
+}
diff --git a/uitest/src/com/vaadin/tests/components/combobox/ComboBoxOnSmallScreenTest.java b/uitest/src/com/vaadin/tests/components/combobox/ComboBoxOnSmallScreenTest.java
new file mode 100644
index 0000000000..f48f2bbdeb
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/components/combobox/ComboBoxOnSmallScreenTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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";
+
+ ComboBoxElement combobox;
+ WebElement popup;
+
+ @Override
+ public void setup() throws Exception {
+ super.setup();
+
+ openTestURL();
+
+ getWindow().setSize(TARGETSIZE);
+
+ combobox = $(ComboBoxElement.class).first();
+ combobox.openPopup();
+
+ popup = findElement(By.className(POPUPCLASSNAME));
+ }
+
+ @Test
+ public void testSuggestionPopupOverlayPosition() {
+ 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 overlaps with the textbox",
+ popupTop >= cbBottom || popupBottom <= cbTop, is(true));
+ }
+
+ @Test
+ public void testSuggestionPopupOverlaySize() {
+ 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 out of the screen", popupTop < 0
+ || popupBottom > rootHeight, is(false));
+ }
+
+ private Window getWindow() {
+ return getDriver().manage().window();
+ }
+
+}
diff --git a/uitest/src/com/vaadin/tests/components/combobox/fi.png b/uitest/src/com/vaadin/tests/components/combobox/fi.png
new file mode 100644
index 0000000000..976a9663ce
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/components/combobox/fi.png
Binary files differ
diff --git a/uitest/src/com/vaadin/tests/components/combobox/fi_small.png b/uitest/src/com/vaadin/tests/components/combobox/fi_small.png
new file mode 100644
index 0000000000..2908973fa4
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/components/combobox/fi_small.png
Binary files differ