Fixes vaadin/framework8-issues#163 Change-Id: Ib68ad5421934c8375a91d7948d860381a5adb9bbtags/8.0.0.alpha9
@@ -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); |
@@ -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; | |||
} | |||
} | |||
} |
@@ -401,6 +401,43 @@ public class ComboBox<T> extends AbstractSingleSelect<T> | |||
getState().emptySelectionAllowed = emptySelectionAllowed; | |||
} | |||
/** | |||
* 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 |
@@ -88,4 +88,9 @@ public class ComboBoxState extends AbstractSingleSelectState { | |||
*/ | |||
public String selectedItemCaption; | |||
/** | |||
* Caption for item which represents empty selection. | |||
*/ | |||
public String emptySelectionCaption = ""; | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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())); | |||
} | |||
} |