diff options
Diffstat (limited to 'compatibility-client/src')
-rw-r--r-- | compatibility-client/src/main/java/com/vaadin/v7/client/ui/VFilterSelect.java | 545 | ||||
-rw-r--r-- | compatibility-client/src/main/java/com/vaadin/v7/client/ui/combobox/ComboBoxConnector.java | 394 |
2 files changed, 393 insertions, 546 deletions
diff --git a/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VFilterSelect.java b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VFilterSelect.java index 38b1bf785d..0b7724f3d0 100644 --- a/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VFilterSelect.java +++ b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VFilterSelect.java @@ -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; - } - } diff --git a/compatibility-client/src/main/java/com/vaadin/v7/client/ui/combobox/ComboBoxConnector.java b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/combobox/ComboBoxConnector.java index 6f851ec792..212a1aa8d1 100644 --- a/compatibility-client/src/main/java/com/vaadin/v7/client/ui/combobox/ComboBoxConnector.java +++ b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/combobox/ComboBoxConnector.java @@ -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(); - } - } |