PopoverAnchoredDialog.java
package com.vaadin.demo.component.popover;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import com.vaadin.demo.domain.DataService;
import com.vaadin.demo.domain.Person;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.checkbox.CheckboxGroup;
import com.vaadin.flow.component.checkbox.CheckboxGroupVariant;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H3;
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.popover.Popover;
import com.vaadin.flow.component.popover.PopoverPosition;
import com.vaadin.flow.data.renderer.LocalDateRenderer;
import com.vaadin.flow.router.Route;
@Route("popover-anchored-dialog")
public class PopoverAnchoredDialog extends Div {
public PopoverAnchoredDialog() {
// tag::gridsnippet[]
Grid<Person> grid = new Grid<>(Person.class, false);
grid.addColumn(Person::getFirstName).setKey("firstName")
.setHeader("First name");
grid.addColumn(Person::getLastName).setKey("lastName")
.setHeader("Last name");
grid.addColumn(Person::getEmail).setKey("email").setHeader("Email");
grid.addColumn(person -> person.getAddress().getPhone()).setKey("phone")
.setHeader("Phone");
grid.addColumn(new LocalDateRenderer<>(
PopoverAnchoredDialog::getPersonBirthday, "yyyy-MM-dd"))
.setKey("birthday").setHeader("Birthday");
grid.addColumn(Person::getProfession).setKey("profession")
.setHeader("Profession");
// end::gridsnippet[]
grid.setItems(DataService.getPeople());
H3 title = new H3("Employees");
Button button = new Button(VaadinIcon.GRID_H.create());
button.addThemeVariants(ButtonVariant.LUMO_ICON);
button.setAriaLabel("Show / hide columns");
HorizontalLayout headerLayout = new HorizontalLayout(title, button);
headerLayout.setAlignItems(FlexComponent.Alignment.BASELINE);
headerLayout.setFlexGrow(1, title);
Popover popover = new Popover();
popover.setModal(true);
popover.setBackdropVisible(true);
popover.setPosition(PopoverPosition.BOTTOM_END);
popover.setTarget(button);
Div heading = new Div("Configure columns");
heading.getStyle().set("font-weight", "600");
heading.getStyle().set("padding", "var(--lumo-space-xs)");
List<String> columns = List.of("firstName", "lastName", "email",
"phone", "birthday", "profession");
// tag::gridsnippet[]
CheckboxGroup<String> group = new CheckboxGroup<>();
group.addThemeVariants(CheckboxGroupVariant.LUMO_VERTICAL);
group.setItems(columns);
group.setItemLabelGenerator((item) -> {
String label = StringUtils
.join(StringUtils.splitByCharacterTypeCamelCase(item), " ");
return StringUtils.capitalize(label.toLowerCase());
});
group.addValueChangeListener((e) -> {
columns.stream().forEach((key) -> {
grid.getColumnByKey(key).setVisible(e.getValue().contains(key));
});
});
// end::gridsnippet[]
Set<String> defaultColumns = Set.of("firstName", "lastName", "email",
"profession");
group.setValue(defaultColumns);
Button showAll = new Button("Show all", (e) -> {
group.setValue(new HashSet<String>(columns));
});
showAll.addThemeVariants(ButtonVariant.LUMO_SMALL);
Button reset = new Button("Reset", (e) -> {
group.setValue(defaultColumns);
});
reset.addThemeVariants(ButtonVariant.LUMO_SMALL);
HorizontalLayout footer = new HorizontalLayout(showAll, reset);
footer.setSpacing(false);
footer.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
popover.add(heading, group, footer);
add(headerLayout, grid, popover);
}
private static LocalDate getPersonBirthday(Person person) {
return person.getBirthday().toInstant().atZone(ZoneId.systemDefault())
.toLocalDate();
}
}
popover-anchored-dialog.tsx
import '@vaadin/icons';
import React, { useEffect } from 'react';
import { useComputed, useSignal } from '@vaadin/hilla-react-signals';
import {
Button,
Checkbox,
CheckboxGroup,
Grid,
GridColumn,
HorizontalLayout,
Icon,
Popover,
} from '@vaadin/react-components';
import { getPeople } from 'Frontend/demo/domain/DataService';
import type Person from 'Frontend/generated/com/vaadin/demo/domain/Person';
type ColumnConfig = { label: string; key: string; visible: boolean };
const DEFAULT_COLUMNS: ColumnConfig[] = [
{ label: 'First name', key: 'firstName', visible: true },
{ label: 'Last name', key: 'lastName', visible: true },
{ label: 'Email', key: 'email', visible: true },
{ label: 'Phone', key: 'address.phone', visible: false },
{ label: 'Birthday', key: 'birthday', visible: false },
{ label: 'Profession', key: 'profession', visible: true },
];
function Example() {
const items = useSignal<Person[]>([]);
const columns = useSignal<ColumnConfig[]>([...DEFAULT_COLUMNS]);
const visibleColumns = useComputed(() =>
columns.value.filter((column) => column.visible).map((column) => column.key)
);
useEffect(() => {
getPeople().then(({ people }) => {
items.value = people;
});
}, []);
return (
<>
<HorizontalLayout style={{ alignItems: 'baseline' }}>
<h3 style={{ flex: 1 }}>Employees</h3>
<Button id="toggle-columns" theme="icon" aria-label="Show / hide columns">
<Icon icon="vaadin:grid-h" />
</Button>
</HorizontalLayout>
<Popover for="toggle-columns" modal withBackdrop position="bottom-end">
<div style={{ fontWeight: '600', padding: 'var(--lumo-space-xs)' }}>Configure columns</div>
<CheckboxGroup theme="vertical" value={visibleColumns.value}>
{columns.value.map((item) => (
<Checkbox
label={item.label}
value={item.key}
onChange={(event) => {
const idx = columns.value.findIndex(({ key }) => key === event.target.value);
columns.value = columns.value.map((column, index) => ({
...column,
visible: idx === index ? event.target.checked : column.visible,
}));
}}
/>
))}
</CheckboxGroup>
<HorizontalLayout style={{ justifyContent: 'space-between' }}>
<Button
theme="small"
onClick={() => {
columns.value = columns.value.map((column) => ({ ...column, visible: true }));
}}
>
Show all
</Button>
<Button
theme="small"
onClick={() => {
columns.value = columns.value.map((column, idx) => ({
...column,
visible: DEFAULT_COLUMNS[idx].visible,
}));
}}
>
Reset
</Button>
</HorizontalLayout>
</Popover>
{/* tag::gridsnippet[] */}
<Grid items={items.value}>
{columns.value.map((item) => (
<GridColumn path={item.key} hidden={!item.visible} key={item.key} />
))}
</Grid>
{/* end::gridsnippet[] */}
</>
);
}
popover-anchored-dialog.ts
import '@vaadin/button';
import '@vaadin/checkbox-group';
import '@vaadin/grid';
import '@vaadin/horizontal-layout';
import '@vaadin/icon';
import '@vaadin/icons';
import '@vaadin/popover';
import { html, LitElement } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import type { CheckboxChangeEvent } from '@vaadin/checkbox';
import { popoverRenderer } from '@vaadin/popover/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';
const DEFAULT_COLUMNS = [
{ label: 'First name', key: 'firstName', visible: true },
{ label: 'Last name', key: 'lastName', visible: true },
{ label: 'Email', key: 'email', visible: true },
{ label: 'Phone', key: 'address.phone', visible: false },
{ label: 'Birthday', key: 'birthday', visible: false },
{ label: 'Profession', key: 'profession', visible: true },
];
@customElement('popover-anchored-dialog')
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 items: Person[] = [];
@state()
private gridColumns = [...DEFAULT_COLUMNS];
protected override async firstUpdated() {
const { people } = await getPeople();
this.items = people;
}
protected override render() {
return html`
<vaadin-horizontal-layout style="align-items: baseline">
<h3 style="flex: 1;">Employees</h3>
<vaadin-button id="toggle-columns" theme="icon" aria-label="Show / hide columns">
<vaadin-icon icon="vaadin:grid-h"></vaadin-icon>
</vaadin-button>
</vaadin-horizontal-layout>
<vaadin-popover
for="toggle-columns"
modal
with-backdrop
position="bottom-end"
${popoverRenderer(this.popoverRenderer, [this.gridColumns])}
></vaadin-popover>
<!-- tag::gridsnippet[] -->
<vaadin-grid .items="${this.items}">
${this.gridColumns.map(
(column) => html`
<vaadin-grid-column
path="${column.key}"
.hidden="${!column.visible}"
></vaadin-grid-column>
`
)}
</vaadin-grid>
<!-- end::gridsnippet[] -->
`;
}
popoverRenderer() {
const visibleColumns = this.gridColumns
.filter((column) => column.visible)
.map((column) => column.key);
return html`
<div style="font-weight: 600; padding: var(--lumo-space-xs);">Configure columns</div>
<vaadin-checkbox-group theme="vertical" .value="${visibleColumns}">
${this.gridColumns.map(
(column) => html`
<vaadin-checkbox
.label="${column.label}"
.value="${column.key}"
@change="${this.onCheckboxChange}"
></vaadin-checkbox>
`
)}
</vaadin-checkbox-group>
<vaadin-horizontal-layout style="justify-content: space-between;">
<vaadin-button theme="small" @click="${this.showAllColumns}">Show all</vaadin-button>
<vaadin-button theme="small" @click="${this.resetColumns}">Reset</vaadin-button>
</vaadin-horizontal-layout>
`;
}
onCheckboxChange(event: CheckboxChangeEvent) {
const idx = this.gridColumns.findIndex(({ key }) => key === event.target.value);
this.gridColumns = this.gridColumns.map((column, index) => ({
...column,
visible: idx === index ? event.target.checked : column.visible,
}));
}
showAllColumns() {
this.gridColumns = this.gridColumns.map((column) => ({ ...column, visible: true }));
}
resetColumns() {
this.gridColumns = this.gridColumns.map((column, idx) => ({
...column,
visible: DEFAULT_COLUMNS[idx].visible,
}));
}
}