Skip to content

Customizing CRUD and listings

Mateu generates a full CRUD UI from your model.

Most real applications need to refine that default UI:

  • hide fields
  • adjust layouts
  • customize lists
  • add actions

This section shows how to do that progressively, without breaking the model-driven approach.


Customize the model first. Customize the orchestrator only when the model is not enough.


@UI("/products")
public class Products extends AutoCrudOrchestrator<Product> {
final ProductAdapter adapter;
public Products(ProductAdapter adapter) {
this.adapter = adapter;
}
@Override
public AutoCrudAdapter<Product> simpleAdapter() {
return adapter;
}
}
public record Product(
String id,
String name,
BigDecimal price,
ProductStatus status
) implements Identifiable {}

This already gives you:

  • list
  • view
  • edit
  • create

Hide fields depending on context.

public record Product(
@HiddenInList
String id,
String name,
BigDecimal price,
@HiddenInEditor
ProductStatus status
) implements Identifiable {}
  • @HiddenInList
  • @HiddenInView
  • @HiddenInEditor
  • @ReadOnly

@EditableOnlyWhenCreating
String id;
@NotNull
BigDecimal price;

Mateu uses standard validation annotations:

  • @NotNull
  • @NotEmpty
  • @Email
  • etc.

@Status
ProductStatus status;

This renders the field as a visual badge instead of plain text.


@Override
public String toString() {
return name;
}

Used in:

  • lists
  • lookups
  • references

@FormLayout(columns = 2)
public record Product(
String name,
BigDecimal price,
ProductStatus status
) {}

@FormSection("General")
String name;
@FormSection("Pricing")
BigDecimal price;

@Style("max-width:600px;margin:auto;")
public class Products {}

@Button
public Object publish() {
return new Message("Published");
}

@Button
public Object discount() {
price = price.multiply(BigDecimal.valueOf(0.9));
return List.of(new Message("Discount applied"), new State(this));
}

At this level, customization moves to the adapter.

public class ProductAdapter extends AutoCrudAdapter<Product> {
final ProductRepository repository;
public ProductAdapter(ProductRepository repository) {
this.repository = repository;
}
@Override
public CrudRepository<Product> repository() {
return repository;
}
}

Typical customizations:

  • filtering
  • sorting
  • search behavior
  • pagination

Use:

  • Callable<?> → dynamic UI
  • @Route → custom pages
  • embedded orchestrators → master-detail

Example:

Callable<?> stats = () -> new HorizontalLayout(
new KPI("Revenue", "10k"),
new KPI("Orders", "120")
);

Do not create custom pages too early. Start with the model and the CRUD. Add custom pages only when the model-driven approach cannot express what you need.


Mateu is backend-driven. Keep logic, validation, and state in Java. The UI is a projection of the server-side model, not an independent stateful application.


Customization in Mateu follows this progression:

  1. Model (annotations)
  2. Validation
  3. Layout
  4. Actions
  5. Adapter
  6. Custom UI (Callable / Route)

Stay in the model as long as possible.

Move to more advanced techniques only when needed.