diff options
6 files changed, 275 insertions, 76 deletions
diff --git a/client/src/main/java/com/vaadin/client/ui/VComboBox.java b/client/src/main/java/com/vaadin/client/ui/VComboBox.java index b19f9944ba..6ecea2d116 100644 --- a/client/src/main/java/com/vaadin/client/ui/VComboBox.java +++ b/client/src/main/java/com/vaadin/client/ui/VComboBox.java @@ -1592,7 +1592,7 @@ public class VComboBox extends Composite implements Field, KeyDownHandler, * <p> * For internal use only. May be removed or replaced in the future. */ - public final List<ComboBoxSuggestion> currentSuggestions = new ArrayList<ComboBoxSuggestion>(); + public final List<ComboBoxSuggestion> currentSuggestions = new ArrayList<>(); /** For internal use only. May be removed or replaced in the future. */ public String serverSelectedKey; @@ -2067,7 +2067,7 @@ public class VComboBox extends Composite implements Field, KeyDownHandler, 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); diff --git a/client/src/main/java/com/vaadin/client/ui/combobox/ComboBoxConnector.java b/client/src/main/java/com/vaadin/client/ui/combobox/ComboBoxConnector.java index 8b56483e8f..a4a9c17f4d 100644 --- a/client/src/main/java/com/vaadin/client/ui/combobox/ComboBoxConnector.java +++ b/client/src/main/java/com/vaadin/client/ui/combobox/ComboBoxConnector.java @@ -15,6 +15,8 @@ */ package com.vaadin.client.ui.combobox; +import java.util.List; + import com.vaadin.client.Profiler; import com.vaadin.client.annotations.OnStateChange; import com.vaadin.client.communication.StateChangeEvent; @@ -25,6 +27,7 @@ import com.vaadin.client.ui.HasErrorIndicator; import com.vaadin.client.ui.HasRequiredIndicator; import com.vaadin.client.ui.SimpleManagedLayout; import com.vaadin.client.ui.VComboBox; +import com.vaadin.client.ui.VComboBox.ComboBoxSuggestion; import com.vaadin.client.ui.VComboBox.DataReceivedHandler; import com.vaadin.shared.EventId; import com.vaadin.shared.Registration; @@ -87,6 +90,15 @@ public class ComboBoxConnector extends AbstractListingConnector Profiler.leave("ComboBoxConnector.onStateChanged update content"); } + @OnStateChange("emptySelectionCaption") + private void onEmptySelectionCaptionChange() { + List<ComboBoxSuggestion> suggestions = getWidget().currentSuggestions; + if (!suggestions.isEmpty() && isFirstPage()) { + suggestions.remove(0); + addEmptySelectionItem(); + } + } + @OnStateChange({ "selectedItemKey", "selectedItemCaption" }) private void onSelectionChange() { getDataReceivedHandler().updateSelectionFromServer( @@ -249,80 +261,7 @@ public class ComboBoxConnector extends AbstractListingConnector public void setDataSource(DataSource<JsonObject> dataSource) { super.setDataSource(dataSource); dataChangeHandlerRegistration = dataSource - .addDataChangeHandler(range -> { - // try to find selected item if requested - if (getState().scrollToSelectedItem - && getState().pageLength > 0 - && getWidget().currentPage < 0 - && getWidget().selectedOptionKey != null) { - // search for the item with the selected key - getWidget().currentPage = 0; - for (int i = 0; i < getDataSource().size(); ++i) { - JsonObject row = getDataSource().getRow(i); - if (row != null) { - String key = getRowKey(row); - if (getWidget().selectedOptionKey.equals(key)) { - if (getWidget().nullSelectionAllowed) { - getWidget().currentPage = (i + 1) - / getState().pageLength; - } else { - getWidget().currentPage = i - / getState().pageLength; - } - break; - } - } - } - } else if (getWidget().currentPage < 0) { - getWidget().currentPage = 0; - } - - getWidget().currentSuggestions.clear(); - - int start = getWidget().currentPage - * getWidget().pageLength; - int end = getWidget().pageLength > 0 - ? start + getWidget().pageLength - : getDataSource().size(); - - if (getWidget().nullSelectionAllowed - && "".equals(getWidget().lastFilter)) { - // add special null selection item... - if (getWidget().currentPage == 0) { - getWidget().currentSuggestions - .add(getWidget().new ComboBoxSuggestion("", - "", null, null)); - } else { - // ...or leave space for it - start = start - 1; - } - // in either case, the last item to show is - // shifted by one - end = end - 1; - } - - for (int i = start; i < end; ++i) { - JsonObject row = getDataSource().getRow(i); - - if (row != null) { - String key = getRowKey(row); - String caption = row - .getString(DataCommunicatorConstants.NAME); - String style = row - .getString(ComboBoxConstants.STYLE); - String untranslatedIconUri = row - .getString(ComboBoxConstants.ICON); - getWidget().currentSuggestions - .add(getWidget().new ComboBoxSuggestion(key, - caption, style, - untranslatedIconUri)); - } - } - getWidget().totalMatches = getDataSource().size() - + (getState().emptySelectionAllowed ? 1 : 0); - - getDataReceivedHandler().dataReceived(); - }); + .addDataChangeHandler(range -> refreshData()); } @Override @@ -335,4 +274,90 @@ public class ComboBoxConnector extends AbstractListingConnector public boolean isRequiredIndicatorVisible() { return getState().required && !isReadOnly(); } + + private void refreshData() { + updateCurrentPage(); + + getWidget().currentSuggestions.clear(); + + int start = getWidget().currentPage * getWidget().pageLength; + int end = getWidget().pageLength > 0 ? start + getWidget().pageLength + : getDataSource().size(); + + if (getWidget().nullSelectionAllowed + && "".equals(getWidget().lastFilter)) { + // add special null selection item... + if (isFirstPage()) { + addEmptySelectionItem(); + } else { + // ...or leave space for it + start = start - 1; + } + // in either case, the last item to show is + // shifted by one + end = end - 1; + } + + updateSuggestions(start, end); + getWidget().totalMatches = getDataSource().size() + + (getState().emptySelectionAllowed ? 1 : 0); + + getDataReceivedHandler().dataReceived(); + } + + private void updateSuggestions(int start, int end) { + for (int i = start; i < end; ++i) { + JsonObject row = getDataSource().getRow(i); + + if (row != null) { + String key = getRowKey(row); + String caption = row.getString(DataCommunicatorConstants.NAME); + String style = row.getString(ComboBoxConstants.STYLE); + String untranslatedIconUri = row + .getString(ComboBoxConstants.ICON); + getWidget().currentSuggestions + .add(getWidget().new ComboBoxSuggestion(key, caption, + style, untranslatedIconUri)); + } + } + } + + private boolean isFirstPage() { + return getWidget().currentPage == 0; + } + + private void addEmptySelectionItem() { + if (isFirstPage()) { + getWidget().currentSuggestions.add(0, + getWidget().new ComboBoxSuggestion("", + getState().emptySelectionCaption, null, null)); + } + } + + private void updateCurrentPage() { + // try to find selected item if requested + if (getState().scrollToSelectedItem && getState().pageLength > 0 + && getWidget().currentPage < 0 + && getWidget().selectedOptionKey != null) { + // search for the item with the selected key + getWidget().currentPage = 0; + for (int i = 0; i < getDataSource().size(); ++i) { + JsonObject row = getDataSource().getRow(i); + if (row != null) { + String key = getRowKey(row); + if (getWidget().selectedOptionKey.equals(key)) { + if (getWidget().nullSelectionAllowed) { + getWidget().currentPage = (i + 1) + / getState().pageLength; + } else { + getWidget().currentPage = i / getState().pageLength; + } + break; + } + } + } + } else if (getWidget().currentPage < 0) { + getWidget().currentPage = 0; + } + } } diff --git a/server/src/main/java/com/vaadin/ui/ComboBox.java b/server/src/main/java/com/vaadin/ui/ComboBox.java index 9034fabf11..093c5e9c7a 100644 --- a/server/src/main/java/com/vaadin/ui/ComboBox.java +++ b/server/src/main/java/com/vaadin/ui/ComboBox.java @@ -402,6 +402,43 @@ public class ComboBox<T> extends AbstractSingleSelect<T> } /** + * Returns the empty selection caption. + * <p> + * The empty string {@code ""} is the default empty selection caption. + * + * @see #setEmptySelectionAllowed(boolean) + * @see #isEmptySelectionAllowed() + * @see #setEmptySelectionCaption(String) + * @see #isSelected(Object) + * @see #select(Object) + * + * @return the empty selection caption, not {@code null} + */ + public String getEmptySelectionCaption() { + return getState(false).emptySelectionCaption; + } + + /** + * Sets the empty selection caption. + * <p> + * The empty string {@code ""} is the default empty selection caption. + * <p> + * If empty selection is allowed via the + * {@link #setEmptySelectionAllowed(boolean)} method (it is by default) then + * the empty item will be shown with the given caption. + * + * @param caption + * the caption to set, not {@code null} + * @see #getNullSelectionItemId() + * @see #isSelected(Object) + * @see #select(Object) + */ + public void setEmptySelectionCaption(String caption) { + Objects.nonNull(caption); + getState().emptySelectionCaption = caption; + } + + /** * Sets the 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. diff --git a/shared/src/main/java/com/vaadin/shared/ui/combobox/ComboBoxState.java b/shared/src/main/java/com/vaadin/shared/ui/combobox/ComboBoxState.java index 154735ce26..01a02a4a5f 100644 --- a/shared/src/main/java/com/vaadin/shared/ui/combobox/ComboBoxState.java +++ b/shared/src/main/java/com/vaadin/shared/ui/combobox/ComboBoxState.java @@ -88,4 +88,9 @@ public class ComboBoxState extends AbstractSingleSelectState { */ public String selectedItemCaption; + /** + * Caption for item which represents empty selection. + */ + public String emptySelectionCaption = ""; + } diff --git a/uitest/src/main/java/com/vaadin/tests/components/combobox/ComboBoxEmptyCaption.java b/uitest/src/main/java/com/vaadin/tests/components/combobox/ComboBoxEmptyCaption.java new file mode 100644 index 0000000000..3d32c54a7e --- /dev/null +++ b/uitest/src/main/java/com/vaadin/tests/components/combobox/ComboBoxEmptyCaption.java @@ -0,0 +1,48 @@ +/* + * 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.tests.components.combobox; + +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.Button; +import com.vaadin.ui.ComboBox; + +/** + * @author Vaadin Ltd + * + */ +public class ComboBoxEmptyCaption extends AbstractTestUI { + + @Override + protected void setup(VaadinRequest request) { + ComboBox<String> combo = new ComboBox<>(); + combo.setItems( + IntStream.range(1, 100).mapToObj(number -> "item" + number) + .collect(Collectors.toList())); + addComponent(combo); + Button setCaption = new Button("Set empty selection caption to 'empty'", + event -> combo.setEmptySelectionCaption("empty")); + Button resetCaption = new Button( + "Set empty selection caption to empty string", + event -> combo.setEmptySelectionCaption("")); + Button disableCaption = new Button("Disable empty selection caption", + event -> combo.setEmptySelectionAllowed(false)); + addComponents(setCaption, resetCaption, disableCaption); + } +} diff --git a/uitest/src/test/java/com/vaadin/tests/components/combobox/ComboBoxEmptyCaptionTest.java b/uitest/src/test/java/com/vaadin/tests/components/combobox/ComboBoxEmptyCaptionTest.java new file mode 100644 index 0000000000..0cb890dfda --- /dev/null +++ b/uitest/src/test/java/com/vaadin/tests/components/combobox/ComboBoxEmptyCaptionTest.java @@ -0,0 +1,84 @@ +/* + * 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.tests.components.combobox; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.vaadin.testbench.elements.ButtonElement; +import com.vaadin.testbench.elements.ComboBoxElement; +import com.vaadin.tests.tb3.MultiBrowserTest; + +/** + * @author Vaadin Ltd + * + */ +public class ComboBoxEmptyCaptionTest extends MultiBrowserTest { + + @Before + public void setUp() { + openTestURL(); + } + + @Test + public void emptyItemCaption() { + ComboBoxElement combo = $(ComboBoxElement.class).first(); + // empty string in caption becomes because of #7506 + ensureSuggestions(combo, " ", "item1", "item2", "item3", "item4", + "item5", "item6", "item7", "item8", "item9"); + } + + @Test + public void hasEmptyItemCaption() { + ComboBoxElement combo = $(ComboBoxElement.class).first(); + // set some caption for the empty selection element + $(ButtonElement.class).first().click(); + ensureSuggestions(combo, "empty", "item1", "item2", "item3", "item4", + "item5", "item6", "item7", "item8", "item9"); + } + + @Test + public void resetEmptyItem() { + ComboBoxElement combo = $(ComboBoxElement.class).first(); + // set some caption for the empty selection element + $(ButtonElement.class).first().click(); + // set empty string back as an empty caption + $(ButtonElement.class).get(1).click(); + ensureSuggestions(combo, " ", "item1", "item2", "item3", "item4", + "item5", "item6", "item7", "item8", "item9"); + } + + @Test + public void disableEmptyItem() { + ComboBoxElement combo = $(ComboBoxElement.class).first(); + // set some caption for the empty selection element + $(ButtonElement.class).get(2).click(); + ensureSuggestions(combo, "item1", "item2", "item3", "item4", "item5", + "item6", "item7", "item8", "item9", "item10"); + } + + private void ensureSuggestions(ComboBoxElement element, + String... suggestions) { + element.openPopup(); + System.out.println(element.getPopupSuggestions()); + Assert.assertEquals(Arrays.asList(suggestions), + new ArrayList<>(element.getPopupSuggestions())); + } +} |