Browse Source

Revert ComboBox in compatibility packages to V7 version (#201).

Change-Id: Icbe1c6a5c0e2b10255424801cada8c11a71decb7
tags/8.0.0.alpha3
Denis Anisimov 7 years ago
parent
commit
211dd5336e

+ 196
- 349
compatibility-client/src/main/java/com/vaadin/v7/client/ui/VFilterSelect.java View File

@@ -31,6 +31,7 @@ import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Unit;
@@ -66,9 +67,12 @@ import com.google.gwt.user.client.ui.TextBox;
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.DeferredWorker;
import com.vaadin.client.Focusable;
import com.vaadin.client.UIDL;
import com.vaadin.client.VConsole;
import com.vaadin.client.WidgetUtil;
import com.vaadin.client.ui.Field;
@@ -83,9 +87,9 @@ import com.vaadin.client.ui.aria.HandlesAriaRequired;
import com.vaadin.client.ui.menubar.MenuBar;
import com.vaadin.client.ui.menubar.MenuItem;
import com.vaadin.shared.AbstractComponentState;
import com.vaadin.shared.EventId;
import com.vaadin.shared.ui.ComponentStateUtil;
import com.vaadin.shared.util.SharedUtil;
import com.vaadin.v7.client.ui.combobox.ComboBoxConnector;
import com.vaadin.v7.shared.ui.combobox.FilteringMode;

/**
@@ -112,22 +116,17 @@ public class VFilterSelect extends Composite
/**
* Constructor
*
* @param key
* item key, empty string for a special null item not in
* container
* @param caption
* item caption
* @param style
* item style name, can be empty string
* @param untranslatedIconUri
* icon URI or null
* @param uidl
* The UIDL recieved from the server
*/
public FilterSelectSuggestion(String key, String caption, String style,
String untranslatedIconUri) {
this.key = key;
this.caption = caption;
this.style = style;
this.untranslatedIconUri = untranslatedIconUri;
public FilterSelectSuggestion(UIDL uidl) {
key = uidl.getStringAttribute("key");
caption = uidl.getStringAttribute("caption");
style = uidl.getStringAttribute("style");

if (uidl.hasAttribute("icon")) {
untranslatedIconUri = uidl.getStringAttribute("icon");
}
}

/**
@@ -139,7 +138,6 @@ public class VFilterSelect extends Composite
@Override
public String getDisplayString() {
final StringBuffer sb = new StringBuffer();
ApplicationConnection client = connector.getConnection();
final Icon icon = client
.getIcon(client.translateVaadinUri(untranslatedIconUri));
if (icon != null) {
@@ -181,15 +179,13 @@ public class VFilterSelect extends Composite
* @return
*/
public String getIconUri() {
ApplicationConnection client = connector.getConnection();
return client.translateVaadinUri(untranslatedIconUri);
}

/**
* Gets the style set for this suggestion item. Styles are typically set
* by a server-side
* {@link com.vaadin.v7.ui.ComboBox.ItemStyleGenerator}. The returned
* style is prefixed by <code>v-filterselect-item-</code>.
* by a server-side {@link com.vaadin.ui.ComboBox.ItemStyleGenerator}.
* The returned style is prefixed by <code>v-filterselect-item-</code>.
*
* @since 7.5.6
* @return the style name to use, or <code>null</code> to not apply any
@@ -247,12 +243,12 @@ public class VFilterSelect extends Composite
return $entry(function(e) {
var deltaX = e.deltaX ? e.deltaX : -0.5*e.wheelDeltaX;
var deltaY = e.deltaY ? e.deltaY : -0.5*e.wheelDeltaY;
// IE8 has only delta y
if (isNaN(deltaY)) {
deltaY = -0.5*e.wheelDelta;
}
@com.vaadin.v7.client.ui.VFilterSelect.JsniUtil::moveScrollFromEvent(*)(widget, deltaX, deltaY, e, e.deltaMode);
});
}-*/;
@@ -306,8 +302,7 @@ public class VFilterSelect extends Composite

// "Scroll" if change exceeds item height
while (Math.abs(deltaSum) >= SCROLL_UNIT_PX) {
if (!filterSelect.dataReceivedHandler
.isWaitingForFilteringResponse()) {
if (!filterSelect.waitingForFilteringResponse) {
// Move selection if page flip is not in progress
if (deltaSum < 0) {
filterSelect.suggestionPopup.selectPrevItem();
@@ -474,6 +469,15 @@ public class VFilterSelect extends Composite
.clearWidth();

setPopupPositionAndShow(popup);
// Fix for #14173
// IE9 and IE10 have a bug, when resize an a element with
// box-shadow.
// IE9 and IE10 need explicit update to remove extra
// box-shadows
if (BrowserInfo.get().isIE9()
|| BrowserInfo.get().isIE10()) {
forceReflow();
}
}
});
}
@@ -604,7 +608,7 @@ public class VFilterSelect extends Composite
public void run() {
debug("VFS.SP.LPS: run()");
if (pagesToScroll != 0) {
if (!dataReceivedHandler.isWaitingForFilteringResponse()) {
if (!waitingForFilteringResponse) {
/*
* Avoid scrolling while we are waiting for a response
* because otherwise the waiting flag will be reset in
@@ -853,6 +857,10 @@ public class VFilterSelect extends Composite
menu.setWidth(Window.getClientWidth() + "px");

}
if (BrowserInfo.get().isIE()
&& BrowserInfo.get().getBrowserMajorVersion() < 10) {
setTdWidth(menu.getElement(), Window.getClientWidth() - 8);
}
}

setPopupPosition(left, top);
@@ -896,6 +904,44 @@ public class VFilterSelect extends Composite
width = WidgetUtil.escapeAttribute(suggestionPopupWidth);
}
menu.setWidth(width);

// IE8 or 9?
if (BrowserInfo.get().isIE()
&& BrowserInfo.get().getBrowserMajorVersion() < 10) {
// using legacy mode?
if (suggestionPopupWidth == null) {
// set the TD widths manually as these browsers do not
// respect display: block; width:100% rules
setTdWidth(menu.getElement(), naturalMenuWidth);
} else {
int compensation = WidgetUtil
.measureHorizontalPaddingAndBorder(
menu.getElement(), 4);
setTdWidth(menu.getElement(),
menu.getOffsetWidth() - compensation);
}

}
}

/**
* Descends to child elements until finds TD elements and sets their
* width in pixels. Can be used to workaround IE8 & 9 TD element
* display: block issues
*
* @param parent
* @param width
*/
private void setTdWidth(Node parent, int width) {
for (int i = 0; i < parent.getChildCount(); i++) {
Node child = parent.getChild(i);
if ("td".equals(child.getNodeName().toLowerCase())) {
((Element) child).getStyle().setWidth(width, Unit.PX);
} else {
setTdWidth(child, width);
}

}
}

/**
@@ -952,10 +998,13 @@ public class VFilterSelect extends Composite
/**
* Updates style names in suggestion popup to help theme building.
*
* @param uidl
* UIDL for the whole combo box
* @param componentState
* shared state of the combo box
*/
public void updateStyleNames(AbstractComponentState componentState) {
public void updateStyleNames(UIDL uidl,
AbstractComponentState componentState) {
debug("VFS.SP: updateStyleNames()");
setStyleName(
VFilterSelect.this.getStylePrimaryName() + "-suggestpopup");
@@ -1073,11 +1122,18 @@ public class VFilterSelect extends Composite

isFirstIteration = false;
}
if (suggestionPopupWidth != null && BrowserInfo.get().isIE()
&& BrowserInfo.get().getBrowserMajorVersion() < 10) {
// set TD width to a low value so that they won't mandate the
// suggestion pop-up width
suggestionPopup.setTdWidth(suggestionPopup.menu.getElement(),
1);
}
}

/**
* Send the current selection to the server. Triggered when a selection
* is made with the ENTER key.
* is made or on a blur event.
*/
public void doSelectedItemAction() {
debug("VFS.SM: doSelectedItemAction()");
@@ -1092,23 +1148,32 @@ public class VFilterSelect extends Composite
}
// null is not visible on pages != 0, and not visible when
// filtering: handle separately
connector.requestFirstPage();
client.updateVariable(paintableId, "filter", "", false);
client.updateVariable(paintableId, "page", 0, false);
client.updateVariable(paintableId, "selected", new String[] {},
immediate);
afterUpdateClientVariables();

suggestionPopup.hide();
return;
}

dataReceivedHandler.doPostFilterWhenReady();
updateSelectionWhenReponseIsReceived = waitingForFilteringResponse;
if (!waitingForFilteringResponse) {
doPostFilterSelectedItemAction();
}
}

/**
* Triggered after a selection has been made.
* Triggered after a selection has been made
*/
public void doPostFilterSelectedItemAction() {
debug("VFS.SM: doPostFilterSelectedItemAction()");
final MenuItem item = getSelectedItem();
final String enteredItemValue = tb.getText();

updateSelectionWhenReponseIsReceived = false;

// check for exact match in menu
int p = getItems().size();
if (p > 0) {
@@ -1136,7 +1201,9 @@ public class VFilterSelect extends Composite
* Store last sent new item string to avoid double sends
*/
lastNewItemString = enteredItemValue;
connector.sendNewItem(enteredItemValue);
client.updateVariable(paintableId, "newitem",
enteredItemValue, immediate);
afterUpdateClientVariables();
}
} else if (item != null && !"".equals(lastFilter)
&& (filteringmode == FilteringMode.CONTAINS
@@ -1330,197 +1397,6 @@ public class VFilterSelect extends Composite

}

/**
* Handler receiving notifications from the connector and updating the
* widget state accordingly.
*
* This class is still subject to change and should not be considered as
* public stable API.
*
* @since 8.0
*/
public class DataReceivedHandler {

private Runnable navigationCallback = null;
/**
* Set true when popupopened has been clicked. Cleared on each
* UIDL-update. This handles the special case where are not filtering
* yet and the selected value has changed on the server-side. See #2119
* <p>
* For internal use only. May be removed or replaced in the future.
*/
private boolean popupOpenerClicked = false;
private boolean performPostFilteringOnDataReceived = false;
/** For internal use only. May be removed or replaced in the future. */
private boolean waitingForFilteringResponse = false;

/**
* Called by the connector when new data for the last requested filter
* is received from the server.
*/
public void dataReceived() {
suggestionPopup.showSuggestions(currentSuggestions, currentPage,
totalMatches);

waitingForFilteringResponse = false;

if (!popupOpenerClicked) {
navigateItemAfterPageChange();
}

if (performPostFilteringOnDataReceived) {
performPostFilteringOnDataReceived = false;
suggestionPopup.menu.doPostFilterSelectedItemAction();
}

popupOpenerClicked = false;
}

/*
* This method navigates to the proper item in the combobox page. This
* should be executed after setSuggestions() method which is called from
* vFilterSelect.showSuggestions(). ShowSuggestions() method builds the
* page content. As far as setSuggestions() method is called as
* deferred, navigateItemAfterPageChange method should be also be called
* as deferred. #11333
*/
private void navigateItemAfterPageChange() {
if (navigationCallback != null) {
// pageChangeCallback is not reset here but after any server
// request in case you are in between two requests both changing
// the page back and forth

// we're paging w/ arrows
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
if (navigationCallback != null) {
navigationCallback.run();
}
}
});
}
}

/**
* Called by the connector when any request has been sent to the server,
* before waiting for a reply.
*/
public void anyRequestSentToServer() {
navigationCallback = null;
}

/**
* Set a callback that is invoked when a page change occurs if there
* have not been intervening requests to the server. The callback is
* reset when any additional request is made to the server.
*
* @param callback
*/
public void setNavigationCallback(Runnable callback) {
navigationCallback = callback;
}

/**
* Record that the popup opener has been clicked and the popup should be
* opened on the next request.
*
* This handles the special case where are not filtering yet and the
* selected value has changed on the server-side. See #2119. The flag is
* cleared on each UIDL reply.
*/
public void popupOpenerClicked() {
popupOpenerClicked = true;
}

/**
* Cancel a pending request to perform post-filtering actions.
*/
private void cancelPendingPostFiltering() {
performPostFilteringOnDataReceived = false;
}

/**
* Called by the connector when it has finished handling any reply from
* the server, regardless of what was updated.
*/
public void serverReplyHandled() {
popupOpenerClicked = false;
}

/**
* For internal use only - this method will be removed in the future.
*
* @return true if the combo box is waiting for a reply from the server
* with a new page of data, false otherwise
*/
public boolean isWaitingForFilteringResponse() {
return waitingForFilteringResponse;
}

/**
* Set a flag that filtering of options is pending a response from the
* server.
*/
private void startWaitingForFilteringResponse() {
waitingForFilteringResponse = true;
}

/**
* Perform the post-filter action either now (if not waiting for a
* server response) or when a response is received.
*/
private void doPostFilterWhenReady() {
if (isWaitingForFilteringResponse()) {
performPostFilteringOnDataReceived = true;
} else {
performPostFilteringOnDataReceived = false;
suggestionPopup.menu.doPostFilterSelectedItemAction();
}
}

/**
* Perform selection (if appropriate) based on a reply from the server.
* When this method is called, the suggestions have been reset if new
* ones (different from the previous list) were received from the
* server.
*
* @param selectedKey
* new selected key or null if none given by the server
* @param selectedCaption
* new selected item caption if sent by the server or null -
* this is used when the selected item is not on the current
* page
* @param oldSuggestionTextMatchTheOldSelection
* true if the old filtering text box contents matched
* exactly the old selection
*/
public void updateSelectionFromServer(String selectedKey,
String selectedCaption,
boolean oldSuggestionTextMatchTheOldSelection) {
// when filtering with empty filter, server sets the selected key
// to "", which we don't select here. Otherwise we won't be able to
// reset back to the item that was selected before filtering
// started.
if (selectedKey != null && !selectedKey.equals("")) {
performSelection(selectedKey,
oldSuggestionTextMatchTheOldSelection,
!isWaitingForFilteringResponse() || popupOpenerClicked);
setSelectedCaption(null);
} else if (!isWaitingForFilteringResponse()
&& selectedCaption != null) {
// scrolling to correct page is disabled, caption is passed as a
// special parameter
setSelectedCaption(selectedCaption);
} else {
if (!isWaitingForFilteringResponse() || popupOpenerClicked) {
resetSelection(popupOpenerClicked);
}
}
}

}

@Deprecated
public static final FilteringMode FILTERINGMODE_OFF = FilteringMode.OFF;
@Deprecated
@@ -1583,7 +1459,10 @@ public class VFilterSelect extends Composite
private IconWidget selectedItemIcon;

/** For internal use only. May be removed or replaced in the future. */
public ComboBoxConnector connector;
public ApplicationConnection client;

/** For internal use only. May be removed or replaced in the future. */
public String paintableId;

/** For internal use only. May be removed or replaced in the future. */
public int currentPage;
@@ -1594,17 +1473,34 @@ public class VFilterSelect extends Composite
* <p>
* For internal use only. May be removed or replaced in the future.
*/
public final List<FilterSelectSuggestion> currentSuggestions = new ArrayList<FilterSelectSuggestion>();
public final List<FilterSelectSuggestion> currentSuggestions = new ArrayList<>();

/** For internal use only. May be removed or replaced in the future. */
public boolean immediate;

/** For internal use only. May be removed or replaced in the future. */
public String selectedOptionKey;

/** For internal use only. May be removed or replaced in the future. */
public boolean waitingForFilteringResponse = false;

/** For internal use only. May be removed or replaced in the future. */
public boolean updateSelectionWhenReponseIsReceived = false;

/** For internal use only. May be removed or replaced in the future. */
public boolean initDone = false;

/** For internal use only. May be removed or replaced in the future. */
public String lastFilter = "";

/** For internal use only. May be removed or replaced in the future. */
public enum Select {
NONE, FIRST, LAST
}

/** For internal use only. May be removed or replaced in the future. */
public Select selectPopupItemWhenResponseIsReceived = Select.NONE;

/**
* The current suggestion selected from the dropdown. This is one of the
* values in currentSuggestions except when filtering, in this case
@@ -1644,6 +1540,15 @@ public class VFilterSelect extends Composite
/** For internal use only. May be removed or replaced in the future. */
public boolean prompting = false;

/**
* Set true when popupopened has been clicked. Cleared on each UIDL-update.
* This handles the special case where are not filtering yet and the
* selected value has changed on the server-side. See #2119
* <p>
* For internal use only. May be removed or replaced in the future.
*/
public boolean popupOpenerClicked;

/** For internal use only. May be removed or replaced in the future. */
public int suggestionPopupMinWidth = 0;

@@ -1667,8 +1572,6 @@ public class VFilterSelect extends Composite
*/
private boolean textInputEnabled = true;

private final DataReceivedHandler dataReceivedHandler = new DataReceivedHandler();

/**
* Default constructor.
*/
@@ -1808,7 +1711,22 @@ public class VFilterSelect extends Composite
* The filter to apply to the components
*/
public void filterOptions(int page, String filter) {
debug("VFS: filterOptions(" + page + ", " + filter + ")");
filterOptions(page, filter, true);
}

/**
* Filters the options at certain page using the given filter
*
* @param page
* The page to filter
* @param filter
* The filter to apply to the options
* @param immediate
* Whether to send the options request immediately
*/
private void filterOptions(int page, String filter, boolean immediate) {
debug("VFS: filterOptions(" + page + ", " + filter + ", " + immediate
+ ")");

if (filter.equals(lastFilter) && currentPage == page) {
if (!suggestionPopup.isAttached()) {
@@ -1829,8 +1747,10 @@ public class VFilterSelect extends Composite
}
}

dataReceivedHandler.startWaitingForFilteringResponse();
connector.requestPage(filter, page);
waitingForFilteringResponse = true;
client.updateVariable(paintableId, "filter", filter, false);
client.updateVariable(paintableId, "page", page, immediate);
afterUpdateClientVariables();

lastFilter = filter;
currentPage = page;
@@ -1928,7 +1848,7 @@ public class VFilterSelect extends Composite
debug("VFS: onSuggestionSelected(" + suggestion.caption + ": "
+ suggestion.key + ")");
}
dataReceivedHandler.cancelPendingPostFiltering();
updateSelectionWhenReponseIsReceived = false;

currentSuggestion = suggestion;
String newKey;
@@ -1951,7 +1871,9 @@ public class VFilterSelect extends Composite
if (!(newKey.equals(selectedOptionKey)
|| ("".equals(newKey) && selectedOptionKey == null))) {
selectedOptionKey = newKey;
connector.sendSelection(selectedOptionKey);
client.updateVariable(paintableId, "selected",
new String[] { selectedOptionKey }, immediate);
afterUpdateClientVariables();

// currentPage = -1; // forget the page
}
@@ -1962,7 +1884,9 @@ public class VFilterSelect extends Composite
// hard to interpret that new clause added here :-(
selectedOptionKey = newKey;
explicitSelectedCaption = null;
connector.sendSelection(selectedOptionKey);
client.updateVariable(paintableId, "selected",
new String[] { selectedOptionKey }, immediate);
afterUpdateClientVariables();
}

suggestionPopup.hide();
@@ -1987,8 +1911,7 @@ public class VFilterSelect extends Composite
if (selectedItemIcon != null) {
panel.remove(selectedItemIcon);
}
selectedItemIcon = new IconWidget(
connector.getConnection().getIcon(iconUri));
selectedItemIcon = new IconWidget(client.getIcon(iconUri));
// Older IE versions don't scale icon correctly if DOM
// contains height and width attributes.
selectedItemIcon.getElement().removeAttribute("height");
@@ -2005,7 +1928,7 @@ public class VFilterSelect extends Composite
}

