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;
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;
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;
/**
/**
* 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");
+ }
}
/**
@Override
public String getDisplayString() {
final StringBuffer sb = new StringBuffer();
- ApplicationConnection client = connector.getConnection();
final Icon icon = client
.getIcon(client.translateVaadinUri(untranslatedIconUri));
if (icon != null) {
* @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
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);
});
}-*/;
// "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();
.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();
+ }
}
});
}
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
menu.setWidth(Window.getClientWidth() + "px");
}
+ if (BrowserInfo.get().isIE()
+ && BrowserInfo.get().getBrowserMajorVersion() < 10) {
+ setTdWidth(menu.getElement(), Window.getClientWidth() - 8);
+ }
}
setPopupPosition(left, top);
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);
+ }
+
+ }
}
/**
/**
* 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");
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()");
}
// 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) {
* 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
}
- /**
- * 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
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;
* <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
/** 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;
*/
private boolean textInputEnabled = true;
- private final DataReceivedHandler dataReceivedHandler = new DataReceivedHandler();
-
/**
* Default constructor.
*/
* 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()) {
}
}
- 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;
debug("VFS: onSuggestionSelected(" + suggestion.caption + ": "
+ suggestion.key + ")");
}
- dataReceivedHandler.cancelPendingPostFiltering();
+ updateSelectionWhenReponseIsReceived = false;
currentSuggestion = suggestion;
String newKey;
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
}
// 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();
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");
}
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();
}
}
}
- /**
- * 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");
}
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);
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
private void selectPrevPage() {
if (currentPage > 0) {
filterOptions(currentPage - 1, lastFilter);
- dataReceivedHandler.setNavigationCallback(new Runnable() {
- @Override
- public void run() {
- suggestionPopup.selectLastItem();
- }
- });
+ selectPopupItemWhenResponseIsReceived = Select.LAST;
}
}
private void selectNextPage() {
if (hasNextPage()) {
filterOptions(currentPage + 1, lastFilter);
- dataReceivedHandler.setNavigationCallback(new Runnable() {
- @Override
- public void run() {
- suggestionPopup.selectFirstItem();
- }
- });
+ selectPopupItemWhenResponseIsReceived = Select.FIRST;
}
}
// 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());
}
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()));
}
}
removeStyleDependentName("focus");
- connector.sendBlurEvent();
+ if (client.hasEventListeners(this, EventId.BLUR)) {
+ client.updateVariable(paintableId, EventId.BLUR, "", true);
+ afterUpdateClientVariables();
+ }
}
/*
* 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
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();
}
return explicitSelectedCaption;
}
- /**
- * Returns a handler receiving notifications from the connector about
- * communications.
- *
- * @return the dataReceivedHandler
- */
- public DataReceivedHandler getDataReceivedHandler() {
- return dataReceivedHandler;
- }
-
}
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)
*/
@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")
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;
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);
}
// 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
//
) {
- // 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.
*/
}
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() {
.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();
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();
- }
-
}
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;
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;
*/
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);
}
* @return the current input prompt, or null if not enabled
*/
public String getInputPrompt() {
- return getState(false).inputPrompt;
+ return inputPrompt;
}
/**
* 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);
}
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;
boolean nullFilteredOut = isFilteringNeeded();
// null option is needed and not filtered out, even if not on
- // current page
+ // current
+ // page
boolean nullOptionVisible = needNullSelectOption
&& !nullFilteredOut;
// 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();
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");
}
}
- 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;
}
/**
* selection
*/
public void setTextInputAllowed(boolean textInputAllowed) {
- getState().textInputAllowed = textInputAllowed;
+ this.textInputAllowed = textInputAllowed;
+ markAsDirty();
}
/**
* @return
*/
public boolean isTextInputAllowed() {
- return getState(false).textInputAllowed;
+ return textInputAllowed;
}
@Override
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.
* {@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
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;
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
/**
* 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;
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--;
}
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);
}
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;
}
} else {
caption = caption.toLowerCase(getLocale());
}
- switch (getFilteringMode()) {
+ switch (filteringMode) {
case CONTAINS:
if (caption.indexOf(filterstring) > -1) {
filteredOptions.add(itemId);
// 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
*
* @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
*/
* @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
*/
* @return the pageLength
*/
public int getPageLength() {
- return getState(false).pageLength;
+ return pageLength;
}
/**
* @since 7.7
*/
public String getPopupWidth() {
- return getState(false).suggestionPopupWidth;
+ return suggestionPopupWidth;
}
/**
* the pageLength to set
*/
public void setPageLength(int pageLength) {
- getState().pageLength = pageLength;
+ this.pageLength = pageLength;
+ markAsDirty();
}
/**
* the width
*/
public void setPopupWidth(String width) {
- getState().suggestionPopupWidth = width;
+ suggestionPopupWidth = width;
+ markAsDirty();
}
/**
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;
@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);
}
}
--- /dev/null
+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());
+
+ }
+}
+++ /dev/null
-/*
- * 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);
-}
{
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;
-
}