From 7b814e581354eead25ebc685d351bcf3cf257fb2 Mon Sep 17 00:00:00 2001 From: Tomi Virkki Date: Thu, 30 May 2024 09:40:28 +0300 Subject: [PATCH] feat: grid empty state content (#6321) --- .../component/grid/it/GridEmptyStatePage.java | 32 ++++ .../component/grid/it/GridEmptyStateIT.java | 61 ++++++++ .../com/vaadin/flow/component/grid/Grid.java | 63 ++++++++ .../component/grid/GridEmptyStateTest.java | 138 ++++++++++++++++++ .../component/grid/testbench/GridElement.java | 15 ++ 5 files changed, 309 insertions(+) create mode 100644 vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/src/main/java/com/vaadin/flow/component/grid/it/GridEmptyStatePage.java create mode 100644 vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/src/test/java/com/vaadin/flow/component/grid/it/GridEmptyStateIT.java create mode 100644 vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridEmptyStateTest.java diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/src/main/java/com/vaadin/flow/component/grid/it/GridEmptyStatePage.java b/vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/src/main/java/com/vaadin/flow/component/grid/it/GridEmptyStatePage.java new file mode 100644 index 00000000000..4a37ac8211e --- /dev/null +++ b/vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/src/main/java/com/vaadin/flow/component/grid/it/GridEmptyStatePage.java @@ -0,0 +1,32 @@ +package com.vaadin.flow.component.grid.it; + +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.data.bean.Person; +import com.vaadin.flow.router.Route; + +@Route("vaadin-grid/empty-state") +public class GridEmptyStatePage extends Div { + public GridEmptyStatePage() { + var grid = new Grid<>(Person.class); + add(grid); + + // Button to set the empty state content + var setEmptyStateContentButton = new Button("Set empty state content", + event -> grid.setEmptyStateText("Custom empty state content")); + setEmptyStateContentButton.setId("set-empty-state-content"); + + // Button to set the grid items + var setItemsButton = new Button("Set items", event -> grid + .setItems(new Person("John", 20), new Person("Jane", 30))); + setItemsButton.setId("set-items"); + + // Button to clear the grid items + var clearItemsButton = new Button("Clear items", + event -> grid.setItems()); + clearItemsButton.setId("clear-items"); + + add(setEmptyStateContentButton, setItemsButton, clearItemsButton); + } +} diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/src/test/java/com/vaadin/flow/component/grid/it/GridEmptyStateIT.java b/vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/src/test/java/com/vaadin/flow/component/grid/it/GridEmptyStateIT.java new file mode 100644 index 00000000000..90d651301f1 --- /dev/null +++ b/vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/src/test/java/com/vaadin/flow/component/grid/it/GridEmptyStateIT.java @@ -0,0 +1,61 @@ +package com.vaadin.flow.component.grid.it; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.openqa.selenium.NoSuchElementException; + +import com.vaadin.flow.component.button.testbench.ButtonElement; +import com.vaadin.flow.component.grid.testbench.GridElement; +import com.vaadin.flow.testutil.TestPath; +import com.vaadin.tests.AbstractComponentIT; + +@TestPath("vaadin-grid/empty-state") +public class GridEmptyStateIT extends AbstractComponentIT { + private GridElement grid; + private ButtonElement setEmptyStateContentButton; + private ButtonElement clearItemsButton; + private ButtonElement setItemsButton; + + @Before + public void init() { + open(); + grid = $(GridElement.class).first(); + setEmptyStateContentButton = $(ButtonElement.class) + .id("set-empty-state-content"); + clearItemsButton = $(ButtonElement.class).id("clear-items"); + setItemsButton = $(ButtonElement.class).id("set-items"); + } + + @Test + public void getEmptyStateContent_throws() { + Assert.assertThrows("No empty state content was found", + NoSuchElementException.class, + () -> grid.getEmptyStateContent()); + } + + @Test + public void setEmptyStateContent_emptyStateContentDisplayed() { + setEmptyStateContentButton.click(); + var content = grid.getEmptyStateContent(); + Assert.assertEquals("Custom empty state content", content.getText()); + Assert.assertTrue(content.isDisplayed()); + } + + @Test + public void setEmptyStateContent_setItems_emptyStateContentNotDisplayed() { + setEmptyStateContentButton.click(); + setItemsButton.click(); + var content = grid.getEmptyStateContent(); + Assert.assertFalse(content.isDisplayed()); + } + + @Test + public void setEmptyStateContent_setItems_clearItems_emptyStateContentDisplayed() { + setEmptyStateContentButton.click(); + setItemsButton.click(); + clearItemsButton.click(); + var content = grid.getEmptyStateContent(); + Assert.assertTrue(content.isDisplayed()); + } +} 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 0b1dee3de9f..03b3194f3ae 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 @@ -64,6 +64,7 @@ import com.vaadin.flow.component.grid.editor.Editor; import com.vaadin.flow.component.grid.editor.EditorImpl; import com.vaadin.flow.component.grid.editor.EditorRenderer; +import com.vaadin.flow.component.html.Span; import com.vaadin.flow.component.page.PendingJavaScriptResult; import com.vaadin.flow.component.shared.SelectionPreservationHandler; import com.vaadin.flow.component.shared.SelectionPreservationMode; @@ -1457,6 +1458,10 @@ public UpdateQueueData getUpdateQueueData() { private PendingJavaScriptResult pendingSorterUpdate; + private static final String EMPTY_STATE_SLOT = "empty-state"; + private Component emptyStateComponent; + private String emptyStateText; + /** * Creates a new instance, with page size of 50. */ @@ -5056,4 +5061,62 @@ private String getUniqueKey(T item) { .map(provider -> provider.apply(item)) .orElse(getDataCommunicator().getKeyMapper().key(item)); } + + /** + * Sets the component to be displayed when the grid is empty. + *

+ * Note: This will also override any empty state content set with + * {@link #setEmptyStateText(String)}. + * + * @param emptyStateComponent + * the component to be displayed when the grid is empty + */ + public void setEmptyStateComponent(Component emptyStateComponent) { + this.emptyStateText = null; + this.emptyStateComponent = emptyStateComponent; + updateEmptyStateContent(); + } + + /** + * Sets the text to be displayed when the grid is empty. + *

+ * Note: This will also override any empty state content set with + * {@link #setEmptyStateComponent(Component)}. + * + * @param emptyStateText + * the text to be displayed when the grid is empty + */ + public void setEmptyStateText(String emptyStateText) { + this.emptyStateComponent = null; + this.emptyStateText = emptyStateText; + updateEmptyStateContent(); + } + + /** + * Returns the component that is displayed when the grid is empty. + * + * @return the component that is displayed when the grid is empty + */ + public Component getEmptyStateComponent() { + return emptyStateComponent; + } + + /** + * Returns the text that is displayed when the grid is empty. + * + * @return the text that is displayed when the grid is empty + */ + public String getEmptyStateText() { + return emptyStateText; + } + + private void updateEmptyStateContent() { + if (emptyStateComponent != null) { + SlotUtils.setSlot(this, EMPTY_STATE_SLOT, emptyStateComponent); + } else if (emptyStateText != null) { + SlotUtils.setSlot(this, EMPTY_STATE_SLOT, new Span(emptyStateText)); + } else { + SlotUtils.clearSlot(this, EMPTY_STATE_SLOT); + } + } } diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridEmptyStateTest.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridEmptyStateTest.java new file mode 100644 index 00000000000..1b72ba4a0e8 --- /dev/null +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/GridEmptyStateTest.java @@ -0,0 +1,138 @@ +/* + * 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.Optional; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.dom.Element; + +public class GridEmptyStateTest { + + private Grid grid; + + @Before + public void setup() { + grid = new Grid<>(); + } + + @Test + public void setEmptyStateComponent_hasEmptyStateComponent() { + var content = new Div(); + grid.setEmptyStateComponent(content); + Assert.assertEquals(content.getElement(), getEmptyStateElement()); + } + + @Test + public void setEmptyStateText_hasEmptyStateText() { + var content = "empty"; + grid.setEmptyStateText(content); + var emptyStateElement = getEmptyStateElement(); + Assert.assertEquals(content, emptyStateElement.getText()); + Assert.assertEquals("span", emptyStateElement.getTag()); + } + + @Test + public void setEmptyStateComponent_overridesEmptyStateText() { + grid.setEmptyStateText("empty"); + var content = new Div(); + grid.setEmptyStateComponent(content); + Assert.assertEquals(content.getElement(), getEmptyStateElement()); + } + + @Test + public void setEmptyStateText_overridesEmptyStateComponent() { + var content = "empty"; + grid.setEmptyStateComponent(new Div()); + grid.setEmptyStateText(content); + var emptyStateElement = getEmptyStateElement(); + Assert.assertEquals(content, emptyStateElement.getText()); + Assert.assertEquals("span", emptyStateElement.getTag()); + } + + @Test + public void setEmptyStateComponentNull_noEmptyStateComponent() { + grid.setEmptyStateText("empty"); + grid.setEmptyStateComponent(null); + Assert.assertTrue(getEmptyStateElementOptional().isEmpty()); + } + + @Test + public void setEmptyStateTextNull_noEmptyStateComponent() { + grid.setEmptyStateText("empty"); + grid.setEmptyStateText(null); + Assert.assertTrue(getEmptyStateElementOptional().isEmpty()); + } + + @Test + public void setEmptyStateComponent_getEmptyStateComponent() { + var content = new Div(); + grid.setEmptyStateComponent(content); + Assert.assertEquals(content, grid.getEmptyStateComponent()); + } + + @Test + public void setEmptyStateText_getEmptyStateText() { + var content = "empty"; + grid.setEmptyStateText(content); + Assert.assertEquals(content, grid.getEmptyStateText()); + } + + @Test + public void setEmptyStateComponent_setEmptyStateText_getEmptyStateComponent() { + grid.setEmptyStateComponent(new Div()); + grid.setEmptyStateText("empty"); + Assert.assertNull(grid.getEmptyStateComponent()); + } + + @Test + public void setEmptyStateText_setEmptyStateComponent_getEmptyStateText() { + grid.setEmptyStateText("empty"); + grid.setEmptyStateComponent(new Div()); + Assert.assertNull(grid.getEmptyStateText()); + } + + @Test + public void setEmptyStateComponent_setEmptyStateTextNull_getEmptyStateComponent() { + grid.setEmptyStateComponent(new Div()); + grid.setEmptyStateText(null); + Assert.assertNull(grid.getEmptyStateComponent()); + Assert.assertNull(grid.getEmptyStateText()); + } + + @Test + public void setEmptyStateText_setEmptyStateComponentNull_getEmptyStateText() { + grid.setEmptyStateText("empty"); + grid.setEmptyStateComponent(null); + Assert.assertNull(grid.getEmptyStateComponent()); + Assert.assertNull(grid.getEmptyStateText()); + } + + private Element getEmptyStateElement() { + return getEmptyStateElementOptional().orElse(null); + } + + private Optional getEmptyStateElementOptional() { + return grid.getElement().getChildren().filter( + child -> child.getAttribute("slot").equals("empty-state")) + .findFirst(); + } +} diff --git a/vaadin-grid-flow-parent/vaadin-grid-testbench/src/main/java/com/vaadin/flow/component/grid/testbench/GridElement.java b/vaadin-grid-flow-parent/vaadin-grid-testbench/src/main/java/com/vaadin/flow/component/grid/testbench/GridElement.java index b292332aa34..30a43be8511 100644 --- a/vaadin-grid-flow-parent/vaadin-grid-testbench/src/main/java/com/vaadin/flow/component/grid/testbench/GridElement.java +++ b/vaadin-grid-flow-parent/vaadin-grid-testbench/src/main/java/com/vaadin/flow/component/grid/testbench/GridElement.java @@ -499,4 +499,19 @@ public List getCells(int rowIndex) { getAllColumns().toArray(new GridColumnElement[0])); } + /** + * Gets the empty state content. + * + * @return the empty state content + * @throws NoSuchElementException + * if no empty state content was found + */ + public TestBenchElement getEmptyStateContent() { + try { + return findElement(By.cssSelector("[slot='empty-state']")); + } catch (NoSuchElementException e) { + throw new NoSuchElementException( + "No empty state content was found"); + } + } }