diff --git a/README.md b/README.md index b7c49dc..e0f3b4c 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,81 @@ Custom templates can be added anywhere in the classpath (ie: src/main/resources) Apply [this fix](https://github.com/docker-library/openjdk/issues/73#issuecomment-451102068) as mentioned in #15 to avoid the "Cannot load from short array because "sun.awt.FontConfiguration.head" is null error. +## Customizing Export Icon Tooltips + +The GridExporter addon allows you to customize the tooltip text displayed when a user hovers over the export icons (Excel, DOCX, PDF, CSV). + +You can set a default tooltip that applies to all export icons, or provide specific tooltips for individual icons. + +### Setting Tooltips + +1. **Default Tooltip:** + Use the `setDefaultExportIconTooltip(String tooltipText)` method to set a common tooltip for all export icons. This tooltip will be used unless a more specific one is set for a particular icon. + + ```java + GridExporter exporter = GridExporter.createFor(grid); + exporter.setDefaultExportIconTooltip("Export grid data"); + ``` + +2. **Specific Tooltips:** + You can set a unique tooltip for each export format using dedicated methods: + * `setExcelExportIconTooltip(String tooltipText)` + * `setDocxExportIconTooltip(String tooltipText)` + * `setPdfExportIconTooltip(String tooltipText)` + * `setCsvExportIconTooltip(String tooltipText)` + + A specific tooltip will always override the default tooltip for that particular icon. + + ```java + // Specific tooltip for Excel, other icons (DOCX, PDF, CSV) will use the default if set. + exporter.setExcelExportIconTooltip("Export to Excel spreadsheet (.xlsx)"); + ``` + +### Behavior and Precedence + +* A specific tooltip, when set for an icon (e.g., `setExcelExportIconTooltip("Specific")`), always takes precedence for that icon. +* When an icon's specific tooltip is not set or is `null`, the default tooltip (set by `setDefaultExportIconTooltip("Default")`) will be applied. +* Should neither a specific nor a default tooltip be configured for an icon (or if both are `null`), the icon will not display a tooltip (its `title` attribute will be removed). + +### Removing or Clearing Tooltips + +* **Passing `null`**: If you pass `null` to a specific tooltip setter (e.g., `setExcelExportIconTooltip(null)`), that specific tooltip is removed. The icon will then attempt to use the default tooltip if one is set. If `null` is passed to `setDefaultExportIconTooltip(String)`, the default tooltip is removed. If an icon has no specific tooltip and the default is removed, it will have no tooltip. +* **Passing an Empty String `""`**: If you pass an empty string to any tooltip setter (e.g., `setExcelExportIconTooltip("")`), the icon will have an intentionally blank tooltip (the `title` attribute will be present but empty). This overrides any default tooltip for that specific icon. + +### Examples + +```java +GridExporter exporter = GridExporter.createFor(grid); + +// Example 1: Set a default tooltip for all export icons +exporter.setDefaultExportIconTooltip("Export grid data"); +// All icons will show "Export grid data" + +// Example 2: Set a specific tooltip for the Excel export icon +// This will override the default for the Excel icon only. +exporter.setExcelExportIconTooltip("Export to Excel spreadsheet (.xlsx)"); +// Excel icon: "Export to Excel spreadsheet (.xlsx)" +// Other icons: "Export grid data" + +// Example 3: Set a specific tooltip for PDF and a default for others +exporter.setDefaultExportIconTooltip("Download file"); +exporter.setPdfExportIconTooltip("Download as PDF document"); +// Excel, DOCX, CSV icons: "Download file" +// PDF icon: "Download as PDF document" + +// Example 4: Clear specific tooltip for CSV; it falls back to default +exporter.setDefaultExportIconTooltip("Default export tooltip"); +exporter.setExcelExportIconTooltip("Excel specific"); // Keep one specific for contrast +exporter.setCsvExportIconTooltip(null); +// CSV icon: "Default export tooltip" +// Excel icon: "Excel specific" +// Other icons (DOCX, PDF): "Default export tooltip" + +// Example 5: Intentionally blank tooltip for DOCX +exporter.setDocxExportIconTooltip(""); +// DOCX icon: Will have a title attribute, but it will be empty. +// Other icons: (Depends on default or other specific settings from previous examples) +``` ## Special configuration when using Spring diff --git a/pom.xml b/pom.xml index 647f482..23bcaf3 100644 --- a/pom.xml +++ b/pom.xml @@ -196,6 +196,24 @@ slf4j-simple test + + org.junit.jupiter + junit-jupiter-api + 5.10.2 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.10.2 + test + + + org.mockito + mockito-core + 5.11.0 + test + com.vaadin vaadin-testbench diff --git a/src/main/java/com/flowingcode/vaadin/addons/gridexporter/GridExporter.java b/src/main/java/com/flowingcode/vaadin/addons/gridexporter/GridExporter.java index 1011723..8fca4ff 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/gridexporter/GridExporter.java +++ b/src/main/java/com/flowingcode/vaadin/addons/gridexporter/GridExporter.java @@ -137,6 +137,12 @@ public class GridExporter implements Serializable { private SerializableSupplier csvCharset; + private String defaultExportIconTooltip = null; + private String excelIconTooltip = null; + private String docxIconTooltip = null; + private String pdfIconTooltip = null; + private String csvIconTooltip = null; + private GridExporter(Grid grid) { this.grid = grid; } @@ -160,6 +166,11 @@ public static GridExporter createFor( .setHref(exporter.getExcelStreamResource(excelCustomTemplate) .forComponent(excelLink)); excelLink.getElement().setAttribute("download", true); + excelLink + .setHref(exporter.getExcelStreamResource(excelCustomTemplate) + .forComponent(excelLink)); + excelLink.getElement().setAttribute("download", true); + exporter.applyTooltip(excelLink, exporter.excelIconTooltip); footerToolbar.add( new FooterToolbarItem(excelLink, FooterToolbarItemPosition.EXPORT_BUTTON)); } @@ -168,21 +179,24 @@ public static GridExporter createFor( docLink.setHref( exporter.getDocxStreamResource(docxCustomTemplate).forComponent(docLink)); docLink.getElement().setAttribute("download", true); + exporter.applyTooltip(docLink, exporter.docxIconTooltip); footerToolbar .add(new FooterToolbarItem(docLink, FooterToolbarItemPosition.EXPORT_BUTTON)); } if (exporter.isPdfExportEnabled()) { - Anchor docLink = new Anchor("", FontAwesome.Regular.FILE_PDF.create()); - docLink.setHref( - exporter.getPdfStreamResource(docxCustomTemplate).forComponent(docLink)); - docLink.getElement().setAttribute("download", true); + Anchor pdfLink = new Anchor("", FontAwesome.Regular.FILE_PDF.create()); + pdfLink.setHref( + exporter.getPdfStreamResource(null).forComponent(pdfLink)); + pdfLink.getElement().setAttribute("download", true); + exporter.applyTooltip(pdfLink, exporter.pdfIconTooltip); footerToolbar - .add(new FooterToolbarItem(docLink, FooterToolbarItemPosition.EXPORT_BUTTON)); + .add(new FooterToolbarItem(pdfLink, FooterToolbarItemPosition.EXPORT_BUTTON)); } if (exporter.isCsvExportEnabled()) { Anchor csvLink = new Anchor("", FontAwesome.Regular.FILE_LINES.create()); csvLink.setHref(exporter.getCsvStreamResource()); csvLink.getElement().setAttribute("download", true); + exporter.applyTooltip(csvLink, exporter.csvIconTooltip); footerToolbar .add(new FooterToolbarItem(csvLink, FooterToolbarItemPosition.EXPORT_BUTTON)); } @@ -812,4 +826,72 @@ public void setCsvCharset(SerializableSupplier charset) { csvCharset = charset; } + private void applyTooltip(Anchor link, String specificTooltipText) { + String finalTooltip = specificTooltipText; + if (finalTooltip == null) { + finalTooltip = this.defaultExportIconTooltip; + } + link.setTitle(finalTooltip); + } + + /** + * Sets the default tooltip text for all export icons. + * This tooltip will be used for any export icon that does not have a specific tooltip set + * via methods like {@link #setExcelExportIconTooltip(String)}. + * + * @param tooltipText The text to display as the tooltip. Passing {@code null} removes the + * default tooltip. An empty string ({@code ""}) should result in the tooltip being cleared. + */ + public void setDefaultExportIconTooltip(String tooltipText) { + this.defaultExportIconTooltip = tooltipText; + } + + /** + * Sets the tooltip text for the Excel export icon. + * This overrides any tooltip set by {@link #setDefaultExportIconTooltip(String)} for the Excel icon. + * + * @param tooltipText The text to display as the tooltip for the Excel icon. Passing {@code null} + * removes this specific tooltip (the default tooltip may then apply). An empty string + * ({@code ""}) should result in the tooltip being cleared. + */ + public void setExcelExportIconTooltip(String tooltipText) { + this.excelIconTooltip = tooltipText; + } + + /** + * Sets the tooltip text for the DOCX (Word) export icon. + * This overrides any tooltip set by {@link #setDefaultExportIconTooltip(String)} for the DOCX icon. + * + * @param tooltipText The text to display as the tooltip for the DOCX icon. Passing {@code null} + * removes this specific tooltip (the default tooltip may then apply). An empty string + * ({@code ""}) should result in the tooltip being cleared. + */ + public void setDocxExportIconTooltip(String tooltipText) { + this.docxIconTooltip = tooltipText; + } + + /** + * Sets the tooltip text for the PDF export icon. + * This overrides any tooltip set by {@link #setDefaultExportIconTooltip(String)} for the PDF icon. + * + * @param tooltipText The text to display as the tooltip for the PDF icon. Passing {@code null} + * removes this specific tooltip (the default tooltip may then apply). An empty string + * ({@code ""}) should result in the tooltip being cleared. + */ + public void setPdfExportIconTooltip(String tooltipText) { + this.pdfIconTooltip = tooltipText; + } + + /** + * Sets the tooltip text for the CSV export icon. + * This overrides any tooltip set by {@link #setDefaultExportIconTooltip(String)} for the CSV icon. + * + * @param tooltipText The text to display as the tooltip for the CSV icon. Passing {@code null} + * removes this specific tooltip (the default tooltip may then apply). An empty string + * ({@code ""}) should result in the tooltip being cleared. + */ + public void setCsvExportIconTooltip(String tooltipText) { + this.csvIconTooltip = tooltipText; + } + } diff --git a/src/test/java/com/flowingcode/vaadin/addons/gridexporter/test/GridExporterTest.java b/src/test/java/com/flowingcode/vaadin/addons/gridexporter/test/GridExporterTest.java new file mode 100644 index 0000000..3bad57e --- /dev/null +++ b/src/test/java/com/flowingcode/vaadin/addons/gridexporter/test/GridExporterTest.java @@ -0,0 +1,147 @@ +package com.flowingcode.vaadin.addons.gridexporter.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.flowingcode.vaadin.addons.gridexporter.GridExporter; +import com.vaadin.flow.component.UI; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.dom.Element; +import java.lang.reflect.Field; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@DisplayName("GridExporter Internal Tooltip State Tests") +class GridExporterTest { + + private Grid mockGrid; + private GridExporter exporter; + + @SuppressWarnings("unchecked") + private static T getPrivateField(Object obj, String fieldName) { + try { + Field field = obj.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(obj); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException("Failed to get private field", e); + } + } + + @BeforeEach + void setUp() { + mockGrid = mock(Grid.class); + Element mockElement = mock(Element.class); + when(mockGrid.getElement()).thenReturn(mockElement); + // Mock UI as it might be accessed by createFor or related methods + UI mockUi = mock(UI.class); + when(mockGrid.getUI()).thenReturn(Optional.of(mockUi)); + + exporter = GridExporter.createFor(mockGrid); + } + + @Nested + @DisplayName("Tooltip Field Initialization Tests") + class InitializationTests { + + @Test + @DisplayName("All tooltip fields should be null initially") + void testInitialTooltipFieldsAreNull() { + assertNull(getPrivateField(exporter, "defaultExportIconTooltip"), "defaultExportIconTooltip should be null"); + assertNull(getPrivateField(exporter, "excelIconTooltip"), "excelIconTooltip should be null"); + assertNull(getPrivateField(exporter, "docxIconTooltip"), "docxIconTooltip should be null"); + assertNull(getPrivateField(exporter, "pdfIconTooltip"), "pdfIconTooltip should be null"); + assertNull(getPrivateField(exporter, "csvIconTooltip"), "csvIconTooltip should be null"); + } + } + + @Nested + @DisplayName("setDefaultExportIconTooltip Setter Tests") + class DefaultTooltipSetterTests { + + @Test + @DisplayName("Should set defaultExportIconTooltip to a given string") + void testSetDefaultTooltip() { + exporter.setDefaultExportIconTooltip("Test Default"); + assertEquals("Test Default", getPrivateField(exporter, "defaultExportIconTooltip")); + } + + @Test + @DisplayName("Should set defaultExportIconTooltip to null") + void testSetDefaultTooltipToNull() { + exporter.setDefaultExportIconTooltip("Initial Value"); // Set a value first + exporter.setDefaultExportIconTooltip(null); + assertNull(getPrivateField(exporter, "defaultExportIconTooltip")); + } + + @Test + @DisplayName("Should set defaultExportIconTooltip to an empty string") + void testSetDefaultTooltipToEmptyString() { + exporter.setDefaultExportIconTooltip("Initial Value"); // Set a value first + exporter.setDefaultExportIconTooltip(""); + assertEquals("", getPrivateField(exporter, "defaultExportIconTooltip")); + } + } + + @Nested + @DisplayName("Specific Tooltip Setter Tests") + class SpecificTooltipSetterTests { + + @Test + @DisplayName("setExcelExportIconTooltip: sets, nullifies, and empties field") + void testSetExcelExportIconTooltip() { + exporter.setExcelExportIconTooltip("Excel Tip"); + assertEquals("Excel Tip", getPrivateField(exporter, "excelIconTooltip")); + + exporter.setExcelExportIconTooltip(null); + assertNull(getPrivateField(exporter, "excelIconTooltip")); + + exporter.setExcelExportIconTooltip(""); + assertEquals("", getPrivateField(exporter, "excelIconTooltip")); + } + + @Test + @DisplayName("setDocxExportIconTooltip: sets, nullifies, and empties field") + void testSetDocxExportIconTooltip() { + exporter.setDocxExportIconTooltip("Docx Tip"); + assertEquals("Docx Tip", getPrivateField(exporter, "docxIconTooltip")); + + exporter.setDocxExportIconTooltip(null); + assertNull(getPrivateField(exporter, "docxIconTooltip")); + + exporter.setDocxExportIconTooltip(""); + assertEquals("", getPrivateField(exporter, "docxIconTooltip")); + } + + @Test + @DisplayName("setPdfExportIconTooltip: sets, nullifies, and empties field") + void testSetPdfExportIconTooltip() { + exporter.setPdfExportIconTooltip("PDF Tip"); + assertEquals("PDF Tip", getPrivateField(exporter, "pdfIconTooltip")); + + exporter.setPdfExportIconTooltip(null); + assertNull(getPrivateField(exporter, "pdfIconTooltip")); + + exporter.setPdfExportIconTooltip(""); + assertEquals("", getPrivateField(exporter, "pdfIconTooltip")); + } + + @Test + @DisplayName("setCsvExportIconTooltip: sets, nullifies, and empties field") + void testSetCsvExportIconTooltip() { + exporter.setCsvExportIconTooltip("CSV Tip"); + assertEquals("CSV Tip", getPrivateField(exporter, "csvIconTooltip")); + + exporter.setCsvExportIconTooltip(null); + assertNull(getPrivateField(exporter, "csvIconTooltip")); + + exporter.setCsvExportIconTooltip(""); + assertEquals("", getPrivateField(exporter, "csvIconTooltip")); + } + } +}