From 50d37eeaccff5c03e4a5a074e5c2577c426786a1 Mon Sep 17 00:00:00 2001 From: ugur-vaadin Date: Thu, 7 Nov 2024 21:35:38 +0200 Subject: [PATCH 01/14] refactor: schedule renderers to only run for visible columns --- .../com/vaadin/flow/component/grid/Grid.java | 165 ++++----- .../grid/GridHiddenColumnRenderingTest.java | 329 ++++++++++++++++++ .../flow/component/gridpro/GridPro.java | 5 - 3 files changed, 394 insertions(+), 105 deletions(-) create mode 100644 vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java index 51112cce321..c7d0809370e 100755 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java @@ -31,6 +31,7 @@ import java.util.Set; import java.util.function.BiFunction; import java.util.function.BinaryOperator; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -431,12 +432,6 @@ public static void setDefaultMultiSortPriority(MultiSortPriority priority) { /** * Server-side component for the {@code } element. * - *

- * Every added column sends data to the client side regardless of its - * visibility state. Don't add a new column at all or use - * {@link Grid#removeColumn(Column)} to avoid sending extra data. - *

- * * @param * type of the underlying grid this column is compatible with */ @@ -448,6 +443,7 @@ public static class Column extends AbstractColumn> { private String columnKey; // defined and used by the user private boolean sortingEnabled; + private boolean rendererSetupScheduled; private Component editorComponent; private EditorRenderer editorRenderer; @@ -488,20 +484,8 @@ public Column(Grid grid, String columnId, Renderer renderer) { super(grid); Objects.requireNonNull(renderer); this.columnInternalId = columnId; - this.renderer = renderer; - comparator = (a, b) -> 0; - - rendering = renderer.render(getElement(), (KeyMapper) getGrid() - .getDataCommunicator().getKeyMapper()); - - Optional> dataGenerator = rendering - .getDataGenerator(); - - if (dataGenerator.isPresent()) { - columnDataGeneratorRegistration = grid - .addDataGenerator(dataGenerator.get()); - } + setRenderer(renderer); } protected void destroyDataGenerators() { @@ -545,32 +529,11 @@ public Renderer getRenderer() { public Column setRenderer(Renderer renderer) { this.renderer = Objects.requireNonNull(renderer, "Renderer must not be null."); - - destroyDataGenerators(); - if (rendering != null) { - rendering.getRegistration().remove(); - } - - rendering = renderer.render(getElement(), (KeyMapper) getGrid() - .getDataCommunicator().getKeyMapper()); - - columnDataGeneratorRegistration = rendering.getDataGenerator() - .map(dataGenerator -> grid - .addDataGenerator((DataGenerator) dataGenerator)) - .orElse(null); - - // The editor renderer is a wrapper around the regular renderer, so - // we need to apply it again afterwards - if (editorRenderer != null) { - Rendering editorRendering = editorRenderer - .render(getElement(), null); - editorDataGeneratorRegistration = editorRendering - .getDataGenerator() - .map(dataGenerator -> grid.addDataGenerator( - (DataGenerator) dataGenerator)) - .orElse(null); - } - + clearRendering(); + rendererSetupScheduled = true; + getElement().getNode() + .runWhenAttached(ui -> scheduleRendererSetup()); + addAttachListener(e -> scheduleRendererSetup()); getGrid().getDataCommunicator().reset(); return this; } @@ -769,7 +732,7 @@ public Column setComparator(Comparator comparator) { * the value provider used to extract the {@link Comparable} * sort key * @return this column - * @see Comparator#comparing(java.util.function.Function) + * @see Comparator#comparing(Function) */ public > Column setComparator( ValueProvider keyExtractor) { @@ -1171,6 +1134,18 @@ public Column setRowHeader(boolean rowHeader) { return this; } + @Override + public void setVisible(boolean visible) { + boolean isInitiallyVisible = isVisible(); + super.setVisible(visible); + if (isInitiallyVisible && !visible) { + clearRendering(); + } + if (!isInitiallyVisible && visible) { + scheduleRendererSetup(); + } + } + @Override protected Column getBottomLevelColumn() { return this; @@ -1180,15 +1155,54 @@ protected Column getBottomLevelColumn() { private void setupColumnEditor() { editorRenderer = new EditorRenderer<>((Editor) grid.getEditor(), columnInternalId); + setupEditorRenderer(); + } + + private void setupRenderer() { + if (renderer == null) { + return; + } + rendering = renderer.render(getElement(), (KeyMapper) getGrid() + .getDataCommunicator().getKeyMapper()); + columnDataGeneratorRegistration = rendering.getDataGenerator() + .map(dataGenerator -> grid + .addDataGenerator((DataGenerator) dataGenerator)) + .orElse(null); + grid.getDataProvider().refreshAll(); + } + private void setupEditorRenderer() { + if (editorRenderer == null) { + return; + } Rendering editorRendering = editorRenderer.render(getElement(), null); + editorDataGeneratorRegistration = editorRendering.getDataGenerator() + .map(dataGenerator -> grid + .addDataGenerator((DataGenerator) dataGenerator)) + .orElse(null); + } - Optional> dataGenerator = editorRendering - .getDataGenerator(); - if (dataGenerator.isPresent()) { - editorDataGeneratorRegistration = grid - .addDataGenerator((DataGenerator) dataGenerator.get()); + private void scheduleRendererSetup() { + if (rendererSetupScheduled) { + return; + } + rendererSetupScheduled = true; + getUI().ifPresent(ui -> ui.beforeClientResponse(this, ctx -> { + if (rendererSetupScheduled && isVisible()) { + setupRenderer(); + // The editor renderer is a wrapper around the regular + // renderer, so we need to apply it again afterward. + setupEditorRenderer(); + } + rendererSetupScheduled = false; + })); + } + + private void clearRendering() { + destroyDataGenerators(); + if (rendering != null) { + rendering.getRegistration().remove(); } } } @@ -1859,11 +1873,6 @@ protected GridArrayUpdater createDefaultArrayUpdater( * see {@link #addColumn(Renderer)}. *

*

- * Every added column sends data to the client side regardless of its - * visibility state. Don't add a new column at all or use - * {@link Grid#removeColumn(Column)} to avoid sending extra data. - *

- *

* NOTE: This method is a shorthand for * {@link #addColumn(ValueProvider, BiFunction)} *

@@ -1891,11 +1900,6 @@ public Column addColumn(ValueProvider valueProvider) { * {@link #addComponentColumn(ValueProvider)}. For using build-in renderers, * see {@link #addColumn(Renderer)}. *

- *

- * Every added column sends data to the client side regardless of its - * visibility state. Don't add a new column at all or use - * {@link Grid#removeColumn(Column)} to avoid sending extra data. - *

* * @param valueProvider * the value provider @@ -1954,11 +1958,6 @@ private String formatValueToSendToTheClient(Object value) { * NOTE: Using {@link ComponentRenderer} is not as efficient as the * built in renderers or using {@link LitRenderer}. *

- *

- * Every added column sends data to the client side regardless of its - * visibility state. Don't add a new column at all or use - * {@link Grid#removeColumn(Column)} to avoid sending extra data. - *

* * @param componentProvider * a value provider that will return a component for the given @@ -1982,12 +1981,6 @@ public Column addComponentColumn( * automatically configured using the return type of the given * {@link ValueProvider}. * - *

- * Every added column sends data to the client side regardless of its - * visibility state. Don't add a new column at all or use - * {@link Grid#removeColumn(Column)} to avoid sending extra data. - *

- * * @see Column#setComparator(ValueProvider) * @see Column#setSortProperty(String...) * @see #removeColumn(Column) @@ -2022,11 +2015,6 @@ public > Column addColumn( * or using {@link LitRenderer}. *

*

- * Every added column sends data to the client side regardless of its - * visibility state. Don't add a new column at all or use - * {@link Grid#removeColumn(Column)} to avoid sending extra data. - *

- *

* NOTE: This method is a shorthand for * {@link #addColumn(Renderer, BiFunction)} *

@@ -2059,11 +2047,6 @@ public Column addColumn(Renderer renderer) { * {@link ComponentRenderer} is not as efficient as the built in renderers * or using {@link LitRenderer}. *

- *

- * Every added column sends data to the client side regardless of its - * visibility state. Don't add a new column at all or use - * {@link Grid#removeColumn(Column)} to avoid sending extra data. - *

* * @param renderer * the renderer used to create the grid cell structure @@ -2167,12 +2150,6 @@ protected BiFunction, String, Column> getDefaultColumnFactory() { * from a bean type with {@link #Grid(Class)}. * *

- * Every added column sends data to the client side regardless of its - * visibility state. Don't add a new column at all or use - * {@link Grid#removeColumn(Column)} to avoid sending extra data. - *

- * - *

* Note: This method is a shorthand for * {@link #addColumn(String, BiFunction)} *

@@ -2207,12 +2184,6 @@ public Column addColumn(String propertyName) { * Note: This method can only be used for a Grid created * from a bean type with {@link #Grid(Class)}. * - *

- * Every added column sends data to the client side regardless of its - * visibility state. Don't add a new column at all or use - * {@link Grid#removeColumn(Column)} to avoid sending extra data. - *

- * * @see #addColumn(String) * @see #removeColumn(Column) * @@ -2284,12 +2255,6 @@ private Object runPropertyValueGetter(PropertyDefinition property, * Note: This method can only be used for a Grid created * from a bean type with {@link #Grid(Class)}. * - *

- * Every added column sends data to the client side regardless of its - * visibility state. Don't add a new column at all or use - * {@link Grid#removeColumn(Column)} to avoid sending extra data. - *

- * * @param propertyNames * the property names of the new columns, not null * @see #addColumn(String) diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java new file mode 100644 index 00000000000..bd617dcc19e --- /dev/null +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java @@ -0,0 +1,329 @@ +/* + * Copyright 2000-2024 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.flow.component.grid; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.IntStream; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.UI; +import com.vaadin.flow.component.html.NativeButton; +import com.vaadin.flow.data.renderer.LitRenderer; +import com.vaadin.flow.data.renderer.Renderer; +import com.vaadin.flow.function.ValueProvider; +import com.vaadin.flow.server.VaadinSession; + +public class GridHiddenColumnRenderingTest { + + private static final int ITEM_COUNT = 10; + + private UI ui; + + private Grid grid; + + private AtomicInteger callCount; + + @Before + public void setup() { + ui = new UI(); + UI.setCurrent(ui); + VaadinSession session = Mockito.mock(VaadinSession.class); + Mockito.when(session.hasLock()).thenReturn(true); + ui.getInternals().setSession(session); + grid = new Grid<>(); + grid.setItems(getItems()); + ui.add(grid); + fakeClientCommunication(); + callCount = new AtomicInteger(0); + } + + @After + public void tearDown() { + UI.setCurrent(null); + } + + @Test + public void columnWithValueProvider_rendererCalledOncePerItem() { + addColumnWithValueProvider(); + initiallyVisibleColumn_assertRendererCalledOncePerItem(); + } + + @Test + public void initiallyHiddenColumnWithValueProvider_rendererNotCalled() { + Grid.Column column = addColumnWithValueProvider(); + initiallyHiddenColumn_assertRendererNotCalled(column); + } + + @Test + public void columnWithValueProvider_setHidden_rendererNotCalled() { + Grid.Column column = addColumnWithValueProvider(); + initiallyVisibleColumn_setHidden_assertRendererNotCalled(column); + } + + @Test + public void initiallyHiddenColumnWithValueProvider_setVisible_rendererCalledOncePerItem() { + Grid.Column column = addColumnWithValueProvider(); + initiallyHiddenColumn_setVisible_assertRendererCalledOncePerItem( + column); + } + + @Test + public void columnWithValueProvider_toggleHiddenTwiceInRoundTrip_rendererCalledOncePerItem() { + Grid.Column column = addColumnWithValueProvider(); + initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( + column); + } + + @Test + public void initiallyHiddenColumnWithValueProvider_toggleHiddenTwiceInRoundTrip_rendererNotCalled() { + Grid.Column column = addColumnWithValueProvider(); + initiallyHiddenColumn_toggleHiddenTwiceInRoundTrip_assertRendererNotCalled( + column); + } + + @Test + public void componentColumn_rendererCalledOncePerItem() { + addComponentColumn(); + initiallyVisibleColumn_assertRendererCalledOncePerItem(); + } + + @Test + public void initiallyHiddenComponentColumn_rendererNotCalled() { + Grid.Column column = addComponentColumn(); + initiallyHiddenColumn_assertRendererNotCalled(column); + } + + @Test + public void componentColumn_setHidden_rendererNotCalled() { + Grid.Column column = addComponentColumn(); + initiallyVisibleColumn_setHidden_assertRendererNotCalled(column); + } + + @Test + public void initiallyHiddenComponentColumn_setVisible_rendererCalledOncePerItem() { + Grid.Column column = addComponentColumn(); + initiallyHiddenColumn_setVisible_assertRendererCalledOncePerItem( + column); + } + + @Test + public void componentColumn_toggleHiddenTwiceInRoundTrip_rendererCalledOncePerItem() { + Grid.Column column = addComponentColumn(); + initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( + column); + } + + @Test + public void initiallyHiddenComponentColumn_toggleHiddenTwiceInRoundTrip_rendererNotCalled() { + Grid.Column column = addComponentColumn(); + initiallyHiddenColumn_toggleHiddenTwiceInRoundTrip_assertRendererNotCalled( + column); + } + + @Test + public void columnWithCustomRenderer_rendererCalledOncePerItem() { + addColumnWithCustomRenderer(); + initiallyVisibleColumn_assertRendererCalledOncePerItem(); + } + + @Test + public void initiallyHiddenColumnWithCustomRenderer_rendererNotCalled() { + Grid.Column column = addColumnWithCustomRenderer(); + initiallyHiddenColumn_assertRendererNotCalled(column); + } + + @Test + public void columnWithCustomRenderer_setHidden_rendererNotCalled() { + Grid.Column column = addColumnWithCustomRenderer(); + initiallyVisibleColumn_setHidden_assertRendererNotCalled(column); + } + + @Test + public void initiallyHiddenColumnWithCustomRenderer_setVisible_rendererCalledOncePerItem() { + Grid.Column column = addColumnWithCustomRenderer(); + initiallyHiddenColumn_setVisible_assertRendererCalledOncePerItem( + column); + } + + @Test + public void columnWithCustomRenderer_toggleHiddenTwiceInRoundTrip_rendererCalledOncePerItem() { + Grid.Column column = addColumnWithCustomRenderer(); + initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( + column); + } + + @Test + public void initiallyHiddenColumnWithCustomRenderer_toggleHiddenTwiceInRoundTrip_rendererNotCalled() { + Grid.Column column = addColumnWithCustomRenderer(); + initiallyHiddenColumn_toggleHiddenTwiceInRoundTrip_assertRendererNotCalled( + column); + } + + @Test + public void columnWithValueProvider_detachAndReattachGrid_rendererCalledOncePerItem() { + addColumnWithValueProvider(); + initiallyVisibleColumn_detachAndReattachGrid_assertRendererCalledOncePerItem(); + } + + @Test + public void initiallyHiddenColumnWithValueProvider_detachAndReattachGrid_rendererNotCalled() { + Grid.Column column = addColumnWithValueProvider(); + initiallyHiddenColumn_detachAndReattachGrid_assertRendererNotCalled( + column); + } + + @Test + public void componentColumn_detachAndReattachGrid_rendererCalledOncePerItem() { + addComponentColumn(); + initiallyVisibleColumn_detachAndReattachGrid_assertRendererCalledOncePerItem(); + } + + @Test + public void initiallyHiddenComponentColumn_detachAndReattachGrid_rendererNotCalled() { + Grid.Column column = addComponentColumn(); + initiallyHiddenColumn_detachAndReattachGrid_assertRendererNotCalled( + column); + } + + @Test + public void columnWithCustomRenderer_detachAndReattachGrid_rendererCalledOncePerItem() { + addColumnWithCustomRenderer(); + initiallyVisibleColumn_detachAndReattachGrid_assertRendererCalledOncePerItem(); + } + + @Test + public void initiallyHiddenColumnWithCustomRenderer_detachAndReattachGrid_rendererNotCalled() { + Grid.Column column = addColumnWithCustomRenderer(); + initiallyHiddenColumn_detachAndReattachGrid_assertRendererNotCalled( + column); + } + + private Grid.Column addColumnWithValueProvider() { + return grid.addColumn(getValueProvider()); + } + + private Grid.Column addComponentColumn() { + return grid.addComponentColumn(this::getComponent); + } + + private Grid.Column addColumnWithCustomRenderer() { + return grid.addColumn(getCustomRenderer()); + } + + private void initiallyHiddenColumn_detachAndReattachGrid_assertRendererNotCalled( + Grid.Column column) { + column.setVisible(false); + ui.remove(grid); + fakeClientCommunication(); + ui.add(grid); + fakeClientCommunication(); + Assert.assertEquals(0, callCount.get()); + } + + private void initiallyVisibleColumn_detachAndReattachGrid_assertRendererCalledOncePerItem() { + ui.remove(grid); + fakeClientCommunication(); + callCount.set(0); + ui.add(grid); + fakeClientCommunication(); + Assert.assertEquals(ITEM_COUNT, callCount.get()); + } + + private void initiallyHiddenColumn_assertRendererNotCalled( + Grid.Column column) { + column.setVisible(false); + fakeClientCommunication(); + Assert.assertEquals(0, callCount.get()); + } + + private void initiallyVisibleColumn_assertRendererCalledOncePerItem() { + fakeClientCommunication(); + Assert.assertEquals(ITEM_COUNT, callCount.get()); + } + + private void initiallyVisibleColumn_setHidden_assertRendererNotCalled( + Grid.Column column) { + fakeClientCommunication(); + callCount.set(0); + column.setVisible(false); + fakeClientCommunication(); + Assert.assertEquals(0, callCount.get()); + } + + private void initiallyHiddenColumn_setVisible_assertRendererCalledOncePerItem( + Grid.Column column) { + column.setVisible(false); + fakeClientCommunication(); + column.setVisible(true); + fakeClientCommunication(); + Assert.assertEquals(ITEM_COUNT, callCount.get()); + } + + private void initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( + Grid.Column column) { + fakeClientCommunication(); + column.setVisible(false); + column.setVisible(true); + callCount.set(0); + fakeClientCommunication(); + Assert.assertEquals(ITEM_COUNT, callCount.get()); + } + + private void initiallyHiddenColumn_toggleHiddenTwiceInRoundTrip_assertRendererNotCalled( + Grid.Column column) { + column.setVisible(false); + fakeClientCommunication(); + column.setVisible(true); + column.setVisible(false); + fakeClientCommunication(); + Assert.assertEquals(0, callCount.get()); + } + + private ValueProvider getValueProvider() { + return s -> { + callCount.incrementAndGet(); + return s; + }; + } + + private Component getComponent(String string) { + callCount.incrementAndGet(); + return new NativeButton(string); + } + + private Renderer getCustomRenderer() { + return LitRenderer. of("
${item.displayName}
") + .withProperty("displayName", getValueProvider()); + } + + private List getItems() { + return IntStream.range(0, ITEM_COUNT).mapToObj(i -> "Item " + i) + .toList(); + } + + private void fakeClientCommunication() { + ui.getInternals().getStateTree().runExecutionsBeforeClientResponse(); + ui.getInternals().getStateTree().collectChanges(ignore -> { + }); + } +} diff --git a/vaadin-grid-pro-flow-parent/vaadin-grid-pro-flow/src/main/java/com/vaadin/flow/component/gridpro/GridPro.java b/vaadin-grid-pro-flow-parent/vaadin-grid-pro-flow/src/main/java/com/vaadin/flow/component/gridpro/GridPro.java index 0d2b6d7cdd5..9ef16db9ac5 100644 --- a/vaadin-grid-pro-flow-parent/vaadin-grid-pro-flow/src/main/java/com/vaadin/flow/component/gridpro/GridPro.java +++ b/vaadin-grid-pro-flow-parent/vaadin-grid-pro-flow/src/main/java/com/vaadin/flow/component/gridpro/GridPro.java @@ -185,11 +185,6 @@ private Object getItemId(E item) { /** * Server-side component for the {@code } element. * - *

- * Every added column sends data to the client side regardless of its - * visibility state. Don't add a new column at all or use - * {@link GridPro#removeColumn(Column)} to avoid sending extra data. - * * @param * type of the underlying grid this column is compatible with */ From fd0d7f05cc1a2984e9fa0d26d71d80cd15d6cc2a Mon Sep 17 00:00:00 2001 From: ugur-vaadin Date: Thu, 7 Nov 2024 23:28:08 +0200 Subject: [PATCH 02/14] fix: remove unnecessary line --- .../src/main/java/com/vaadin/flow/component/grid/Grid.java | 1 - 1 file changed, 1 deletion(-) diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java index c7d0809370e..ce0de9913ae 100755 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java @@ -530,7 +530,6 @@ public Column setRenderer(Renderer renderer) { this.renderer = Objects.requireNonNull(renderer, "Renderer must not be null."); clearRendering(); - rendererSetupScheduled = true; getElement().getNode() .runWhenAttached(ui -> scheduleRendererSetup()); addAttachListener(e -> scheduleRendererSetup()); From dae866680ea364ad2a69a17c6e572aadffd4698d Mon Sep 17 00:00:00 2001 From: ugur-vaadin Date: Fri, 8 Nov 2024 15:26:50 +0200 Subject: [PATCH 03/14] fix: send items only once to the client --- .../com/vaadin/flow/component/grid/Grid.java | 1 - .../grid/GridHiddenColumnRenderingTest.java | 37 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java index ce0de9913ae..6e855445944 100755 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java @@ -533,7 +533,6 @@ public Column setRenderer(Renderer renderer) { getElement().getNode() .runWhenAttached(ui -> scheduleRendererSetup()); addAttachListener(e -> scheduleRendererSetup()); - getGrid().getDataCommunicator().reset(); return this; } diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java index bd617dcc19e..c13b7e865aa 100644 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java @@ -28,6 +28,7 @@ import com.vaadin.flow.component.Component; import com.vaadin.flow.component.UI; import com.vaadin.flow.component.html.NativeButton; +import com.vaadin.flow.data.provider.DataProvider; import com.vaadin.flow.data.renderer.LitRenderer; import com.vaadin.flow.data.renderer.Renderer; import com.vaadin.flow.function.ValueProvider; @@ -218,6 +219,42 @@ public void initiallyHiddenColumnWithCustomRenderer_detachAndReattachGrid_render column); } + @Test + public void columnWithCustomRenderer_setAnotherRenderer_onlyNewRendererCalled() { + Grid.Column column = addColumnWithCustomRenderer(); + fakeClientCommunication(); + callCount.set(0); + AtomicInteger newRendererCallCount = new AtomicInteger(0); + Renderer newRenderer = LitRenderer + . of("${item.displayName}") + .withProperty("displayName", s -> { + newRendererCallCount.incrementAndGet(); + return s; + }); + column.setRenderer(newRenderer); + fakeClientCommunication(); + Assert.assertEquals(0, callCount.get()); + Assert.assertEquals(ITEM_COUNT, newRendererCallCount.get()); + } + + @Test + public void addColumn_itemsSentOnlyOnce() { + List items = getItems(); + AtomicInteger fetchCount = new AtomicInteger(0); + DataProvider dataProvider = DataProvider + .fromCallbacks(query -> { + fetchCount.incrementAndGet(); + return items.stream().skip(query.getOffset()) + .limit(query.getLimit()); + }, query -> items.size()); + grid.setDataProvider(dataProvider); + fakeClientCommunication(); + fetchCount.set(0); + addColumnWithValueProvider(); + fakeClientCommunication(); + Assert.assertEquals(1, fetchCount.get()); + } + private Grid.Column addColumnWithValueProvider() { return grid.addColumn(getValueProvider()); } From d1b3c46550d466b44a4910470ee66ca966751756 Mon Sep 17 00:00:00 2001 From: ugur-vaadin Date: Mon, 11 Nov 2024 23:02:48 +0200 Subject: [PATCH 04/14] refactor: patch data generator instead of scheduling renderer --- .../com/vaadin/flow/component/grid/Grid.java | 125 +++++++++--------- .../grid/GridHiddenColumnRenderingTest.java | 16 +-- 2 files changed, 71 insertions(+), 70 deletions(-) diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java index 6e855445944..e22ffe4a5ae 100755 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java @@ -443,7 +443,6 @@ public static class Column extends AbstractColumn> { private String columnKey; // defined and used by the user private boolean sortingEnabled; - private boolean rendererSetupScheduled; private Component editorComponent; private EditorRenderer editorRenderer; @@ -484,8 +483,38 @@ public Column(Grid grid, String columnId, Renderer renderer) { super(grid); Objects.requireNonNull(renderer); this.columnInternalId = columnId; + this.renderer = renderer; + comparator = (a, b) -> 0; - setRenderer(renderer); + + rendering = renderer.render(getElement(), (KeyMapper) getGrid() + .getDataCommunicator().getKeyMapper()); + + Optional> dataGenerator = rendering + .getDataGenerator(); + + if (dataGenerator.isPresent()) { + var generator = dataGenerator.get(); + DataGenerator conditionalDataGenerator = (item, + jsonObject) -> { + if (Column.this.isVisible()) { + generator.generateData(item, jsonObject); + } + }; + + columnDataGeneratorRegistration = grid + .addDataGenerator(conditionalDataGenerator); + } + } + + @Override + public void setVisible(boolean visible) { + boolean resetDataCommunicator = visible && !isVisible(); + super.setVisible(visible); + if (resetDataCommunicator) { + getGrid().getDataCommunicator().reset(); + + } } protected void destroyDataGenerators() { @@ -529,10 +558,33 @@ public Renderer getRenderer() { public Column setRenderer(Renderer renderer) { this.renderer = Objects.requireNonNull(renderer, "Renderer must not be null."); - clearRendering(); - getElement().getNode() - .runWhenAttached(ui -> scheduleRendererSetup()); - addAttachListener(e -> scheduleRendererSetup()); + + destroyDataGenerators(); + if (rendering != null) { + rendering.getRegistration().remove(); + } + + rendering = renderer.render(getElement(), (KeyMapper) getGrid() + .getDataCommunicator().getKeyMapper()); + + columnDataGeneratorRegistration = rendering.getDataGenerator() + .map(dataGenerator -> grid + .addDataGenerator((DataGenerator) dataGenerator)) + .orElse(null); + + // The editor renderer is a wrapper around the regular renderer, so + // we need to apply it again afterwards + if (editorRenderer != null) { + Rendering editorRendering = editorRenderer + .render(getElement(), null); + editorDataGeneratorRegistration = editorRendering + .getDataGenerator() + .map(dataGenerator -> grid.addDataGenerator( + (DataGenerator) dataGenerator)) + .orElse(null); + } + + getGrid().getDataCommunicator().reset(); return this; } @@ -1132,18 +1184,6 @@ public Column setRowHeader(boolean rowHeader) { return this; } - @Override - public void setVisible(boolean visible) { - boolean isInitiallyVisible = isVisible(); - super.setVisible(visible); - if (isInitiallyVisible && !visible) { - clearRendering(); - } - if (!isInitiallyVisible && visible) { - scheduleRendererSetup(); - } - } - @Override protected Column getBottomLevelColumn() { return this; @@ -1153,54 +1193,15 @@ protected Column getBottomLevelColumn() { private void setupColumnEditor() { editorRenderer = new EditorRenderer<>((Editor) grid.getEditor(), columnInternalId); - setupEditorRenderer(); - } - private void setupRenderer() { - if (renderer == null) { - return; - } - rendering = renderer.render(getElement(), (KeyMapper) getGrid() - .getDataCommunicator().getKeyMapper()); - columnDataGeneratorRegistration = rendering.getDataGenerator() - .map(dataGenerator -> grid - .addDataGenerator((DataGenerator) dataGenerator)) - .orElse(null); - grid.getDataProvider().refreshAll(); - } - - private void setupEditorRenderer() { - if (editorRenderer == null) { - return; - } Rendering editorRendering = editorRenderer.render(getElement(), null); - editorDataGeneratorRegistration = editorRendering.getDataGenerator() - .map(dataGenerator -> grid - .addDataGenerator((DataGenerator) dataGenerator)) - .orElse(null); - } - private void scheduleRendererSetup() { - if (rendererSetupScheduled) { - return; - } - rendererSetupScheduled = true; - getUI().ifPresent(ui -> ui.beforeClientResponse(this, ctx -> { - if (rendererSetupScheduled && isVisible()) { - setupRenderer(); - // The editor renderer is a wrapper around the regular - // renderer, so we need to apply it again afterward. - setupEditorRenderer(); - } - rendererSetupScheduled = false; - })); - } - - private void clearRendering() { - destroyDataGenerators(); - if (rendering != null) { - rendering.getRegistration().remove(); + Optional> dataGenerator = editorRendering + .getDataGenerator(); + if (dataGenerator.isPresent()) { + editorDataGeneratorRegistration = grid + .addDataGenerator((DataGenerator) dataGenerator.get()); } } } diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java index c13b7e865aa..144eeaf160a 100644 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java @@ -89,9 +89,9 @@ public void initiallyHiddenColumnWithValueProvider_setVisible_rendererCalledOnce } @Test - public void columnWithValueProvider_toggleHiddenTwiceInRoundTrip_rendererCalledOncePerItem() { + public void columnWithValueProvider_toggleHiddenTwiceInRoundTrip_rendererCalledAtMostOncePerItem() { Grid.Column column = addColumnWithValueProvider(); - initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( + initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledAtMostOncePerItem( column); } @@ -128,9 +128,9 @@ public void initiallyHiddenComponentColumn_setVisible_rendererCalledOncePerItem( } @Test - public void componentColumn_toggleHiddenTwiceInRoundTrip_rendererCalledOncePerItem() { + public void componentColumn_toggleHiddenTwiceInRoundTrip_rendererCalledAtMostOncePerItem() { Grid.Column column = addComponentColumn(); - initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( + initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledAtMostOncePerItem( column); } @@ -167,9 +167,9 @@ public void initiallyHiddenColumnWithCustomRenderer_setVisible_rendererCalledOnc } @Test - public void columnWithCustomRenderer_toggleHiddenTwiceInRoundTrip_rendererCalledOncePerItem() { + public void columnWithCustomRenderer_toggleHiddenTwiceInRoundTrip_rendererCalledAtMostOncePerItem() { Grid.Column column = addColumnWithCustomRenderer(); - initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( + initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledAtMostOncePerItem( column); } @@ -316,14 +316,14 @@ private void initiallyHiddenColumn_setVisible_assertRendererCalledOncePerItem( Assert.assertEquals(ITEM_COUNT, callCount.get()); } - private void initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( + private void initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledAtMostOncePerItem( Grid.Column column) { fakeClientCommunication(); column.setVisible(false); column.setVisible(true); callCount.set(0); fakeClientCommunication(); - Assert.assertEquals(ITEM_COUNT, callCount.get()); + Assert.assertTrue(callCount.get() <= ITEM_COUNT); } private void initiallyHiddenColumn_toggleHiddenTwiceInRoundTrip_assertRendererNotCalled( From 0ce730731e84d3d4fcedae9e3ddbbf58f7543f5b Mon Sep 17 00:00:00 2001 From: ugur-vaadin Date: Mon, 11 Nov 2024 23:04:36 +0200 Subject: [PATCH 05/14] chore: remove extra empty lines --- .../src/main/java/com/vaadin/flow/component/grid/Grid.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java index e22ffe4a5ae..871a78892b5 100755 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java @@ -501,7 +501,6 @@ public Column(Grid grid, String columnId, Renderer renderer) { generator.generateData(item, jsonObject); } }; - columnDataGeneratorRegistration = grid .addDataGenerator(conditionalDataGenerator); } @@ -513,7 +512,6 @@ public void setVisible(boolean visible) { super.setVisible(visible); if (resetDataCommunicator) { getGrid().getDataCommunicator().reset(); - } } From 6c9be867e4fd6f20729dba46aa29fe100b1980ae Mon Sep 17 00:00:00 2001 From: ugur-vaadin Date: Tue, 12 Nov 2024 10:29:40 +0200 Subject: [PATCH 06/14] fix: override all methods when patching data generator --- .../com/vaadin/flow/component/grid/Grid.java | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java index 871a78892b5..238f5c84371 100755 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java @@ -495,10 +495,31 @@ public Column(Grid grid, String columnId, Renderer renderer) { if (dataGenerator.isPresent()) { var generator = dataGenerator.get(); - DataGenerator conditionalDataGenerator = (item, - jsonObject) -> { - if (Column.this.isVisible()) { - generator.generateData(item, jsonObject); + + // Use an anonymous class instead of Lambda to prevent potential + // deserialization issues when used with Grid + // see https://github.com/vaadin/flow-components/issues/6256 + var conditionalDataGenerator = new DataGenerator() { + @Override + public void generateData(T item, JsonObject jsonObject) { + if (Column.this.isVisible()) { + generator.generateData(item, jsonObject); + } + } + + @Override + public void destroyData(T item) { + generator.destroyData(item); + } + + @Override + public void destroyAllData() { + generator.destroyAllData(); + } + + @Override + public void refreshData(T item) { + generator.refreshData(item); } }; columnDataGeneratorRegistration = grid From 5e9e1494a249d90e7d4fa07d56765dd58e3fcd60 Mon Sep 17 00:00:00 2001 From: ugur-vaadin Date: Tue, 12 Nov 2024 11:06:34 +0200 Subject: [PATCH 07/14] test: revert unit test asserions --- .../grid/GridHiddenColumnRenderingTest.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java index 144eeaf160a..c13b7e865aa 100644 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java @@ -89,9 +89,9 @@ public void initiallyHiddenColumnWithValueProvider_setVisible_rendererCalledOnce } @Test - public void columnWithValueProvider_toggleHiddenTwiceInRoundTrip_rendererCalledAtMostOncePerItem() { + public void columnWithValueProvider_toggleHiddenTwiceInRoundTrip_rendererCalledOncePerItem() { Grid.Column column = addColumnWithValueProvider(); - initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledAtMostOncePerItem( + initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( column); } @@ -128,9 +128,9 @@ public void initiallyHiddenComponentColumn_setVisible_rendererCalledOncePerItem( } @Test - public void componentColumn_toggleHiddenTwiceInRoundTrip_rendererCalledAtMostOncePerItem() { + public void componentColumn_toggleHiddenTwiceInRoundTrip_rendererCalledOncePerItem() { Grid.Column column = addComponentColumn(); - initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledAtMostOncePerItem( + initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( column); } @@ -167,9 +167,9 @@ public void initiallyHiddenColumnWithCustomRenderer_setVisible_rendererCalledOnc } @Test - public void columnWithCustomRenderer_toggleHiddenTwiceInRoundTrip_rendererCalledAtMostOncePerItem() { + public void columnWithCustomRenderer_toggleHiddenTwiceInRoundTrip_rendererCalledOncePerItem() { Grid.Column column = addColumnWithCustomRenderer(); - initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledAtMostOncePerItem( + initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( column); } @@ -316,14 +316,14 @@ private void initiallyHiddenColumn_setVisible_assertRendererCalledOncePerItem( Assert.assertEquals(ITEM_COUNT, callCount.get()); } - private void initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledAtMostOncePerItem( + private void initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( Grid.Column column) { fakeClientCommunication(); column.setVisible(false); column.setVisible(true); callCount.set(0); fakeClientCommunication(); - Assert.assertTrue(callCount.get() <= ITEM_COUNT); + Assert.assertEquals(ITEM_COUNT, callCount.get()); } private void initiallyHiddenColumn_toggleHiddenTwiceInRoundTrip_assertRendererNotCalled( From 87a6f0530a81e0db9fec4e6e94171faf3ca0934b Mon Sep 17 00:00:00 2001 From: ugur-vaadin Date: Thu, 7 Nov 2024 21:35:38 +0200 Subject: [PATCH 08/14] refactor: schedule renderers to only run for visible columns --- .../com/vaadin/flow/component/grid/Grid.java | 105 +++++++++++------- 1 file changed, 62 insertions(+), 43 deletions(-) diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java index 238f5c84371..9187b345282 100755 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java @@ -443,6 +443,7 @@ public static class Column extends AbstractColumn> { private String columnKey; // defined and used by the user private boolean sortingEnabled; + private boolean rendererSetupScheduled; private Component editorComponent; private EditorRenderer editorRenderer; @@ -483,10 +484,7 @@ public Column(Grid grid, String columnId, Renderer renderer) { super(grid); Objects.requireNonNull(renderer); this.columnInternalId = columnId; - this.renderer = renderer; - comparator = (a, b) -> 0; - rendering = renderer.render(getElement(), (KeyMapper) getGrid() .getDataCommunicator().getKeyMapper()); @@ -527,15 +525,6 @@ public void refreshData(T item) { } } - @Override - public void setVisible(boolean visible) { - boolean resetDataCommunicator = visible && !isVisible(); - super.setVisible(visible); - if (resetDataCommunicator) { - getGrid().getDataCommunicator().reset(); - } - } - protected void destroyDataGenerators() { if (columnDataGeneratorRegistration != null) { columnDataGeneratorRegistration.remove(); @@ -577,32 +566,11 @@ public Renderer getRenderer() { public Column setRenderer(Renderer renderer) { this.renderer = Objects.requireNonNull(renderer, "Renderer must not be null."); - - destroyDataGenerators(); - if (rendering != null) { - rendering.getRegistration().remove(); - } - - rendering = renderer.render(getElement(), (KeyMapper) getGrid() - .getDataCommunicator().getKeyMapper()); - - columnDataGeneratorRegistration = rendering.getDataGenerator() - .map(dataGenerator -> grid - .addDataGenerator((DataGenerator) dataGenerator)) - .orElse(null); - - // The editor renderer is a wrapper around the regular renderer, so - // we need to apply it again afterwards - if (editorRenderer != null) { - Rendering editorRendering = editorRenderer - .render(getElement(), null); - editorDataGeneratorRegistration = editorRendering - .getDataGenerator() - .map(dataGenerator -> grid.addDataGenerator( - (DataGenerator) dataGenerator)) - .orElse(null); - } - + clearRendering(); + rendererSetupScheduled = true; + getElement().getNode() + .runWhenAttached(ui -> scheduleRendererSetup()); + addAttachListener(e -> scheduleRendererSetup()); getGrid().getDataCommunicator().reset(); return this; } @@ -1203,6 +1171,18 @@ public Column setRowHeader(boolean rowHeader) { return this; } + @Override + public void setVisible(boolean visible) { + boolean isInitiallyVisible = isVisible(); + super.setVisible(visible); + if (isInitiallyVisible && !visible) { + clearRendering(); + } + if (!isInitiallyVisible && visible) { + scheduleRendererSetup(); + } + } + @Override protected Column getBottomLevelColumn() { return this; @@ -1212,15 +1192,54 @@ protected Column getBottomLevelColumn() { private void setupColumnEditor() { editorRenderer = new EditorRenderer<>((Editor) grid.getEditor(), columnInternalId); + setupEditorRenderer(); + } + + private void setupRenderer() { + if (renderer == null) { + return; + } + rendering = renderer.render(getElement(), (KeyMapper) getGrid() + .getDataCommunicator().getKeyMapper()); + columnDataGeneratorRegistration = rendering.getDataGenerator() + .map(dataGenerator -> grid + .addDataGenerator((DataGenerator) dataGenerator)) + .orElse(null); + grid.getDataProvider().refreshAll(); + } + private void setupEditorRenderer() { + if (editorRenderer == null) { + return; + } Rendering editorRendering = editorRenderer.render(getElement(), null); + editorDataGeneratorRegistration = editorRendering.getDataGenerator() + .map(dataGenerator -> grid + .addDataGenerator((DataGenerator) dataGenerator)) + .orElse(null); + } - Optional> dataGenerator = editorRendering - .getDataGenerator(); - if (dataGenerator.isPresent()) { - editorDataGeneratorRegistration = grid - .addDataGenerator((DataGenerator) dataGenerator.get()); + private void scheduleRendererSetup() { + if (rendererSetupScheduled) { + return; + } + rendererSetupScheduled = true; + getUI().ifPresent(ui -> ui.beforeClientResponse(this, ctx -> { + if (rendererSetupScheduled && isVisible()) { + setupRenderer(); + // The editor renderer is a wrapper around the regular + // renderer, so we need to apply it again afterward. + setupEditorRenderer(); + } + rendererSetupScheduled = false; + })); + } + + private void clearRendering() { + destroyDataGenerators(); + if (rendering != null) { + rendering.getRegistration().remove(); } } } From 907ff8c6cd45bcd4b790de30aa89dcd4fa0575cb Mon Sep 17 00:00:00 2001 From: ugur-vaadin Date: Thu, 7 Nov 2024 23:28:08 +0200 Subject: [PATCH 09/14] fix: remove unnecessary line --- .../src/main/java/com/vaadin/flow/component/grid/Grid.java | 1 - 1 file changed, 1 deletion(-) diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java index 9187b345282..73c19014a50 100755 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java @@ -567,7 +567,6 @@ public Column setRenderer(Renderer renderer) { this.renderer = Objects.requireNonNull(renderer, "Renderer must not be null."); clearRendering(); - rendererSetupScheduled = true; getElement().getNode() .runWhenAttached(ui -> scheduleRendererSetup()); addAttachListener(e -> scheduleRendererSetup()); From a7e6dbf57bba81496e9f9a8d0f10fc7b3c5680fa Mon Sep 17 00:00:00 2001 From: ugur-vaadin Date: Fri, 8 Nov 2024 15:26:50 +0200 Subject: [PATCH 10/14] fix: send items only once to the client --- .../src/main/java/com/vaadin/flow/component/grid/Grid.java | 1 - 1 file changed, 1 deletion(-) diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java index 73c19014a50..3ffaaedb1e3 100755 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java @@ -570,7 +570,6 @@ public Column setRenderer(Renderer renderer) { getElement().getNode() .runWhenAttached(ui -> scheduleRendererSetup()); addAttachListener(e -> scheduleRendererSetup()); - getGrid().getDataCommunicator().reset(); return this; } From 7007c3b5357477308a6fddeb00973e6742772708 Mon Sep 17 00:00:00 2001 From: ugur-vaadin Date: Mon, 11 Nov 2024 23:02:48 +0200 Subject: [PATCH 11/14] refactor: patch data generator instead of scheduling renderer --- .../com/vaadin/flow/component/grid/Grid.java | 96 +++++++------------ .../grid/GridHiddenColumnRenderingTest.java | 16 ++-- 2 files changed, 43 insertions(+), 69 deletions(-) diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java index 3ffaaedb1e3..d45af86dc14 100755 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java @@ -443,7 +443,6 @@ public static class Column extends AbstractColumn> { private String columnKey; // defined and used by the user private boolean sortingEnabled; - private boolean rendererSetupScheduled; private Component editorComponent; private EditorRenderer editorRenderer; @@ -484,7 +483,10 @@ public Column(Grid grid, String columnId, Renderer renderer) { super(grid); Objects.requireNonNull(renderer); this.columnInternalId = columnId; + this.renderer = renderer; + comparator = (a, b) -> 0; + rendering = renderer.render(getElement(), (KeyMapper) getGrid() .getDataCommunicator().getKeyMapper()); @@ -566,10 +568,33 @@ public Renderer getRenderer() { public Column setRenderer(Renderer renderer) { this.renderer = Objects.requireNonNull(renderer, "Renderer must not be null."); - clearRendering(); - getElement().getNode() - .runWhenAttached(ui -> scheduleRendererSetup()); - addAttachListener(e -> scheduleRendererSetup()); + + destroyDataGenerators(); + if (rendering != null) { + rendering.getRegistration().remove(); + } + + rendering = renderer.render(getElement(), (KeyMapper) getGrid() + .getDataCommunicator().getKeyMapper()); + + columnDataGeneratorRegistration = rendering.getDataGenerator() + .map(dataGenerator -> grid + .addDataGenerator((DataGenerator) dataGenerator)) + .orElse(null); + + // The editor renderer is a wrapper around the regular renderer, so + // we need to apply it again afterwards + if (editorRenderer != null) { + Rendering editorRendering = editorRenderer + .render(getElement(), null); + editorDataGeneratorRegistration = editorRendering + .getDataGenerator() + .map(dataGenerator -> grid.addDataGenerator( + (DataGenerator) dataGenerator)) + .orElse(null); + } + + getGrid().getDataCommunicator().reset(); return this; } @@ -1169,18 +1194,6 @@ public Column setRowHeader(boolean rowHeader) { return this; } - @Override - public void setVisible(boolean visible) { - boolean isInitiallyVisible = isVisible(); - super.setVisible(visible); - if (isInitiallyVisible && !visible) { - clearRendering(); - } - if (!isInitiallyVisible && visible) { - scheduleRendererSetup(); - } - } - @Override protected Column getBottomLevelColumn() { return this; @@ -1190,54 +1203,15 @@ protected Column getBottomLevelColumn() { private void setupColumnEditor() { editorRenderer = new EditorRenderer<>((Editor) grid.getEditor(), columnInternalId); - setupEditorRenderer(); - } - - private void setupRenderer() { - if (renderer == null) { - return; - } - rendering = renderer.render(getElement(), (KeyMapper) getGrid() - .getDataCommunicator().getKeyMapper()); - columnDataGeneratorRegistration = rendering.getDataGenerator() - .map(dataGenerator -> grid - .addDataGenerator((DataGenerator) dataGenerator)) - .orElse(null); - grid.getDataProvider().refreshAll(); - } - private void setupEditorRenderer() { - if (editorRenderer == null) { - return; - } Rendering editorRendering = editorRenderer.render(getElement(), null); - editorDataGeneratorRegistration = editorRendering.getDataGenerator() - .map(dataGenerator -> grid - .addDataGenerator((DataGenerator) dataGenerator)) - .orElse(null); - } - - private void scheduleRendererSetup() { - if (rendererSetupScheduled) { - return; - } - rendererSetupScheduled = true; - getUI().ifPresent(ui -> ui.beforeClientResponse(this, ctx -> { - if (rendererSetupScheduled && isVisible()) { - setupRenderer(); - // The editor renderer is a wrapper around the regular - // renderer, so we need to apply it again afterward. - setupEditorRenderer(); - } - rendererSetupScheduled = false; - })); - } - private void clearRendering() { - destroyDataGenerators(); - if (rendering != null) { - rendering.getRegistration().remove(); + Optional> dataGenerator = editorRendering + .getDataGenerator(); + if (dataGenerator.isPresent()) { + editorDataGeneratorRegistration = grid + .addDataGenerator((DataGenerator) dataGenerator.get()); } } } diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java index c13b7e865aa..144eeaf160a 100644 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java @@ -89,9 +89,9 @@ public void initiallyHiddenColumnWithValueProvider_setVisible_rendererCalledOnce } @Test - public void columnWithValueProvider_toggleHiddenTwiceInRoundTrip_rendererCalledOncePerItem() { + public void columnWithValueProvider_toggleHiddenTwiceInRoundTrip_rendererCalledAtMostOncePerItem() { Grid.Column column = addColumnWithValueProvider(); - initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( + initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledAtMostOncePerItem( column); } @@ -128,9 +128,9 @@ public void initiallyHiddenComponentColumn_setVisible_rendererCalledOncePerItem( } @Test - public void componentColumn_toggleHiddenTwiceInRoundTrip_rendererCalledOncePerItem() { + public void componentColumn_toggleHiddenTwiceInRoundTrip_rendererCalledAtMostOncePerItem() { Grid.Column column = addComponentColumn(); - initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( + initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledAtMostOncePerItem( column); } @@ -167,9 +167,9 @@ public void initiallyHiddenColumnWithCustomRenderer_setVisible_rendererCalledOnc } @Test - public void columnWithCustomRenderer_toggleHiddenTwiceInRoundTrip_rendererCalledOncePerItem() { + public void columnWithCustomRenderer_toggleHiddenTwiceInRoundTrip_rendererCalledAtMostOncePerItem() { Grid.Column column = addColumnWithCustomRenderer(); - initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( + initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledAtMostOncePerItem( column); } @@ -316,14 +316,14 @@ private void initiallyHiddenColumn_setVisible_assertRendererCalledOncePerItem( Assert.assertEquals(ITEM_COUNT, callCount.get()); } - private void initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( + private void initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledAtMostOncePerItem( Grid.Column column) { fakeClientCommunication(); column.setVisible(false); column.setVisible(true); callCount.set(0); fakeClientCommunication(); - Assert.assertEquals(ITEM_COUNT, callCount.get()); + Assert.assertTrue(callCount.get() <= ITEM_COUNT); } private void initiallyHiddenColumn_toggleHiddenTwiceInRoundTrip_assertRendererNotCalled( From 78217d5038a2fd800bacc7a639d33756e78e6693 Mon Sep 17 00:00:00 2001 From: ugur-vaadin Date: Mon, 11 Nov 2024 23:04:36 +0200 Subject: [PATCH 12/14] chore: remove extra empty lines --- .../main/java/com/vaadin/flow/component/grid/Grid.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java index d45af86dc14..238f5c84371 100755 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java @@ -527,6 +527,15 @@ public void refreshData(T item) { } } + @Override + public void setVisible(boolean visible) { + boolean resetDataCommunicator = visible && !isVisible(); + super.setVisible(visible); + if (resetDataCommunicator) { + getGrid().getDataCommunicator().reset(); + } + } + protected void destroyDataGenerators() { if (columnDataGeneratorRegistration != null) { columnDataGeneratorRegistration.remove(); From 972429379fbfe5e640d92a43d04e158c3db47339 Mon Sep 17 00:00:00 2001 From: ugur-vaadin Date: Tue, 12 Nov 2024 11:06:34 +0200 Subject: [PATCH 13/14] test: revert unit test asserions --- .../grid/GridHiddenColumnRenderingTest.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java index 144eeaf160a..c13b7e865aa 100644 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java @@ -89,9 +89,9 @@ public void initiallyHiddenColumnWithValueProvider_setVisible_rendererCalledOnce } @Test - public void columnWithValueProvider_toggleHiddenTwiceInRoundTrip_rendererCalledAtMostOncePerItem() { + public void columnWithValueProvider_toggleHiddenTwiceInRoundTrip_rendererCalledOncePerItem() { Grid.Column column = addColumnWithValueProvider(); - initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledAtMostOncePerItem( + initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( column); } @@ -128,9 +128,9 @@ public void initiallyHiddenComponentColumn_setVisible_rendererCalledOncePerItem( } @Test - public void componentColumn_toggleHiddenTwiceInRoundTrip_rendererCalledAtMostOncePerItem() { + public void componentColumn_toggleHiddenTwiceInRoundTrip_rendererCalledOncePerItem() { Grid.Column column = addComponentColumn(); - initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledAtMostOncePerItem( + initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( column); } @@ -167,9 +167,9 @@ public void initiallyHiddenColumnWithCustomRenderer_setVisible_rendererCalledOnc } @Test - public void columnWithCustomRenderer_toggleHiddenTwiceInRoundTrip_rendererCalledAtMostOncePerItem() { + public void columnWithCustomRenderer_toggleHiddenTwiceInRoundTrip_rendererCalledOncePerItem() { Grid.Column column = addColumnWithCustomRenderer(); - initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledAtMostOncePerItem( + initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( column); } @@ -316,14 +316,14 @@ private void initiallyHiddenColumn_setVisible_assertRendererCalledOncePerItem( Assert.assertEquals(ITEM_COUNT, callCount.get()); } - private void initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledAtMostOncePerItem( + private void initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( Grid.Column column) { fakeClientCommunication(); column.setVisible(false); column.setVisible(true); callCount.set(0); fakeClientCommunication(); - Assert.assertTrue(callCount.get() <= ITEM_COUNT); + Assert.assertEquals(ITEM_COUNT, callCount.get()); } private void initiallyHiddenColumn_toggleHiddenTwiceInRoundTrip_assertRendererNotCalled( From 71be4a3ab98b0931e94f95fb0ba7a76b213f8a4d Mon Sep 17 00:00:00 2001 From: ugur-vaadin Date: Mon, 18 Nov 2024 21:32:55 +0200 Subject: [PATCH 14/14] feat: introduce generate data when hidden api --- .../flow/component/grid/AbstractColumn.java | 8 +- .../com/vaadin/flow/component/grid/Grid.java | 116 ++++- .../grid/GridHiddenColumnRenderingTest.java | 407 ++++++++++-------- .../flow/component/gridpro/GridPro.java | 6 + 4 files changed, 351 insertions(+), 186 deletions(-) diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/AbstractColumn.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/AbstractColumn.java index c5342c570f5..0868af9a985 100644 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/AbstractColumn.java +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/AbstractColumn.java @@ -73,12 +73,14 @@ public Grid getGrid() { /** * {@inheritDoc} *

- * Note that column related data is sent to the client side even if the - * column is invisible. Use {@link Grid#removeColumn(Column)} to remove - * column (or don't add the column all) and avoid sending extra data. + * By default, every added column sends data to the client side regardless + * of its visibility state. To avoid sending extra data, either remove the + * column using {@link Grid#removeColumn(Column)} or use + * {@link Column#setGenerateDataWhenHidden(boolean)}. *

* * @see Grid#removeColumn(Column) + * @see Column#setGenerateDataWhenHidden(boolean) */ @Override public void setVisible(boolean visible) { diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java index 238f5c84371..3da9ee36ecb 100755 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/Grid.java @@ -432,6 +432,13 @@ public static void setDefaultMultiSortPriority(MultiSortPriority priority) { /** * Server-side component for the {@code } element. * + *

+ * By default, every added column sends data to the client side regardless + * of its visibility state. To avoid sending extra data, either remove the + * column using {@link Grid#removeColumn(Column)} or use + * {@link #setGenerateDataWhenHidden(boolean)}. + *

+ * * @param * type of the underlying grid this column is compatible with */ @@ -443,6 +450,7 @@ public static class Column extends AbstractColumn> { private String columnKey; // defined and used by the user private boolean sortingEnabled; + private boolean generateDataWhenHidden = true; private Component editorComponent; private EditorRenderer editorRenderer; @@ -502,7 +510,8 @@ public Column(Grid grid, String columnId, Renderer renderer) { var conditionalDataGenerator = new DataGenerator() { @Override public void generateData(T item, JsonObject jsonObject) { - if (Column.this.isVisible()) { + if (isGenerateDataWhenHidden() + || Column.this.isVisible()) { generator.generateData(item, jsonObject); } } @@ -529,7 +538,9 @@ public void refreshData(T item) { @Override public void setVisible(boolean visible) { - boolean resetDataCommunicator = visible && !isVisible(); + // Data has to be generated for previously hidden columns + boolean resetDataCommunicator = !isGenerateDataWhenHidden() + && visible && !isVisible(); super.setVisible(visible); if (resetDataCommunicator) { getGrid().getDataCommunicator().reset(); @@ -926,6 +937,49 @@ public boolean isSortable() { return sortingEnabled; } + /** + * Sets whether the data for this column should be generated and sent to + * the client even when the column is hidden. By default, data for + * hidden columns is generated and sent to the client. + *

+ * Setting this property to {@code false} will prevent the data for this + * column from being generated and sent to the client when the column is + * hidden. Alternatively, you can remove the column using + * {@link Grid#removeColumn(Column)} or avoid adding the column + * altogether. + *

+ * + * @param generateDataWhenHidden + * {@code true} to generate data even when the column is + * hidden, {@code false} otherwise + * @return this column + */ + public Column setGenerateDataWhenHidden( + boolean generateDataWhenHidden) { + if (this.generateDataWhenHidden == generateDataWhenHidden) { + return this; + } + // Data has to be generated for hidden columns. + if (!isVisible() && generateDataWhenHidden + && !isGenerateDataWhenHidden()) { + getGrid().getDataCommunicator().reset(); + } + this.generateDataWhenHidden = generateDataWhenHidden; + return this; + } + + /** + * Returns whether the data for this column is generated and sent to the + * client when the column is hidden. The default is {@code true}. + * + * @return {@code true} if data is generated even when the column is + * hidden, {@code false} otherwise + * @see #setGenerateDataWhenHidden(boolean) + */ + public boolean isGenerateDataWhenHidden() { + return generateDataWhenHidden; + } + /** * Sets a header text to the column. *

@@ -1891,6 +1945,12 @@ protected GridArrayUpdater createDefaultArrayUpdater( * see {@link #addColumn(Renderer)}. *

*

+ * By default, every added column sends data to the client side regardless + * of its visibility state. To avoid sending extra data, either remove the + * column using {@link #removeColumn(Column)} or use + * {@link Column#setGenerateDataWhenHidden(boolean)}. + *

+ *

* NOTE: This method is a shorthand for * {@link #addColumn(ValueProvider, BiFunction)} *

@@ -1918,6 +1978,12 @@ public Column addColumn(ValueProvider valueProvider) { * {@link #addComponentColumn(ValueProvider)}. For using build-in renderers, * see {@link #addColumn(Renderer)}. *

+ *

+ * By default, every added column sends data to the client side regardless + * of its visibility state. To avoid sending extra data, either remove the + * column using {@link #removeColumn(Column)} or use + * {@link Column#setGenerateDataWhenHidden(boolean)}. + *

* * @param valueProvider * the value provider @@ -1977,6 +2043,13 @@ private String formatValueToSendToTheClient(Object value) { * built in renderers or using {@link LitRenderer}. *

* + *

+ * By default, every added column sends data to the client side regardless + * of its visibility state. To avoid sending extra data, either remove the + * column using {@link #removeColumn(Column)} or use + * {@link Column#setGenerateDataWhenHidden(boolean)}. + *

+ * * @param componentProvider * a value provider that will return a component for the given * item @@ -1999,6 +2072,13 @@ public Column addComponentColumn( * automatically configured using the return type of the given * {@link ValueProvider}. * + *

+ * By default, every added column sends data to the client side regardless + * of its visibility state. To avoid sending extra data, either remove the + * column using {@link #removeColumn(Column)} or use + * {@link Column#setGenerateDataWhenHidden(boolean)}. + *

+ * * @see Column#setComparator(ValueProvider) * @see Column#setSortProperty(String...) * @see #removeColumn(Column) @@ -2033,6 +2113,12 @@ public > Column addColumn( * or using {@link LitRenderer}. *

*

+ * By default, every added column sends data to the client side regardless + * of its visibility state. To avoid sending extra data, either remove the + * column using {@link #removeColumn(Column)} or use + * {@link Column#setGenerateDataWhenHidden(boolean)}. + *

+ *

* NOTE: This method is a shorthand for * {@link #addColumn(Renderer, BiFunction)} *

@@ -2065,6 +2151,12 @@ public Column addColumn(Renderer renderer) { * {@link ComponentRenderer} is not as efficient as the built in renderers * or using {@link LitRenderer}. *

+ *

+ * By default, every added column sends data to the client side regardless + * of its visibility state. To avoid sending extra data, either remove the + * column using {@link #removeColumn(Column)} or use + * {@link Column#setGenerateDataWhenHidden(boolean)}. + *

* * @param renderer * the renderer used to create the grid cell structure @@ -2168,6 +2260,13 @@ protected BiFunction, String, Column> getDefaultColumnFactory() { * from a bean type with {@link #Grid(Class)}. * *

+ * By default, every added column sends data to the client side regardless + * of its visibility state. To avoid sending extra data, either remove the + * column using {@link #removeColumn(Column)} or use + * {@link Column#setGenerateDataWhenHidden(boolean)}. + *

+ * + *

* Note: This method is a shorthand for * {@link #addColumn(String, BiFunction)} *

@@ -2202,6 +2301,13 @@ public Column addColumn(String propertyName) { * Note: This method can only be used for a Grid created * from a bean type with {@link #Grid(Class)}. * + *

+ * By default, every added column sends data to the client side regardless + * of its visibility state. To avoid sending extra data, either remove the + * column using {@link #removeColumn(Column)} or use + * {@link Column#setGenerateDataWhenHidden(boolean)}. + *

+ * * @see #addColumn(String) * @see #removeColumn(Column) * @@ -2272,6 +2378,12 @@ private Object runPropertyValueGetter(PropertyDefinition property, *

* Note: This method can only be used for a Grid created * from a bean type with {@link #Grid(Class)}. + *

+ * By default, every added column sends data to the client side regardless + * of its visibility state. To avoid sending extra data, either remove the + * column using {@link #removeColumn(Column)} or use + * {@link Column#setGenerateDataWhenHidden(boolean)}. + *

* * @param propertyNames * the property names of the new columns, not null diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java index c13b7e865aa..df5117db76d 100644 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridHiddenColumnRenderingTest.java @@ -17,6 +17,8 @@ import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.stream.IntStream; import org.junit.After; @@ -48,7 +50,7 @@ public class GridHiddenColumnRenderingTest { public void setup() { ui = new UI(); UI.setCurrent(ui); - VaadinSession session = Mockito.mock(VaadinSession.class); + var session = Mockito.mock(VaadinSession.class); Mockito.when(session.hasLock()).thenReturn(true); ui.getInternals().setSession(session); grid = new Grid<>(); @@ -64,168 +66,233 @@ public void tearDown() { } @Test - public void columnWithValueProvider_rendererCalledOncePerItem() { - addColumnWithValueProvider(); - initiallyVisibleColumn_assertRendererCalledOncePerItem(); - } - - @Test - public void initiallyHiddenColumnWithValueProvider_rendererNotCalled() { - Grid.Column column = addColumnWithValueProvider(); - initiallyHiddenColumn_assertRendererNotCalled(column); - } - - @Test - public void columnWithValueProvider_setHidden_rendererNotCalled() { - Grid.Column column = addColumnWithValueProvider(); - initiallyVisibleColumn_setHidden_assertRendererNotCalled(column); - } - - @Test - public void initiallyHiddenColumnWithValueProvider_setVisible_rendererCalledOncePerItem() { - Grid.Column column = addColumnWithValueProvider(); - initiallyHiddenColumn_setVisible_assertRendererCalledOncePerItem( - column); - } - - @Test - public void columnWithValueProvider_toggleHiddenTwiceInRoundTrip_rendererCalledOncePerItem() { - Grid.Column column = addColumnWithValueProvider(); - initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( - column); + public void generateDataWhenHiddenTrueByDefault() { + var column = grid.addColumn(s -> s); + Assert.assertTrue(column.isGenerateDataWhenHidden()); } @Test - public void initiallyHiddenColumnWithValueProvider_toggleHiddenTwiceInRoundTrip_rendererNotCalled() { - Grid.Column column = addColumnWithValueProvider(); - initiallyHiddenColumn_toggleHiddenTwiceInRoundTrip_assertRendererNotCalled( - column); + public void setGenerateDataWhenHidden_valueIsCorrectlySet() { + var column = grid.addColumn(s -> s).setGenerateDataWhenHidden(false); + Assert.assertFalse(column.isGenerateDataWhenHidden()); + column.setGenerateDataWhenHidden(true); + Assert.assertTrue(column.isGenerateDataWhenHidden()); } @Test - public void componentColumn_rendererCalledOncePerItem() { - addComponentColumn(); - initiallyVisibleColumn_assertRendererCalledOncePerItem(); - } - - @Test - public void initiallyHiddenComponentColumn_rendererNotCalled() { - Grid.Column column = addComponentColumn(); - initiallyHiddenColumn_assertRendererNotCalled(column); + public void dataGeneratorCalledOncePerItem() { + runTestCodeForMultipleColumns(column -> { + fakeClientCommunication(); + assertCalledOncePerItem(); + }); } @Test - public void componentColumn_setHidden_rendererNotCalled() { - Grid.Column column = addComponentColumn(); - initiallyVisibleColumn_setHidden_assertRendererNotCalled(column); + public void setGenerateDataWhenHiddenFalse_dataGeneratorCalledOncePerItem() { + runTestCodeForMultipleColumns(column -> { + column.setGenerateDataWhenHidden(false); + fakeClientCommunication(); + assertCalledOncePerItem(); + }); } @Test - public void initiallyHiddenComponentColumn_setVisible_rendererCalledOncePerItem() { - Grid.Column column = addComponentColumn(); - initiallyHiddenColumn_setVisible_assertRendererCalledOncePerItem( - column); + public void initiallyHiddenColumn_dataGeneratorCalledOncePerItem() { + runTestCodeForMultipleColumns(column -> { + column.setVisible(false); + fakeClientCommunication(); + assertCalledOncePerItem(); + }); } @Test - public void componentColumn_toggleHiddenTwiceInRoundTrip_rendererCalledOncePerItem() { - Grid.Column column = addComponentColumn(); - initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( - column); + public void initiallyHiddenColumn_setGenerateDataWhenHiddenFalse_dataGeneratorNotCalled() { + runTestCodeForMultipleColumns(column -> { + column.setVisible(false); + fakeClientCommunication(); + resetCallCount(); + column.setGenerateDataWhenHidden(false); + fakeClientCommunication(); + assertNotCalled(); + }); } @Test - public void initiallyHiddenComponentColumn_toggleHiddenTwiceInRoundTrip_rendererNotCalled() { - Grid.Column column = addComponentColumn(); - initiallyHiddenColumn_toggleHiddenTwiceInRoundTrip_assertRendererNotCalled( - column); + public void setHidden_dataGeneratorNotCalled() { + runTestCodeForMultipleColumns(column -> { + fakeClientCommunication(); + resetCallCount(); + column.setVisible(false); + fakeClientCommunication(); + assertNotCalled(); + }); } @Test - public void columnWithCustomRenderer_rendererCalledOncePerItem() { - addColumnWithCustomRenderer(); - initiallyVisibleColumn_assertRendererCalledOncePerItem(); + public void setGenerateDataWhenHiddenFalse_setHidden_dataGeneratorNotCalled() { + runTestCodeForMultipleColumns(column -> { + column.setGenerateDataWhenHidden(false); + fakeClientCommunication(); + resetCallCount(); + column.setVisible(false); + fakeClientCommunication(); + assertNotCalled(); + }); } @Test - public void initiallyHiddenColumnWithCustomRenderer_rendererNotCalled() { - Grid.Column column = addColumnWithCustomRenderer(); - initiallyHiddenColumn_assertRendererNotCalled(column); + public void initiallyHiddenColumn_setVisible_dataGeneratorNotCalled() { + runTestCodeForMultipleColumns(column -> { + column.setVisible(false); + fakeClientCommunication(); + resetCallCount(); + column.setVisible(true); + fakeClientCommunication(); + assertNotCalled(); + }); } @Test - public void columnWithCustomRenderer_setHidden_rendererNotCalled() { - Grid.Column column = addColumnWithCustomRenderer(); - initiallyVisibleColumn_setHidden_assertRendererNotCalled(column); + public void initiallyHiddenColumn_setGenerateDataWhenHiddenFalse_setVisible_dataGeneratorCalledOncePerItem() { + runTestCodeForMultipleColumns(column -> { + column.setGenerateDataWhenHidden(false); + column.setVisible(false); + fakeClientCommunication(); + column.setVisible(true); + fakeClientCommunication(); + assertCalledOncePerItem(); + }); } @Test - public void initiallyHiddenColumnWithCustomRenderer_setVisible_rendererCalledOncePerItem() { - Grid.Column column = addColumnWithCustomRenderer(); - initiallyHiddenColumn_setVisible_assertRendererCalledOncePerItem( - column); + public void toggleHiddenTwiceInRoundTrip_dataGeneratorNotCalled() { + runTestCodeForMultipleColumns(column -> { + fakeClientCommunication(); + column.setVisible(false); + column.setVisible(true); + resetCallCount(); + fakeClientCommunication(); + assertNotCalled(); + }); } @Test - public void columnWithCustomRenderer_toggleHiddenTwiceInRoundTrip_rendererCalledOncePerItem() { - Grid.Column column = addColumnWithCustomRenderer(); - initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( - column); + public void setGenerateDataWhenHiddenFalse_toggleHiddenTwiceInRoundTrip_dataGeneratorCalledOncePerItem() { + runTestCodeForMultipleColumns(column -> { + column.setGenerateDataWhenHidden(false); + fakeClientCommunication(); + column.setVisible(false); + column.setVisible(true); + resetCallCount(); + fakeClientCommunication(); + assertCalledOncePerItem(); + }); } @Test - public void initiallyHiddenColumnWithCustomRenderer_toggleHiddenTwiceInRoundTrip_rendererNotCalled() { - Grid.Column column = addColumnWithCustomRenderer(); - initiallyHiddenColumn_toggleHiddenTwiceInRoundTrip_assertRendererNotCalled( - column); + public void initiallyHiddenColumn_toggleHiddenTwiceInRoundTrip_dataGeneratorNotCalled() { + runTestCodeForMultipleColumns(column -> { + column.setVisible(false); + fakeClientCommunication(); + resetCallCount(); + column.setVisible(true); + column.setVisible(false); + fakeClientCommunication(); + assertNotCalled(); + }); } @Test - public void columnWithValueProvider_detachAndReattachGrid_rendererCalledOncePerItem() { - addColumnWithValueProvider(); - initiallyVisibleColumn_detachAndReattachGrid_assertRendererCalledOncePerItem(); + public void initiallyHiddenColumn_setGenerateDataWhenHiddenFalse_toggleHiddenTwiceInRoundTrip_dataGeneratorNotCalled() { + runTestCodeForMultipleColumns(column -> { + column.setVisible(false); + fakeClientCommunication(); + resetCallCount(); + column.setGenerateDataWhenHidden(false); + column.setVisible(true); + column.setVisible(false); + fakeClientCommunication(); + assertNotCalled(); + }); } @Test - public void initiallyHiddenColumnWithValueProvider_detachAndReattachGrid_rendererNotCalled() { - Grid.Column column = addColumnWithValueProvider(); - initiallyHiddenColumn_detachAndReattachGrid_assertRendererNotCalled( - column); + public void detachAndReattachGrid_dataGeneratorCalledOncePerItem() { + runTestCodeForMultipleColumns(column -> { + ui.remove(grid); + fakeClientCommunication(); + resetCallCount(); + ui.add(grid); + fakeClientCommunication(); + assertCalledOncePerItem(); + }); } @Test - public void componentColumn_detachAndReattachGrid_rendererCalledOncePerItem() { - addComponentColumn(); - initiallyVisibleColumn_detachAndReattachGrid_assertRendererCalledOncePerItem(); + public void setGenerateDataWhenHiddenFalse_detachAndReattachGrid_dataGeneratorCalledOncePerItem() { + runTestCodeForMultipleColumns(column -> { + column.setGenerateDataWhenHidden(false); + ui.remove(grid); + fakeClientCommunication(); + resetCallCount(); + ui.add(grid); + fakeClientCommunication(); + assertCalledOncePerItem(); + }); } @Test - public void initiallyHiddenComponentColumn_detachAndReattachGrid_rendererNotCalled() { - Grid.Column column = addComponentColumn(); - initiallyHiddenColumn_detachAndReattachGrid_assertRendererNotCalled( - column); + public void initiallyHiddenColumn_detachAndReattachGrid_dataGeneratorCalledOncePerItem() { + runTestCodeForMultipleColumns(column -> { + column.setVisible(false); + ui.remove(grid); + fakeClientCommunication(); + resetCallCount(); + ui.add(grid); + fakeClientCommunication(); + assertCalledOncePerItem(); + }); } @Test - public void columnWithCustomRenderer_detachAndReattachGrid_rendererCalledOncePerItem() { - addColumnWithCustomRenderer(); - initiallyVisibleColumn_detachAndReattachGrid_assertRendererCalledOncePerItem(); + public void initiallyHiddenColumn_setGenerateDataWhenHiddenFalse_detachAndReattachGrid_dataGeneratorNotCalled() { + runTestCodeForMultipleColumns(column -> { + column.setGenerateDataWhenHidden(false); + column.setVisible(false); + ui.remove(grid); + fakeClientCommunication(); + ui.add(grid); + fakeClientCommunication(); + assertNotCalled(); + }); } @Test - public void initiallyHiddenColumnWithCustomRenderer_detachAndReattachGrid_rendererNotCalled() { - Grid.Column column = addColumnWithCustomRenderer(); - initiallyHiddenColumn_detachAndReattachGrid_assertRendererNotCalled( - column); + public void columnWithCustomRenderer_setAnotherRenderer_onlyNewRendererCalled() { + var column = addColumnWithCustomRenderer(); + fakeClientCommunication(); + resetCallCount(); + var newRendererCallCount = new AtomicInteger(0); + var newRenderer = LitRenderer + . of("${item.displayName}") + .withProperty("displayName", s -> { + newRendererCallCount.incrementAndGet(); + return s; + }); + column.setRenderer(newRenderer); + fakeClientCommunication(); + assertNotCalled(); + Assert.assertEquals(ITEM_COUNT, newRendererCallCount.get()); } @Test - public void columnWithCustomRenderer_setAnotherRenderer_onlyNewRendererCalled() { - Grid.Column column = addColumnWithCustomRenderer(); + public void columnWithCustomRenderer_setGenerateDataWhenHiddenFalse_setAnotherRenderer_onlyNewRendererCalled() { + var column = addColumnWithCustomRenderer() + .setGenerateDataWhenHidden(false); fakeClientCommunication(); - callCount.set(0); - AtomicInteger newRendererCallCount = new AtomicInteger(0); - Renderer newRenderer = LitRenderer + resetCallCount(); + var newRendererCallCount = new AtomicInteger(0); + var newRenderer = LitRenderer . of("${item.displayName}") .withProperty("displayName", s -> { newRendererCallCount.incrementAndGet(); @@ -233,20 +300,19 @@ public void columnWithCustomRenderer_setAnotherRenderer_onlyNewRendererCalled() }); column.setRenderer(newRenderer); fakeClientCommunication(); - Assert.assertEquals(0, callCount.get()); + assertNotCalled(); Assert.assertEquals(ITEM_COUNT, newRendererCallCount.get()); } @Test public void addColumn_itemsSentOnlyOnce() { - List items = getItems(); - AtomicInteger fetchCount = new AtomicInteger(0); - DataProvider dataProvider = DataProvider - .fromCallbacks(query -> { - fetchCount.incrementAndGet(); - return items.stream().skip(query.getOffset()) - .limit(query.getLimit()); - }, query -> items.size()); + var items = getItems(); + var fetchCount = new AtomicInteger(0); + var dataProvider = DataProvider. fromCallbacks(query -> { + fetchCount.incrementAndGet(); + return items.stream().skip(query.getOffset()) + .limit(query.getLimit()); + }, query -> items.size()); grid.setDataProvider(dataProvider); fakeClientCommunication(); fetchCount.set(0); @@ -255,85 +321,48 @@ public void addColumn_itemsSentOnlyOnce() { Assert.assertEquals(1, fetchCount.get()); } - private Grid.Column addColumnWithValueProvider() { - return grid.addColumn(getValueProvider()); - } - - private Grid.Column addComponentColumn() { - return grid.addComponentColumn(this::getComponent); - } - - private Grid.Column addColumnWithCustomRenderer() { - return grid.addColumn(getCustomRenderer()); - } - - private void initiallyHiddenColumn_detachAndReattachGrid_assertRendererNotCalled( - Grid.Column column) { - column.setVisible(false); - ui.remove(grid); - fakeClientCommunication(); - ui.add(grid); - fakeClientCommunication(); - Assert.assertEquals(0, callCount.get()); - } - - private void initiallyVisibleColumn_detachAndReattachGrid_assertRendererCalledOncePerItem() { - ui.remove(grid); + @Test + public void addColumn_setGenerateDataWhenHiddenFalse_itemsSentOnlyOnce() { + var items = getItems(); + var fetchCount = new AtomicInteger(0); + var dataProvider = DataProvider. fromCallbacks(query -> { + fetchCount.incrementAndGet(); + return items.stream().skip(query.getOffset()) + .limit(query.getLimit()); + }, query -> items.size()); + grid.setDataProvider(dataProvider); fakeClientCommunication(); - callCount.set(0); - ui.add(grid); + fetchCount.set(0); + addColumnWithValueProvider().setGenerateDataWhenHidden(false); fakeClientCommunication(); - Assert.assertEquals(ITEM_COUNT, callCount.get()); + Assert.assertEquals(1, fetchCount.get()); } - private void initiallyHiddenColumn_assertRendererNotCalled( - Grid.Column column) { - column.setVisible(false); - fakeClientCommunication(); - Assert.assertEquals(0, callCount.get()); + private void runTestCodeForMultipleColumns( + Consumer> testCode) { + getColumnSuppliers().forEach(columnSupplier -> { + resetCallCount(); + var column = columnSupplier.get(); + testCode.accept(column); + grid.removeColumn(column); + }); } - private void initiallyVisibleColumn_assertRendererCalledOncePerItem() { - fakeClientCommunication(); - Assert.assertEquals(ITEM_COUNT, callCount.get()); + private List>> getColumnSuppliers() { + return List.of(this::addColumnWithValueProvider, + this::addComponentColumn, this::addColumnWithCustomRenderer); } - private void initiallyVisibleColumn_setHidden_assertRendererNotCalled( - Grid.Column column) { - fakeClientCommunication(); - callCount.set(0); - column.setVisible(false); - fakeClientCommunication(); - Assert.assertEquals(0, callCount.get()); - } - - private void initiallyHiddenColumn_setVisible_assertRendererCalledOncePerItem( - Grid.Column column) { - column.setVisible(false); - fakeClientCommunication(); - column.setVisible(true); - fakeClientCommunication(); - Assert.assertEquals(ITEM_COUNT, callCount.get()); + private Grid.Column addColumnWithValueProvider() { + return grid.addColumn(getValueProvider()); } - private void initiallyVisibleColumn_toggleHiddenTwiceInRoundTrip_assertRendererCalledOncePerItem( - Grid.Column column) { - fakeClientCommunication(); - column.setVisible(false); - column.setVisible(true); - callCount.set(0); - fakeClientCommunication(); - Assert.assertEquals(ITEM_COUNT, callCount.get()); + private Grid.Column addComponentColumn() { + return grid.addComponentColumn(this::getComponent); } - private void initiallyHiddenColumn_toggleHiddenTwiceInRoundTrip_assertRendererNotCalled( - Grid.Column column) { - column.setVisible(false); - fakeClientCommunication(); - column.setVisible(true); - column.setVisible(false); - fakeClientCommunication(); - Assert.assertEquals(0, callCount.get()); + private Grid.Column addColumnWithCustomRenderer() { + return grid.addColumn(getCustomRenderer()); } private ValueProvider getValueProvider() { @@ -363,4 +392,20 @@ private void fakeClientCommunication() { ui.getInternals().getStateTree().collectChanges(ignore -> { }); } + + private void assertNotCalled() { + Assert.assertEquals(0, getCallCount()); + } + + private void assertCalledOncePerItem() { + Assert.assertEquals(ITEM_COUNT, getCallCount()); + } + + private int getCallCount() { + return callCount.get(); + } + + private void resetCallCount() { + callCount.set(0); + } } diff --git a/vaadin-grid-pro-flow-parent/vaadin-grid-pro-flow/src/main/java/com/vaadin/flow/component/gridpro/GridPro.java b/vaadin-grid-pro-flow-parent/vaadin-grid-pro-flow/src/main/java/com/vaadin/flow/component/gridpro/GridPro.java index 9ef16db9ac5..d75ccf7be7d 100644 --- a/vaadin-grid-pro-flow-parent/vaadin-grid-pro-flow/src/main/java/com/vaadin/flow/component/gridpro/GridPro.java +++ b/vaadin-grid-pro-flow-parent/vaadin-grid-pro-flow/src/main/java/com/vaadin/flow/component/gridpro/GridPro.java @@ -185,6 +185,12 @@ private Object getItemId(E item) { /** * Server-side component for the {@code } element. * + *

+ * By default, every added column sends data to the client side regardless + * of its visibility state. To avoid sending extra data, either remove the + * column using {@link #removeColumn(Column)} or use + * {@link Column#setGenerateDataWhenHidden(boolean)}. + * * @param * type of the underlying grid this column is compatible with */