CrudLocalization.java
package com.vaadin.demo.component.crud;
import com.vaadin.demo.domain.Person;
import com.vaadin.flow.component.crud.BinderCrudEditor;
import com.vaadin.flow.component.crud.Crud;
import com.vaadin.flow.component.crud.CrudEditor;
import com.vaadin.flow.component.crud.CrudI18n;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.textfield.EmailField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.router.Route;
import java.util.Arrays;
import java.util.List;
@Route("crud-localization")
public class CrudLocalization extends Div {
private Crud<Person> crud;
private String FIRST_NAME = "firstName";
private String LAST_NAME = "lastName";
private String EMAIL = "email";
private String PROFESSION = "profession";
private String EDIT_COLUMN = "vaadin-crud-edit-column";
public CrudLocalization() {
crud = new Crud<>(Person.class, createEditor());
setupGrid();
setupDataProvider();
setupI18n();
add(crud);
}
private CrudEditor<Person> createEditor() {
TextField firstName = new TextField("First name");
TextField lastName = new TextField("Last name");
EmailField email = new EmailField("Email");
TextField profession = new TextField("Profession");
FormLayout form = new FormLayout(firstName, lastName, email,
profession);
Binder<Person> binder = new Binder<>(Person.class);
binder.forField(firstName).asRequired().bind(Person::getFirstName,
Person::setFirstName);
binder.forField(lastName).asRequired().bind(Person::getLastName,
Person::setLastName);
binder.forField(email).asRequired().bind(Person::getEmail,
Person::setEmail);
binder.forField(profession).asRequired().bind(Person::getProfession,
Person::setProfession);
return new BinderCrudEditor<>(binder, form);
}
private void setupGrid() {
Grid<Person> grid = crud.getGrid();
// Only show these columns (all columns shown by default):
List<String> visibleColumns = Arrays.asList(FIRST_NAME, LAST_NAME,
EMAIL, PROFESSION, EDIT_COLUMN);
grid.getColumns().forEach(column -> {
String key = column.getKey();
if (!visibleColumns.contains(key)) {
grid.removeColumn(column);
}
});
// Reorder the columns (alphabetical by default)
grid.setColumnOrder(grid.getColumnByKey(FIRST_NAME),
grid.getColumnByKey(LAST_NAME), grid.getColumnByKey(EMAIL),
grid.getColumnByKey(PROFESSION),
grid.getColumnByKey(EDIT_COLUMN));
// Translate headers
grid.getColumnByKey(FIRST_NAME).setHeader("Etunimi");
grid.getColumnByKey(LAST_NAME).setHeader("Sukunimi");
grid.getColumnByKey(EMAIL).setHeader("Sähköposti");
grid.getColumnByKey(PROFESSION).setHeader("Ammatti");
}
private void setupDataProvider() {
PersonDataProvider dataProvider = new PersonDataProvider();
crud.setDataProvider(dataProvider);
crud.addDeleteListener(
deleteEvent -> dataProvider.delete(deleteEvent.getItem()));
crud.addSaveListener(
saveEvent -> dataProvider.persist(saveEvent.getItem()));
}
private void setupI18n() {
// tag::snippet[]
CrudI18n i18n = CrudI18n.createDefault();
i18n.setNewItem("Luo uusi");
i18n.setEditItem("Muuta tietoja");
i18n.setSaveItem("Tallenna");
i18n.setCancel("Peruuta");
i18n.setDeleteItem("Poista...");
i18n.setEditLabel("Muokkaa");
CrudI18n.Confirmations.Confirmation delete = i18n.getConfirm()
.getDelete();
delete.setTitle("Poista kohde");
delete.setContent(
"Haluatko varmasti poistaa tämän kohteen? Poistoa ei voi perua.");
delete.getButton().setConfirm("Poista");
delete.getButton().setDismiss("Peruuta");
CrudI18n.Confirmations.Confirmation cancel = i18n.getConfirm()
.getCancel();
cancel.setTitle("Hylkää muutokset");
cancel.setContent("Kohteessa on tallentamattomia muutoksia.");
cancel.getButton().setConfirm("Hylkää");
cancel.getButton().setDismiss("Peruuta");
crud.setI18n(i18n);
// end::snippet[]
}
}
PersonDataProvider.java
package com.vaadin.demo.component.crud;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
import com.vaadin.demo.domain.DataService;
import com.vaadin.demo.domain.Person;
import com.vaadin.flow.component.crud.CrudFilter;
import com.vaadin.flow.data.provider.AbstractBackEndDataProvider;
import com.vaadin.flow.data.provider.Query;
import com.vaadin.flow.data.provider.SortDirection;
import static java.util.Comparator.naturalOrder;
// Person data provider
public class PersonDataProvider
extends AbstractBackEndDataProvider<Person, CrudFilter> {
// A real app should hook up something like JPA
final List<Person> DATABASE = new ArrayList<>(DataService.getPeople());
private Consumer<Long> sizeChangeListener;
@Override
protected Stream<Person> fetchFromBackEnd(Query<Person, CrudFilter> query) {
int offset = query.getOffset();
int limit = query.getLimit();
Stream<Person> stream = DATABASE.stream();
if (query.getFilter().isPresent()) {
stream = stream.filter(predicate(query.getFilter().get()))
.sorted(comparator(query.getFilter().get()));
}
return stream.skip(offset).limit(limit);
}
@Override
protected int sizeInBackEnd(Query<Person, CrudFilter> query) {
// For RDBMS just execute a SELECT COUNT(*) ... WHERE query
long count = fetchFromBackEnd(query).count();
if (sizeChangeListener != null) {
sizeChangeListener.accept(count);
}
return (int) count;
}
void setSizeChangeListener(Consumer<Long> listener) {
sizeChangeListener = listener;
}
private static Predicate<Person> predicate(CrudFilter filter) {
// For RDBMS just generate a WHERE clause
return filter.getConstraints().entrySet().stream()
.map(constraint -> (Predicate<Person>) person -> {
try {
Object value = valueOf(constraint.getKey(), person);
return value != null && value.toString().toLowerCase()
.contains(constraint.getValue().toLowerCase());
} catch (Exception e) {
e.printStackTrace();
return false;
}
}).reduce(Predicate::and).orElse(e -> true);
}
private static Comparator<Person> comparator(CrudFilter filter) {
// For RDBMS just generate an ORDER BY clause
return filter.getSortOrders().entrySet().stream().map(sortClause -> {
try {
Comparator<Person> comparator = Comparator.comparing(
person -> (Comparable) valueOf(sortClause.getKey(),
person));
if (sortClause.getValue() == SortDirection.DESCENDING) {
comparator = comparator.reversed();
}
return comparator;
} catch (Exception ex) {
return (Comparator<Person>) (o1, o2) -> 0;
}
}).reduce(Comparator::thenComparing).orElse((o1, o2) -> 0);
}
private static Object valueOf(String fieldName, Person person) {
try {
Field field = Person.class.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(person);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
void persist(Person item) {
if (item.getId() == null) {
item.setId(DATABASE.stream().map(Person::getId).max(naturalOrder())
.orElse(0) + 1);
}
final Optional<Person> existingItem = find(item.getId());
if (existingItem.isPresent()) {
int position = DATABASE.indexOf(existingItem.get());
DATABASE.remove(existingItem.get());
DATABASE.add(position, item);
} else {
DATABASE.add(item);
}
}
Optional<Person> find(Integer id) {
return DATABASE.stream().filter(entity -> entity.getId().equals(id))
.findFirst();
}
void delete(Person item) {
DATABASE.removeIf(entity -> entity.getId().equals(item.getId()));
}
}
PersonDataProvider.java
crud-localization.tsx
import React, { useEffect } from 'react';
import { useSignal } from '@vaadin/hilla-react-signals';
import { ComboBox } from '@vaadin/react-components/ComboBox.js';
import { EmailField } from '@vaadin/react-components/EmailField.js';
import { Grid } from '@vaadin/react-components/Grid.js';
import { GridColumn } from '@vaadin/react-components/GridColumn.js';
import { TextField } from '@vaadin/react-components/TextField.js';
import { Crud, crudPath } from '@vaadin/react-components-pro/Crud.js';
import { CrudEditColumn } from '@vaadin/react-components-pro/CrudEditColumn.js';
import { getPeople } from 'Frontend/demo/domain/DataService';
import type Person from 'Frontend/generated/com/vaadin/demo/domain/Person';
function Example() {
const items = useSignal<Person[]>([]);
useEffect(() => {
getPeople().then(({ people }) => {
items.value = people;
});
}, []);
// tag::snippet[]
const i18n = {
newItem: 'Luo uusi',
editItem: 'Muuta tietoja',
saveItem: 'Tallenna',
cancel: 'Peruuta',
deleteItem: 'Poista...',
editLabel: 'Muokkaa',
confirm: {
delete: {
title: 'Poista kohde',
content: 'Haluatko varmasti poistaa tämän kohteen? Poistoa ei voi perua.',
button: {
confirm: 'Poista',
dismiss: 'Peruuta',
},
},
cancel: {
title: 'Hylkää muutokset',
content: 'Kohteessa on tallentamattomia muutoksia',
button: {
confirm: 'Hylkää',
dismiss: 'Peruuta',
},
},
},
};
return (
<Crud
editorPosition="aside"
include="firstName, lastName, email, profession"
items={items.value}
i18n={i18n}
>
<Grid slot="grid">
<GridColumn path="firstName" header="Etunimi" />
<GridColumn path="lastName" header="Sukunimi" />
<GridColumn path="email" header="Sähköposti" />
<GridColumn path="profession" header="Ammatti" />
<CrudEditColumn />
</Grid>
<div slot="form">
<TextField {...crudPath('firstName')} label="Etunimi" required />
<TextField {...crudPath('lastName')} label="Sukunimi" required />
<EmailField {...crudPath('email')} label="Sähköposti" required />
<ComboBox
{...crudPath('profession')}
label="Ammatti"
items={[...new Set(items.value.map((i) => i.profession))]}
/>
</div>
</Crud>
);
// end::snippet[]
}
crud-localization.ts
import '@vaadin/crud';
import { html, LitElement } from 'lit';
import { customElement, query, state } from 'lit/decorators.js';
import type { Crud } from '@vaadin/crud';
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('crud-localization')
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[] = [];
@query('vaadin-crud')
private crud!: Crud<Person>;
protected override async firstUpdated() {
const { people } = await getPeople();
this.items = people;
this.crud.i18n = {
newItem: 'Luo uusi',
editItem: 'Muuta tietoja',
saveItem: 'Tallenna',
cancel: 'Peruuta',
deleteItem: 'Poista...',
editLabel: 'Muokkaa',
confirm: {
delete: {
title: 'Poista kohde',
content: 'Haluatko varmasti poistaa tämän kohteen? Poistoa ei voi perua.',
button: {
confirm: 'Poista',
dismiss: 'Peruuta',
},
},
cancel: {
title: 'Hylkää muutokset',
content: 'Kohteessa on tallentamattomia muutoksia',
button: {
confirm: 'Hylkää',
dismiss: 'Peruuta',
},
},
},
};
}
protected override render() {
return html`
<!-- tag::snippethtml[] -->
<vaadin-crud
editor-position="aside"
include="firstName, lastName, email, profession"
.items="${this.items}"
>
<vaadin-grid slot="grid">
<vaadin-grid-column path="firstName" header="Etunimi"></vaadin-grid-column>
<vaadin-grid-column path="lastName" header="Sukunimi"></vaadin-grid-column>
<vaadin-grid-column path="email" header="Sähköposti"></vaadin-grid-column>
<vaadin-grid-column path="profession" header="Ammatti"></vaadin-grid-column>
<vaadin-crud-edit-column></vaadin-crud-edit-column>
</vaadin-grid>
<vaadin-form-layout slot="form">
<vaadin-text-field path="firstName" label="Etunimi" required></vaadin-text-field>
<vaadin-text-field path="lastName" label="Sukunimi" required></vaadin-text-field>
<vaadin-email-field path="email" label="Sähköposti" required></vaadin-email-field>
<vaadin-combo-box
path="profession"
label="Ammatti"
.items="${[...new Set(this.items.map((i) => i.profession))]}"
></vaadin-combo-box>
</vaadin-form-layout>
</vaadin-crud>
<!-- end::snippethtml[] -->
`;
}
}