diff --git a/pom.xml b/pom.xml index 3d44729..601340d 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.vaadin.addons.flowingcode grid-helpers - 2.1.1-SNAPSHOT + 2.2.0-SNAPSHOT Grid Helpers Add-on Grid Helpers Add-on for Vaadin Flow https://www.flowingcode.com/en/open-source/ diff --git a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java index fc08616..4987dfc 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java +++ b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleHelper.java @@ -2,7 +2,7 @@ * #%L * Grid Helpers Add-on * %% - * Copyright (C) 2022 - 2024 Flowing Code + * Copyright (C) 2022 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ package com.flowingcode.vaadin.addons.gridhelpers; +import com.vaadin.flow.component.Component; import com.vaadin.flow.component.ComponentEventListener; import com.vaadin.flow.component.ComponentUtil; import com.vaadin.flow.component.checkbox.Checkbox; @@ -33,11 +34,16 @@ import com.vaadin.flow.component.contextmenu.SubMenu; import com.vaadin.flow.component.grid.Grid; import com.vaadin.flow.component.grid.Grid.Column; +import com.vaadin.flow.component.html.Span; import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.component.menubar.MenuBar; import com.vaadin.flow.component.menubar.MenuBarVariant; +import com.vaadin.flow.dom.Style; +import com.vaadin.flow.dom.Style.AlignItems; import com.vaadin.flow.shared.Registration; import java.io.Serializable; +import java.util.Locale; +import java.util.Objects; import java.util.Optional; import lombok.NonNull; import lombok.RequiredArgsConstructor; @@ -48,13 +54,19 @@ class ColumnToggleHelper implements Serializable { private static final String GRID_HELPER_TOGGLE_THEME = "gridHelperToggle"; + private static final String TOGGLE_ALIGN_ITEMS_PROPERTY = "--fc-grid-helper-toggle--align-items"; + private static final String TOGGLE_CAPTION_DATA = GridHelper.class.getName() + "#TOGGLE_CAPTION"; private static final String HIDABLE_DATA = GridHelper.class.getName() + "#HIDABLE"; private final GridHelper helper; - private Column menuToggleColumn; + private MenuBar menuToggle; + + private Component icon; + + private String toggleLabel; public void setColumnToggleVisible(boolean visible) { // https://cookbook.vaadin.com/grid-column-toggle @@ -66,7 +78,40 @@ public void setColumnToggleVisible(boolean visible) { } public boolean isColumnToggleVisible() { - return menuToggleColumn != null && menuToggleColumn.isVisible(); + return menuToggle != null; + } + + private Component getColumnToggleIcon() { + if (icon == null) { + icon = VaadinIcon.GRID_H.create(); + } + return icon; + } + + public void setColumnToggleIcon(Component icon) { + this.icon = Objects.requireNonNull(icon); + if (isColumnToggleVisible()) { + showColumnToggle(); + } + } + + public void setColumnToggleLabel(String label) { + this.toggleLabel = label; + if (isColumnToggleVisible()) { + showColumnToggle(); + } + } + + public void setColumnToggleAlignment(AlignItems alignment) { + // the custom property is set on the grid and inherited by the slotted menu bar, + // so that it survives the re-creation of the toggle + Style style = helper.getGrid().getStyle(); + if (alignment == null) { + style.remove(TOGGLE_ALIGN_ITEMS_PROPERTY); + } else { + style.set( + TOGGLE_ALIGN_ITEMS_PROPERTY, alignment.name().replace('_', '-').toLowerCase(Locale.ROOT)); + } } private void showColumnToggle() { @@ -74,18 +119,20 @@ private void showColumnToggle() { .ifPresent( toggle -> { Grid grid = helper.getGrid(); - if (menuToggleColumn == null) { - menuToggleColumn = grid.addColumn(t -> "").setWidth("auto").setFlexGrow(0); - } else { - menuToggleColumn.setVisible(true); - } - grid.getHeaderRows().get(0).getCell(menuToggleColumn).setComponent(toggle); + removeMenuToggle(); + menuToggle = toggle; + grid.getElement().appendChild(toggle.getElement()); }); } private void hideColumnToggle() { - if (menuToggleColumn != null) { - menuToggleColumn.setVisible(false); + removeMenuToggle(); + } + + private void removeMenuToggle() { + if (menuToggle != null) { + menuToggle.getElement().removeFromParent(); + menuToggle = null; } } @@ -93,8 +140,12 @@ private Optional createMenuToggle() { Grid grid = helper.getGrid(); MenuBar menuBar = new MenuBar(); + menuBar.getThemeNames().add(MenuBarVariant.LUMO_TERTIARY.getVariantName()); menuBar.getThemeNames().add(MenuBarVariant.LUMO_TERTIARY_INLINE.getVariantName()); - MenuItem menuItem = menuBar.addItem(VaadinIcon.ELLIPSIS_DOTS_V.create()); + MenuItem menuItem = menuBar.addItem(getColumnToggleIcon()); + if (toggleLabel != null) { + menuItem.add(new Span(toggleLabel)); + } SubMenu subMenu = menuItem.getSubMenu(); for (Column column : grid.getColumns()) { @@ -110,6 +161,7 @@ private Optional createMenuToggle() { } menuBar.getThemeNames().add(GRID_HELPER_TOGGLE_THEME); + menuBar.getElement().setAttribute("slot", "fc-column-toggle"); return Optional.of(menuBar).filter(_menuBar -> !_menuBar.getItems().isEmpty()); } @@ -170,7 +222,4 @@ public void setHidingToggleCaption(Column column, String caption) { } } - Column getMenuToggleColumn() { - return menuToggleColumn; - } } diff --git a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java index 8ab6408..a3674c7 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java +++ b/src/main/java/com/flowingcode/vaadin/addons/gridhelpers/GridHelper.java @@ -2,7 +2,7 @@ * #%L * Grid Helpers Add-on * %% - * Copyright (C) 2022 - 2025 Flowing Code + * Copyright (C) 2022 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,19 +38,19 @@ import com.vaadin.flow.component.grid.HeaderRow; import com.vaadin.flow.component.grid.HeaderRow.HeaderCell; import com.vaadin.flow.component.grid.ItemClickEvent; +import com.vaadin.flow.component.icon.IconFactory; +import com.vaadin.flow.dom.Style; import com.vaadin.flow.function.SerializableFunction; import com.vaadin.flow.function.SerializablePredicate; import com.vaadin.flow.shared.Registration; import java.io.Serializable; import java.util.Collection; +import java.util.Objects; import lombok.AccessLevel; import lombok.Getter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; @SuppressWarnings("serial") @JsModule("./fcGridHelper/connector.js") -@CssImport(value = "./fcGridHelper/vaadin-menu-bar.css", themeFor = "vaadin-menu-bar") @CssImport(value = GridHelper.GRID_STYLES, themeFor = "vaadin-grid") @CssImport( value = "./fcGridHelper/vaadin-context-menu-item.css", @@ -67,10 +67,9 @@ @CssImport( value = "./fcGridHelper/vaadin-checkbox.css", themeFor = "vaadin-checkbox") +@CssImport(value = "./fcGridHelper/styles.css") public final class GridHelper implements Serializable { - private static final Logger logger = LoggerFactory.getLogger(GridHelper.class); - private static final String ARROW_SELECTION_PROPERTY = "_fcghArrowSelection"; private static final String ENHANCED_SELECTION_PROPERTY = "_fcghEnhancedSelection"; @@ -142,11 +141,6 @@ private GridHelper(Grid grid) { } } - @SuppressWarnings("unchecked") - private static GridHelper getHelper(Column column) { - return getHelper((Grid) column.getGrid()); - } - private static GridHelper getHelper(Grid grid) { @SuppressWarnings("unchecked") GridHelper helper = ComponentUtil.getData(grid, GridHelper.class); @@ -309,6 +303,50 @@ public static boolean isColumnToggleVisible(Grid grid) { return getHelper(grid).columnToggleHelper.isColumnToggleVisible(); } + /** + * Sets the icon of the menu that toggles the visibility of grid columns. + * + * @param grid the grid to be configured + * @param icon the component that is rendered in the column toggle + * @throws NullPointerException if {@code icon} is {@code null} + */ + public static void setColumnToggleIcon(Grid grid, Component icon) { + getHelper(grid).columnToggleHelper.setColumnToggleIcon(icon); + } + + /** + * Sets the icon of the menu that toggles the visibility of grid columns. + * + * @param grid the grid to be configured + * @param icon the factory of the icon that is rendered in the column toggle + * @throws NullPointerException if {@code icon} is {@code null} + */ + public static void setColumnToggleIcon(Grid grid, IconFactory icon) { + setColumnToggleIcon(grid, icon.create()); + } + + /** + * Sets the text that is rendered next to the icon of the menu that toggles the visibility of grid + * columns. + * + * @param grid the grid to be configured + * @param label the text to show before the icon, or {@code null} to show the icon alone + */ + public static void setColumnToggleLabel(Grid grid, String label) { + getHelper(grid).columnToggleHelper.setColumnToggleLabel(label); + } + + /** + * Sets the vertical alignment of the menu that toggles the visibility of grid columns, relative to + * the first header row. + * + * @param grid the grid to be configured + * @param alignment the alignment of the column toggle, or {@code null} to restore the default + */ + public static void setColumnToggleAlignment(Grid grid, Style.AlignItems alignment) { + getHelper(grid).columnToggleHelper.setColumnToggleAlignment(alignment); + } + /** * Returns whether this column can be hidden by the user. Default is {@code false}. * @@ -362,8 +400,20 @@ public static String getHidingToggleCaption(Column column) { return getHelper(column.getGrid()).columnToggleHelper.getHidingToggleCaption(column); } + /** + * Returns whether the given column renders the column toggle. + * + * @param column the column to test, not {@code null} + * @return always {@code false} + * @throws NullPointerException if {@code column} is {@code null} + * @deprecated Since 2.2.0, the column toggle is rendered in the {@code fc-column-toggle} slot + * instead of in a dedicated column, so no column is a menu toggle column and this + * method always returns {@code false}. + */ + @Deprecated(forRemoval = true, since = "2.2.0") public static boolean isMenuToggleColumn(Column column) { - return column == getHelper(column).columnToggleHelper.getMenuToggleColumn(); + Objects.requireNonNull(column); + return false; } // Empty Label diff --git a/src/main/resources/META-INF/frontend/fcGridHelper/connector.js b/src/main/resources/META-INF/frontend/fcGridHelper/connector.js index 9ee5833..2eb609f 100644 --- a/src/main/resources/META-INF/frontend/fcGridHelper/connector.js +++ b/src/main/resources/META-INF/frontend/fcGridHelper/connector.js @@ -29,7 +29,19 @@ import { Grid } from '@vaadin/grid/src/vaadin-grid.js'; (function () { window.Vaadin.Flow.fcGridHelperConnector = { initLazy: grid => { - + // initLazy runs on every attach, and grid.fcGridHelper is replaced below, + // so the observers of the previous initialization have to be released here + if (grid.fcGridHelper) { + grid.fcGridHelper._toggleHeightResizeObserver.disconnect(); + grid.fcGridHelper._toggleHeightMutationObserver?.disconnect(); + } + + if (!grid.shadowRoot.querySelector('slot[name="fc-column-toggle"]')) { + const slot = document.createElement('slot'); + slot.setAttribute('name','fc-column-toggle'); + grid.shadowRoot.appendChild(slot); + } + //https://cookbook.vaadin.com/grid-arrow-selection grid.addEventListener('keyup', function(e) { if (e.keyCode == 32) return; @@ -138,9 +150,57 @@ import { Grid } from '@vaadin/grid/src/vaadin-grid.js'; observer.width=undefined; grid.dispatchEvent(new CustomEvent("fcgh-responsive-step", { detail: {step: -1} })); } + }, + + _toggleHeightResizeObserver : new ResizeObserver(() => { + grid.fcGridHelper._updateToggleHeight(); + }), + + // keep --_fc-grid-helper--header-row-height in sync with the first visible header row, + // so that the column toggle defaults to the height of that row + _observeToggleHeight : function() { + const thead = grid.shadowRoot.querySelector("thead#header"); + if (!thead) return; + + if (!grid.fcGridHelper._toggleHeightMutationObserver) { + grid.fcGridHelper._toggleHeightMutationObserver = new MutationObserver(() => { + grid.fcGridHelper._updateToggleHeight(); + }); + grid.fcGridHelper._toggleHeightMutationObserver.observe(thead, + {childList: true, subtree: true, attributes: true, attributeFilter: ['hidden']}); + } + + grid.fcGridHelper._updateToggleHeight(); + }, + + _updateToggleHeight : function() { + const row = grid.shadowRoot.querySelector("thead#header tr:not([hidden])"); + const observer = grid.fcGridHelper._toggleHeightResizeObserver; + + if (row !== grid.fcGridHelper._toggleHeightRow) { + if (grid.fcGridHelper._toggleHeightRow) { + observer.unobserve(grid.fcGridHelper._toggleHeightRow); + } + grid.fcGridHelper._toggleHeightRow = row; + if (row) { + observer.observe(row); + } + } + + const height = row ? row.getBoundingClientRect().height : 0; + if (height !== grid.fcGridHelper._toggleHeight) { + grid.fcGridHelper._toggleHeight = height; + if (height > 0) { + grid.style.setProperty('--_fc-grid-helper--header-row-height', height+'px'); + } else { + grid.style.removeProperty('--_fc-grid-helper--header-row-height'); + } + } } - + }; + + grid.fcGridHelper._observeToggleHeight(); } } diff --git a/src/main/resources/META-INF/frontend/fcGridHelper/styles.css b/src/main/resources/META-INF/frontend/fcGridHelper/styles.css new file mode 100644 index 0000000..05cf1e2 --- /dev/null +++ b/src/main/resources/META-INF/frontend/fcGridHelper/styles.css @@ -0,0 +1,71 @@ +/*- + * #%L + * Grid Helpers Add-on + * %% + * Copyright (C) 2022 - 2026 Flowing Code + * %% + * 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. + * #L% + */ +vaadin-menu-bar[theme~="gridHelperToggle"][slot] { + position: absolute; + top: 0; + right: 12px; + display: flex; + height: var(--_fc-grid-helper--header-row-height, unset); + align-items: var(--fc-grid-helper-toggle--align-items, start); + --_fc-grid-helper-toggle--background: var(--fc-grid-helper-toggle--background, + var(--lumo-base-color, var(--vaadin-background-color, unset))); +} + +[theme~="fcGh-dense"] > vaadin-menu-bar[theme~="gridHelperToggle"][slot] { + align-items: var(--fc-grid-helper-toggle--align-items, center); + top: calc(var(--vaadin-aura-theme, 0) * 2px - 1px); +} + +vaadin-menu-bar[theme~="gridHelperToggle"][slot] vaadin-menu-bar-button::part(suffix) { + display:none; +} + +vaadin-menu-bar[theme~="gridHelperToggle"][slot] > vaadin-menu-bar-button { + padding: 0; + margin: 1px 0; + border: 0; + border-radius: 0; + --lumo-size-m: 0; + --_fc-grid-helper-toggle--button-size: var(--fc-grid-helper-toggle--button-size, 16px); + height: var(--_fc-grid-helper-toggle--button-size); +} + +vaadin-menu-bar[theme~="gridHelperToggle"][slot] > vaadin-menu-bar-button > vaadin-menu-bar-item::part(content) { + font-size: var(--fc-grid-helper-toggle--font-size, 80%); + gap: var(--fc-grid-helper-toggle--gap, 4px); + flex-direction: var(--fc-grid-helper-toggle--flex-direction, row-reverse); +} + +vaadin-menu-bar[theme~="gridHelperToggle"][slot] > vaadin-menu-bar-button vaadin-icon { + --vaadin-icon-visual-size: var(--_fc-grid-helper-toggle--button-size); + --lumo-icon-size-m: var(--_fc-grid-helper-toggle--button-size); + --vaadin-icon-size: var(--_fc-grid-helper-toggle--button-size); + padding: 0; + background: var(--_fc-grid-helper-toggle--background); +} + +[theme~="fcGh-dense"] > vaadin-menu-bar[theme~="gridHelperToggle"][slot] > vaadin-menu-bar-button { + background: var(--_fc-grid-helper-toggle--background); +} + +vaadin-menu-bar[theme~="gridHelperToggle"][slot] > vaadin-menu-bar-button::before { + display: none; +} + diff --git a/src/main/resources/META-INF/frontend/fcGridHelper/vaadin-grid.css b/src/main/resources/META-INF/frontend/fcGridHelper/vaadin-grid.css index 675f114..7785872 100644 --- a/src/main/resources/META-INF/frontend/fcGridHelper/vaadin-grid.css +++ b/src/main/resources/META-INF/frontend/fcGridHelper/vaadin-grid.css @@ -42,15 +42,6 @@ min-height: calc(var(--lumo-size-xxs) - var(--_lumo-grid-border-width)); } -:host { - --fcgh-toggle-right: 0; -} - -:host([overflow~="right"]), :host([overflow~="left"]), -:host([overflow~="start"]), :host([overflow~="end"]) { - --fcgh-toggle-right: 24px; -} - table[aria-multiselectable="true"] [part~="fcGh-noselect"][first-column] ::slotted(*) { opacity: var(--fcgh-noselect-opacity, 0.5); pointer-events: none; @@ -72,3 +63,7 @@ table[aria-multiselectable="true"] [part~="fcGh-noselect"][first-column] ::slott height : var(--height-by-rows); max-height : var(--height-by-rows); } + +:host [part~='last-column-cell'] ::slotted(vaadin-grid-cell-content) { + padding-right: calc(var(--fc-grid-helper-toggle--button-size, 16px) - 2px); +} diff --git a/src/main/resources/META-INF/frontend/fcGridHelper/vaadin-menu-bar.css b/src/main/resources/META-INF/frontend/fcGridHelper/vaadin-menu-bar.css deleted file mode 100644 index 2a3c774..0000000 --- a/src/main/resources/META-INF/frontend/fcGridHelper/vaadin-menu-bar.css +++ /dev/null @@ -1,31 +0,0 @@ -/*- - * #%L - * Grid Helpers Add-on - * %% - * Copyright (C) 2022 - 2024 Flowing Code - * %% - * 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. - * #L% - */ - -:host([theme~="gridHelperToggle"]) { - position: absolute; - top: 0; - right: var(--fcgh-toggle-right); - height: 100%; -} - -:host([theme~="gridHelperToggle"]) [part="container"] { - vertical-align: middle; - height: 100%; -} diff --git a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/AllFeaturesDemo.java b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/AllFeaturesDemo.java index fad5d65..04f11a6 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/AllFeaturesDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/AllFeaturesDemo.java @@ -2,7 +2,7 @@ * #%L * Grid Helpers Add-on * %% - * Copyright (C) 2022 - 2025 Flowing Code + * Copyright (C) 2022 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,8 +35,11 @@ import com.vaadin.flow.component.grid.Grid; import com.vaadin.flow.component.grid.Grid.SelectionMode; import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.component.icon.Icon; import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.orderedlayout.FlexComponent.Alignment; import com.vaadin.flow.component.orderedlayout.FlexComponent.JustifyContentMode; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; @@ -44,6 +47,8 @@ import com.vaadin.flow.component.splitlayout.SplitLayout; import com.vaadin.flow.component.textfield.IntegerField; import com.vaadin.flow.data.binder.Binder; +import com.vaadin.flow.data.renderer.ComponentRenderer; +import com.vaadin.flow.dom.Style.AlignItems; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; import java.util.Arrays; @@ -51,6 +56,7 @@ import java.util.List; import java.util.Map; import java.util.stream.IntStream; +import lombok.AllArgsConstructor; import lombok.experimental.ExtensionMethod; @SuppressWarnings("serial") @@ -100,6 +106,7 @@ public AllFeaturesDemo() { grid.setItems(TestData.initializeData()); grid.setSelectionMode(SelectionMode.MULTI); + ToggleIcon.DEFAULT.apply(grid); grid.getElement().getStyle().set("flex-grow", "1"); GridHelper.getHeaderStyles(grid, grid.getHeaderRows().get(0).getCells().get(1)) @@ -188,6 +195,20 @@ public AllFeaturesDemo() { updateHeightByRowsField(grid, heightByRowsField); heightByRowsField.setStepButtonsVisible(true); + Select toggleIconSelect = new Select<>(); + toggleIconSelect.setLabel("Column toggle icon"); + toggleIconSelect.setItems(ToggleIcon.values()); + toggleIconSelect.setRenderer(new ComponentRenderer<>(item -> { + Icon itemIcon = item.icon.create(); + itemIcon.setSize("var(--lumo-icon-size-s)"); + HorizontalLayout itemLayout = new HorizontalLayout(itemIcon, new Span(item.caption)); + itemLayout.setAlignItems(Alignment.CENTER); + itemLayout.setSpacing(false); + itemLayout.getThemeList().add("spacing-s"); + return itemLayout; + })); + binder.forField(toggleIconSelect).bind(this::getColumnToggleIcon, this::setColumnToggleIcon); + binder.getFields().map(Component.class::cast).forEach(features::add); CompatibilityLabel label = new CompatibilityLabel("Features"); @@ -210,8 +231,37 @@ public AllFeaturesDemo() { grid.addToolbarFooter(hl); } + @AllArgsConstructor + private enum ToggleIcon { + DEFAULT("Default icon", VaadinIcon.GRID_H, null, null), + ELLIPSIS_DOTS("Ellipsis icon, centered", VaadinIcon.ELLIPSIS_DOTS_V, AlignItems.CENTER, null), + CARET_DOWN("Caret down, with label", VaadinIcon.CARET_DOWN, null, "Columns"); + + private final String caption; + + private final VaadinIcon icon; + + private final AlignItems alignment; + + private final String label; + + @Override + public String toString() { + return caption; + } + + void apply(Grid grid) { + grid.setColumnToggleIcon(icon); + grid.setColumnToggleAlignment(alignment); + grid.setColumnToggleLabel(label); + } + + } + private final Map> checkboxes = new HashMap<>(); + private ToggleIcon toggleIcon = ToggleIcon.DEFAULT; + private Checkbox newCheckbox(String labelText, SelectionMode... modes) { Checkbox checkbox = new Checkbox(labelText); if (modes.length > 0) { @@ -262,6 +312,15 @@ private boolean hasSelectionFilter(Grid grid) { return grid.getSelectionFilter() != null; } + private ToggleIcon getColumnToggleIcon(Grid grid) { + return toggleIcon; + } + + private void setColumnToggleIcon(Grid grid, ToggleIcon value) { + toggleIcon = value; + value.apply(grid); + } + private void setDenseTheme(Grid grid, boolean value) { GridHelper.setDenseTheme(grid, value); } diff --git a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleMenuDemo.java b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleMenuDemo.java index ef0e2f0..4c4feed 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleMenuDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/ColumnToggleMenuDemo.java @@ -2,7 +2,7 @@ * #%L * Grid Helpers Add-on * %% - * Copyright (C) 2022 - 2024 Flowing Code + * Copyright (C) 2022 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import com.vaadin.flow.component.grid.Grid; import com.vaadin.flow.component.grid.Grid.Column; import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.component.notification.Notification; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; @@ -43,6 +44,7 @@ public ColumnToggleMenuDemo() { Column lastNameColumn = grid.addColumn(Person::getLastName).setHeader("Last name"); Column countryColumn = grid.addColumn(Person::getCountry).setHeader("Country"); + GridHelper.setColumnToggleIcon(grid, VaadinIcon.CARET_DOWN); GridHelper.setHidingToggleCaption(firstNameColumn, "First name"); GridHelper.setHidingToggleCaption(lastNameColumn, "Last name"); GridHelper.setHidingToggleCaption(countryColumn, "Country"); diff --git a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/LombokDemo.java b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/LombokDemo.java index 7ef7911..589c245 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/LombokDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/LombokDemo.java @@ -2,7 +2,7 @@ * #%L * Grid Helpers Add-on * %% - * Copyright (C) 2022 - 2024 Flowing Code + * Copyright (C) 2022 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,9 +24,9 @@ import com.vaadin.flow.component.grid.Grid; import com.vaadin.flow.component.grid.Grid.SelectionMode; import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; - import lombok.experimental.ExtensionMethod; @PageTitle("Using Lombok") @@ -55,6 +55,7 @@ public LombokDemo() { grid.getColumns().forEach(c -> c.setAutoWidth(true)); grid.setColumnToggleVisible(true); + grid.setColumnToggleIcon(VaadinIcon.CARET_DOWN); grid.setSelectionColumnFrozen(true); grid.setSelectOnClick(true); grid.setSelectionFilter(Person::isActive); diff --git a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/ColumnToggleIT.java b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/ColumnToggleIT.java index 4366562..275d86d 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/ColumnToggleIT.java +++ b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/ColumnToggleIT.java @@ -52,13 +52,24 @@ public void testColumnToggleVisible() { $server.setColumnToggleVisible(true); assertNotNull("ColumnToggle should be present", grid.getColumnToggleButton()); - assertThat(grid.getVisibleColumns(), hasSize(nColumns + 1)); + assertThat(grid.getVisibleColumns(), hasSize(nColumns)); $server.setColumnToggleVisible(false); assertNull("ColumnToggle should be absent", grid.getColumnToggleButton()); assertThat(grid.getVisibleColumns(), hasSize(nColumns)); } + @Test + public void testColumnToggleSlot() { + $server.setColumnToggleVisible(true); + assertNotNull("ColumnToggle should be present", grid.getColumnToggle()); + assertEquals("fc-column-toggle", grid.getColumnToggleSlotName()); + + $server.setColumnToggleVisible(false); + assertNull("ColumnToggle should be absent", grid.getColumnToggle()); + assertNull(grid.getColumnToggleSlotName()); + } + @Test public void testColumnToggleClick() { int nColumns = grid.getVisibleColumns().size(); @@ -66,9 +77,7 @@ public void testColumnToggleClick() { $server.setColumnToggleVisible(true); grid.getColumnToggleButton().click(); - // the toggle is rendered in its own column assertThat(grid.getColumnToggleElements(), hasSize(nColumns)); - assertThat(grid.getVisibleColumns(), hasSize(++nColumns)); grid.getColumnToggleElements().get(0).setChecked(false); assertThat(grid.getVisibleColumns(), hasSize(nColumns - 1)); diff --git a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/GridHelperElement.java b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/GridHelperElement.java index ad44b82..6a6b53c 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/GridHelperElement.java +++ b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/it/GridHelperElement.java @@ -26,6 +26,7 @@ import com.vaadin.testbench.ElementQuery; import com.vaadin.testbench.ElementQuery.AttributeMatch.Comparison; import com.vaadin.testbench.TestBenchElement; +import java.time.Duration; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -33,8 +34,10 @@ import java.util.Optional; import lombok.NonNull; import org.openqa.selenium.By; +import org.openqa.selenium.StaleElementReferenceException; import org.openqa.selenium.TimeoutException; import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.FluentWait; public class GridHelperElement extends MyGridElement { @@ -76,6 +79,26 @@ public TestBenchElement getColumnToggleButton() { } } + /** + * Returns the column toggle menu bar, which is slotted into the grid, or {@code null} if the + * column toggle is not visible. + */ + public TestBenchElement getColumnToggle() { + List elements = + findElements(By.cssSelector("vaadin-menu-bar[theme~='gridHelperToggle']")); + return (TestBenchElement) elements.stream().findFirst().orElse(null); + } + + /** + * Returns the name of the slot that the column toggle is assigned to, or {@code null} if the + * column toggle is not visible or is not assigned to any slot. + */ + public String getColumnToggleSlotName() { + return (String) executeScript( + "const toggle = this.querySelector('vaadin-menu-bar[theme~=\"gridHelperToggle\"]');" + + "return toggle && toggle.assignedSlot && toggle.assignedSlot.name;"); + } + public List getColumnToggleElements() { try { ElementQuery query; @@ -102,19 +125,40 @@ public TestBenchElement getToolbarFooter() { public TestBenchElement getSelectionCheckbox(int rowIndex) { // assumes that Grid is in multi-selection mode - TestBenchElement cell = getSlottedCell(getRow(rowIndex)); - List elements = cell.findElements(By.tagName("vaadin-checkbox")); - return (TestBenchElement) elements.stream().findFirst().orElse(null); + return getSlottedCellChild(rowIndex, "vaadin-checkbox"); } public TestBenchElement getSelectionRadioButton(int rowIndex) { // assumes that Grid is in single-selection mode - TestBenchElement cell = getSlottedCell(getRow(rowIndex)); - List elements = cell.findElements(By.tagName("vaadin-radio-button")); - return (TestBenchElement) elements.stream().findFirst().orElse(null); + return getSlottedCellChild(rowIndex, "vaadin-radio-button"); + } + + /** + * Returns the first child with the given tag name in the first slotted cell of a row, or + * {@code null} if the cell has no such child. + * + *

