diff --git a/flow-data/src/main/java/com/vaadin/flow/data/provider/DataCommunicator.java b/flow-data/src/main/java/com/vaadin/flow/data/provider/DataCommunicator.java index 5a07677071d..4415bbd3b68 100644 --- a/flow-data/src/main/java/com/vaadin/flow/data/provider/DataCommunicator.java +++ b/flow-data/src/main/java/com/vaadin/flow/data/provider/DataCommunicator.java @@ -436,6 +436,15 @@ public void refresh(T data) { requestFlushUpdatedData(); } + /** + * Schedules a re-render of the items that are currently in the viewport to + * refresh their content with the latest data from the generators. + */ + protected void refreshViewport() { + resendEntireRange = true; + requestFlush(); + } + /** * Confirm update with the given {@code updateId}. * diff --git a/flow-data/src/main/java/com/vaadin/flow/data/provider/hierarchy/HierarchicalDataCommunicator.java b/flow-data/src/main/java/com/vaadin/flow/data/provider/hierarchy/HierarchicalDataCommunicator.java index cc4bf3c5ede..bbeee8d0ff4 100644 --- a/flow-data/src/main/java/com/vaadin/flow/data/provider/hierarchy/HierarchicalDataCommunicator.java +++ b/flow-data/src/main/java/com/vaadin/flow/data/provider/hierarchy/HierarchicalDataCommunicator.java @@ -257,6 +257,11 @@ public void refresh(T item, boolean refreshChildren) { requestFlush().invalidateItem(item); } + @Override + protected void refreshViewport() { + requestFlush().invalidateViewport(); + } + @Override public Stream fetchFromProvider(int offset, int limit) { return fetchDataProviderChildren(null, Range.withLength(offset, limit)); diff --git a/flow-data/src/test/java/com/vaadin/flow/data/provider/DataCommunicatorTest.java b/flow-data/src/test/java/com/vaadin/flow/data/provider/DataCommunicatorTest.java index 686213e0ec1..2c9c68241dd 100644 --- a/flow-data/src/test/java/com/vaadin/flow/data/provider/DataCommunicatorTest.java +++ b/flow-data/src/test/java/com/vaadin/flow/data/provider/DataCommunicatorTest.java @@ -57,6 +57,7 @@ import com.vaadin.flow.server.VaadinServletService; import com.vaadin.flow.server.VaadinSession; +import elemental.json.JsonObject; import elemental.json.JsonValue; @RunWith(Parameterized.class) @@ -247,6 +248,33 @@ public void reattach_same_roundtrip_refresh_nothing() { Assert.assertNull("Expected no communication after reattach", lastSet); } + @Test + public void refreshViewport_updatedRangeSent() { + var compositeDataGenerator = new CompositeDataGenerator(); + dataCommunicator = new DataCommunicator<>(compositeDataGenerator, arrayUpdater, + data -> { + }, element.getNode()) { + }; + dataCommunicator.setDataProvider(createDataProvider(), null); + dataCommunicator.setViewportRange(0, 6); + + var count = new AtomicInteger(0); + compositeDataGenerator.addDataGenerator(new DataGenerator() { + @Override + public void generateData(Item item, JsonObject json) { + json.put("count", String.valueOf(count.get())); + } + }); + + fakeClientCommunication(); + Assert.assertEquals(Range.withLength(0, 6), lastSet); + lastSet = null; + + dataCommunicator.refreshViewport(); + fakeClientCommunication(); + Assert.assertEquals(Range.withLength(0, 6), lastSet); + } + @Test public void setFlushRequest_remove_setFlushRequest_reattach_noEndlessFlushLoop() { AtomicInteger listenerInvocationCounter = new AtomicInteger(0); diff --git a/flow-data/src/test/java/com/vaadin/flow/data/provider/hierarchy/HierarchicalDataCommunicatorDataRefreshTest.java b/flow-data/src/test/java/com/vaadin/flow/data/provider/hierarchy/HierarchicalDataCommunicatorDataRefreshTest.java index 50cf8b76675..b8bfa4b12f2 100644 --- a/flow-data/src/test/java/com/vaadin/flow/data/provider/hierarchy/HierarchicalDataCommunicatorDataRefreshTest.java +++ b/flow-data/src/test/java/com/vaadin/flow/data/provider/hierarchy/HierarchicalDataCommunicatorDataRefreshTest.java @@ -2,6 +2,7 @@ import java.util.Arrays; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.Assert; import org.junit.Before; @@ -354,4 +355,30 @@ public void refreshAllItems_allItemsRemovedFromKeyMapper() { Assert.assertFalse(keyMapper.has(new Item("Item 0-0"))); Assert.assertFalse(keyMapper.has(new Item("Item 0-0-0"))); } + + @Test + public void refreshViewport_updatedRangeSent() { + populateTreeData(treeData, 6, 1, 1); + dataCommunicator.expand( + Arrays.asList(new Item("Item 1"), new Item("Item 1-0"))); + dataCommunicator.setViewportRange(0, 6); + + var count = new AtomicInteger(0); + compositeDataGenerator.addDataGenerator(new DataGenerator() { + @Override + public void generateData(Item item, JsonObject json) { + json.put("count", String.valueOf(count.get())); + } + }); + + fakeClientCommunication(); + assertArrayUpdateItems("count", "0", "0", "0", "0", "0", "0"); + + Mockito.clearInvocations(arrayUpdater, arrayUpdate); + + count.incrementAndGet(); + dataCommunicator.refreshViewport(); + fakeClientCommunication(); + assertArrayUpdateItems("count", "1", "1", "1", "1", "1", "1"); + } }