private void afterSelectedItemIconChange() {
if (BrowserInfo.get().isWebkit()) {
if (BrowserInfo.get().isWebkit() || BrowserInfo.get().isIE8()) {
// Some browsers need a nudge to reposition the text field
forceReflow();
}
@@ -2015,88 +1938,6 @@ public class VFilterSelect extends Composite
}
}

/**
* Perform selection based on a message from the server.
*
* This method is called when the server gave a non-empty selected item key,
* whereas null selection is handled by {@link #resetSelection()} and the
* special case where the selected item is not on the current page is
* handled separately by the caller.
*
* @param selectedKey
* non-empty selected item key
* @param oldSuggestionTextMatchTheOldSelection
* true if the suggestion box text matched the previous selection
* before the message from the server updating the selection
* @param updatePromptAndSelectionIfMatchFound
*/
private void performSelection(String selectedKey,
boolean oldSuggestionTextMatchTheOldSelection,
boolean updatePromptAndSelectionIfMatchFound) {
// some item selected
for (FilterSelectSuggestion suggestion : currentSuggestions) {
String suggestionKey = suggestion.getOptionKey();
if (!suggestionKey.equals(selectedKey)) {
continue;
}
// at this point, suggestion key matches the new selection key
if (updatePromptAndSelectionIfMatchFound) {
if (!suggestionKey.equals(selectedOptionKey)
|| suggestion.getReplacementString()
.equals(tb.getText())
|| oldSuggestionTextMatchTheOldSelection) {
// Update text field if we've got a new
// selection
// Also update if we've got the same text to
// retain old text selection behavior
// OR if selected item caption is changed.
setPromptingOff(suggestion.getReplacementString());
selectedOptionKey = suggestionKey;
}
}
currentSuggestion = suggestion;
setSelectedItemIcon(suggestion.getIconUri());
// only a single item can be selected
break;
}
}