+ * Showing or hiding the selection column makes the grid re-render the column tree, which + * invalidates the row and its slots. The lookup is retried while that happens, so that a stale + * element or a cell whose slot is not rendered yet is not reported as an absent child. + */ + private TestBenchElement getSlottedCellChild(int rowIndex, String tagName) { + try { + return new FluentWait<>(getDriver()) + .withTimeout(Duration.ofSeconds(2)) + .pollingEvery(Duration.ofMillis(50)) + .ignoring(org.openqa.selenium.NoSuchElementException.class, + StaleElementReferenceException.class) + .until(driver -> { + TestBenchElement cell = getSlottedCell(getRow(rowIndex)); + return cell.findElements(By.tagName(tagName)).stream().findFirst() + .map(TestBenchElement.class::cast).orElse(null); + }); + } catch (TimeoutException e) { + return null; + } } - private TestBenchElement getSlottedCell(WebElement e) { String slot = e.findElement(By.tagName("slot")).getAttribute("name"); return findElement(By.cssSelector(String.format("[slot='%s']", slot))); diff --git a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/test/GridHelperTest.java b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/test/GridHelperTest.java index 9dc31cc..5bf4819 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/test/GridHelperTest.java +++ b/src/test/java/com/flowingcode/vaadin/addons/gridhelpers/test/GridHelperTest.java @@ -2,7 +2,7 @@ * #%L * Grid Helpers Add-on * %% - * Copyright (C) 2022 - 2024 Flowing Code + * Copyright (C) 2022 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -184,11 +184,11 @@ public void testSelectOnClick() { } @Test - public void testMenuToggleColumn() { + public void testMenuToggleSlot() { grid.setColumnToggleVisible(true); - Column toggleColumn=grid.getColumns().get(grid.getColumns().size()-1); - assertTrue(GridHelper.isMenuToggleColumn(toggleColumn)); + assertTrue(grid.getElement().getChildren() + .anyMatch(e -> "fc-column-toggle".equals(e.getAttribute("slot")))); } @Test diff --git a/src/test/resources/META-INF/resources/gridhelpers/styles.css b/src/test/resources/META-INF/resources/gridhelpers/styles.css index 1430b4a..70860fd 100644 --- a/src/test/resources/META-INF/resources/gridhelpers/styles.css +++ b/src/test/resources/META-INF/resources/gridhelpers/styles.css @@ -2,7 +2,7 @@ * #%L * Grid Helpers Add-on * %% - * Copyright (C) 2022 - 2024 Flowing Code + * Copyright (C) 2022 - 2026 Flowing Code * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,10 @@ * #L% */ +.grid-helpers-all-features vaadin-grid:not([theme~="fcGh-dense"]) vaadin-menu-bar-button:has([icon="vaadin:caret-down"]) { + margin-top: -1px; +} + label.label { color: var(--lumo-body-text-color); font-size: var(--lumo-font-size-s);