Skip to content

Commit

Permalink
feat: add virtual list item accessible name generator (#6977)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomivirkki authored Dec 20, 2024
1 parent 3e0de5d commit 5e2315f
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,21 @@ private void createListWithStrings() {
evt -> list.setItems("Another item 1", "Another item 2"));
NativeButton setEmptyList = new NativeButton("Change list 3",
evt -> list.setItems());
NativeButton setItemAccessibleNameGenerator = new NativeButton(
"Set item accessible name generator",
evt -> list.setItemAccessibleNameGenerator(
item -> item.contains("2") ? null
: "Accessible " + item));

list.setId("list-with-strings");
setListWith3Items.setId("list-with-strings-3-items");
setListWith2Items.setId("list-with-strings-2-items");
setEmptyList.setId("list-with-strings-0-items");
setItemAccessibleNameGenerator
.setId("list-with-strings-accessible-name");

add(list, setListWith3Items, setListWith2Items, setEmptyList);
add(list, setListWith3Items, setListWith2Items, setEmptyList,
setItemAccessibleNameGenerator);
}

private void createDataProviderWithStrings() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,27 @@ public void listWithStrings() {
clickToSet0Items_listIsUpdated(listId, "list-with-strings-0-items");
}

@Test
public void accessibleName() {
String listId = "list-with-strings";

var virtualList = $(VirtualListElement.class).id(listId);

var firstChildElement = virtualList
.findElement(By.xpath("//*[text()='Item 1']"));

Assert.assertFalse(firstChildElement.hasAttribute("aria-label"));

clickElementWithJs("list-with-strings-accessible-name");

Assert.assertEquals("Accessible Item 1",
firstChildElement.getAttribute("aria-label"));

var secondChildElement = virtualList
.findElement(By.xpath("//*[text()='Item 2']"));
Assert.assertFalse(secondChildElement.hasAttribute("aria-label"));
}

@Test
public void dataProviderWithStrings() {
String listId = "dataprovider-with-strings";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,14 @@
import com.vaadin.flow.data.renderer.LitRenderer;
import com.vaadin.flow.data.renderer.Renderer;
import com.vaadin.flow.dom.DisabledUpdateMode;
import com.vaadin.flow.function.SerializableFunction;
import com.vaadin.flow.function.ValueProvider;
import com.vaadin.flow.internal.JsonUtils;
import com.vaadin.flow.server.Command;
import com.vaadin.flow.shared.Registration;

import elemental.json.Json;
import elemental.json.JsonObject;
import elemental.json.JsonValue;

/**
Expand Down Expand Up @@ -119,6 +121,8 @@ public void initialize() {

private Renderer<T> renderer;

private SerializableFunction<T, String> itemAccessibleNameGenerator = item -> null;

private final CompositeDataGenerator<T> dataGenerator = new CompositeDataGenerator<>();
private final List<Registration> renderingRegistrations = new ArrayList<>();
private transient T placeholderItem;
Expand All @@ -134,6 +138,7 @@ public void initialize() {
public VirtualList() {
setRenderer((ValueProvider<T, String>) String::valueOf);
addAttachListener((e) -> this.setPlaceholderItem(this.placeholderItem));
dataGenerator.addDataGenerator(this::generateItemAccessibleName);
}

private void initConnector() {
Expand All @@ -144,6 +149,13 @@ private void initConnector() {
getElement());
}

private void generateItemAccessibleName(T item, JsonObject jsonObject) {
var accessibleName = this.itemAccessibleNameGenerator.apply(item);
if (accessibleName != null) {
jsonObject.put("accessibleName", accessibleName);
}
}

@Override
public void setDataProvider(DataProvider<T, ?> dataProvider) {
Objects.requireNonNull(dataProvider, "The dataProvider cannot be null");
Expand Down Expand Up @@ -324,4 +336,32 @@ public void scrollToStart() {
public void scrollToEnd() {
scrollToIndex(Integer.MAX_VALUE);
}

/**
* A function that generates accessible names for virtual list items. The
* function gets the item as an argument and the return value should be a
* string representing that item. The result gets applied to the
* corresponding virtual list child element as an `aria-label` attribute.
*
* @param itemAccessibleNameGenerator
* the item accessible name generator to set, not {@code null}
* @throws NullPointerException
* if {@code itemAccessibleNameGenerator} is {@code null}
*/
public void setItemAccessibleNameGenerator(
SerializableFunction<T, String> itemAccessibleNameGenerator) {
Objects.requireNonNull(itemAccessibleNameGenerator,
"Item accessible name generator can not be null");
this.itemAccessibleNameGenerator = itemAccessibleNameGenerator;
getDataCommunicator().reset();
}

/**
* Gets the function that generates accessible names for virtual list items.
*
* @return the item accessible name generator
*/
public SerializableFunction<T, String> getItemAccessibleNameGenerator() {
return itemAccessibleNameGenerator;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ window.Vaadin.Flow.virtualListConnector = {
list.$connector = {};
list.$connector.placeholderItem = { __placeholder: true };

list.itemAccessibleNameGenerator = (item) => item && item.accessibleName;

const updateRequestedItem = function () {
/*
* TODO virtual list seems to do a small index adjustment after scrolling
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.junit.rules.ExpectedException;

import com.vaadin.flow.component.virtuallist.VirtualList;
import com.vaadin.flow.function.SerializableFunction;

public class VirtualListTest {

Expand Down Expand Up @@ -49,4 +50,20 @@ public void paging_setPagingEnabled_throws() {
exceptionRule.expectMessage("VirtualList does not support paging");
virtualList.getDataCommunicator().setPagingEnabled(true);
}

@Test
public void setItemAccessibleNameGenerator_get() {
VirtualList<String> virtualList = new VirtualList<>();
SerializableFunction<String, String> itemAccessibleNameGenerator = item -> "Accessible "
+ item;
virtualList.setItemAccessibleNameGenerator(itemAccessibleNameGenerator);
Assert.assertEquals(itemAccessibleNameGenerator,
virtualList.getItemAccessibleNameGenerator());
}

@Test(expected = NullPointerException.class)
public void setItemAccessibleNameGenerator_nullThrows() {
VirtualList<String> virtualList = new VirtualList<>();
virtualList.setItemAccessibleNameGenerator(null);
}
}

0 comments on commit 5e2315f

Please sign in to comment.