virtualList
virtualList efficiently displays lists with custom item content by rendering only visible items.
XML Element |
|
|---|---|
Java Class |
|
Basics
virtualList is connected to a data container. By default, it shows the instance name of the entity in the container. Using the renderer handler, you can display custom content.
Alternatively, you can render items using a nested fragmentRenderer element. Refer to the Fragment Renderer section for more information.
Below is an example of using virtualList in a list view instead of dataGrid:
<data readOnly="true">
<collection id="customersDc"
class="io.jmix.uisamples.entity.Customer">
<fetchPlan extends="_base"/>
<loader id="customersDl">
<query>
<![CDATA[select e from Customer e]]>
</query>
</loader>
</collection>
</data>
<facets>
<dataLoadCoordinator auto="true"/>
</facets>
<virtualList id="virtualList" width="30em"
itemsContainer="customersDc"
alignSelf="CENTER"/>
@Autowired
protected UiComponents uiComponents;
@Autowired
protected MetadataTools metadataTools;
@Supply(to = "virtualList", subject = "renderer")
protected Renderer<Customer> virtualListRenderer() {
return new ComponentRenderer<>(customer -> {
VerticalLayout infoLayout = createVerticalLayout();
infoLayout.addClassNames("info-layout");
H4 customerName = new H4(customer.getInstanceName());
Span gradeSpan = createGradeSpan(customer.getGrade());
infoLayout.add(customerName, gradeSpan);
HorizontalLayout infoLine = createHorizontalLayout();
infoLine.setAlignItems(FlexComponent.Alignment.CENTER);
H5 emailLabel = new H5("Email:");
Span email = new Span(customer.getEmail());
infoLine.add(emailLabel, email);
HorizontalLayout infoLine2 = createHorizontalLayout();
H5 ageLabel = new H5("Age:");
Span age = new Span(String.valueOf(customer.getAge()));
infoLine2.add(ageLabel, age);
VerticalLayout additionalInfoLayout = createVerticalLayout();
additionalInfoLayout.add(infoLine, infoLine2);
JmixDetails infoDetails = uiComponents.create(JmixDetails.class);
infoDetails.setSummaryText("Additional information");
infoDetails.add(additionalInfoLayout);
infoLayout.add(infoDetails, new Hr());
return infoLayout;
});
}
protected VerticalLayout createVerticalLayout() {
VerticalLayout layout = uiComponents.create(VerticalLayout.class);
layout.setSpacing(false);
layout.setPadding(false);
return layout;
}
protected HorizontalLayout createHorizontalLayout() {
HorizontalLayout layout = uiComponents.create(HorizontalLayout.class);
layout.setPadding(false);
return layout;
}
protected Span createGradeSpan(@Nullable CustomerGrade grade) {
Span gradeSpan = new Span(metadataTools.format(grade));
if (grade != null) {
ThemeList gradeThemeList = gradeSpan.getElement().getThemeList();
switch (grade) {
case STANDARD -> gradeThemeList.add("badge contrast");
case PREMIUM -> gradeThemeList.add("badge primary");
default -> gradeThemeList.add("badge");
}
}
return gradeSpan;
}
Note that items in virtualList can’t be selected or navigated using the keyboard. The standard List Component Actions will not work with virtualList. If needed, define your own actions for CRUD operations.
Custom Item Renderer
A component renderer can create custom content for every list item. This example renders each enum value with a label and an icon button.
<virtualList id="virtualList" width="15em"
itemsEnum="io.jmix.uisamples.entity.Day"
alignSelf="CENTER"/>
@Autowired
protected UiComponents uiComponents;
@Autowired
protected MetadataTools metadataTools;
@Autowired
protected Notifications notifications;
@Supply(to = "virtualList", subject = "renderer")
protected Renderer<Day> virtualListRenderer() {
return new ComponentRenderer<>(this::createDayRenderer);
}
protected HorizontalLayout createDayRenderer(Day day) {
HorizontalLayout layout = uiComponents.create(HorizontalLayout.class);
layout.setPadding(false);
String dayValue = metadataTools.format(day);
H3 label = new H3(dayValue);
JmixButton button = uiComponents.create(JmixButton.class);
button.addThemeVariants(ButtonVariant.LUMO_ICON);
button.addClassName(StyleUtility.Button.LINK_BUTTON);
button.addClickListener(__ -> showNotification(dayValue));
Icon icon = switch (day) {
case MONDAY -> VaadinIcon.BRIEFCASE.create();
case TUESDAY -> VaadinIcon.LINE_CHART.create();
case WEDNESDAY -> VaadinIcon.TROPHY.create();
case THURSDAY -> VaadinIcon.GROUP.create();
case FRIDAY -> VaadinIcon.CASH.create();
case SATURDAY -> VaadinIcon.GLASS.create();
case SUNDAY -> VaadinIcon.BED.create();
};
button.setIcon(icon);
layout.add(button, label);
return layout;
}
protected void showNotification(String dayValue) {
String message = String.format("You've clicked on %s!", StringUtils.capitalize(dayValue));
notifications.show(message);
}
Inline Editor
You can combine a component renderer, a data container, and detail dialogs to create and edit list items without a separate grid.
<data>
<collection id="foodDc" class="io.jmix.uisamples.entity.Food">
<loader id="foodDl"/>
</collection>
</data>
<hbox width="100%" padding="false" alignItems="CENTER">
<h2 id="menusItemsTabTitle" text="msg://foodListTitle"/>
<button id="addBtn"
classNames="virtual-list-inline-editor-add-button"
themeNames="primary"
icon="PLUS"
text="msg:///actions.Add"/>
</hbox>
<html id="foodDescription" content="msg://foodListDescription"/>
<virtualList id="foodList" itemsContainer="foodDc" width="100%"/>
@Autowired
private Messages messages;
@Autowired
private DialogWindows dialogWindows;
@ViewComponent
private DataContext dataContext;
@ViewComponent
private CollectionContainer<Food> foodDc;
@Supply(to = "foodList", subject = "renderer")
public Renderer<Food> foodListRenderer() {
return new ComponentRenderer<>(item -> {
HorizontalLayout rootCardLayout = new HorizontalLayout();
rootCardLayout.setMargin(true);
VerticalLayout infoLayout = new VerticalLayout();
infoLayout.setSpacing(false);
infoLayout.setPadding(false);
infoLayout.setWidth("30%");
Avatar avatar = new Avatar();
avatar.addThemeVariants(AvatarVariant.XLARGE);
if (item.getIcon() != null && item.getIcon().length > 0) {
String iconFileName = "%s.png".formatted(item.getTitle());
InputStreamDownloadHandler handler = DownloadHandler.fromInputStream(event -> {
byte[] icon = item.getIcon();
ByteArrayInputStream inputStream = new ByteArrayInputStream(item.getIcon());
return new DownloadResponse(inputStream, iconFileName, "image/png", icon.length);
});
avatar.setImageHandler(handler);
}
VerticalLayout verticalLayout = new VerticalLayout();
verticalLayout.setWidthFull();
HorizontalLayout itemDetailLayout = new HorizontalLayout();
itemDetailLayout.add(new Text(item.getDescription()));
itemDetailLayout.add(new Html(
messages.formatMessage(getClass(), "foodListItemDescription", item.getPrice()))
);
itemDetailLayout.setPadding(false);
itemDetailLayout.setAlignItems(FlexComponent.Alignment.CENTER);
infoLayout.add(new Html(messages.formatMessage(getClass(), "foodItemTitle", item.getTitle())));
infoLayout.add(itemDetailLayout);
VerticalLayout buttonsPanel = new VerticalLayout();
buttonsPanel.setWidth("AUTO");
buttonsPanel.setPadding(false);
buttonsPanel.setSpacing(false);
Button detailButton = new Button(new Icon(VaadinIcon.PENCIL));
detailButton.setText(messages.getMessage("actions.Edit"));
detailButton.addClickListener(e -> dialogWindows.detail(this, Food.class)
.withViewClass(FoodDetailView.class)
.editEntity(item)
.withAfterCloseListener(closeEvent -> {
if (closeEvent.closedWith(StandardOutcome.SAVE)) {
foodDc.replaceItem(closeEvent.getSource().getView().getEditedEntity());
}
})
.open());
detailButton.addClassName(StyleUtility.Button.LINK_BUTTON);
Button removeButton = new Button(new Icon(VaadinIcon.TRASH));
removeButton.setText(messages.getMessage("actions.Remove"));
removeButton.addThemeVariants(ButtonVariant.ERROR);
removeButton.addClassName(StyleUtility.Button.LINK_BUTTON);
removeButton.addClickListener(e -> {
foodDc.getMutableItems().remove(item);
dataContext.remove(dataContext.merge(item));
});
buttonsPanel.add(detailButton, removeButton);
rootCardLayout.add(avatar, infoLayout, buttonsPanel);
return rootCardLayout;
});
}
@Subscribe(id = "addBtn", subject = "clickListener")
public void onAddBtnClick(final ClickEvent<JmixButton> event) {
dialogWindows.detail(this, Food.class)
.withViewClass(FoodDetailView.class)
.newEntity()
.withAfterCloseListener(closeEvent -> {
if (closeEvent.closedWith(StandardOutcome.SAVE)) {
foodDc.replaceItem(closeEvent.getSource().getView().getEditedEntity());
}
})
.open();
}
Attributes
The following attributes are specific to virtualList:
| Name | Description | Default |
|---|---|---|
Sets the name of a data container which contains a list of items. |
— |
|
Defines the enumeration class for creating a list of items. |
— |
The following shared attributes are supported by virtualList:
Handlers
The following handlers are specific to virtualList:
| Name | Description |
|---|---|
A custom renderer for list items can only be set in the view controller using Java code. |
|
It’s possible to specify a renderer using |
The following shared handlers are supported by virtualList: