Fixes vaadin/framework8-issues#163 Change-Id: Ib68ad5421934c8375a91d7948d860381a5adb9bbtags/8.0.0.alpha9
* <p> | * <p> | ||||
* For internal use only. May be removed or replaced in the future. | * 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. */ | /** For internal use only. May be removed or replaced in the future. */ | ||||
public String serverSelectedKey; | public String serverSelectedKey; | ||||
Unit.PX); | Unit.PX); | ||||
} | } | ||||
private static Set<Integer> navigationKeyCodes = new HashSet<Integer>(); | |||||
private static Set<Integer> navigationKeyCodes = new HashSet<>(); | |||||
static { | static { | ||||
navigationKeyCodes.add(KeyCodes.KEY_DOWN); | navigationKeyCodes.add(KeyCodes.KEY_DOWN); | ||||
navigationKeyCodes.add(KeyCodes.KEY_UP); | navigationKeyCodes.add(KeyCodes.KEY_UP); |
*/ | */ | ||||
package com.vaadin.client.ui.combobox; | package com.vaadin.client.ui.combobox; | ||||
import java.util.List; | |||||
import com.vaadin.client.Profiler; | import com.vaadin.client.Profiler; | ||||
import com.vaadin.client.annotations.OnStateChange; | import com.vaadin.client.annotations.OnStateChange; | ||||
import com.vaadin.client.communication.StateChangeEvent; | import com.vaadin.client.communication.StateChangeEvent; | ||||
import com.vaadin.client.ui.HasRequiredIndicator; | import com.vaadin.client.ui.HasRequiredIndicator; | ||||
import com.vaadin.client.ui.SimpleManagedLayout; | import com.vaadin.client.ui.SimpleManagedLayout; | ||||
import com.vaadin.client.ui.VComboBox; | import com.vaadin.client.ui.VComboBox; | ||||
import com.vaadin.client.ui.VComboBox.ComboBoxSuggestion; | |||||
import com.vaadin.client.ui.VComboBox.DataReceivedHandler; | import com.vaadin.client.ui.VComboBox.DataReceivedHandler; | ||||
import com.vaadin.shared.EventId; | import com.vaadin.shared.EventId; | ||||
import com.vaadin.shared.Registration; | import com.vaadin.shared.Registration; | ||||
Profiler.leave("ComboBoxConnector.onStateChanged update content"); | 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" }) | @OnStateChange({ "selectedItemKey", "selectedItemCaption" }) | ||||
private void onSelectionChange() { | private void onSelectionChange() { | ||||
getDataReceivedHandler().updateSelectionFromServer( | getDataReceivedHandler().updateSelectionFromServer( | ||||
public void setDataSource(DataSource<JsonObject> dataSource) { | public void setDataSource(DataSource<JsonObject> dataSource) { | ||||
super.setDataSource(dataSource); | super.setDataSource(dataSource); | ||||
dataChangeHandlerRegistration = 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 | @Override | ||||
public boolean isRequiredIndicatorVisible() { | public boolean isRequiredIndicatorVisible() { | ||||
return getState().required && !isReadOnly(); | 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; | |||||
} | |||||
} | |||||
} | } |
getState().emptySelectionAllowed = emptySelectionAllowed; | 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 | * 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 | * units (e.g. "50%") it's possible to set the popup's width relative to the |
*/ | */ | ||||
public String selectedItemCaption; | public String selectedItemCaption; | ||||
/** | |||||
* Caption for item which represents empty selection. | |||||
*/ | |||||
public String emptySelectionCaption = ""; | |||||
} | } |
/* | |||||
* 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); | |||||
} | |||||
} |
/* | |||||
* 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())); | |||||
} | |||||
} |