/**
* Reset the selection of the combo box to an empty string if focused, the
* input prompt if not.
*
* This method also clears the current suggestion and the selected option
* key.
*/
private void resetSelection(boolean useSelectedCaption) {
// select nulled
if (!focused) {
// TODO it is unclear whether this is really needed anymore -
// client.updateComponent used to overwrite all styles so we had to
// set them again
setPromptingOff("");
if (enabled && !readonly) {
setPromptingOn();
}
} else {
// we have focus in field, prompting can't be set on,
// instead just clear the input if the value has changed from
// something else to null
if (selectedOptionKey != null
|| (allowNewItem && !tb.getValue().isEmpty())) {
if (useSelectedCaption && getSelectedCaption() != null) {
tb.setValue(getSelectedCaption());
tb.selectAll();
} else {
tb.setValue("");
}
}
}
currentSuggestion = null; // #13217
setSelectedItemIcon(null);
selectedOptionKey = null;
}

private void forceReflow() {
WidgetUtil.setStyleTemporarily(tb.getElement(), "zoom", "1");
}
@@ -2116,7 +1957,7 @@ public class VFilterSelect extends Composite
Unit.PX);
}

private static Set<Integer> navigationKeyCodes = new HashSet<Integer>();
private static Set<Integer> navigationKeyCodes = new HashSet<>();
static {
navigationKeyCodes.add(KeyCodes.KEY_DOWN);
navigationKeyCodes.add(KeyCodes.KEY_UP);
@@ -2141,7 +1982,7 @@ public class VFilterSelect extends Composite
if (enableDebug) {
debug("VFS: key down: " + keyCode);
}
if (dataReceivedHandler.isWaitingForFilteringResponse()
if (waitingForFilteringResponse
&& navigationKeyCodes.contains(keyCode)) {
/*
* Keyboard navigation events should not be handled while we are
@@ -2290,12 +2131,7 @@ public class VFilterSelect extends Composite
private void selectPrevPage() {
if (currentPage > 0) {
filterOptions(currentPage - 1, lastFilter);
dataReceivedHandler.setNavigationCallback(new Runnable() {
@Override
public void run() {
suggestionPopup.selectLastItem();
}
});
selectPopupItemWhenResponseIsReceived = Select.LAST;
}
}

@@ -2305,12 +2141,7 @@ public class VFilterSelect extends Composite
private void selectNextPage() {
if (hasNextPage()) {
filterOptions(currentPage + 1, lastFilter);
dataReceivedHandler.setNavigationCallback(new Runnable() {
@Override
public void run() {
suggestionPopup.selectFirstItem();
}
});
selectPopupItemWhenResponseIsReceived = Select.FIRST;
}
}

@@ -2394,8 +2225,13 @@ public class VFilterSelect extends Composite
// ask suggestionPopup if it was just closed, we are using GWT
// Popup's auto close feature
if (!suggestionPopup.isJustClosed()) {
filterOptions(-1, "");
dataReceivedHandler.popupOpenerClicked();
// If a focus event is not going to be sent, send the options
// request immediately; otherwise queue in the same burst as the
// focus event. Fixes #8321.
boolean immediate = focused
|| !client.hasEventListeners(this, EventId.FOCUS);
filterOptions(-1, "", immediate);
popupOpenerClicked = true;
lastFilter = "";
}
DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
@@ -2488,9 +2324,14 @@ public class VFilterSelect extends Composite
}
addStyleDependentName("focus");

connector.sendFocusEvent();
if (client.hasEventListeners(this, EventId.FOCUS)) {
client.updateVariable(paintableId, EventId.FOCUS, "", true);
afterUpdateClientVariables();
}

connector.getConnection().getVTooltip()
ComponentConnector connector = ConnectorMap.get(client)
.getConnector(this);
client.getVTooltip()
.showAssistive(connector.getTooltipInfo(getElement()));
}

@@ -2552,7 +2393,10 @@ public class VFilterSelect extends Composite
}
removeStyleDependentName("focus");

connector.sendBlurEvent();
if (client.hasEventListeners(this, EventId.BLUR)) {
client.updateVariable(paintableId, EventId.BLUR, "", true);
afterUpdateClientVariables();
}
}

/*
@@ -2578,7 +2422,10 @@ public class VFilterSelect extends Composite
* For internal use only. May be removed or replaced in the future.
*/
public void updateRootWidth() {
if (connector.isUndefinedWidth()) {
ComponentConnector paintable = ConnectorMap.get(client)
.getConnector(this);

if (paintable.isUndefinedWidth()) {

/*
* When the select has a undefined with we need to check that we are
@@ -2734,9 +2581,19 @@ public class VFilterSelect extends Composite
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;
}

@Override
public boolean isWorkPending() {
return dataReceivedHandler.isWaitingForFilteringResponse()
return waitingForFilteringResponse
|| suggestionPopup.lazyPageScroller.isRunning();
}

@@ -2765,14 +2622,4 @@ public class VFilterSelect extends Composite
return explicitSelectedCaption;
}

/**
* Returns a handler receiving notifications from the connector about
* communications.
*
* @return the dataReceivedHandler
*/
public DataReceivedHandler getDataReceivedHandler() {
return dataReceivedHandler;
}

}

+ 197
- 197
compatibility-client/src/main/java/com/vaadin/v7/client/ui/combobox/ComboBoxConnector.java View File

@@ -19,65 +19,28 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.Paintable;
import com.vaadin.client.Profiler;
import com.vaadin.client.UIDL;
import com.vaadin.client.communication.RpcProxy;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.SimpleManagedLayout;
import com.vaadin.shared.EventId;
import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc;
import com.vaadin.shared.ui.Connect;
import com.vaadin.v7.client.ui.AbstractFieldConnector;
import com.vaadin.v7.client.ui.VFilterSelect;
import com.vaadin.v7.client.ui.VFilterSelect.DataReceivedHandler;
import com.vaadin.v7.client.ui.VFilterSelect.FilterSelectSuggestion;
import com.vaadin.v7.shared.ui.combobox.ComboBoxServerRpc;
import com.vaadin.v7.shared.ui.combobox.ComboBoxConstants;
import com.vaadin.v7.shared.ui.combobox.ComboBoxState;
import com.vaadin.v7.shared.ui.combobox.FilteringMode;
import com.vaadin.v7.ui.ComboBox;

@Connect(ComboBox.class)
public class ComboBoxConnector extends AbstractFieldConnector
implements Paintable, SimpleManagedLayout {

protected ComboBoxServerRpc rpc = RpcProxy.create(ComboBoxServerRpc.class,
this);

protected FocusAndBlurServerRpc focusAndBlurRpc = RpcProxy
.create(FocusAndBlurServerRpc.class, this);

@Override
protected void init() {
super.init();
getWidget().connector = this;
}

@Override
public void onStateChanged(StateChangeEvent stateChangeEvent) {
super.onStateChanged(stateChangeEvent);

Profiler.enter("ComboBoxConnector.onStateChanged update content");

getWidget().readonly = isReadOnly();
getWidget().updateReadOnly();

getWidget().setTextInputEnabled(getState().textInputAllowed);

if (getState().inputPrompt != null) {
getWidget().inputPrompt = getState().inputPrompt;
} else {
getWidget().inputPrompt = "";
}

getWidget().pageLength = getState().pageLength;

getWidget().filteringmode = getState().filteringMode;

getWidget().suggestionPopupWidth = getState().suggestionPopupWidth;

Profiler.leave("ComboBoxConnector.onStateChanged update content");
}
// oldSuggestionTextMatchTheOldSelection is used to detect when it's safe to
// update textbox text by a changed item caption.
private boolean oldSuggestionTextMatchTheOldSelection;

/*
* (non-Javadoc)
@@ -87,13 +50,35 @@ public class ComboBoxConnector extends AbstractFieldConnector
*/
@Override
public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
// Save details
getWidget().client = client;
getWidget().paintableId = uidl.getId();

getWidget().readonly = isReadOnly();
getWidget().updateReadOnly();

if (!isRealUpdate(uidl)) {
return;
}

// Inverse logic here to make the default case (text input enabled)
// work without additional UIDL messages
boolean noTextInput = uidl
.hasAttribute(ComboBoxConstants.ATTR_NO_TEXT_INPUT)
&& uidl.getBooleanAttribute(
ComboBoxConstants.ATTR_NO_TEXT_INPUT);
getWidget().setTextInputEnabled(!noTextInput);

// not a FocusWidget -> needs own tabindex handling
getWidget().tb.setTabIndex(getState().tabIndex);

if (uidl.hasAttribute("filteringmode")) {
getWidget().filteringmode = FilteringMode
.valueOf(uidl.getStringAttribute("filteringmode"));
}

getWidget().immediate = getState().immediate;

getWidget().nullSelectionAllowed = uidl.hasAttribute("nullselect");

getWidget().nullSelectItem = uidl.hasAttribute("nullselectitem")
@@ -101,7 +86,33 @@ public class ComboBoxConnector extends AbstractFieldConnector

getWidget().currentPage = uidl.getIntVariable("page");

getWidget().suggestionPopup.updateStyleNames(getState());
if (uidl.hasAttribute("pagelength")) {
getWidget().pageLength = uidl.getIntAttribute("pagelength");
}

if (uidl.hasAttribute(ComboBoxConstants.ATTR_INPUTPROMPT)) {
// input prompt changed from server
getWidget().inputPrompt = uidl
.getStringAttribute(ComboBoxConstants.ATTR_INPUTPROMPT);
} else {
getWidget().inputPrompt = "";
}

if (uidl.hasAttribute("suggestionPopupWidth")) {
getWidget().suggestionPopupWidth = uidl
.getStringAttribute("suggestionPopupWidth");
} else {
getWidget().suggestionPopupWidth = null;
}

if (uidl.hasAttribute("suggestionPopupWidth")) {
getWidget().suggestionPopupWidth = uidl
.getStringAttribute("suggestionPopupWidth");
} else {
getWidget().suggestionPopupWidth = null;
}

getWidget().suggestionPopup.updateStyleNames(uidl, getState());

getWidget().allowNewItem = uidl.hasAttribute("allownewitem");
getWidget().lastNewItemString = null;
@@ -113,21 +124,12 @@ public class ComboBoxConnector extends AbstractFieldConnector
getWidget().totalMatches = 0;
}

List<FilterSelectSuggestion> newSuggestions = new ArrayList<FilterSelectSuggestion>();
List<FilterSelectSuggestion> newSuggestions = new ArrayList<>();

for (final Iterator<?> i = options.getChildIterator(); i.hasNext();) {
final UIDL optionUidl = (UIDL) i.next();
String key = optionUidl.getStringAttribute("key");
String caption = optionUidl.getStringAttribute("caption");
String style = optionUidl.getStringAttribute("style");

String untranslatedIconUri = null;
if (optionUidl.hasAttribute("icon")) {
untranslatedIconUri = optionUidl.getStringAttribute("icon");
}

final FilterSelectSuggestion suggestion = getWidget().new FilterSelectSuggestion(
key, caption, style, untranslatedIconUri);
optionUidl);
newSuggestions.add(suggestion);
}

@@ -139,15 +141,13 @@ public class ComboBoxConnector extends AbstractFieldConnector
// popup. Popup needs to be repopulated with suggestions from UIDL.
boolean popupOpenAndCleared = false;

// oldSuggestionTextMatchTheOldSelection is used to detect when it's
// safe to update textbox text by a changed item caption.
boolean oldSuggestionTextMatchesTheOldSelection = false;
oldSuggestionTextMatchTheOldSelection = false;

if (suggestionsChanged) {
oldSuggestionTextMatchesTheOldSelection = isWidgetsCurrentSelectionTextInTextBox();
oldSuggestionTextMatchTheOldSelection = isWidgetsCurrentSelectionTextInTextBox();
getWidget().currentSuggestions.clear();

if (!getDataReceivedHandler().isWaitingForFilteringResponse()) {
if (!getWidget().waitingForFilteringResponse) {
/*
* Clear the current suggestions as the server response always
* includes the new ones. Exception is when filtering, then we
@@ -180,34 +180,62 @@ public class ComboBoxConnector extends AbstractFieldConnector
//
) {

// single selected key (can be empty string) or empty array for null
// selection
String[] selectedKeys = uidl.getStringArrayVariable("selected");
String selectedKey = null;
if (selectedKeys.length == 1) {
selectedKey = selectedKeys[0];
}
// selected item caption in case it is not on the current page
String selectedCaption = null;
if (uidl.hasAttribute("selectedCaption")) {
selectedCaption = uidl.getStringAttribute("selectedCaption");
}

getDataReceivedHandler().updateSelectionFromServer(selectedKey,
selectedCaption, oldSuggestionTextMatchesTheOldSelection);
// when filtering with empty filter, server sets the selected key
// to "", which we don't select here. Otherwise we won't be able to
// reset back to the item that was selected before filtering
// started.
if (selectedKeys.length > 0 && !selectedKeys[0].equals("")) {
performSelection(selectedKeys[0]);
// if selected key is available, assume caption is know based on
// it as well and clear selected caption
getWidget().setSelectedCaption(null);

} else if (!getWidget().waitingForFilteringResponse
&& uidl.hasAttribute("selectedCaption")) {
// scrolling to correct page is disabled, caption is passed as a
// special parameter
getWidget().setSelectedCaption(
uidl.getStringAttribute("selectedCaption"));
} else {
resetSelection();
}
}

// TODO even this condition should probably be moved to the handler
if ((getDataReceivedHandler().isWaitingForFilteringResponse()
&& getWidget().lastFilter.toLowerCase()
.equals(uidl.getStringVariable("filter")))
if ((getWidget().waitingForFilteringResponse && getWidget().lastFilter
.toLowerCase().equals(uidl.getStringVariable("filter")))
|| popupOpenAndCleared) {
getDataReceivedHandler().dataReceived();

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
public void execute() {
navigateItemAfterPageChange();
}
});
}

if (getWidget().updateSelectionWhenReponseIsReceived) {
getWidget().suggestionPopup.menu
.doPostFilterSelectedItemAction();
}
}

// Calculate minimum textarea width
getWidget().updateSuggestionPopupMinWidth();

getWidget().popupOpenerClicked = false;

/*
* if this is our first time we need to recalculate the root width.
*/
@@ -223,9 +251,58 @@ public class ComboBoxConnector extends AbstractFieldConnector
}

getWidget().initDone = true;
}

/*
* This method navigates to the proper item in the combobox page. This
* should be executed after setSuggestions() method which is called from
* vFilterSelect.showSuggestions(). ShowSuggestions() method builds the page
* content. As far as setSuggestions() method is called as deferred,
* navigateItemAfterPageChange method should be also be called as deferred.
* #11333
*/
private void navigateItemAfterPageChange() {
if (getWidget().selectPopupItemWhenResponseIsReceived == VFilterSelect.Select.LAST) {
getWidget().suggestionPopup.selectLastItem();
} else {
getWidget().suggestionPopup.selectFirstItem();
}

// 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
}

// TODO this should perhaps be moved to be a part of dataReceived()
getDataReceivedHandler().serverReplyHandled();
private void performSelection(String selectedKey) {
// some item selected
for (FilterSelectSuggestion suggestion : getWidget().currentSuggestions) {
String suggestionKey = suggestion.getOptionKey();
if (!suggestionKey.equals(selectedKey)) {
continue;
}
if (!getWidget().waitingForFilteringResponse
|| getWidget().popupOpenerClicked) {
if (!suggestionKey.equals(getWidget().selectedOptionKey)
|| suggestion.getReplacementString()
.equals(getWidget().tb.getText())
|| oldSuggestionTextMatchTheOldSelection) {
// Update text field if we've got a new
// selection
// Also update if we've got the same text to
// retain old text selection behavior
// OR if selected item caption is changed.
getWidget()
.setPromptingOff(suggestion.getReplacementString());
getWidget().selectedOptionKey = suggestionKey;
}
}
getWidget().currentSuggestion = suggestion;
getWidget().setSelectedItemIcon(suggestion.getIconUri());
// only a single item can be selected
break;
}
}

private boolean isWidgetsCurrentSelectionTextInTextBox() {
@@ -234,15 +311,50 @@ public class ComboBoxConnector extends AbstractFieldConnector
.equals(getWidget().tb.getText());
}

private void resetSelection() {
if (!getWidget().waitingForFilteringResponse
|| getWidget().popupOpenerClicked) {
// select nulled
if (!getWidget().focused) {
/*
* client.updateComponent overwrites all styles so we must
* ALWAYS set the prompting style at this point, even though we
* think it has been set already...
*/
getWidget().setPromptingOff("");
if (getWidget().enabled && !getWidget().readonly) {
getWidget().setPromptingOn();
}
} else {
// we have focus in field, prompting can't be set on, instead
// just clear the input if the value has changed from something
// else to null
if (getWidget().selectedOptionKey != null
|| (getWidget().allowNewItem
&& !getWidget().tb.getValue().isEmpty())) {

boolean openedPopupWithNonScrollingMode = (getWidget().popupOpenerClicked
&& getWidget().getSelectedCaption() != null);
if (!openedPopupWithNonScrollingMode) {
getWidget().tb.setValue("");
} else {
getWidget().tb
.setValue(getWidget().getSelectedCaption());
getWidget().tb.selectAll();
}
}
}
getWidget().currentSuggestion = null; // #13217
getWidget().setSelectedItemIcon(null);
getWidget().selectedOptionKey = null;
}
}

@Override
public VFilterSelect getWidget() {
return (VFilterSelect) super.getWidget();
}

private DataReceivedHandler getDataReceivedHandler() {
return getWidget().getDataReceivedHandler();
}

@Override
public ComboBoxState getState() {
return (ComboBoxState) super.getState();
@@ -263,116 +375,4 @@ public class ComboBoxConnector extends AbstractFieldConnector
getWidget().tb.setEnabled(widgetEnabled);
}

/*
* These methods exist to move communications out of VFilterSelect, and may
* be refactored/removed in the future
*/

/**
* Send a message about a newly created item to the server.
*
* This method is for internal use only and may be removed in future
* versions.
*
* @since 8.0
* @param itemValue
* user entered string value for the new item
*/
public void sendNewItem(String itemValue) {
rpc.createNewItem(itemValue);
afterSendRequestToServer();
}

/**
* Send a message to the server to request the first page of items without
* filtering or selection.
*
* This method is for internal use only and may be removed in future
* versions.
*
* @since 8.0
*/
public void requestFirstPage() {
sendSelection(null);
requestPage("", 0);
}

/**
* Send a message to the server to request a page of items with a given
* filter.
*
* This method is for internal use only and may be removed in future
* versions.
*
* @since 8.0
* @param filter
* the current filter string
* @param page
* the page number to get
*/
public void requestPage(String filter, int page) {
rpc.requestPage(filter, page);
afterSendRequestToServer();
}

/**
* Send a message to the server updating the current selection.
*
* This method is for internal use only and may be removed in future
* versions.
*
* @since 8.0
* @param selection
* the current selection
*/
public void sendSelection(String selection) {
rpc.setSelectedItem(selection);
afterSendRequestToServer();
}

/**
* Notify the server that the combo box received focus.
*
* For timing reasons, ConnectorFocusAndBlurHandler is not used at the
* moment.
*
* This method is for internal use only and may be removed in future
* versions.
*
* @since 8.0
*/
public void sendFocusEvent() {
boolean registeredListeners = hasEventListener(EventId.FOCUS);
if (registeredListeners) {
focusAndBlurRpc.focus();
afterSendRequestToServer();
}
}

/**
* Notify the server that the combo box lost focus.
*
* For timing reasons, ConnectorFocusAndBlurHandler is not used at the
* moment.
*
* This method is for internal use only and may be removed in future
* versions.
*
* @since 8.0
*/
public void sendBlurEvent() {
boolean registeredListeners = hasEventListener(EventId.BLUR);
if (registeredListeners) {
focusAndBlurRpc.blur();
afterSendRequestToServer();
}
}

/*
* Called after any request to server.
*/
private void afterSendRequestToServer() {
getDataReceivedHandler().anyRequestSentToServer();
}

}

+ 165
- 148
compatibility-server/src/main/java/com/vaadin/v7/ui/ComboBox.java View File

@@ -27,16 +27,14 @@ import java.util.Map;
import com.vaadin.event.FieldEvents;
import com.vaadin.event.FieldEvents.BlurEvent;
import com.vaadin.event.FieldEvents.BlurListener;
import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcImpl;
import com.vaadin.event.FieldEvents.FocusEvent;
import com.vaadin.event.FieldEvents.FocusListener;
import com.vaadin.server.PaintException;
import com.vaadin.server.PaintTarget;
import com.vaadin.server.Resource;
import com.vaadin.ui.Component;
import com.vaadin.v7.data.Container;
import com.vaadin.v7.data.util.filter.SimpleStringFilter;
import com.vaadin.v7.shared.ui.combobox.ComboBoxServerRpc;
import com.vaadin.v7.shared.ui.combobox.ComboBoxConstants;
import com.vaadin.v7.shared.ui.combobox.ComboBoxState;
import com.vaadin.v7.shared.ui.combobox.FilteringMode;

@@ -79,59 +77,18 @@ public class ComboBox extends AbstractSelect
public String getStyle(ComboBox source, Object itemId);
}

private ComboBoxServerRpc rpc = new ComboBoxServerRpc() {
@Override
public void createNewItem(String itemValue) {
if (isNewItemsAllowed()) {
// New option entered (and it is allowed)
if (itemValue != null && itemValue.length() > 0) {
getNewItemHandler().addNewItem(itemValue);
// rebuild list
filterstring = null;
prevfilterstring = null;
}
}
}

@Override
public void setSelectedItem(String item) {
if (item == null) {
setValue(null, true);
} else {
final Object id = itemIdMapper.get(item);
if (id != null && id.equals(getNullSelectionItemId())) {
setValue(null, true);
} else {
setValue(id, true);
}
}
}
private String inputPrompt = null;

@Override
public void requestPage(String filter, int page) {
filterstring = filter;
if (filterstring != null) {
filterstring = filterstring.toLowerCase(getLocale());
}
currentPage = page;

// TODO this should trigger a data-only update instead of a full
// repaint
requestRepaint();
}
};

FocusAndBlurServerRpcImpl focusBlurRpc = new FocusAndBlurServerRpcImpl(
this) {
@Override
protected void fireEvent(Component.Event event) {
ComboBox.this.fireEvent(event);
}
};
/**
* Holds value of property pageLength. 0 disables paging.
*/
protected int pageLength = 10;

// Current page when the user is 'paging' trough options
private int currentPage = -1;

private FilteringMode filteringMode = FilteringMode.STARTSWITH;

private String filterstring;
private String prevfilterstring;

@@ -166,35 +123,40 @@ public class ComboBox extends AbstractSelect
*/
private boolean scrollToSelectedItem = true;

private String suggestionPopupWidth = null;

/**
* If text input is not allowed, the ComboBox behaves like a pretty
* NativeSelect - the user can not enter any text and clicking the text
* field opens the drop down with options
*/
private boolean textInputAllowed = true;

private ItemStyleGenerator itemStyleGenerator = null;

public ComboBox() {
init();
initDefaults();
}

public ComboBox(String caption, Collection<?> options) {
super(caption, options);
init();
initDefaults();
}

public ComboBox(String caption, Container dataSource) {
super(caption, dataSource);
init();
initDefaults();
}

public ComboBox(String caption) {
super(caption);
init();
initDefaults();
}

/**
* Initialize the ComboBox with default settings and register client to
* server RPC implementation.
* Initialize the ComboBox with default settings
*/
private void init() {
registerRpc(rpc);
registerRpc(focusBlurRpc);

private void initDefaults() {
setNewItemsAllowed(false);
setImmediate(true);
}
@@ -206,7 +168,7 @@ public class ComboBox extends AbstractSelect
* @return the current input prompt, or null if not enabled
*/
public String getInputPrompt() {
return getState(false).inputPrompt;
return inputPrompt;
}

/**
@@ -217,46 +179,42 @@ public class ComboBox extends AbstractSelect
* the desired input prompt, or null to disable
*/
public void setInputPrompt(String inputPrompt) {
getState().inputPrompt = inputPrompt;
this.inputPrompt = inputPrompt;
markAsDirty();
}

private boolean isFilteringNeeded() {
return filterstring != null && filterstring.length() > 0
&& getFilteringMode() != FilteringMode.OFF;
}

/**
* A class representing an item in a ComboBox for server to client
* communication. This class is for internal use only and subject to change.
*
* @since 8.0
*/
private static class ComboBoxItem implements Serializable {
String key = "";
String caption = "";
String style = null;
Resource icon = null;

// constructor for a null item
public ComboBoxItem() {
}

public ComboBoxItem(String key, String caption, String style,
Resource icon) {
this.key = key;
this.caption = caption;
this.style = style;
this.icon = icon;
}
&& filteringMode != FilteringMode.OFF;
}

@Override
public void paintContent(PaintTarget target) throws PaintException {
isPainting = true;
try {
if (inputPrompt != null) {
target.addAttribute(ComboBoxConstants.ATTR_INPUTPROMPT,
inputPrompt);
}

if (!textInputAllowed) {
target.addAttribute(ComboBoxConstants.ATTR_NO_TEXT_INPUT, true);
}

// clear caption change listeners
getCaptionChangeListener().clear();

// The tab ordering number
if (getTabIndex() != 0) {
target.addAttribute("tabindex", getTabIndex());
}

// If the field is modified, but not committed, set modified
// attribute
if (isModified()) {
target.addAttribute("modified", true);
}

if (isNewItemsAllowed()) {
target.addAttribute("allownewitem", true);
}
@@ -274,9 +232,20 @@ public class ComboBox extends AbstractSelect
String[] selectedKeys = new String[(getValue() == null
&& getNullSelectionItemId() == null ? 0 : 1)];

target.addAttribute("pagelength", pageLength);

if (suggestionPopupWidth != null) {
target.addAttribute("suggestionPopupWidth",
suggestionPopupWidth);
}

target.addAttribute("filteringmode", getFilteringMode().toString());

// Paints the options and create array of selected id keys
int keyIndex = 0;

target.startTag("options");

if (currentPage < 0) {
optionRequest = false;
currentPage = 0;
@@ -285,7 +254,8 @@ public class ComboBox extends AbstractSelect

boolean nullFilteredOut = isFilteringNeeded();
// null option is needed and not filtered out, even if not on
// current page
// current
// page
boolean nullOptionVisible = needNullSelectOption
&& !nullFilteredOut;

@@ -296,18 +266,20 @@ public class ComboBox extends AbstractSelect
// filtering
options = getFilteredOptions();
filteredSize = options.size();
options = sanitetizeList(options, nullOptionVisible);
options = sanitizeList(options, nullOptionVisible);
}

final boolean paintNullSelection = needNullSelectOption
&& currentPage == 0 && !nullFilteredOut;

List<ComboBoxItem> items = new ArrayList<>();

if (paintNullSelection) {
ComboBoxItem item = new ComboBoxItem();
item.style = getItemStyle(null);
items.add(item);
target.startTag("so");
target.addAttribute("caption", "");
target.addAttribute("key", "");

paintItemStyle(target, null);

target.endTag("so");
}

final Iterator<?> i = options.iterator();
@@ -327,31 +299,24 @@ public class ComboBox extends AbstractSelect
final String key = itemIdMapper.key(id);
final String caption = getItemCaption(id);
final Resource icon = getItemIcon(id);

getCaptionChangeListener().addNotifierForItem(id);

// Prepare to paint the option
ComboBoxItem item = new ComboBoxItem(key, caption,
getItemStyle(id), icon);
items.add(item);
// Paints the option
target.startTag("so");
if (icon != null) {
target.addAttribute("icon", icon);
}
target.addAttribute("caption", caption);
if (id != null && id.equals(getNullSelectionItemId())) {
target.addAttribute("nullselection", true);
}
target.addAttribute("key", key);
if (keyIndex < selectedKeys.length && isSelected(id)) {
// at most one item can be selected at a time
selectedKeys[keyIndex++] = key;
}
}

// paint the items
target.startTag("options");
for (ComboBoxItem item : items) {
target.startTag("so");
if (item.icon != null) {
target.addAttribute("icon", item.icon);
}
target.addAttribute("caption", item.caption);
target.addAttribute("key", item.key);
if (item.style != null) {
target.addAttribute("style", item.style);
}
paintItemStyle(target, id);

target.endTag("so");
}
@@ -389,11 +354,14 @@ public class ComboBox extends AbstractSelect

}

private String getItemStyle(Object itemId) throws PaintException {
private void paintItemStyle(PaintTarget target, Object itemId)
throws PaintException {
if (itemStyleGenerator != null) {
return itemStyleGenerator.getStyle(this, itemId);
String style = itemStyleGenerator.getStyle(this, itemId);
if (style != null && !style.isEmpty()) {
target.addAttribute("style", style);
}
}
return null;
}

/**
@@ -409,7 +377,8 @@ public class ComboBox extends AbstractSelect
* selection
*/
public void setTextInputAllowed(boolean textInputAllowed) {
getState().textInputAllowed = textInputAllowed;
this.textInputAllowed = textInputAllowed;
markAsDirty();
}

/**
@@ -421,7 +390,7 @@ public class ComboBox extends AbstractSelect
* @return
*/
public boolean isTextInputAllowed() {
return getState(false).textInputAllowed;
return textInputAllowed;
}

@Override
@@ -429,11 +398,6 @@ public class ComboBox extends AbstractSelect
return (ComboBoxState) super.getState();
}

@Override
protected ComboBoxState getState(boolean markAsDirty) {
return (ComboBoxState) super.getState(markAsDirty);
}

/**
* Returns the filtered options for the current page using a container
* filter.
@@ -446,7 +410,7 @@ public class ComboBox extends AbstractSelect
* {@link #canUseContainerFilter()}).
*
* Use {@link #getFilteredOptions()} and
* {@link #sanitetizeList(List, boolean)} if this is not the case.
* {@link #sanitizeList(List, boolean)} if this is not the case.
*
* @param needNullSelectOption
* @return filtered list of options (may be empty) or null if cannot use
@@ -455,7 +419,7 @@ public class ComboBox extends AbstractSelect
protected List<?> getOptionsWithFilter(boolean needNullSelectOption) {
Container container = getContainerDataSource();

if (getPageLength() == 0 && !isFilteringNeeded()) {
if (pageLength == 0 && !isFilteringNeeded()) {
// no paging or filtering: return all items
filteredSize = container.size();
assert filteredSize >= 0;
@@ -470,7 +434,7 @@ public class ComboBox extends AbstractSelect

Filterable filterable = (Filterable) container;

Filter filter = buildFilter(filterstring, getFilteringMode());
Filter filter = buildFilter(filterstring, filteringMode);

// adding and removing filters leads to extraneous item set
// change events from the underlying container, but the ComboBox does
@@ -563,23 +527,25 @@ public class ComboBox extends AbstractSelect

/**
* Makes correct sublist of given list of options.
*
* <p>
* If paint is not an option request (affected by page or filter change),
* page will be the one where possible selection exists.
*
* <p>
* Detects proper first and last item in list to return right page of
* options. Also, if the current page is beyond the end of the list, it will
* be adjusted.
* <p>
* Package private only for testing purposes.
*
* @param options
* @param needNullSelectOption
* flag to indicate if nullselect option needs to be taken into
* consideration
*/
private List<?> sanitetizeList(List<?> options,
boolean needNullSelectOption) {
if (getPageLength() != 0 && options.size() > getPageLength()) {
List<?> sanitizeList(List<?> options, boolean needNullSelectOption) {
int totalRows = options.size() + (needNullSelectOption ? 1 : 0);
if (pageLength != 0 && totalRows > pageLength) {
// options will not fit on one page

int indexToEnsureInView = -1;

@@ -625,7 +591,7 @@ public class ComboBox extends AbstractSelect
int size) {
// Not all options are visible, find out which ones are on the
// current "page".
int first = currentPage * getPageLength();
int first = currentPage * pageLength;
if (needNullSelectOption && currentPage > 0) {
first--;
}
@@ -652,7 +618,7 @@ public class ComboBox extends AbstractSelect
private int getLastItemIndexOnCurrentPage(boolean needNullSelectOption,
int size, int first) {
// page length usable for non-null items
int effectivePageLength = getPageLength()
int effectivePageLength = pageLength
- (needNullSelectOption && (currentPage == 0) ? 1 : 0);
return Math.min(size - 1, first + effectivePageLength - 1);
}
@@ -680,12 +646,12 @@ public class ComboBox extends AbstractSelect
int indexToEnsureInView, int size) {
if (indexToEnsureInView != -1) {
int newPage = (indexToEnsureInView + (needNullSelectOption ? 1 : 0))
/ getPageLength();
/ pageLength;
page = newPage;
}
// adjust the current page if beyond the end of the list
if (page * getPageLength() > size) {
page = (size + (needNullSelectOption ? 1 : 0)) / getPageLength();
if (page * pageLength > size) {
page = (size + (needNullSelectOption ? 1 : 0)) / pageLength;
}
return page;
}
@@ -728,7 +694,7 @@ public class ComboBox extends AbstractSelect
} else {
caption = caption.toLowerCase(getLocale());
}
switch (getFilteringMode()) {
switch (filteringMode) {
case CONTAINS:
if (caption.indexOf(filterstring) > -1) {
filteredOptions.add(itemId);
@@ -757,17 +723,66 @@ public class ComboBox extends AbstractSelect
// Not calling super.changeVariables due the history of select
// component hierarchy

// all the client to server requests are now handled by RPC
// Selection change
if (variables.containsKey("selected")) {
final String[] ka = (String[]) variables.get("selected");

// Single select mode
if (ka.length == 0) {

// Allows deselection only if the deselected item is visible
final Object current = getValue();
final Collection<?> visible = getVisibleItemIds();
if (visible != null && visible.contains(current)) {
setValue(null, true);
}
} else {
final Object id = itemIdMapper.get(ka[0]);
if (id != null && id.equals(getNullSelectionItemId())) {
setValue(null, true);
} else {
setValue(id, true);
}
}
}

String newFilter;
if ((newFilter = (String) variables.get("filter")) != null) {
// this is a filter request
currentPage = ((Integer) variables.get("page")).intValue();
filterstring = newFilter;
if (filterstring != null) {
filterstring = filterstring.toLowerCase(getLocale());
}
requestRepaint();
} else if (isNewItemsAllowed()) {
// New option entered (and it is allowed)
final String newitem = (String) variables.get("newitem");
if (newitem != null && newitem.length() > 0) {
getNewItemHandler().addNewItem(newitem);
// rebuild list
filterstring = null;
prevfilterstring = null;
}
}

if (variables.containsKey(FocusEvent.EVENT_ID)) {
fireEvent(new FocusEvent(this));
}
if (variables.containsKey(BlurEvent.EVENT_ID)) {
fireEvent(new BlurEvent(this));
}

}

@Override
public void setFilteringMode(FilteringMode filteringMode) {
getState().filteringMode = filteringMode;
this.filteringMode = filteringMode;
}

@Override
public FilteringMode getFilteringMode() {
return getState(false).filteringMode;
return filteringMode;
}

@Override
@@ -797,7 +812,7 @@ public class ComboBox extends AbstractSelect
*
* @deprecated As of 7.0, use {@link ListSelect}, {@link OptionGroup} or
* {@link TwinColSelect} instead
* @see com.vaadin.v7.ui.AbstractSelect#setMultiSelect(boolean)
* @see com.vaadin.ui.AbstractSelect#setMultiSelect(boolean)
* @throws UnsupportedOperationException
* if trying to activate multiselect mode
*/
@@ -816,7 +831,7 @@ public class ComboBox extends AbstractSelect
* @deprecated As of 7.0, use {@link ListSelect}, {@link OptionGroup} or
* {@link TwinColSelect} instead
*
* @see com.vaadin.v7.ui.AbstractSelect#isMultiSelect()
* @see com.vaadin.ui.AbstractSelect#isMultiSelect()
*
* @return false
*/
@@ -832,7 +847,7 @@ public class ComboBox extends AbstractSelect
* @return the pageLength
*/
public int getPageLength() {
return getState(false).pageLength;
return pageLength;
}

/**
@@ -842,7 +857,7 @@ public class ComboBox extends AbstractSelect
* @since 7.7
*/
public String getPopupWidth() {
return getState(false).suggestionPopupWidth;
return suggestionPopupWidth;
}

/**
@@ -853,7 +868,8 @@ public class ComboBox extends AbstractSelect
* the pageLength to set
*/
public void setPageLength(int pageLength) {
getState().pageLength = pageLength;
this.pageLength = pageLength;
markAsDirty();
}

/**
@@ -867,7 +883,8 @@ public class ComboBox extends AbstractSelect
* the width
*/
public void setPopupWidth(String width) {
getState().suggestionPopupWidth = width;
suggestionPopupWidth = width;
markAsDirty();
}

/**

+ 7
- 19
compatibility-server/src/test/java/com/vaadin/v7/tests/server/components/ComboBoxValueChangeTest.java View File

@@ -1,10 +1,10 @@
package com.vaadin.v7.tests.server.components;

import java.util.HashMap;
import java.util.Map;

import org.junit.Before;

import com.vaadin.server.ServerRpcManager;
import com.vaadin.server.ServerRpcMethodInvocation;
import com.vaadin.v7.shared.ui.combobox.ComboBoxServerRpc;
import com.vaadin.v7.tests.server.component.abstractfield.AbstractFieldValueChangeTestBase;
import com.vaadin.v7.ui.AbstractField;
import com.vaadin.v7.ui.ComboBox;
@@ -20,28 +20,16 @@ public class ComboBoxValueChangeTest

@Before
public void setUp() {
ComboBox combo = new ComboBox() {
@Override
public String getConnectorId() {
return "id";
}
};
ComboBox combo = new ComboBox();
combo.addItem("myvalue");
super.setUp(combo);
}

@Override
protected void setValue(AbstractField<Object> field) {
ComboBox combo = (ComboBox) field;
ServerRpcMethodInvocation invocation = new ServerRpcMethodInvocation(
combo.getConnectorId(), ComboBoxServerRpc.class,
"setSelectedItem", 1);
invocation.setParameters(new Object[] { "myvalue" });
try {
ServerRpcManager.applyInvocation(combo, invocation);
} catch (Exception e) {
throw new RuntimeException(e);
}
Map<String, Object> variables = new HashMap<>();
variables.put("selected", new String[] { "myvalue" });
((ComboBox) field).changeVariables(field, variables);
}

}

+ 158
- 0
compatibility-server/src/test/java/com/vaadin/v7/ui/ComboBoxTest.java View File

@@ -0,0 +1,158 @@
package com.vaadin.v7.ui;

import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import com.vaadin.v7.shared.ui.combobox.FilteringMode;

public class ComboBoxTest {

private ComboBox comboBox;

@Before
public void setup() {
comboBox = new ComboBox();
comboBox.setLocale(Locale.ENGLISH);
}

@Test
public void options_noFilter() {
ComboBox comboBox = new ComboBox();
for (int i = 0; i < 10; i++) {
comboBox.addItem("" + i);
}

List<?> options = comboBox.getFilteredOptions();
Assert.assertEquals(10, options.size());
for (int i = 0; i < 10; i++) {
Assert.assertEquals("" + i, options.get(i));
}

}

@Test
public void options_inMemoryFilteringStartsWith() {
for (int i = 0; i < 21; i++) {
comboBox.addItem("" + i);
}

setFilterAndCurrentPage(comboBox, "1", 0);

List<?> options = comboBox.getFilteredOptions();
Assert.assertEquals(11, options.size());

}

@Test
public void options_inMemoryFilteringContains() {
comboBox.setFilteringMode(FilteringMode.CONTAINS);
for (int i = 0; i < 21; i++) {
comboBox.addItem("" + i);
}

setFilterAndCurrentPage(comboBox, "2", 0);
List<?> options = comboBox.getFilteredOptions();
Assert.assertEquals(3, options.size());

}

private static void setFilterAndCurrentPage(ComboBox comboBox,
String filterString, int currentPage) {
Map<String, Object> variables = new HashMap<>();
variables.put("filter", filterString);
variables.put("page", currentPage);
comboBox.changeVariables(null, variables);

}

@Test
public void getOptions_moreThanOnePage_noNullItem() {
int nrOptions = comboBox.getPageLength() * 2;
for (int i = 0; i < nrOptions; i++) {
comboBox.addItem("" + i);
}
setFilterAndCurrentPage(comboBox, "", 0);

List<?> goingToClient = comboBox
.sanitizeList(comboBox.getFilteredOptions(), false);
Assert.assertEquals(comboBox.getPageLength(), goingToClient.size());

}

@Test
public void getOptions_moreThanOnePage_nullItem() {
int nrOptions = comboBox.getPageLength() * 2;
for (int i = 0; i < nrOptions; i++) {
comboBox.addItem("" + i);
}

setFilterAndCurrentPage(comboBox, "", 0);
List<?> goingToClient = comboBox
.sanitizeList(comboBox.getFilteredOptions(), true);
// Null item is shown on first page
Assert.assertEquals(comboBox.getPageLength() - 1, goingToClient.size());

setFilterAndCurrentPage(comboBox, "", 1);
goingToClient = comboBox.sanitizeList(comboBox.getFilteredOptions(),
true);
// Null item is not shown on the second page
Assert.assertEquals(comboBox.getPageLength(), goingToClient.size());

}

@Test
public void getOptions_lessThanOnePage_noNullItem() {
int nrOptions = comboBox.getPageLength() / 2;
for (int i = 0; i < nrOptions; i++) {
comboBox.addItem("" + i);
}
setFilterAndCurrentPage(comboBox, "", 0);

List<?> goingToClient = comboBox
.sanitizeList(comboBox.getFilteredOptions(), false);
Assert.assertEquals(nrOptions, goingToClient.size());

}

@Test
public void getOptions_lessThanOnePage_withNullItem() {
int nrOptions = comboBox.getPageLength() / 2;
for (int i = 0; i < nrOptions; i++) {
comboBox.addItem("" + i);
}
setFilterAndCurrentPage(comboBox, "", 0);

List<?> goingToClient = comboBox
.sanitizeList(comboBox.getFilteredOptions(), true);
// All items + null still fit on one page
Assert.assertEquals(nrOptions, goingToClient.size());

}

@Test
public void getOptions_exactlyOnePage_withNullItem() {
int nrOptions = comboBox.getPageLength();
for (int i = 0; i < nrOptions; i++) {
comboBox.addItem("" + i);
}
setFilterAndCurrentPage(comboBox, "", 0);

List<?> goingToClient = comboBox
.sanitizeList(comboBox.getFilteredOptions(), true);
// Null item on first page
Assert.assertEquals(nrOptions - 1, goingToClient.size());

setFilterAndCurrentPage(comboBox, "", 1);
goingToClient = comboBox.sanitizeList(comboBox.getFilteredOptions(),
true);
// All but one was on the first page
Assert.assertEquals(1, goingToClient.size());

}
}

+ 0
- 53
compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/combobox/ComboBoxServerRpc.java View File

@@ -1,53 +0,0 @@
/*
* Copyright 2000-2016 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.v7.shared.ui.combobox;

import com.vaadin.shared.communication.ServerRpc;

/**
* Client to server RPC interface for ComboBox.
*
* @since 8.0
*/
public interface ComboBoxServerRpc extends ServerRpc {
/**
* Create a new item in the combo box. This method can only be used when the
* ComboBox is configured to allow the creation of new items by the user.
*
* @param itemValue
* user entered string value for the new item
*/
public void createNewItem(String itemValue);

/**
* Set the current selection.
*
* @param item
* the id of a single item or null to deselect the current value
*/
public void setSelectedItem(String item);

/**
* Request the server to send a page of the item list.
*
* @param filter
* filter string interpreted according to the current filtering
* mode
* @param page
* zero based page number
*/
public void requestPage(String filter, int page);
}

+ 0
- 36
compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/combobox/ComboBoxState.java View File

@@ -26,40 +26,4 @@ public class ComboBoxState extends AbstractSelectState {
{
primaryStyleName = "v-filterselect";
}

/**
* If text input is not allowed, the ComboBox behaves like a pretty
* NativeSelect - the user can not enter any text and clicking the text
* field opens the drop down with options.
*
* @since 8.0
*/
public boolean textInputAllowed = true;

/**
* A textual prompt that is displayed when the select would otherwise be
* empty, to prompt the user for input.
*
* @since 8.0
*/
public String inputPrompt = null;

/**
* Number of items to show per page or 0 to disable paging.
*/
public int pageLength = 10;

/**
* Current filtering mode (look for match of the user typed string in the
* beginning of the item caption or anywhere in the item caption).
*/
public FilteringMode filteringMode = FilteringMode.STARTSWITH;

/**
* Suggestion pop-up's width as a CSS string. By using relative units (e.g.
* "50%") it's possible to set the popup's width relative to the ComboBox
* itself.
*/
public String suggestionPopupWidth = null;

}

Loading…
Cancel
Save