diff options
author | Antti Tanhuanpää <antti@vaadin.com> | 2014-06-30 17:07:50 +0300 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2014-07-28 11:53:51 +0000 |
commit | 28702c006fe124988c03c99eea4e2d609407e47e (patch) | |
tree | 5232db1537cb4b049dcbe27b39ef0d5970251a2b | |
parent | 9b19675dffec603bc7e8fe6d973ed4edafaff136 (diff) | |
download | vaadin-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.java | 381 | ||||
-rw-r--r-- | client/src/com/vaadin/client/ui/combobox/ComboBoxConnector.java | 39 | ||||
-rw-r--r-- | client/src/com/vaadin/client/ui/menubar/MenuBar.java | 108 | ||||
-rw-r--r-- | uitest/src/com/vaadin/tests/components/combobox/ComboBoxOnSmallScreen.java | 76 | ||||
-rw-r--r-- | uitest/src/com/vaadin/tests/components/combobox/ComboBoxOnSmallScreenTest.java | 84 | ||||
-rw-r--r-- | uitest/src/com/vaadin/tests/components/combobox/fi.png | bin | 0 -> 25094 bytes | |||
-rw-r--r-- | uitest/src/com/vaadin/tests/components/combobox/fi_small.png | bin | 0 -> 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 Binary files differnew file mode 100644 index 0000000000..976a9663ce --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/combobox/fi.png diff --git a/uitest/src/com/vaadin/tests/components/combobox/fi_small.png b/uitest/src/com/vaadin/tests/components/combobox/fi_small.png Binary files differnew file mode 100644 index 0000000000..2908973fa4 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/combobox/fi_small.png |