Virtual List
API: TypeScript / Java
Source: TypeScript / Java
Virtual List allows you to render a long list of items inside a scrollable container without sacrificing performance. Each item is rendered on the fly as the user scrolls the list.
To use the component, you need to assign it a set of data items and a renderer that’s used to render each individual data item. The height of an item is determined by its content and can change dynamically.
Open in a
new tab
new tab
Source code
virtual-list-basic.ts
import { html, LitElement, css } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { live } from 'lit/directives/live.js';
import '@vaadin/avatar';
import '@vaadin/details';
import type { Details } from '@vaadin/details';
import '@vaadin/horizontal-layout';
import '@vaadin/vertical-layout';
import '@vaadin/virtual-list';
import { virtualListRenderer } from '@vaadin/virtual-list/lit.js';
import type { VirtualListLitRenderer } from '@vaadin/virtual-list/lit.js';
import { getPeople } from 'Frontend/demo/domain/DataService';
import type Person from 'Frontend/generated/com/vaadin/demo/domain/Person';
import { applyTheme } from 'Frontend/generated/theme';
@customElement('virtual-list-basic')
export class Example extends LitElement {
static override styles = css`
vaadin-avatar {
height: 64px;
width: 64px;
}
`;
protected override createRenderRoot() {
const root = super.createRenderRoot();
// Apply custom theme (only supported if your app uses one)
applyTheme(root);
return root;
}
@state()
private people?: Person[];
private expandedPeople = new Set<Person>();
protected override async firstUpdated() {
const { people } = await getPeople();
this.people = people;
}
private personCardRenderer: VirtualListLitRenderer<Person> = (person) => html`
<vaadin-horizontal-layout theme="spacing margin">
<vaadin-avatar
.img="${person.pictureUrl}"
.name="${`${person.firstName} ${person.lastName}`}"
></vaadin-avatar>
<vaadin-vertical-layout>
<b>${person.firstName} ${person.lastName}</b>
<span>${person.profession}</span>
<vaadin-details
.opened="${live(this.expandedPeople.has(person))}"
@click="${(e: Event) => {
const details = e.currentTarget as Details;
if (details.opened) {
this.expandedPeople.add(person);
} else {
this.expandedPeople.delete(person);
}
}}"
>
<div slot="summary">Contact information</div>
<vaadin-vertical-layout>
<span>${person.email}</span>
<span>${person.address.phone}</span>
</vaadin-vertical-layout>
</vaadin-details>
</vaadin-vertical-layout>
</vaadin-horizontal-layout>
`;
protected override render() {
return html`
<!-- tag::snippet[] -->
<vaadin-virtual-list
.items="${this.people}"
${virtualListRenderer(this.personCardRenderer, [])}
></vaadin-virtual-list>
<!-- end::snippet[] -->
`;
}
}
Person.ts
import Address from './Address';
/**
* This module is generated from com.vaadin.demo.domain.Person.
* All changes to this file are overridden. Consider editing the corresponding Java file if necessary.
* @see {@link file:///srv/jenkins/workspace/docs/docs-site-v23/docs/src/main/java/com/vaadin/demo/domain/Person.java}
*/
export default interface Person {
firstName: string;
lastName: string;
email: string;
birthday: string;
id: number;
subscriber: boolean;
membership: string;
pictureUrl: string;
profession: string;
address: Address;
managerId?: number;
manager: boolean;
status: string;
}
Person.ts
VirtualListBasic.java
package com.vaadin.demo.component.virtuallist;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.avatar.Avatar;
import com.vaadin.flow.component.details.Details;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.virtuallist.VirtualList;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.dom.ElementFactory;
import com.vaadin.flow.router.Route;
import java.util.List;
import com.vaadin.demo.domain.DataService;
import com.vaadin.demo.domain.Person;
@Route("virtual-list-basic")
public class VirtualListBasic extends Div {
private List<Person> people = DataService.getPeople();
private ComponentRenderer<Component, Person> personCardRenderer = new ComponentRenderer<>(
person -> {
HorizontalLayout cardLayout = new HorizontalLayout();
cardLayout.setMargin(true);
Avatar avatar = new Avatar(person.getFullName(),
person.getPictureUrl());
avatar.setHeight("64px");
avatar.setWidth("64px");
VerticalLayout infoLayout = new VerticalLayout();
infoLayout.setSpacing(false);
infoLayout.setPadding(false);
infoLayout.getElement().appendChild(
ElementFactory.createStrong(person.getFullName()));
infoLayout.add(new Div(new Text(person.getProfession())));
VerticalLayout contactLayout = new VerticalLayout();
contactLayout.setSpacing(false);
contactLayout.setPadding(false);
contactLayout.add(new Div(new Text(person.getEmail())));
contactLayout
.add(new Div(new Text(person.getAddress().getPhone())));
infoLayout
.add(new Details("Contact information", contactLayout));
cardLayout.add(avatar, infoLayout);
return cardLayout;
});
public VirtualListBasic() {
// tag::snippet[]
VirtualList<Person> list = new VirtualList<>();
list.setItems(people);
list.setRenderer(personCardRenderer);
add(list);
// end::snippet[]
}
}
Person.java
package com.vaadin.demo.domain;
import java.util.Date;
import javax.annotation.Nonnull;
// tag::snippet[]
public class Person {
@Nonnull
private String firstName;
@Nonnull
private String lastName;
@Nonnull
private String email;
@Nonnull
private Date birthday;
@Nonnull
private Integer id;
@Nonnull
private Boolean subscriber;
@Nonnull
private String membership;
@Nonnull
private String pictureUrl;
@Nonnull
private String profession;
@Nonnull
private Address address;
private Integer managerId;
@Nonnull
private Boolean manager;
@Nonnull
private String status;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFullName() {
return firstName + " " + lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public boolean isSubscriber() {
return subscriber;
}
public void setSubscriber(boolean subscriber) {
this.subscriber = subscriber;
}
public String getMembership() {
return membership;
}
public void setMembership(String membership) {
this.membership = membership;
}
public String getPictureUrl() {
return pictureUrl;
}
public void setPictureUrl(String pictureUrl) {
this.pictureUrl = pictureUrl;
}
public String getProfession() {
return profession;
}
public void setProfession(String profession) {
this.profession = profession;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Override
public int hashCode() {
return id;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Person)) {
return false;
}
Person other = (Person) obj;
return id == other.id;
}
public Integer getManagerId() {
return managerId;
}
public void setManagerId(Integer managerId) {
this.managerId = managerId;
}
public boolean isManager() {
return manager;
}
public void setManager(boolean manager) {
this.manager = manager;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}
// end::snippet[]
F93C6D28-18D6-4403-B3B6-0D13F8705BED