* Fixes #11642. ComboBox with pageLength 0 should be updated if DataProvider changes * added comments, fixed importstags/8.10.0.alpha1
*/ | */ | ||||
private String pendingNewItemValue = null; | private String pendingNewItemValue = null; | ||||
/** | |||||
* If this flag is toggled, even unpaged data sources should be updated on | |||||
* reset. | |||||
*/ | |||||
private boolean forceDataSourceUpdate = false; | |||||
@Override | @Override | ||||
protected void init() { | protected void init() { | ||||
super.init(); | super.init(); | ||||
getWidget().setEmptySelectionCaption(getState().emptySelectionCaption); | getWidget().setEmptySelectionCaption(getState().emptySelectionCaption); | ||||
} | } | ||||
@OnStateChange("forceDataSourceUpdate") | |||||
private void onForceDataSourceUpdate() { | |||||
forceDataSourceUpdate = getState().forceDataSourceUpdate; | |||||
} | |||||
@OnStateChange({ "selectedItemKey", "selectedItemCaption", | @OnStateChange({ "selectedItemKey", "selectedItemCaption", | ||||
"selectedItemIcon" }) | "selectedItemIcon" }) | ||||
private void onSelectionChange() { | private void onSelectionChange() { | ||||
@Override | @Override | ||||
public void resetDataAndSize(int estimatedNewDataSize) { | public void resetDataAndSize(int estimatedNewDataSize) { | ||||
if (getState().pageLength == 0) { | if (getState().pageLength == 0) { | ||||
if (getWidget().suggestionPopup.isShowing()) { | |||||
if (getWidget().suggestionPopup.isShowing() | |||||
|| forceDataSourceUpdate) { | |||||
dataSource.ensureAvailability(0, estimatedNewDataSize); | dataSource.ensureAvailability(0, estimatedNewDataSize); | ||||
} | } | ||||
if (forceDataSourceUpdate) { | |||||
rpc.resetForceDataSourceUpdate(); | |||||
} | |||||
// else lets just wait till the popup is opened before | // else lets just wait till the popup is opened before | ||||
// everything is fetched to it. this could be optimized later on | // everything is fetched to it. this could be optimized later on | ||||
// to fetch everything if in-memory data is used. | // to fetch everything if in-memory data is used. |
import java.util.logging.Logger; | import java.util.logging.Logger; | ||||
import java.util.stream.Stream; | import java.util.stream.Stream; | ||||
import org.jsoup.nodes.Element; | |||||
import com.vaadin.data.HasFilterableDataProvider; | |||||
import com.vaadin.data.HasValue; | |||||
import com.vaadin.data.ValueProvider; | |||||
import com.vaadin.data.provider.CallbackDataProvider; | import com.vaadin.data.provider.CallbackDataProvider; | ||||
import com.vaadin.data.provider.DataChangeEvent; | |||||
import com.vaadin.data.provider.DataCommunicator; | import com.vaadin.data.provider.DataCommunicator; | ||||
import com.vaadin.data.provider.DataGenerator; | import com.vaadin.data.provider.DataGenerator; | ||||
import com.vaadin.data.provider.DataKeyMapper; | import com.vaadin.data.provider.DataKeyMapper; | ||||
import com.vaadin.data.provider.DataProvider; | import com.vaadin.data.provider.DataProvider; | ||||
import com.vaadin.data.provider.InMemoryDataProvider; | |||||
import com.vaadin.data.provider.ListDataProvider; | import com.vaadin.data.provider.ListDataProvider; | ||||
import org.jsoup.nodes.Element; | |||||
import com.vaadin.data.HasFilterableDataProvider; | |||||
import com.vaadin.data.HasValue; | |||||
import com.vaadin.data.ValueProvider; | |||||
import com.vaadin.event.FieldEvents; | import com.vaadin.event.FieldEvents; | ||||
import com.vaadin.event.FieldEvents.BlurEvent; | import com.vaadin.event.FieldEvents.BlurEvent; | ||||
import com.vaadin.event.FieldEvents.BlurListener; | import com.vaadin.event.FieldEvents.BlurListener; | ||||
getState().currentFilterText = filterText; | getState().currentFilterText = filterText; | ||||
filterSlot.accept(filterText); | filterSlot.accept(filterText); | ||||
} | } | ||||
@Override | |||||
public void resetForceDataSourceUpdate() { | |||||
getState().forceDataSourceUpdate = false; | |||||
} | |||||
}; | }; | ||||
/** | /** | ||||
filterSlot = filter -> providerFilterSlot | filterSlot = filter -> providerFilterSlot | ||||
.accept(convertOrNull.apply(filter)); | .accept(convertOrNull.apply(filter)); | ||||
// This workaround is done to fix issue #11642 for unpaged comboboxes. | |||||
// Data sources for on the client need to be updated after data provider | |||||
// refreshAll so that serverside selection works even before the dropdown | |||||
// is opened. Only done for in-memory data providers for performance | |||||
// reasons. | |||||
if (dataProvider instanceof InMemoryDataProvider) { | |||||
dataProvider.addDataProviderListener(event -> { | |||||
if ((!(event instanceof DataChangeEvent.DataRefreshEvent)) | |||||
&& (getPageLength() == 0)) { | |||||
getState().forceDataSourceUpdate = true; | |||||
} | |||||
}); | |||||
} | |||||
} | } | ||||
/** | /** |
* mode | * mode | ||||
*/ | */ | ||||
public void setFilter(String filter); | public void setFilter(String filter); | ||||
/** | |||||
* Reset the force update flag once the list contents have been updated. | |||||
* | |||||
* @since | |||||
*/ | |||||
public void resetForceDataSourceUpdate(); | |||||
} | } |
* @since 7.0 | * @since 7.0 | ||||
*/ | */ | ||||
public class ComboBoxState extends AbstractSingleSelectState { | public class ComboBoxState extends AbstractSingleSelectState { | ||||
{ | { | ||||
// TODO ideally this would be v-combobox, but that would affect a lot of | // TODO ideally this would be v-combobox, but that would affect a lot of | ||||
// themes | // themes | ||||
*/ | */ | ||||
public String currentFilterText; | public String currentFilterText; | ||||
/** | |||||
* Ensure the data source is updated when backing dataprovider has been | |||||
* refreshed. | |||||
* | |||||
* @since | |||||
*/ | |||||
public boolean forceDataSourceUpdate; | |||||
} | } |
package com.vaadin.tests.components.combobox; | |||||
import java.util.ArrayList; | |||||
import java.util.Objects; | |||||
import com.vaadin.annotations.Widgetset; | |||||
import com.vaadin.data.provider.ListDataProvider; | |||||
import com.vaadin.server.VaadinRequest; | |||||
import com.vaadin.tests.components.AbstractTestUI; | |||||
import com.vaadin.ui.Button; | |||||
import com.vaadin.ui.ComboBox; | |||||
import com.vaadin.ui.HorizontalLayout; | |||||
import com.vaadin.ui.VerticalLayout; | |||||
@Widgetset("com.vaadin.DefaultWidgetSet") | |||||
public class ComboBoxPageLengthZeroRefreshItemAfterDataProviderUpdate | |||||
extends AbstractTestUI { | |||||
private static final class Bean { | |||||
@Override | |||||
public boolean equals(Object o) { | |||||
if (this == o) | |||||
return true; | |||||
if (o == null || getClass() != o.getClass()) | |||||
return false; | |||||
Bean bean = (Bean) o; | |||||
return Objects.equals(name, bean.name); | |||||
} | |||||
@Override | |||||
public int hashCode() { | |||||
return Objects.hash(name); | |||||
} | |||||
private String name; | |||||
public Bean(String name) { | |||||
setName(name); | |||||
} | |||||
public String getName() { | |||||
return name; | |||||
} | |||||
public void setName(String name) { | |||||
this.name = name; | |||||
} | |||||
} | |||||
private ArrayList<Bean> list = new ArrayList<>(); | |||||
private ArrayList<Bean> list2 = new ArrayList<>(); | |||||
private int counter = 0; | |||||
@Override | |||||
protected void setup(VaadinRequest request) { | |||||
HorizontalLayout root = new HorizontalLayout(); | |||||
root.setSizeFull(); | |||||
VerticalLayout leftLayout = new VerticalLayout(); | |||||
VerticalLayout rightLayout = new VerticalLayout(); | |||||
root.addComponent(leftLayout); | |||||
root.addComponent(rightLayout); | |||||
addComponent(root); | |||||
final ComboBox<Bean> comboBoxPageLengthZero = new ComboBox<>(); | |||||
comboBoxPageLengthZero.setId("combo-0"); | |||||
comboBoxPageLengthZero.setPageLength(0); | |||||
final ComboBox<Bean> comboBoxRegular = new ComboBox<>(); | |||||
comboBoxRegular.setId("combo-n"); | |||||
comboBoxPageLengthZero.setItemCaptionGenerator(bean -> bean.getName()); | |||||
comboBoxRegular.setItemCaptionGenerator(bean -> bean.getName()); | |||||
ListDataProvider<Bean> dataProvider = new ListDataProvider<>(list); | |||||
ListDataProvider<Bean> dataProvider2 = new ListDataProvider<>(list2); | |||||
comboBoxPageLengthZero.setDataProvider(dataProvider); | |||||
comboBoxRegular.setDataProvider(dataProvider2); | |||||
createComponents(leftLayout, comboBoxPageLengthZero, dataProvider, | |||||
true); | |||||
createComponents(rightLayout, comboBoxRegular, dataProvider2, false); | |||||
} | |||||
private void createComponents(VerticalLayout layout, | |||||
ComboBox<Bean> comboBox, ListDataProvider<Bean> dataProvider, | |||||
boolean pageLengthZero) { | |||||
Button updateValues = new Button( | |||||
"1. Refresh backing list and dataprovider, select first item", | |||||
e -> { | |||||
Bean selected = null; | |||||
if (pageLengthZero) { | |||||
list.clear(); | |||||
list.add(new Bean("foo" + (++counter))); | |||||
list.add(new Bean("foo" + (++counter))); | |||||
selected = list.get(0); | |||||
} else { | |||||
list2.clear(); | |||||
list2.add(new Bean("bar" + (++counter))); | |||||
list2.add(new Bean("bar" + (++counter))); | |||||
selected = list2.get(0); | |||||
} | |||||
dataProvider.refreshAll(); | |||||
comboBox.setValue(selected); | |||||
}); | |||||
updateValues.setId(pageLengthZero ? "update-0" : "update-n"); | |||||
Button refresh = new Button( | |||||
"2. Update item returned by combobox's getValue and call refreshItem on data provider", | |||||
e -> { | |||||
Bean currentValue = comboBox.getValue(); | |||||
currentValue.setName(currentValue.getName() + "(updated)"); | |||||
dataProvider.refreshItem(currentValue); | |||||
}); | |||||
refresh.setId(pageLengthZero ? "refresh-0" : "refresh-n"); | |||||
layout.addComponents(updateValues, refresh, comboBox); | |||||
} | |||||
} |
package com.vaadin.tests.components.combobox; | |||||
import static org.junit.Assert.assertEquals; | |||||
import org.junit.Test; | |||||
import com.vaadin.testbench.elements.ButtonElement; | |||||
import com.vaadin.testbench.elements.ComboBoxElement; | |||||
import com.vaadin.tests.tb3.MultiBrowserTest; | |||||
import org.openqa.selenium.By; | |||||
public class ComboBoxPageLengthZeroRefreshItemAfterDataProviderUpdateTest | |||||
extends MultiBrowserTest { | |||||
@Test | |||||
public void refreshItemAfterDataProviderUpdateWithPageLengthZero() { | |||||
openTestURL(); | |||||
waitForElementVisible(By.id("combo-0")); | |||||
ComboBoxElement pageLengthZeroCombo = $(ComboBoxElement.class) | |||||
.id("combo-0"); | |||||
ButtonElement updateButton = $(ButtonElement.class).id("update-0"); | |||||
ButtonElement refreshButton = $(ButtonElement.class).id("refresh-0"); | |||||
String comboText = getComboBoxInputTextAfterUpdateAndRefresh( | |||||
pageLengthZeroCombo, updateButton, refreshButton); | |||||
assertEquals("Expected item containing (updated), got " + comboText, | |||||
true, comboText.contains("(updated)")); | |||||
} | |||||
@Test | |||||
public void refreshItemAfterDataProviderUpdateWithDefaultPageLength() { | |||||
openTestURL(); | |||||
waitForElementVisible(By.id("combo-n")); | |||||
ComboBoxElement pageLengthRegularCombo = $(ComboBoxElement.class) | |||||
.id("combo-n"); | |||||
ButtonElement updateButton = $(ButtonElement.class).id("update-n"); | |||||
ButtonElement refreshButton = $(ButtonElement.class).id("refresh-n"); | |||||
String comboText = getComboBoxInputTextAfterUpdateAndRefresh( | |||||
pageLengthRegularCombo, updateButton, refreshButton); | |||||
assertEquals("Expected item containing (updated), got " + comboText, | |||||
true, comboText.contains("(updated)")); | |||||
} | |||||
public String getComboBoxInputTextAfterUpdateAndRefresh( | |||||
ComboBoxElement combo, ButtonElement updateDataProvider, | |||||
ButtonElement refreshItem) { | |||||
updateDataProvider.click(); | |||||
refreshItem.click(); | |||||
return combo.getValue(); | |||||
} | |||||
} |