Skip to content

Commit 92c78c5

Browse files
feat: Add tooltip customization for export icons
Implements the ability to set default and specific tooltips for the export icons (Excel, DOCX, PDF, CSV) in GridExporter. Key changes: - Added `setDefaultExportIconTooltip(String)` to set a general tooltip for all export icons. - Added specific setters like `setExcelExportIconTooltip(String)` to customize tooltips for individual export types. Specific tooltips override the default. - Tooltips are applied to the `Anchor` components in the `createFor` method. Passing `null` as a tooltip value effectively clears the tooltip (or falls back to default), while an empty string `""` sets an intentionally blank tooltip. - Unit tests verify the internal state of tooltip configurations. Direct DOM testing of tooltip attributes was omitted to avoid adding test-specific fields to the component. - Updated JavaDocs and README.md with details on the new feature. Fixes #114
1 parent e0362ab commit 92c78c5

File tree

3 files changed

+337
-5
lines changed

3 files changed

+337
-5
lines changed

README.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,81 @@ Custom templates can be added anywhere in the classpath (ie: src/main/resources)
102102

103103
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.
104104

105+
## Customizing Export Icon Tooltips
106+
107+
The GridExporter addon allows you to customize the tooltip text displayed when a user hovers over the export icons (Excel, DOCX, PDF, CSV).
108+
109+
You can set a default tooltip that applies to all export icons, or provide specific tooltips for individual icons.
110+
111+
### Setting Tooltips
112+
113+
1. **Default Tooltip:**
114+
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.
115+
116+
```java
117+
GridExporter<Person> exporter = GridExporter.createFor(grid);
118+
exporter.setDefaultExportIconTooltip("Export grid data");
119+
```
120+
121+
2. **Specific Tooltips:**
122+
You can set a unique tooltip for each export format using dedicated methods:
123+
* `setExcelExportIconTooltip(String tooltipText)`
124+
* `setDocxExportIconTooltip(String tooltipText)`
125+
* `setPdfExportIconTooltip(String tooltipText)`
126+
* `setCsvExportIconTooltip(String tooltipText)`
127+
128+
A specific tooltip will always override the default tooltip for that particular icon.
129+
130+
```java
131+
// Specific tooltip for Excel, other icons (DOCX, PDF, CSV) will use the default if set.
132+
exporter.setExcelExportIconTooltip("Export to Excel spreadsheet (.xlsx)");
133+
```
134+
135+
### Behavior and Precedence
136+
137+
* If a specific tooltip is set for an icon (e.g., `setExcelExportIconTooltip("Specific")`), it is always used for that icon.
138+
* If a specific tooltip is *not* set for an icon, or if it's set to `null`, but a default tooltip is set (e.g., `setDefaultExportIconTooltip("Default")`), the default tooltip is used.
139+
* If neither a specific nor a default tooltip is set (or both are `null`) for an icon, it will have no tooltip (the `title` attribute will be removed from the anchor).
140+
141+
### Removing or Clearing Tooltips
142+
143+
* **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.
144+
* **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.
145+
146+
### Examples
147+
148+
```java
149+
GridExporter<Person> exporter = GridExporter.createFor(grid);
150+
151+
// Example 1: Set a default tooltip for all export icons
152+
exporter.setDefaultExportIconTooltip("Export grid data");
153+
// All icons will show "Export grid data"
154+
155+
// Example 2: Set a specific tooltip for the Excel export icon
156+
// This will override the default for the Excel icon only.
157+
exporter.setExcelExportIconTooltip("Export to Excel spreadsheet (.xlsx)");
158+
// Excel icon: "Export to Excel spreadsheet (.xlsx)"
159+
// Other icons: "Export grid data"
160+
161+
// Example 3: Set a specific tooltip for PDF and a default for others
162+
exporter.setDefaultExportIconTooltip("Download file");
163+
exporter.setPdfExportIconTooltip("Download as PDF document");
164+
// Excel, DOCX, CSV icons: "Download file"
165+
// PDF icon: "Download as PDF document"
166+
167+
// Example 4: Clear specific tooltip for CSV; it falls back to default
168+
exporter.setDefaultExportIconTooltip("Default export tooltip");
169+
exporter.setExcelExportIconTooltip("Excel specific"); // Keep one specific for contrast
170+
exporter.setCsvExportIconTooltip(null);
171+
// CSV icon: "Default export tooltip"
172+
// Excel icon: "Excel specific"
173+
// Other icons (DOCX, PDF): "Default export tooltip"
174+
175+
// Example 5: Intentionally blank tooltip for DOCX
176+
exporter.setDocxExportIconTooltip("");
177+
// DOCX icon: Will have a title attribute, but it will be empty.
178+
// Other icons: (Depends on default or other specific settings from previous examples)
179+
```
105180
106181
## Special configuration when using Spring
107182

