Change-Id: Icbe1c6a5c0e2b10255424801cada8c11a71decb7tags/8.0.0.alpha3
@@ -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; | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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(); | |||
} | |||
/** |
@@ -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); | |||
} | |||
} |
@@ -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()); | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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; | |||
} |