Browse Source

Add scrollbars to ComboBox suggestion popup if low on screen estate (#11929)

Change-Id: Idfeb20a385fc68c6527f1947bdbf238d9d4af918
tags/7.3.0.rc1
Antti Tanhuanpää 10 years ago
parent
commit
28702c006f

+ 266
- 115
client/src/com/vaadin/client/ui/VFilterSelect.java View File

@@ -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;
}

}

+ 20
- 19
client/src/com/vaadin/client/ui/combobox/ComboBoxConnector.java View File

@@ -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) {

+ 106
- 2
client/src/com/vaadin/client/ui/menubar/MenuBar.java View File

@@ -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();
}

/**

+ 76
- 0
uitest/src/com/vaadin/tests/components/combobox/ComboBoxOnSmallScreen.java View File

@@ -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;
}
}

+ 84
- 0
uitest/src/com/vaadin/tests/components/combobox/ComboBoxOnSmallScreenTest.java View File

@@ -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();
}

}

BIN
uitest/src/com/vaadin/tests/components/combobox/fi.png View File


BIN
uitest/src/com/vaadin/tests/components/combobox/fi_small.png View File


Loading…
Cancel
Save