src/main/java/com/flowingcode/vaadin/addons/gridexporter/GridExporter.java

Lines changed: 115 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,12 @@ public class GridExporter<T> implements Serializable {
137137

138138
private SerializableSupplier<Charset> csvCharset;
139139

140+
private String defaultExportIconTooltip = null;
141+
private String excelIconTooltip = null;
142+
private String docxIconTooltip = null;
143+
private String pdfIconTooltip = null;
144+
private String csvIconTooltip = null;
145+
140146
private GridExporter(Grid<T> grid) {
141147
this.grid = grid;
142148
}
@@ -160,6 +166,17 @@ public static <T> GridExporter<T> createFor(
160166
.setHref(exporter.getExcelStreamResource(excelCustomTemplate)
161167
.forComponent(excelLink));
162168
excelLink.getElement().setAttribute("download", true);
169+
String finalExcelTooltip = null;
170+
if (exporter.excelIconTooltip != null) {
171+
finalExcelTooltip = exporter.excelIconTooltip;
172+
} else if (exporter.defaultExportIconTooltip != null) {
173+
finalExcelTooltip = exporter.defaultExportIconTooltip;
174+
}
175+
if (finalExcelTooltip != null && !finalExcelTooltip.isEmpty()) {
176+
excelLink.setTitle(finalExcelTooltip);
177+
} else {
178+
excelLink.setTitle(null);
179+
}
163180
footerToolbar.add(
164181
new FooterToolbarItem(excelLink, FooterToolbarItemPosition.EXPORT_BUTTON));
165182
}
@@ -168,21 +185,54 @@ public static <T> GridExporter<T> createFor(
168185
docLink.setHref(
169186
exporter.getDocxStreamResource(docxCustomTemplate).forComponent(docLink));
170187
docLink.getElement().setAttribute("download", true);
188+
String finalDocxTooltip = null;
189+
if (exporter.docxIconTooltip != null) {
190+
finalDocxTooltip = exporter.docxIconTooltip;
191+
} else if (exporter.defaultExportIconTooltip != null) {
192+
finalDocxTooltip = exporter.defaultExportIconTooltip;
193+
}
194+
if (finalDocxTooltip != null && !finalDocxTooltip.isEmpty()) {
195+
docLink.setTitle(finalDocxTooltip);
196+
} else {
197+
docLink.setTitle(null);
198+
}
171199
footerToolbar
172200
.add(new FooterToolbarItem(docLink, FooterToolbarItemPosition.EXPORT_BUTTON));
173201
}
174202
if (exporter.isPdfExportEnabled()) {
175-
Anchor docLink = new Anchor("", FontAwesome.Regular.FILE_PDF.create());
176-
docLink.setHref(
177-
exporter.getPdfStreamResource(docxCustomTemplate).forComponent(docLink));
178-
docLink.getElement().setAttribute("download", true);
203+
Anchor pdfLink = new Anchor("", FontAwesome.Regular.FILE_PDF.create()); // Renamed to pdfLink
204+
pdfLink.setHref(
205+
exporter.getPdfStreamResource(docxCustomTemplate).forComponent(pdfLink));
206+
pdfLink.getElement().setAttribute("download", true);
207+
String finalPdfTooltip = null;
208+
if (exporter.pdfIconTooltip != null) {
209+
finalPdfTooltip = exporter.pdfIconTooltip;
210+
} else if (exporter.defaultExportIconTooltip != null) {
211+
finalPdfTooltip = exporter.defaultExportIconTooltip;
212+
}
213+
if (finalPdfTooltip != null && !finalPdfTooltip.isEmpty()) {
214+
pdfLink.setTitle(finalPdfTooltip);
215+
} else {
216+
pdfLink.setTitle(null);
217+
}
179218
footerToolbar
180-
.add(new FooterToolbarItem(docLink, FooterToolbarItemPosition.EXPORT_BUTTON));
219+
.add(new FooterToolbarItem(pdfLink, FooterToolbarItemPosition.EXPORT_BUTTON));
181220
}
182221
if (exporter.isCsvExportEnabled()) {
183222
Anchor csvLink = new Anchor("", FontAwesome.Regular.FILE_LINES.create());
184223
csvLink.setHref(exporter.getCsvStreamResource());
185224
csvLink.getElement().setAttribute("download", true);
225+
String finalCsvTooltip = null;
226+
if (exporter.csvIconTooltip != null) {
227+
finalCsvTooltip = exporter.csvIconTooltip;
228+
} else if (exporter.defaultExportIconTooltip != null) {
229+
finalCsvTooltip = exporter.defaultExportIconTooltip;
230+
}
231+
if (finalCsvTooltip != null && !finalCsvTooltip.isEmpty()) {
232+
csvLink.setTitle(finalCsvTooltip);
233+
} else {
234+
csvLink.setTitle(null);
235+
}
186236
footerToolbar
187237
.add(new FooterToolbarItem(csvLink, FooterToolbarItemPosition.EXPORT_BUTTON));
188238
}
@@ -812,4 +862,64 @@ public void setCsvCharset(SerializableSupplier<Charset> charset) {
812862
csvCharset = charset;
813863
}
814864

865+
/**
866+
* Sets the default tooltip text for all export icons.
867+
* This tooltip will be used for any export icon that does not have a specific tooltip set
868+
* via methods like {@link #setExcelExportIconTooltip(String)}.
869+
*
870+
* @param tooltipText The text to display as the tooltip. Passing {@code null} removes the
871+
* default tooltip. An empty string ({@code ""}) should result in the tooltip being cleared.
872+
*/
873+
public void setDefaultExportIconTooltip(String tooltipText) {
874+
this.defaultExportIconTooltip = tooltipText;
875+
}
876+
877+
/**
878+
* Sets the tooltip text for the Excel export icon.
879+
* This overrides any tooltip set by {@link #setDefaultExportIconTooltip(String)} for the Excel icon.
880+
*
881+
* @param tooltipText The text to display as the tooltip for the Excel icon. Passing {@code null}
882+
* removes this specific tooltip (the default tooltip may then apply). An empty string
883+
* ({@code ""}) should result in the tooltip being cleared.
884+
*/
885+
public void setExcelExportIconTooltip(String tooltipText) {
886+
this.excelIconTooltip = tooltipText;
887+
}
888+
889+
/**
890+
* Sets the tooltip text for the DOCX (Word) export icon.
891+
* This overrides any tooltip set by {@link #setDefaultExportIconTooltip(String)} for the DOCX icon.
892+
*
893+
* @param tooltipText The text to display as the tooltip for the DOCX icon. Passing {@code null}
894+
* removes this specific tooltip (the default tooltip may then apply). An empty string
895+
* ({@code ""}) should result in the tooltip being cleared.
896+
*/
897+
public void setDocxExportIconTooltip(String tooltipText) {
898+
this.docxIconTooltip = tooltipText;
899+
}
900+
901+
/**
902+
* Sets the tooltip text for the PDF export icon.
903+
* This overrides any tooltip set by {@link #setDefaultExportIconTooltip(String)} for the PDF icon.
904+
*
905+
* @param tooltipText The text to display as the tooltip for the PDF icon. Passing {@code null}
906+
* removes this specific tooltip (the default tooltip may then apply). An empty string
907+
* ({@code ""}) should result in the tooltip being cleared.
908+
*/
909+
public void setPdfExportIconTooltip(String tooltipText) {
910+
this.pdfIconTooltip = tooltipText;
911+
}
912+
913+
/**
914+
* Sets the tooltip text for the CSV export icon.
915+
* This overrides any tooltip set by {@link #setDefaultExportIconTooltip(String)} for the CSV icon.
916+
*
917+
* @param tooltipText The text to display as the tooltip for the CSV icon. Passing {@code null}
918+
* removes this specific tooltip (the default tooltip may then apply). An empty string
919+
* ({@code ""}) should result in the tooltip being cleared.
920+
*/
921+
public void setCsvExportIconTooltip(String tooltipText) {
922+
this.csvIconTooltip = tooltipText;
923+
}
924+
815925
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package com.flowingcode.vaadin.addons.gridexporter.test;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNull;
5+
import static org.mockito.Mockito.mock;
6+
import static org.mockito.Mockito.when;
7+
8+
import com.flowingcode.vaadin.addons.gridexporter.GridExporter;
9+
import com.vaadin.flow.component.UI;
10+
import com.vaadin.flow.component.grid.Grid;
11+
import com.vaadin.flow.dom.Element;
12+
import java.lang.reflect.Field;
13+
import java.util.Optional;
14+
import org.junit.jupiter.api.BeforeEach;
15+
import org.junit.jupiter.api.DisplayName;
16+
import org.junit.jupiter.api.Nested;
17+
import org.junit.jupiter.api.Test;
18+
19+
@DisplayName("GridExporter Internal Tooltip State Tests")
20+
class GridExporterTest {
21+
22+
private Grid<Object> mockGrid;
23+
private GridExporter<Object> exporter;
24+
25+
@SuppressWarnings("unchecked")
26+
private static <T> T getPrivateField(Object obj, String fieldName) {
27+
try {
28+
Field field = obj.getClass().getDeclaredField(fieldName);
29+
field.setAccessible(true);
30+
return (T) field.get(obj);
31+
} catch (NoSuchFieldException | IllegalAccessException e) {
32+
throw new RuntimeException("Failed to get private field", e);
33+
}
34+
}
35+
36+
@BeforeEach
37+
void setUp() {
38+
mockGrid = mock(Grid.class);
39+
Element mockElement = mock(Element.class);
40+
when(mockGrid.getElement()).thenReturn(mockElement);
41+
// Mock UI as it might be accessed by createFor or related methods
42+
UI mockUi = mock(UI.class);
43+
when(mockGrid.getUI()).thenReturn(Optional.of(mockUi));
44+
45+
exporter = GridExporter.createFor(mockGrid);
46+
}
47+
48+
@Nested
49+
@DisplayName("Tooltip Field Initialization Tests")
50+
class InitializationTests {
51+
52+
@Test
53+
@DisplayName("All tooltip fields should be null initially")
54+
void testInitialTooltipFieldsAreNull() {
55+
assertNull(getPrivateField(exporter, "defaultExportIconTooltip"), "defaultExportIconTooltip should be null");
56+
assertNull(getPrivateField(exporter, "excelIconTooltip"), "excelIconTooltip should be null");
57+
assertNull(getPrivateField(exporter, "docxIconTooltip"), "docxIconTooltip should be null");
58+
assertNull(getPrivateField(exporter, "pdfIconTooltip"), "pdfIconTooltip should be null");
59+
assertNull(getPrivateField(exporter, "csvIconTooltip"), "csvIconTooltip should be null");
60+
}
61+
}
62+
63+
@Nested
64+
@DisplayName("setDefaultExportIconTooltip Setter Tests")
65+
class DefaultTooltipSetterTests {
66+
67+
@Test
68+
@DisplayName("Should set defaultExportIconTooltip to a given string")
69+
void testSetDefaultTooltip() {
70+
exporter.setDefaultExportIconTooltip("Test Default");
71+
assertEquals("Test Default", getPrivateField(exporter, "defaultExportIconTooltip"));
72+
}
73+
74+
@Test
75+
@DisplayName("Should set defaultExportIconTooltip to null")
76+
void testSetDefaultTooltipToNull() {
77+
exporter.setDefaultExportIconTooltip("Initial Value"); // Set a value first
78+
exporter.setDefaultExportIconTooltip(null);
79+
assertNull(getPrivateField(exporter, "defaultExportIconTooltip"));
80+
}
81+
82+
@Test
83+
@DisplayName("Should set defaultExportIconTooltip to an empty string")
84+
void testSetDefaultTooltipToEmptyString() {
85+
exporter.setDefaultExportIconTooltip("Initial Value"); // Set a value first
86+
exporter.setDefaultExportIconTooltip("");
87+
assertEquals("", getPrivateField(exporter, "defaultExportIconTooltip"));
88+
}
89+
}
90+
91+
@Nested
92+
@DisplayName("Specific Tooltip Setter Tests")
93+
class SpecificTooltipSetterTests {
94+
95+
@Test
96+
@DisplayName("setExcelExportIconTooltip: sets, nullifies, and empties field")
97+
void testSetExcelExportIconTooltip() {
98+
exporter.setExcelExportIconTooltip("Excel Tip");
99+
assertEquals("Excel Tip", getPrivateField(exporter, "excelIconTooltip"));
100+
101+
exporter.setExcelExportIconTooltip(null);
102+
assertNull(getPrivateField(exporter, "excelIconTooltip"));
103+
104+
exporter.setExcelExportIconTooltip("");
105+
assertEquals("", getPrivateField(exporter, "excelIconTooltip"));
106+
}
107+
108+
@Test
109+
@DisplayName("setDocxExportIconTooltip: sets, nullifies, and empties field")
110+
void testSetDocxExportIconTooltip() {
111+
exporter.setDocxExportIconTooltip("Docx Tip");
112+
assertEquals("Docx Tip", getPrivateField(exporter, "docxIconTooltip"));
113+
114+
exporter.setDocxExportIconTooltip(null);
115+
assertNull(getPrivateField(exporter, "docxIconTooltip"));
116+
117+
exporter.setDocxExportIconTooltip("");
118+
assertEquals("", getPrivateField(exporter, "docxIconTooltip"));
119+
}
120+
121+
@Test
122+
@DisplayName("setPdfExportIconTooltip: sets, nullifies, and empties field")
123+
void testSetPdfExportIconTooltip() {
124+
exporter.setPdfExportIconTooltip("PDF Tip");
125+
assertEquals("PDF Tip", getPrivateField(exporter, "pdfIconTooltip"));
126+
127+
exporter.setPdfExportIconTooltip(null);
128+
assertNull(getPrivateField(exporter, "pdfIconTooltip"));
129+
130+
exporter.setPdfExportIconTooltip("");
131+
assertEquals("", getPrivateField(exporter, "pdfIconTooltip"));
132+
}
133+
134+
@Test
135+
@DisplayName("setCsvExportIconTooltip: sets, nullifies, and empties field")
136+
void testSetCsvExportIconTooltip() {
137+
exporter.setCsvExportIconTooltip("CSV Tip");
138+
assertEquals("CSV Tip", getPrivateField(exporter, "csvIconTooltip"));
139+
140+
exporter.setCsvExportIconTooltip(null);
141+
assertNull(getPrivateField(exporter, "csvIconTooltip"));
142+
143+
exporter.setCsvExportIconTooltip("");
144+
assertEquals("", getPrivateField(exporter, "csvIconTooltip"));
145+
}
146+
}
147+
}

0 commit comments

Comments
 (0)