> Markdown version of [CRUD](https://vaadin.com/docs/v14/ds/components/crud). Section index: [llms.txt](https://vaadin.com/docs/v14/ds/llms.txt)

# CRUD

> **Note: Commercial feature**
>
> A commercial Vaadin subscription is required to use CRUD in your project.
>
> - [Start Free Trial](https://vaadin.com/trial)
>
> - [See Pricing](https://vaadin.com/pricing)

CRUD is a component for managing a dataset. It allows for easy displaying, editing, creating, and deleting of items.

`CrudBasic.java`

```java
crud = new Crud<>(
  Person.class,
  createEditor()
);

setupGrid();
setupDataProvider();

add(crud);
```

`PersonDataProvider.java`

```java
package com.vaadin.demo.component.crud;

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 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 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()));
  }
}
```

## <a id="columns"></a>Columns

CRUD automatically generates columns for each field in the provided dataset. You can add columns and configure or remove existing ones.

`CrudColumns.java`

```java
crud = new Crud<>(
  Person.class,
  createEditor()
);

setupGrid();
setupDataProvider();

add(crud);
// Only show these columns (all columns shown by default):
List<String> visibleColumns = Arrays.asList(
  FIRST_NAME,
  EMAIL,
  PROFESSION,
  BIRTHDAY,
  EDIT_COLUMN
);
grid.getColumns().forEach(column -> {
  String key = column.getKey();
  if (!visibleColumns.contains(key)) {
    grid.removeColumn(column);
  }
});
```

`PersonDataProvider.java`

```java
package com.vaadin.demo.component.crud;

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 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 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()));
  }
}
```

## <a id="editor"></a>Editor

Data is edited using CRUD’s editor UI.

### <a id="opening-the-editor"></a>Opening the Editor

By default, the editor is opened by clicking the edit Button in the last column, but this button column can be removed in favor of another way to engage the editor. For example, you can have it open using double click:

`CrudOpenEditor.java`

```java
Grid<Person> grid = crud.getGrid();

// Remove edit column
Crud.removeEditColumn(grid);
// grid.removeColumnByKey(EDIT_COLUMN);
// grid.removeColumn(grid.getColumnByKey(EDIT_COLUMN));

// Open editor on double click
grid.addItemDoubleClickListener(event ->
  crud.edit(event.getItem(), Crud.EditMode.EXISTING_ITEM)
);
```

`PersonDataProvider.java`

```java
package com.vaadin.demo.component.crud;

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 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 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()));
  }
}
```

### <a id="editor-position"></a>Editor Position

The editor can be positioned in an overlay (default), on the side or at the bottom.

#### <a id="overlay-default"></a>Overlay (Default)

The overlay position renders the editor in a modal overlay. Overlays are not constrained to the CRUD’s size which makes them ideal for complex forms. However, they block the user from viewing and interacting with the Grid beneath.

#### <a id="aside"></a>Aside

The `aside` position displays the editor as an overlay next to the grid. Use this position when there’s sufficient horizontal space to accommodate both the grid and the editor, and it’s beneficial for the user to be able to view and interact with the grid while the editor is open. Aside positioning is also a good fit for [single-column forms](https://vaadin.com/docs/v14/ds/components/form-layout.md).

`CrudEditorAside.java`

```java
crud.setEditorPosition(CrudEditorPosition.ASIDE);
```

`PersonDataProvider.java`

```java
package com.vaadin.demo.component.crud;

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 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 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()));
  }
}
```

> **Note: Grid Width**
>
> The opening and closing of an aside editor affects the grid’s width. Fixed width columns are recommended to prevent them from resizing each time.

#### <a id="bottom"></a>Bottom

The bottom position can be useful when the user needs to see as many columns in the grid as possible while editing, when horizontal space is limited, or when a wider editor form is desired.

`CrudEditorBottom.java`

```java
crud.setEditorPosition(CrudEditorPosition.BOTTOM);
```

`PersonDataProvider.java`

```java
package com.vaadin.demo.component.crud;

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 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 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()));
  }
}
```

When using a bottom positioned editor, make sure there’s enough vertical space to comfortably fit both the grid and the editor. Also note that a bottom positioned editor is a bad fit for longer forms.

> **Note: Small viewports**
>
> On small viewports, like mobile phones, the editor always opens up as a full-screen overlay, regardless of this configuration.

### <a id="editor-content"></a>Editor Content

The editor’s content is fully configurable, except for the header and footer.

`CrudEditorContent.java`

```java
crud = new Crud<>(
  Person.class,
  createEditor()
);

...

private CrudEditor<Person> createEditor() {
  TextField firstName = new TextField("First name");
  TextField lastName = new TextField("Last name");
  EmailField email = new EmailField("Email");
  ComboBox<String> profession = new ComboBox<>("Profession");
  profession.setItems(professions);

  FormLayout form = new FormLayout(firstName, lastName, email, profession);
  form.setColspan(email, 2);
  form.setColspan(profession, 2);
  form.setMaxWidth("480px");
  form.setResponsiveSteps(
    new FormLayout.ResponsiveStep("0", 1),
    new FormLayout.ResponsiveStep("30em", 2)
  );

  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);
}
```

`PersonDataProvider.java`

```java
package com.vaadin.demo.component.crud;

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 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 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()));
  }
}
```

#### <a id="editor-actions"></a>Editor Actions

The editor contains three Buttons:

- **“Delete”** shows a confirmation dialog asking the user to verify whether they wish to delete the item

- **“Cancel”** closes the editor unless there are unsaved changes, in which case a confirmation dialog is shown and the user can either discard the changes or go back to editing

- **“Save”** saves the changes and closes the editor (disabled until a change is made)

## <a id="grid-replacement"></a>Grid Replacement

CRUD’s default Grid is replaceable. This is useful when you wish to customise the Grid, for example, to place the edit Button in the first column. See [Grid documentation](https://vaadin.com/docs/v14/ds/components/grid.md) for details on configuring grids.

`CrudGridReplacement.java`

```java
crud = new Crud<>(
  Person.class,
  createGrid(),
  createEditor()
);

...

private Grid<Person> createGrid() {
  Grid<Person> grid = new Grid<>();
  Crud.addEditColumn(grid);
  grid.addColumn(Person::getFirstName).setHeader("First name");
  grid.addColumn(Person::getLastName).setHeader("Last name");
  grid.addColumn(Person::getEmail).setHeader("Email");
  grid.addColumn(Person::getProfession).setHeader("Profession");
  return grid;
}
```

`PersonDataProvider.java`

```java
package com.vaadin.demo.component.crud;

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 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 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()));
  }
}
```

> **Note: Edit Column**
>
> You need to explicitly add an edit column to the replacement Grid in order to edit items. In addition, Grid does not have sorting and filtering enabled by default.

## <a id="toolbar"></a>Toolbar

Creating new items is done via the “New item” Button in CRUD’s toolbar. Both the toolbar and its Button are customizable. For example, you can use the toolbar to display statistics such as the dataset’s size or the number of search results.

`CrudToolbar.java`

```java
Html total = new Html("<span>Total: <b>" + dataProvider.DATABASE.size() + "</b> employees</span>");

Button button = new Button("New employee", VaadinIcon.PLUS.create());
button.addClickListener(event -> {
  crud.edit(new Person(), Crud.EditMode.NEW_ITEM);
});
button.addThemeVariants(ButtonVariant.LUMO_TERTIARY);

HorizontalLayout toolbar = new HorizontalLayout(total, button);
toolbar.setAlignItems(FlexComponent.Alignment.CENTER);
toolbar.setFlexGrow(1, toolbar);
toolbar.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
toolbar.setSpacing(false);

crud.setToolbar(toolbar);
```

`PersonDataProvider.java`

```java
package com.vaadin.demo.component.crud;

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 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 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()));
  }
}
```

## <a id="sorting-filtering"></a>Sorting & Filtering

Sorting and filtering can be disabled. For more information about sorting and filtering, please see the basic [Grid documentation](https://vaadin.com/docs/v14/ds/components/grid.md).

`CrudSortingFiltering.java`

```java
crud = new Crud<>(
  Person.class,
  createGrid(),
  createEditor()
);

...

private CrudGrid<Person> createGrid() {
  // Create a new CrudGrid to disable filtering (last boolean parameter)
  CrudGrid<Person> grid = new CrudGrid<>(Person.class, false);

  // Disable sorting
  grid.setSortableColumns();

...

return grid;
}
```

`PersonDataProvider.java`

```java
package com.vaadin.demo.component.crud;

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 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 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()));
  }
}
```

## <a id="localization"></a>Localization

CRUD supports full localization through customizable labels for its buttons and the editor’s title.

`CrudLocalization.java`

```java
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);
```

`PersonDataProvider.java`

```java
package com.vaadin.demo.component.crud;

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 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 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()));
  }
}
```

## <a id="related-components"></a>Related Components

| Component                                                           | Usage recommendations                           |
| ------------------------------------------------------------------- | ----------------------------------------------- |
| [Grid Pro](https://vaadin.com/docs/v14/ds/components/grid-pro.md)   | Component for showing and editing tabular data. |
| [Grid](https://vaadin.com/docs/v14/ds/components/grid.md)           | Component for showing tabular data.             |
| [Tree Grid](https://vaadin.com/docs/v14/ds/components/tree-grid.md) | Component for showing hierarchical data.        |

`D42EF2FA-FF30-444F-B62C-0F159498A262`
