Skip to content

Commit 5e2315f

Browse files
authored
feat: add virtual list item accessible name generator (#6977)
1 parent 3e0de5d commit 5e2315f

File tree

5 files changed

+89
-1
lines changed

5 files changed

+89
-1
lines changed

vaadin-virtual-list-flow-parent/vaadin-virtual-list-flow-integration-tests/src/main/java/com/vaadin/flow/component/virtuallist/tests/VirtualListPage.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,21 @@ private void createListWithStrings() {
107107
evt -> list.setItems("Another item 1", "Another item 2"));
108108
NativeButton setEmptyList = new NativeButton("Change list 3",
109109
evt -> list.setItems());
110+
NativeButton setItemAccessibleNameGenerator = new NativeButton(
111+
"Set item accessible name generator",
112+
evt -> list.setItemAccessibleNameGenerator(
113+
item -> item.contains("2") ? null
114+
: "Accessible " + item));
110115

111116
list.setId("list-with-strings");
112117
setListWith3Items.setId("list-with-strings-3-items");
113118
setListWith2Items.setId("list-with-strings-2-items");
114119
setEmptyList.setId("list-with-strings-0-items");
120+
setItemAccessibleNameGenerator
121+
.setId("list-with-strings-accessible-name");
115122

116-
add(list, setListWith3Items, setListWith2Items, setEmptyList);
123+
add(list, setListWith3Items, setListWith2Items, setEmptyList,
124+
setItemAccessibleNameGenerator);
117125
}
118126

