From a1437a5520b373af8ab1bbcdac818746223d2d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petter=20Holmstr=C3=B6m?= Date: Thu, 27 Aug 2026 11:26:09 +0300 Subject: [PATCH 1/4] docs: document CRUD customization API and editor behavior Addresses vaadin/agentic-dx-improvement#103, which reported that the Flow CRUD documentation under-documents the component's extension points, forcing readers to inspect the jar to make an adoption decision. - Document the CrudEditor interface as a public extension point, with a non-Binder editor example that validates itself and renders a top-of-form error summary. - Specify the Save button enablement rules, the role of setDirty(), and how to keep Save enabled at all times via getSaveButton().setEnabled(true). - Document the getSaveButton()/getCancelButton()/getDeleteButton()/ getNewButton() accessors, with an example of hiding Delete for datasets that archive rather than remove records. - Collect the programmatic editor controls (edit, setOpened, setEditorPosition, setEditOnClick) into one table. - Note that CRUD has no manual row reordering, and point at grid replacement. - Explain how CrudFilter maps sorting and filtering onto a lazy backend. - Surface the no-border variant and CrudVariant on the main page, and link to the styling page. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01CvBbsmxH7A3F8WyMz2hfZZ --- articles/components/crud/index.adoc | 163 +++++++++++++++++- articles/components/crud/styling.adoc | 2 + frontend/demo/component/crud/crud-imports.ts | 7 + .../demo/component/crud/CrudCustomEditor.java | 68 ++++++++ .../component/crud/CrudEditorButtons.java | 91 ++++++++++ .../demo/component/crud/PersonCrudEditor.java | 109 ++++++++++++ 6 files changed, 439 insertions(+), 1 deletion(-) create mode 100644 frontend/demo/component/crud/crud-imports.ts create mode 100644 src/main/java/com/vaadin/demo/component/crud/CrudCustomEditor.java create mode 100644 src/main/java/com/vaadin/demo/component/crud/CrudEditorButtons.java create mode 100644 src/main/java/com/vaadin/demo/component/crud/PersonCrudEditor.java diff --git a/articles/components/crud/index.adoc b/articles/components/crud/index.adoc index 37a479ebec..2880162267 100644 --- a/articles/components/crud/index.adoc +++ b/articles/components/crud/index.adoc @@ -244,11 +244,141 @@ endif::[] -- -==== Editor Actions +=== Editor Actions The editor contains three Buttons: _Delete_, _Cancel_, and _Save_. The Delete shows a confirmation dialog asking the user to verify whether they wish to delete the item. Whereas the Cancel closes the editor unless there are unsaved changes. If so, a confirmation dialog is shown and the user can either discard the changes or go back to editing. The Save button, when clicked, saves the changes and closes the editor. This is disabled until a change is made. +==== Save Button State [badge-flow]#Flow# + +Save is enabled as soon as the editor becomes dirty -- that is, as soon as the user changes the value of a field in it. Validity doesn't factor into this: Save is enabled for invalid input, too. Clicking it runs the editor's [methodname]`validate()` method, and when validation fails, the editor stays open and nothing is saved. + +An editor that doesn't propagate its field changes -- one built from a composite component that wraps its fields, for example -- can leave Save permanently disabled. Call [methodname]`setDirty(true)` on the CRUD to enable it explicitly in such a case. + +Some applications need Save to be enabled at all times. A common accessibility pattern is to let the user submit at any point and then show a summary of what needs fixing. Set the enabled state of the Save Button directly to get this: CRUD stops managing that Button's state from then on, leaving it enabled regardless of whether the editor is dirty. The editor's [methodname]`validate()` method still decides whether a save goes through. + +[source,java] +---- +crud.getSaveButton().setEnabled(true); +---- + +See <<#custom-editor,Custom Editor>> for an editor that pairs this with an error summary. + + +==== Editor Button Access [badge-flow]#Flow# + +CRUD's Buttons are ordinary [classname]`Button` instances that you can access from the server to change their state, appearance, or behavior: + +[cols="1,3"] +|=== +|Method |Description + +|[methodname]`getSaveButton()` +|The editor's Save Button. Setting its enabled state hands its management over to you -- see <<#save-button-state,Save Button State>>. + +|[methodname]`getCancelButton()` +|The editor's Cancel Button. + +|[methodname]`getDeleteButton()` +|The editor's Delete Button. CRUD hides it automatically while a new item is being created. + +|[methodname]`getNewButton()` and [methodname]`setNewButton()` +|The toolbar's _New item_ Button. See <<#toolbar,Toolbar>> for an example of replacing it. +|=== + +Set the Button labels through <<#localization,Localization>>, not [methodname]`setText()`: CRUD writes the localized labels onto its default Buttons, overwriting anything set that way. + +Some datasets shouldn't allow deletion at all -- for example, records that are archived or deactivated instead of removed, so that history is preserved. Hide the Delete Button with CSS in that case. + +[.example,themes="lumo,aura"] +-- +[source,typescript] +---- +include::{root}/frontend/demo/component/crud/crud-imports.ts[preimport,hidden] +---- + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/component/crud/CrudEditorButtons.java[render,tags=snippet,indent=0] +---- +-- + + +[#custom-editor] +=== Custom Editor [badge-flow]#Flow# + +[classname]`BinderCrudEditor` is the stock editor implementation, but it isn't the only option. [interfacename]`CrudEditor` is a public interface, so you can supply any implementation -- with its own layout, its own state handling, and its own validation -- through the [classname]`Crud` constructor or [methodname]`setEditor()`. + +CRUD calls the interface's methods at these points: + +[cols="1,3"] +|=== +|Method |When It's Called + +|[methodname]`setItem(item, validate)` +|When the editor is opened, for a new or an existing item. The second parameter tells the editor whether to validate the item immediately. + +|[methodname]`getItem()` +|Whenever CRUD needs the item currently being edited, such as when it builds a save or delete event. + +|[methodname]`validate()` +|When Save is clicked. Returning `false` keeps the editor open and cancels the save. + +|[methodname]`writeItemChanges()` +|After [methodname]`validate()` passes, to copy the input into the item, before the save event is fired. + +|[methodname]`clear()` +|When the editor is closed, whether by saving, deleting, or cancelling. + +|[methodname]`getView()` +|When the editor is set, to get the form to place inside CRUD. +|=== + +The editor in the example below validates the item itself and renders all problems in a summary at the top of the form. It's paired with an always-enabled Save Button, so the user can submit at any point and be told what's missing. + +[.example,themes="lumo,aura"] +-- +[source,typescript] +---- +include::{root}/frontend/demo/component/crud/crud-imports.ts[preimport,hidden] +---- + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/component/crud/CrudCustomEditor.java[render,tags=snippet,indent=0] +---- + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/component/crud/PersonCrudEditor.java[render,indent=0] +---- +-- + + +=== Controlling the Editor Programmatically [badge-flow]#Flow# + +The editor doesn't have to be opened by the user. These methods drive it from the server: + +[cols="1,3"] +|=== +|Method |Description + +|[methodname]`edit(item, EditMode.EXISTING_ITEM)` +|Opens the editor for an item that's already in the dataset. + +|[methodname]`edit(item, EditMode.NEW_ITEM)` +|Opens the editor for a new item, which is what the _New item_ Button does. See <<#toolbar,Toolbar>> for an example. + +|[methodname]`setOpened(boolean)` +|Opens or closes the editor without changing the item being edited. + +|[methodname]`setEditorPosition(CrudEditorPosition)` +|Sets where the editor is rendered: `OVERLAY`, which is the default, `ASIDE`, or `BOTTOM`. See <<#editor-position,Editor Position>>. + +|[methodname]`setEditOnClick(boolean)` +|Opens the editor when the user clicks a row, instead of requiring the edit Button. This removes the edit column from CRUD's built-in grid. +|=== + == Grid Replacement @@ -360,6 +490,10 @@ endif::[] By default, CRUD allows sorting and filtering of any column. For more information about sorting and filtering, see the <<../grid#,Grid documentation>>. +.No Manual Reordering +[NOTE] +CRUD sorts and filters, but it doesn't support manual reordering -- letting the user drag rows or move them with up and down controls to set a display order of their own. If you need that, use <<#grid-replacement,Grid Replacement>> and add the reordering controls to the replacement Grid. + === Disabling Sorting & Filtering @@ -403,6 +537,26 @@ endif::[] -- +=== Lazy Backend Loading [badge-flow]#Flow# + +CRUD's built-in grid passes the user's sorting and filtering to the data provider as a [classname]`CrudFilter`, so that the backend can do the work instead of the server holding the full dataset in memory. A data provider used with that grid has to accept this filter type: [classname]`CrudGrid` throws an [classname]`IllegalArgumentException` for anything that isn't a `DataProvider`. + +[classname]`CrudFilter` carries two maps, both keyed by column key: + +- [methodname]`getConstraints()` maps a column to the text the user typed into its filter field. Translate these into a `WHERE` clause. +- [methodname]`getSortOrders()` maps a column to a [classname]`SortDirection`. Translate these into an `ORDER BY` clause. + +The filter is handed to the data provider on every fetch, and CRUD refreshes the grid whenever the user changes a filter field or a sort order. The [classname]`PersonDataProvider` used in the examples on this page shows the full pattern: it extends [classname]`AbstractBackEndDataProvider`, converts the constraints into a predicate and the sort orders into a comparator, and applies the offset and limit from the query. + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/component/crud/PersonDataProvider.java[indent=0] +---- + +.Honoring the Filter +[NOTE] +The data provider is responsible for applying the filter. If [methodname]`fetchFromBackEnd()` ignores it, the grid's filter fields and sort indicators still appear, but using them has no effect. + == Item Initialization @@ -469,6 +623,13 @@ endif::[] -- +== Styling + +CRUD has a `no-border` style variant, which removes the border around the component. In Flow, apply it with [methodname]`addThemeVariants(CrudVariant.NO_BORDER)`. In Lit and React, set `theme="no-border"` on the component. + +Beyond that variant, CRUD exposes CSS custom properties for its background, borders, toolbar, and editor. See <> for the full list. + + == Related Components |=== diff --git a/articles/components/crud/styling.adoc b/articles/components/crud/styling.adoc index 43976ee1f0..508daebf2e 100644 --- a/articles/components/crud/styling.adoc +++ b/articles/components/crud/styling.adoc @@ -21,6 +21,8 @@ CRUD supports the following style variants: |=== +In Flow, apply the variant with [methodname]`addThemeVariants(CrudVariant.NO_BORDER)`. + include::../_styling-section-theming-props.adoc[tag=style-properties] diff --git a/frontend/demo/component/crud/crud-imports.ts b/frontend/demo/component/crud/crud-imports.ts new file mode 100644 index 0000000000..f4172997d6 --- /dev/null +++ b/frontend/demo/component/crud/crud-imports.ts @@ -0,0 +1,7 @@ +import 'Frontend/demo/init'; +import '@vaadin/crud'; +import '@vaadin/email-field'; +import '@vaadin/form-layout'; +import '@vaadin/text-field'; + +// This file only has the required imports for the Java-only CRUD examples diff --git a/src/main/java/com/vaadin/demo/component/crud/CrudCustomEditor.java b/src/main/java/com/vaadin/demo/component/crud/CrudCustomEditor.java new file mode 100644 index 0000000000..93b5997ee7 --- /dev/null +++ b/src/main/java/com/vaadin/demo/component/crud/CrudCustomEditor.java @@ -0,0 +1,68 @@ +package com.vaadin.demo.component.crud; + +import java.util.Arrays; +import java.util.List; + +import com.vaadin.demo.DemoExporter; // hidden-source-line +import com.vaadin.demo.domain.Person; +import com.vaadin.flow.component.crud.Crud; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.router.Route; + +@Route("crud-custom-editor") +public class CrudCustomEditor extends Div { + + private Crud crud; + + private String FIRST_NAME = "firstName"; + private String LAST_NAME = "lastName"; + private String EMAIL = "email"; + private String EDIT_COLUMN = "vaadin-crud-edit-column"; + + public CrudCustomEditor() { + // tag::snippet[] + crud = new Crud<>(Person.class, new PersonCrudEditor()); + + // The editor reports validation errors itself, so Save can stay + // enabled at all times. + crud.getSaveButton().setEnabled(true); + // end::snippet[] + + setupGrid(); + setupDataProvider(); + + add(crud); + } + + private void setupGrid() { + Grid grid = crud.getGrid(); + + // Only show these columns (all columns shown by default): + List visibleColumns = Arrays.asList(FIRST_NAME, LAST_NAME, + EMAIL, 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(EDIT_COLUMN)); + } + + private void setupDataProvider() { + PersonDataProvider dataProvider = new PersonDataProvider(); + crud.setDataProvider(dataProvider); + crud.addDeleteListener( + deleteEvent -> dataProvider.delete(deleteEvent.getItem())); + crud.addSaveListener( + saveEvent -> dataProvider.persist(saveEvent.getItem())); + } + + public static class Exporter extends DemoExporter { // hidden-source-line + } // hidden-source-line +} diff --git a/src/main/java/com/vaadin/demo/component/crud/CrudEditorButtons.java b/src/main/java/com/vaadin/demo/component/crud/CrudEditorButtons.java new file mode 100644 index 0000000000..bd12de644e --- /dev/null +++ b/src/main/java/com/vaadin/demo/component/crud/CrudEditorButtons.java @@ -0,0 +1,91 @@ +package com.vaadin.demo.component.crud; + +import java.util.Arrays; +import java.util.List; + +import com.vaadin.demo.DemoExporter; // hidden-source-line +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.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; + +@Route("crud-editor-buttons") +public class CrudEditorButtons extends Div { + + private Crud crud; + + private String FIRST_NAME = "firstName"; + private String LAST_NAME = "lastName"; + private String EMAIL = "email"; + private String EDIT_COLUMN = "vaadin-crud-edit-column"; + + public CrudEditorButtons() { + crud = new Crud<>(Person.class, createEditor()); + + // tag::snippet[] + // Records in this dataset are archived rather than removed, so the + // editor shouldn't offer a Delete action at all. Hide it with CSS: + // CRUD manages the button's `hidden` attribute itself, which makes + // setVisible(false) ineffective. + crud.getDeleteButton().getStyle().set("display", "none"); + // end::snippet[] + + setupGrid(); + setupDataProvider(); + + add(crud); + } + + private CrudEditor createEditor() { + TextField firstName = new TextField("First name"); + TextField lastName = new TextField("Last name"); + EmailField email = new EmailField("Email"); + FormLayout form = new FormLayout(firstName, lastName, email); + + Binder 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); + + return new BinderCrudEditor<>(binder, form); + } + + private void setupGrid() { + Grid grid = crud.getGrid(); + + // Only show these columns (all columns shown by default): + List visibleColumns = Arrays.asList(FIRST_NAME, LAST_NAME, + EMAIL, 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(EDIT_COLUMN)); + } + + private void setupDataProvider() { + PersonDataProvider dataProvider = new PersonDataProvider(); + crud.setDataProvider(dataProvider); + crud.addSaveListener( + saveEvent -> dataProvider.persist(saveEvent.getItem())); + } + + public static class Exporter extends DemoExporter { // hidden-source-line + } // hidden-source-line +} diff --git a/src/main/java/com/vaadin/demo/component/crud/PersonCrudEditor.java b/src/main/java/com/vaadin/demo/component/crud/PersonCrudEditor.java new file mode 100644 index 0000000000..7b75cc47bd --- /dev/null +++ b/src/main/java/com/vaadin/demo/component/crud/PersonCrudEditor.java @@ -0,0 +1,109 @@ +package com.vaadin.demo.component.crud; + +import java.util.ArrayList; +import java.util.List; + +import com.vaadin.demo.domain.Person; +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.crud.CrudEditor; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.html.ListItem; +import com.vaadin.flow.component.html.UnorderedList; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.EmailField; +import com.vaadin.flow.component.textfield.TextField; + +// An editor that validates the item itself and reports all problems in a +// single summary at the top of the form, instead of using a Binder. +public class PersonCrudEditor implements CrudEditor { + + private final TextField firstName = new TextField("First name"); + private final TextField lastName = new TextField("Last name"); + private final EmailField email = new EmailField("Email"); + + private final Div errorSummary = new Div(); + private final VerticalLayout view; + + private Person item; + + public PersonCrudEditor() { + // Announce the errors to screen readers when the summary appears. + errorSummary.getElement().setAttribute("role", "alert"); + errorSummary.setVisible(false); + + view = new VerticalLayout(errorSummary, + new FormLayout(firstName, lastName, email)); + view.setPadding(false); + } + + // Called when the editor is opened, for both new and existing items. + @Override + public void setItem(Person item, boolean validate) { + this.item = item; + firstName.setValue(orEmpty(item.getFirstName())); + lastName.setValue(orEmpty(item.getLastName())); + email.setValue(orEmpty(item.getEmail())); + + if (validate) { + validate(); + } + } + + @Override + public Person getItem() { + return item; + } + + // Called when the editor is closed, after a save, delete, or cancel. + @Override + public void clear() { + item = null; + firstName.clear(); + lastName.clear(); + email.clear(); + errorSummary.removeAll(); + errorSummary.setVisible(false); + } + + // Called when Save is clicked. Returning false keeps the editor open and + // leaves the item unchanged. + @Override + public boolean validate() { + List errors = new ArrayList<>(); + if (firstName.isEmpty()) { + errors.add("Enter a first name."); + } + if (lastName.isEmpty()) { + errors.add("Enter a last name."); + } + if (!email.getValue().contains("@")) { + errors.add("Enter a valid email address."); + } + + UnorderedList messages = new UnorderedList(); + errors.forEach(error -> messages.add(new ListItem(error))); + errorSummary.removeAll(); + errorSummary.add(messages); + errorSummary.setVisible(!errors.isEmpty()); + + return errors.isEmpty(); + } + + // Called after validate() has passed, before the save event is fired. + @Override + public void writeItemChanges() { + item.setFirstName(firstName.getValue()); + item.setLastName(lastName.getValue()); + item.setEmail(email.getValue()); + } + + @Override + public Component getView() { + return view; + } + + private static String orEmpty(String value) { + return value == null ? "" : value; + } +} From cd98f9a5276edda6fa0c06d31e9f469a7ac0a577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petter=20Holmstr=C3=B6m?= Date: Thu, 27 Aug 2026 12:27:30 +0300 Subject: [PATCH 2/4] docs: use the CRUD product name in the styling page title Vaadin.ProductName flagged "= Crud Styling". The sibling styling pages all spell out the component name in their titles, as does the CRUD index page. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01CvBbsmxH7A3F8WyMz2hfZZ --- articles/components/crud/styling.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/articles/components/crud/styling.adoc b/articles/components/crud/styling.adoc index 508daebf2e..10a5fb87e3 100644 --- a/articles/components/crud/styling.adoc +++ b/articles/components/crud/styling.adoc @@ -5,7 +5,7 @@ description: Styling API reference for the CRUD component. meta-description: Style the Vaadin CRUD component for a polished and user-friendly data management interface. order: 50 --- -= Crud Styling += CRUD Styling == Style Variants From c19a8616077d8ea487d23bc6fedcdb74d3374096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petter=20Holmstr=C3=B6m?= Date: Thu, 27 Aug 2026 14:04:58 +0300 Subject: [PATCH 3/4] docs: fix CRUD anchors and drop a stray render attribute - Add explicit [#save-button-state] and [#editor-button-access] anchors. The [badge-flow]#Flow# suffix leaks into the generated heading ids ("save-button-state-flow"), so the two cross-references pointed at anchors that don't exist. Verified in a browser: the page now has no broken in-page links. - Include PersonCrudEditor.java without `render`. It isn't a Component, so there's nothing to render; the convention for a supporting class is a plain listing, as in articles/flow/binding-data/index.adoc. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01CvBbsmxH7A3F8WyMz2hfZZ --- articles/components/crud/index.adoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/articles/components/crud/index.adoc b/articles/components/crud/index.adoc index 2880162267..b0c513d64e 100644 --- a/articles/components/crud/index.adoc +++ b/articles/components/crud/index.adoc @@ -249,6 +249,7 @@ endif::[] The editor contains three Buttons: _Delete_, _Cancel_, and _Save_. The Delete shows a confirmation dialog asking the user to verify whether they wish to delete the item. Whereas the Cancel closes the editor unless there are unsaved changes. If so, a confirmation dialog is shown and the user can either discard the changes or go back to editing. The Save button, when clicked, saves the changes and closes the editor. This is disabled until a change is made. +[#save-button-state] ==== Save Button State [badge-flow]#Flow# Save is enabled as soon as the editor becomes dirty -- that is, as soon as the user changes the value of a field in it. Validity doesn't factor into this: Save is enabled for invalid input, too. Clicking it runs the editor's [methodname]`validate()` method, and when validation fails, the editor stays open and nothing is saved. @@ -265,6 +266,7 @@ crud.getSaveButton().setEnabled(true); See <<#custom-editor,Custom Editor>> for an editor that pairs this with an error summary. +[#editor-button-access] ==== Editor Button Access [badge-flow]#Flow# CRUD's Buttons are ordinary [classname]`Button` instances that you can access from the server to change their state, appearance, or behavior: @@ -350,7 +352,7 @@ include::{root}/src/main/java/com/vaadin/demo/component/crud/CrudCustomEditor.ja [source,java] ---- -include::{root}/src/main/java/com/vaadin/demo/component/crud/PersonCrudEditor.java[render,indent=0] +include::{root}/src/main/java/com/vaadin/demo/component/crud/PersonCrudEditor.java[indent=0] ---- -- From 744bdc345a732779d1ede021700faed9156f7398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petter=20Holmstr=C3=B6m?= Date: Thu, 27 Aug 2026 14:19:52 +0300 Subject: [PATCH 4/4] docs: address review feedback on the CRUD page - Remove the Styling section; it duplicated styling.adoc. - Drop frontend/demo/component/crud/crud-imports.ts and point the two Flow-only examples at the existing crud-editor-content.ts instead. It already imports crud, email-field, form-layout, and text-field, so no new file is needed. - Frame hiding the Delete Button as a workaround rather than a technique, and warn against setVisible(false), which CRUD undoes when the editor opens for an existing item. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01CvBbsmxH7A3F8WyMz2hfZZ --- articles/components/crud/index.adoc | 17 +++++++---------- frontend/demo/component/crud/crud-imports.ts | 7 ------- .../demo/component/crud/CrudEditorButtons.java | 5 ++--- 3 files changed, 9 insertions(+), 20 deletions(-) delete mode 100644 frontend/demo/component/crud/crud-imports.ts diff --git a/articles/components/crud/index.adoc b/articles/components/crud/index.adoc index b0c513d64e..074f9df2fa 100644 --- a/articles/components/crud/index.adoc +++ b/articles/components/crud/index.adoc @@ -290,13 +290,13 @@ CRUD's Buttons are ordinary [classname]`Button` instances that you can access fr Set the Button labels through <<#localization,Localization>>, not [methodname]`setText()`: CRUD writes the localized labels onto its default Buttons, overwriting anything set that way. -Some datasets shouldn't allow deletion at all -- for example, records that are archived or deactivated instead of removed, so that history is preserved. Hide the Delete Button with CSS in that case. +Some datasets shouldn't allow deletion at all -- for example, records that are archived or deactivated instead of removed, so that history is preserved. There's no API for removing the Delete Button, so the only way to do this at present is to hide it with CSS. [.example,themes="lumo,aura"] -- [source,typescript] ---- -include::{root}/frontend/demo/component/crud/crud-imports.ts[preimport,hidden] +include::{root}/frontend/demo/component/crud/crud-editor-content.ts[preimport,hidden] ---- [source,java] @@ -305,6 +305,10 @@ include::{root}/src/main/java/com/vaadin/demo/component/crud/CrudEditorButtons.j ---- -- +.Hiding Delete Is a Workaround +[NOTE] +Setting `display: none` isn't advocated by the CRUD API; it's the only thing that works today. Don't reach for [methodname]`setVisible(false)`: CRUD manages the Delete Button's `hidden` attribute itself, and it clears the attribute each time the editor opens for an existing item, which makes the Button reappear. + [#custom-editor] === Custom Editor [badge-flow]#Flow# @@ -342,7 +346,7 @@ The editor in the example below validates the item itself and renders all proble -- [source,typescript] ---- -include::{root}/frontend/demo/component/crud/crud-imports.ts[preimport,hidden] +include::{root}/frontend/demo/component/crud/crud-editor-content.ts[preimport,hidden] ---- [source,java] @@ -625,13 +629,6 @@ endif::[] -- -== Styling - -CRUD has a `no-border` style variant, which removes the border around the component. In Flow, apply it with [methodname]`addThemeVariants(CrudVariant.NO_BORDER)`. In Lit and React, set `theme="no-border"` on the component. - -Beyond that variant, CRUD exposes CSS custom properties for its background, borders, toolbar, and editor. See <> for the full list. - - == Related Components |=== diff --git a/frontend/demo/component/crud/crud-imports.ts b/frontend/demo/component/crud/crud-imports.ts deleted file mode 100644 index f4172997d6..0000000000 --- a/frontend/demo/component/crud/crud-imports.ts +++ /dev/null @@ -1,7 +0,0 @@ -import 'Frontend/demo/init'; -import '@vaadin/crud'; -import '@vaadin/email-field'; -import '@vaadin/form-layout'; -import '@vaadin/text-field'; - -// This file only has the required imports for the Java-only CRUD examples diff --git a/src/main/java/com/vaadin/demo/component/crud/CrudEditorButtons.java b/src/main/java/com/vaadin/demo/component/crud/CrudEditorButtons.java index bd12de644e..b62846b445 100644 --- a/src/main/java/com/vaadin/demo/component/crud/CrudEditorButtons.java +++ b/src/main/java/com/vaadin/demo/component/crud/CrudEditorButtons.java @@ -31,9 +31,8 @@ public CrudEditorButtons() { // tag::snippet[] // Records in this dataset are archived rather than removed, so the - // editor shouldn't offer a Delete action at all. Hide it with CSS: - // CRUD manages the button's `hidden` attribute itself, which makes - // setVisible(false) ineffective. + // editor shouldn't offer a Delete action at all. There's no API for + // removing the Button, so hide it with CSS. crud.getDeleteButton().getStyle().set("display", "none"); // end::snippet[]