Tree Grid
API: TypeScript / Java
Source: TypeScript / Java
Tree Grid is a component for displaying hierarchical tabular data grouped into expandable and collapsible nodes.
Open in a
new tab
new tab
Source code
tree-grid-basic.ts
import { html, LitElement } from 'lit';
import { customElement } from 'lit/decorators.js';
import '@vaadin/grid';
import type { GridDataProviderCallback, GridDataProviderParams } from '@vaadin/grid';
import '@vaadin/grid/vaadin-grid-tree-column.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('tree-grid-basic')
export class Example extends LitElement {
protected override createRenderRoot() {
const root = super.createRenderRoot();
// Apply custom theme (only supported if your app uses one)
applyTheme(root);
return root;
}
// tag::snippet[]
async dataProvider(
params: GridDataProviderParams<Person>,
callback: GridDataProviderCallback<Person>
) {
// The requested page and the full length of the corresponding
// hierarchy level is requested from the data service
const { people, hierarchyLevelSize } = await getPeople({
count: params.pageSize,
startIndex: params.page * params.pageSize,
managerId: params.parentItem ? params.parentItem.id : null,
});
callback(people, hierarchyLevelSize);
}
protected override render() {
return html`
<vaadin-grid .dataProvider="${this.dataProvider}">
<vaadin-grid-tree-column
path="firstName"
item-has-children-path="manager"
></vaadin-grid-tree-column>
<vaadin-grid-column path="lastName"></vaadin-grid-column>
<vaadin-grid-column path="email"></vaadin-grid-column>
</vaadin-grid>
`;
}
// end::snippet[]
}
TreeGridBasic.java
package com.vaadin.demo.component.treegrid;
import com.vaadin.demo.domain.DataService;
import com.vaadin.demo.domain.Person;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.treegrid.TreeGrid;
import com.vaadin.flow.router.Route;
import java.util.List;
@Route("tree-grid-basic")
public class TreeGridBasic extends Div {
private List<Person> managers = DataService.getManagers();
public TreeGridBasic() {
// tag::snippet[]
TreeGrid<Person> treeGrid = new TreeGrid<>();
treeGrid.setItems(managers, this::getStaff);
treeGrid.addHierarchyColumn(Person::getFirstName)
.setHeader("First name");
treeGrid.addColumn(Person::getLastName).setHeader("Last name");
treeGrid.addColumn(Person::getEmail).setHeader("Email");
// end::snippet[]
add(treeGrid);
}
public List<Person> getStaff(Person manager) {
return DataService.getPeople(manager.getId());
}
}
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[]
Note
|
Features shared with Grid
Tree Grid is an extension of the Grid component and all Grid’s features are available in Tree Grid as well.
|
Tree Column
The tree column is a column that contains the toggles for expanding and collapsing nodes. Nodes are opened and closed by clicking a tree column’s cell. They can also be toggled programmatically.
Open in a
new tab
new tab
Source code
tree-grid-column.ts
import { html, LitElement } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import '@vaadin/button';
import '@vaadin/grid';
import type { GridDataProviderCallback, GridDataProviderParams } from '@vaadin/grid';
import '@vaadin/grid/vaadin-grid-tree-column.js';
import '@vaadin/horizontal-layout';
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('tree-grid-column')
export class Example extends LitElement {
protected override createRenderRoot() {
const root = super.createRenderRoot();
// Apply custom theme (only supported if your app uses one)
applyTheme(root);
return root;
}
private managers: Person[] = [];
private dataProvider = async (
params: GridDataProviderParams<Person>,
callback: GridDataProviderCallback<Person>
) => {
const { people, hierarchyLevelSize } = await getPeople({
count: params.pageSize,
startIndex: params.page * params.pageSize,
managerId: params.parentItem ? params.parentItem.id : null,
});
if (!params.parentItem) {
this.managers = people;
}
callback(people, hierarchyLevelSize);
};
// tag::snippet[]
@state()
private expandedItems: unknown[] = [];
protected override render() {
return html`
<vaadin-horizontal-layout
style="align-items: center; height: var(--lumo-size-xl);"
theme="spacing"
>
<h3 style="flex-grow: 1; margin: 0;">Employee</h3>
<vaadin-button @click="${this.expandAll}">Expand All</vaadin-button>
<vaadin-button @click="${this.collapseAll}">Collapse All</vaadin-button>
</vaadin-horizontal-layout>
<vaadin-grid
.dataProvider="${this.dataProvider}"
.itemIdPath="${'id'}"
.expandedItems="${this.expandedItems}"
>
<vaadin-grid-tree-column
path="firstName"
item-has-children-path="manager"
></vaadin-grid-tree-column>
<vaadin-grid-column path="lastName"></vaadin-grid-column>
<vaadin-grid-column path="email"></vaadin-grid-column>
</vaadin-grid>
`;
}
private expandAll() {
this.expandedItems = [...this.managers];
}
private collapseAll() {
this.expandedItems = [];
}
// end::snippet[]
}
TreeGridColumn.java
package com.vaadin.demo.component.treegrid;
import com.vaadin.demo.domain.DataService;
import com.vaadin.demo.domain.Person;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.treegrid.TreeGrid;
import com.vaadin.flow.router.Route;
import java.util.List;
@Route("tree-grid-column")
public class TreeGridColumn extends Div {
private List<Person> managers = DataService.getManagers();
public TreeGridColumn() {
TreeGrid<Person> treeGrid = new TreeGrid<>();
treeGrid.setItems(managers, this::getStaff);
treeGrid.addHierarchyColumn(Person::getFirstName)
.setHeader("First name");
treeGrid.addColumn(Person::getLastName).setHeader("Last name");
treeGrid.addColumn(Person::getEmail).setHeader("Email");
H3 employees = new H3("Employees");
employees.getStyle().set("margin", "0");
// tag::snippet[]
Button expand = new Button("Expand All");
expand.addClickListener(event -> treeGrid.expand(managers));
Button collapse = new Button("Collapse All");
collapse.addClickListener(event -> treeGrid.collapse(managers));
// end::snippet[]
HorizontalLayout header = new HorizontalLayout(employees, expand,
collapse);
header.setAlignItems(FlexComponent.Alignment.CENTER);
header.setHeight("var(--lumo-space-xl)");
header.setFlexGrow(1, employees);
add(header, treeGrid);
}
public List<Person> getStaff(Person manager) {
return DataService.getPeople(manager.getId());
}
}
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[]
Rich Content
Like Grid, Tree Grid supports rich content.
Open in a
new tab
new tab
Source code
tree-grid-rich-content.ts
import { html, LitElement } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import '@vaadin/avatar';
import '@vaadin/button';
import '@vaadin/grid';
import type { GridDataProviderCallback, GridDataProviderParams } from '@vaadin/grid';
import '@vaadin/grid/vaadin-grid-tree-toggle.js';
import type { GridTreeToggleExpandedChangedEvent } from '@vaadin/grid/vaadin-grid-tree-toggle.js';
import { columnBodyRenderer } from '@vaadin/grid/lit.js';
import type { GridColumnBodyLitRenderer } from '@vaadin/grid/lit.js';
import '@vaadin/horizontal-layout';
import '@vaadin/icon';
import '@vaadin/icons';
import '@vaadin/vertical-layout';
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('tree-grid-rich-content')
export class Example extends LitElement {
protected override createRenderRoot() {
const root = super.createRenderRoot();
// Apply custom theme (only supported if your app uses one)
applyTheme(root);
return root;
}
@state()
private expandedItems: Person[] = [];
async dataProvider(
params: GridDataProviderParams<Person>,
callback: GridDataProviderCallback<Person>
) {
const { people, hierarchyLevelSize } = await getPeople({
count: params.pageSize,
startIndex: params.page * params.pageSize,
managerId: params.parentItem ? params.parentItem.id : null,
});
callback(people, hierarchyLevelSize);
}
// tag::snippet[]
private employeeRenderer: GridColumnBodyLitRenderer<Person> = (person, model) => html`
<vaadin-grid-tree-toggle
.leaf="${!person.manager}"
.level="${model.level ?? 0}"
@expanded-changed="${(e: GridTreeToggleExpandedChangedEvent) => {
if (e.detail.value) {
this.expandedItems = [...this.expandedItems, person];
} else {
this.expandedItems = this.expandedItems.filter((p) => p.id !== person.id);
}
}}"
.expanded="${!!model.expanded}"
>
<vaadin-horizontal-layout style="align-items: center;" theme="spacing">
<vaadin-avatar
img="${person.pictureUrl}"
name="${`${person.firstName} ${person.lastName}`}"
></vaadin-avatar>
<vaadin-vertical-layout style="line-height: var(--lumo-line-height-m);">
<span>${person.firstName} ${person.lastName}</span>
<span
style="font-size: var(--lumo-font-size-s); color: var(--lumo-secondary-text-color);"
>
${person.profession}
</span>
</vaadin-vertical-layout>
</vaadin-horizontal-layout>
</vaadin-grid-tree-toggle>
`;
private contactRenderer: GridColumnBodyLitRenderer<Person> = (person) => html`
<vaadin-vertical-layout
style="font-size: var(--lumo-font-size-s); line-height: var(--lumo-line-height-m);"
>
<a href="mailto:${person.email}" style="align-items: center; display: flex;">
<vaadin-icon
icon="vaadin:envelope"
style="height: var(--lumo-icon-size-s); margin-inline-end: var(--lumo-space-s); width: var(--lumo-icon-size-s);"
></vaadin-icon>
<span>${person.email}</span>
</a>
<a href="tel:${person.address.phone}" style="align-items: center; display: flex;">
<vaadin-icon
icon="vaadin:phone"
style="height: var(--lumo-icon-size-s); margin-inline-end: var(--lumo-space-s); width: var(--lumo-icon-size-s);"
></vaadin-icon>
<span>${person.address.phone}</span>
</a>
</vaadin-vertical-layout>
`;
protected override render() {
return html`
<vaadin-grid .dataProvider="${this.dataProvider}" .expandedItems="${this.expandedItems}">
<vaadin-grid-column
auto-width
header="Employee"
${columnBodyRenderer(this.employeeRenderer, [])}
></vaadin-grid-column>
<vaadin-grid-column
auto-width
header="Contact"
${columnBodyRenderer(this.contactRenderer, [])}
></vaadin-grid-column>
</vaadin-grid>
`;
}
// end::snippet[]
}
TreeGridRichContent.java
package com.vaadin.demo.component.treegrid;
import com.vaadin.demo.domain.DataService;
import com.vaadin.demo.domain.Person;
import com.vaadin.flow.component.avatar.Avatar;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.treegrid.TreeGrid;
import com.vaadin.flow.router.Route;
import java.util.List;
@Route("tree-grid-rich-content")
public class TreeGridRichContent extends Div {
private List<Person> managers = DataService.getManagers();
public TreeGridRichContent() {
TreeGrid<Person> treeGrid = new TreeGrid<>();
treeGrid.setItems(managers, this::getStaff);
// tag::snippet[]
treeGrid.addComponentHierarchyColumn(person -> {
Avatar avatar = new Avatar();
avatar.setName(person.getFullName());
avatar.setImage(person.getPictureUrl());
Span fullName = new Span(person.getFullName());
Span profession = new Span(person.getProfession());
profession.getStyle()
.set("color", "var(--lumo-secondary-text-color)")
.set("font-size", "var(--lumo-font-size-s)");
VerticalLayout column = new VerticalLayout(fullName, profession);
column.getStyle().set("line-height", "var(--lumo-line-height-m)");
column.setPadding(false);
column.setSpacing(false);
HorizontalLayout row = new HorizontalLayout(avatar, column);
row.setAlignItems(FlexComponent.Alignment.CENTER);
row.setSpacing(true);
return row;
}).setHeader("Employee");
treeGrid.addComponentColumn(person -> {
Icon emailIcon = createIcon(VaadinIcon.ENVELOPE);
Span email = new Span(person.getEmail());
Anchor emailLink = new Anchor();
emailLink.add(emailIcon, email);
emailLink.setHref("mailto:" + person.getEmail());
emailLink.getStyle().set("align-items", "center").set("display",
"flex");
Icon phoneIcon = createIcon(VaadinIcon.PHONE);
Span phone = new Span(person.getAddress().getPhone());
Anchor phoneLink = new Anchor();
phoneLink.add(phoneIcon, phone);
phoneLink.setHref("tel:" + person.getAddress().getPhone());
phoneLink.getStyle().set("align-items", "center").set("display",
"flex");
VerticalLayout column = new VerticalLayout(emailLink, phoneLink);
column.getStyle().set("font-size", "var(--lumo-font-size-s)")
.set("line-height", "var(--lumo-line-height-m)");
column.setPadding(false);
column.setSpacing(false);
return column;
}).setHeader("Contact");
// end::snippet[]
add(treeGrid);
}
private Icon createIcon(VaadinIcon vaadinIcon) {
Icon icon = vaadinIcon.create();
icon.getStyle().set("margin-inline-end", "var(--lumo-space-s)");
icon.setSize("var(--lumo-icon-size-s)");
return icon;
}
public List<Person> getStaff(Person manager) {
return DataService.getPeople(manager.getId());
}
}
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[]