119127
private void createDataProviderWithStrings() {

vaadin-virtual-list-flow-parent/vaadin-virtual-list-flow-integration-tests/src/test/java/com/vaadin/flow/component/virtuallist/tests/VirtualListIT.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,27 @@ public void listWithStrings() {
6565
clickToSet0Items_listIsUpdated(listId, "list-with-strings-0-items");
6666
}
6767

68+
@Test
69+
public void accessibleName() {
70+
String listId = "list-with-strings";
71+
72+
var virtualList = $(VirtualListElement.class).id(listId);
73+
74+
var firstChildElement = virtualList
75+
.findElement(By.xpath("//*[text()='Item 1']"));
76+
77+
Assert.assertFalse(firstChildElement.hasAttribute("aria-label"));
78+
79+
clickElementWithJs("list-with-strings-accessible-name");
80+
81+
Assert.assertEquals("Accessible Item 1",
82+
firstChildElement.getAttribute("aria-label"));
83+
84+
var secondChildElement = virtualList
85+
.findElement(By.xpath("//*[text()='Item 2']"));
86+
Assert.assertFalse(secondChildElement.hasAttribute("aria-label"));
87+
}
88+
6889
@Test
6990
public void dataProviderWithStrings() {
7091
String listId = "dataprovider-with-strings";

vaadin-virtual-list-flow-parent/vaadin-virtual-list-flow/src/main/java/com/vaadin/flow/component/virtuallist/VirtualList.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,14 @@
4040
import com.vaadin.flow.data.renderer.LitRenderer;
4141
import com.vaadin.flow.data.renderer.Renderer;
4242
import com.vaadin.flow.dom.DisabledUpdateMode;
43+
import com.vaadin.flow.function.SerializableFunction;
4344
import com.vaadin.flow.function.ValueProvider;
4445
import com.vaadin.flow.internal.JsonUtils;
4546
import com.vaadin.flow.server.Command;
4647
import com.vaadin.flow.shared.Registration;
4748

4849
import elemental.json.Json;
50+
import elemental.json.JsonObject;
4951
import elemental.json.JsonValue;
5052

5153
/**
@@ -119,6 +121,8 @@ public void initialize() {
119121

120122
private Renderer<T> renderer;
121123

124+
private SerializableFunction<T, String> itemAccessibleNameGenerator = item -> null;
125+
122126
private final CompositeDataGenerator<T> dataGenerator = new CompositeDataGenerator<>();
123127
private final List<Registration> renderingRegistrations = new ArrayList<>();
124128
private transient T placeholderItem;
@@ -134,6 +138,7 @@ public void initialize() {
134138
public VirtualList() {
135139
setRenderer((ValueProvider<T, String>) String::valueOf);
136140
addAttachListener((e) -> this.setPlaceholderItem(this.placeholderItem));
141+
dataGenerator.addDataGenerator(this::generateItemAccessibleName);
137142
}
138143

139144
private void initConnector() {
@@ -144,6 +149,13 @@ private void initConnector() {
144149
getElement());
145150
}
146151

152+
private void generateItemAccessibleName(T item, JsonObject jsonObject) {
153+
var accessibleName = this.itemAccessibleNameGenerator.apply(item);
154+
if (accessibleName != null) {
155+
jsonObject.put("accessibleName", accessibleName);
156+
}
157+
}
158+
147159
@Override
148160
public void setDataProvider(DataProvider<T, ?> dataProvider) {
149161
Objects.requireNonNull(dataProvider, "The dataProvider cannot be null");
@@ -324,4 +336,32 @@ public void scrollToStart() {
324336
public void scrollToEnd() {
325337
scrollToIndex(Integer.MAX_VALUE);
326338
}
339+
340+
/**
341+
* A function that generates accessible names for virtual list items. The
342+
* function gets the item as an argument and the return value should be a
343+
* string representing that item. The result gets applied to the
344+
* corresponding virtual list child element as an `aria-label` attribute.
345+
*
346+
* @param itemAccessibleNameGenerator
347+
* the item accessible name generator to set, not {@code null}
348+
* @throws NullPointerException
349+
* if {@code itemAccessibleNameGenerator} is {@code null}
350+
*/
351+
public void setItemAccessibleNameGenerator(
352+
SerializableFunction<T, String> itemAccessibleNameGenerator) {
353+
Objects.requireNonNull(itemAccessibleNameGenerator,
354+
"Item accessible name generator can not be null");
355+
this.itemAccessibleNameGenerator = itemAccessibleNameGenerator;
356+
getDataCommunicator().reset();
357+
}
358+
359+
/**
360+
* Gets the function that generates accessible names for virtual list items.
361+
*
362+
* @return the item accessible name generator
363+
*/
364+
public SerializableFunction<T, String> getItemAccessibleNameGenerator() {
365+
return itemAccessibleNameGenerator;
366+
}
327367
}

vaadin-virtual-list-flow-parent/vaadin-virtual-list-flow/src/main/resources/META-INF/resources/frontend/virtualListConnector.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ window.Vaadin.Flow.virtualListConnector = {
1515
list.$connector = {};
1616
list.$connector.placeholderItem = { __placeholder: true };
1717

18+
list.itemAccessibleNameGenerator = (item) => item && item.accessibleName;
19+
1820
const updateRequestedItem = function () {
1921
/*
2022
* TODO virtual list seems to do a small index adjustment after scrolling

vaadin-virtual-list-flow-parent/vaadin-virtual-list-flow/src/test/java/com/vaadin/flow/component/virtuallist/tests/VirtualListTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.junit.rules.ExpectedException;
2222

2323
import com.vaadin.flow.component.virtuallist.VirtualList;
24+
import com.vaadin.flow.function.SerializableFunction;
2425

2526
public class VirtualListTest {
2627

@@ -49,4 +50,20 @@ public void paging_setPagingEnabled_throws() {
4950
exceptionRule.expectMessage("VirtualList does not support paging");
5051
virtualList.getDataCommunicator().setPagingEnabled(true);
5152
}
53+
54+
@Test
55+
public void setItemAccessibleNameGenerator_get() {
56+
VirtualList<String> virtualList = new VirtualList<>();
57+
SerializableFunction<String, String> itemAccessibleNameGenerator = item -> "Accessible "
58+
+ item;
59+
virtualList.setItemAccessibleNameGenerator(itemAccessibleNameGenerator);
60+
Assert.assertEquals(itemAccessibleNameGenerator,
61+
virtualList.getItemAccessibleNameGenerator());
62+
}
63+
64+
@Test(expected = NullPointerException.class)
65+
public void setItemAccessibleNameGenerator_nullThrows() {
66+
VirtualList<String> virtualList = new VirtualList<>();
67+
virtualList.setItemAccessibleNameGenerator(null);
68+
}
5269
}

0 commit comments

Comments
 (0)