Crud UI Add-on

Alejandro Duarte:

Stefan Throm:
I get the following error:

Caused by: java.lang.NoSuchMethodError: com.vaadin.ui.Grid.asSingleSelect()Lcom/vaadin/ui/SingleSelect;
at org.vaadin.crudui.crud.impl.GridCrud.updateButtons(GridCrud.java:128)
at org.vaadin.crudui.crud.impl.GridCrud.initLayout(GridCrud.java:89)
at org.vaadin.crudui.crud.impl.GridCrud.(GridCrud.java:60)
at org.vaadin.crudui.crud.impl.GridCrud.(GridCrud.java:43)

Caused by following command:

orgCrud = new GridCrud<>(Organisation.class, new HorizontalSplitCrudLayout());

This should be fixed in the latest versions of the add-on. Cheers!

Many Thanks.
It works perfectly.

Alejandro Duarte:

Thomas Wagner:
Is it possible to set a default value when opening the Dialog for new items? I tried the fieldCreationListener and fieldProvider-methods, but the value gets always overridden.

Vaadin 10

Fixed. You should be able to use a FieldCreationListener to set the field value in v3.8.1. For example:

formFactory.setFieldCreationListener(CrudOperation.ADD, "name", f -> f.setValue("default name"));

Thank you very much.

I created two fields of type Long but they were disabled in the form and the value of the field keeps defaulting to null. Please how can I solve this? validation api is a dependency of the project.

Joyce Adelusi:
I created two fields of type Long but they were disabled in the form and the value of the field keeps defaulting to null. Please how can I solve this? validation api is a dependency of the project.

Can you double check that you have the correct getters and setters for the field?

Hello, could you update some of these issues for the version 4.1.0

For example, I would like to update the captions of the windows and buttons on the form, but I can’t find how to do that.
You mention on a previous post to check WindowBasedCrudLayout.setWindowCaption or HorizontalSplitCrudLayout.setFormCaption.
But I can’t find those on this version.

I found the CrudFormFactory class but I rather not create my own forms unless is necessary, I just want to change some captions, and maybe the number of column on the form layout.

Thank you in advance

For example, I would like to update the captions of the windows and buttons on the form, but I can’t find how to do that.

Check the CrudFormFactory::buildCaption and AbstractAutoGeneratedCrudFormFactory::setButtonCaption methods.

Alejandro Duarte:

For example, I would like to update the captions of the windows and buttons on the form, but I can’t find how to do that.

Check the CrudFormFactory::buildCaption and AbstractAutoGeneratedCrudFormFactory::setButtonCaption methods.

I am right now working on some CRUD operations. This Component CrudUI is amazing to use, but I am right now facing some issues. I want to go to new location as I save the data by clicking the ADD button, but I was not able to find the solution. So I thought to add external component to the crudUI. What is the easy or any way to add external Button Component to crudUI? If you can help me with this it would be grateful. Thanks!

Doesn’t something like the following work?

crud.setAddOperation(bot -> {
  ... save logic ...
  UI.getCurrent().navigate(TheView.class);
}

You can add buttons to the crud layout. For example:

crud.getCrudLayout().addToolbarComponent(theButton);

Check the examples at https://github.com/alejandro-du/crudui

Hi Alejandro, is it possible to add an event on the cancel button that checks if there are changes on the form ?
e.g You have unsaved changes on this form

okeme djbabs:
Hi Alejandro, is it possible to add an event on the cancel button that checks if there are changes on the form ?
e.g You have unsaved changes on this form

The add-on doesn’t have this functionality. However, you can use a custom CrudFormFactory to implement this. You could, for example, extend AbstractAutoGeneratedCrudFormFactory and override buildCancelButton(ClickEvent<Button>>) to add a click listener that checks if binder.hasChanges().

Deepak Chainani:

Deepak Chainani:

Alejandro Duarte:

Unfortunatelly, there’s not good API to achive this. Maybe you can try something like this, but I’m not sure if this would work well:

TextField someField = new TextField("Some field");
        formFactory.setFieldProvider("someField", () -> someField);

        formFactory.setFieldProvider("anotherField", () -> {
            Checkbox activeField = new Checkbox("Active");
            activeField.addValueChangeListener(v -> someField.setEnabled(false));
            return activeField;
        });

This works! Thanks for the help :slight_smile:

The above code changed seems to be working fine. Thanks for the help. But there is one small problem, the DatePicker component which I have set using setFieldProvider() shows the date format at some instant it shows dd/mm/yyyy and later it displays mm/dd/yyyy, whereas there is another DatePicker field which is added with DefaultFieldProvider shows the date format as dd/mm/yyyy always. I would like have it consistent as dd/mm/yyyy for all the DatePicker fields. I tried setting Locale to Default and UK but still the problem persists. I have attached screenshot.

Hi Deepack or Alejandro, have been able to resolve this issue about datepickers, because I’ve the same problem:

DefaultCrudFormFactory<GPIP> formFactory =
		new DefaultCrudFormFactory<>(GPIP.class);
crud.setCrudFormFactory(formFactory);

DatePicker since = new DatePicker();
DatePicker until = new DatePicker();
since.addValueChangeListener(event -> {
	...
});
until.addValueChangeListener(event -> {
	...
});
formFactory.setFieldProvider("since", () -> since);
formFactory.setFieldProvider("until", () -> until);

I hope there is a solution, thanks and regards

Hi Alejandro,

I am currently looking at whether I could use your addon for my project. I find your addon super useful, thanks for sharing. However, I still have a few questions. Maybe you can give me your opinion on this.

Understanding error handling using the ADD operation as an example

Scenario is that the bean validation was successful, but there is an error in the backend when saving the data. I will describe my understanding so you can point out where I am wrong.

The click on update leads to execution

public abstract class AbstractAutoGeneratedCrudFormFactory<T> extends AbstractCrudFormFactory<T> {
...
    protected Button buildOperationButton(CrudOperation operation, T domainObject, ComponentEventListener<ClickEvent<Button>> clickListener) {
	...
        button.addClickListener(event -> {
            if (binder.writeBeanIfValid(domainObject)) {
                try {
                    clickListener.onComponentEvent(event);
                } catch (Exception e) {
                    showError(operation, e);
                }
            } else {
                Notification.show(validationErrorMessage);
            }

Next, the operationPerformedClickEvent of the GridCrud Impl comes into play, which requires that an exception is thrown, which is caught by the AbstractAutoGeneratedCrudFormFactory and thus it doesn’t get to the success message

public class GridCrud<T> extends AbstractCrud<T> {
...
    protected void showForm(CrudOperation operation, T domainObject, boolean readOnly, String successMessage, ComponentEventListener<ClickEvent<Button>> buttonClickListener) {
	...
        }, operationPerformedClickEvent -> {
            buttonClickListener.onComponentEvent(operationPerformedClickEvent);
            if (!clickRowToUpdate) {
                crudLayout.hideForm();
            }
            showNotification(successMessage);
        });

Here is the implementation of the updateButton in this example in the GridCrud and my first questions

public class GridCrud<T> extends AbstractCrud<T> {
...
    protected void updateButtonClicked() {
        T domainObject = grid.asSingleSelect().getValue();
        showForm(CrudOperation.UPDATE, domainObject, false, savedMessage, event -> {
            try {
                T updatedObject = updateOperation.perform(domainObject);
                grid.asSingleSelect().clear();
                refreshGrid();
                grid.asSingleSelect().setValue(updatedObject);
                // TODO: grid.scrollTo(updatedObject);
            } catch (IllegalArgumentException ignore) {
            } catch (CrudOperationException e1) {
                refreshGrid();
            } catch (Exception e2) {
                refreshGrid();
                throw e2;
            }
        });
    }
  • Question 1: Why is no exception thrown in case of an IllegalArgumentException or CrudOperationException? In my opinion this wrongly leads to a success message.

Let’s follow the path that it was not one of the mentioned exceptions and an exception is thrown. If the showError implementation of the AbstractAutoGeneratedCrudFormFactory comes on the scene again

public abstract class AbstractAutoGeneratedCrudFormFactory<T> extends AbstractCrudFormFactory<T> {
...
    @Override
    public void showError(CrudOperation operation, Exception e) {
        if (errorListener != null) {
            errorListener.accept(e);
        } else {
            if (CrudOperationException.class.isAssignableFrom(e.getClass())) {
                // FIXME no Notification.Type
                Notification.show(e.getMessage());
            } else {
                Notification.show(e.getMessage());
                throw new RuntimeException("Error executing " + operation.name() + " operation", e);
            }
        }
    }

How is error handling thought of here?

  • Question 2: CrudOperationException only notification to the user? But currently can’t “arrive” here at all, because it is currently caught before.
  • Question 3: Rest of exceptions notification and global error handling to ensure logging?
  • Question 4: errorListener to hook its own error handling?

I would be grateful for any feedback.

Greetings
Knut

Hi Alejandro,

another question.

In my use case, I don’t want to provide a list of records and therefore not a grid, but just one record with a readonly form plus editing capability.

I have currently solved this via a slimmed down variation of the GridCrud class.

public class FormCrud<T> extends AbstractCrud<T> {
...
	// Additional OperationListener to load a single record
    protected FindOperationListener<T> findOperation = t -> null;

    public void setFindOperation(FindOperationListener<T> findOperation) {
        this.findOperation = findOperation;
    }

    // Used to provide the backend service with the identifier without knowing the data structure of the identifier
    protected T findDomainObject;

    public void setFindDomainObject(T o) {
        this.findDomainObject = o;
        refreshForm();
    }
	
	...
	
    public void refreshForm() {
        // To make the readonly form in the MainComponent independent from the form for editing
        DefaultCrudFormFactory<T> readonlyFormFactory = new DefaultCrudFormFactory<>(domainType);
        readonlyFormFactory.setUseBeanValidation(false);

        // Load domain object via new OperationListener
        T readonlyObject = findOperation.find(findDomainObject).orElse(readonlyFormFactory.getNewInstanceSupplier().get());
        
        Component readonlyForm = readonlyFormFactory.buildNewForm(CrudOperation.READ, readonlyObject, true, null,null);
        crudLayout.setMainComponent(readonlyForm);
    }

To make the readonly form in the MainComponent independent from the form for editing, another instance of the CrudFormFactory is created.

The approach works. What do you think about this approach? Have I possibly overlooked something or do you have another idea for the realization?

Thanks again!
Knut

Hi Alejandro,

I have forgotten one aspect.

I have a requirement that the field labels should be displayed to the left of the field instead of above it. The hints I found in the forum point to using the addFormItem method of the FormLayout.

I have applied this in a variation of the DefaultCrudFormFactory

Instead of


    @Override
    public Component buildNewForm(CrudOperation operation, T domainObject, boolean readOnly, ComponentEventListener<ClickEvent<Button>> cancelButtonClickListener, ComponentEventListener<ClickEvent<Button>> operationButtonClickListener) {
        FormLayout formLayout = new FormLayout();
        formLayout.setSizeFull();
        formLayout.setResponsiveSteps(responsiveSteps);

        List<HasValueAndElement> fields = buildFields(operation, domainObject, readOnly);
        fields.stream()
                .forEach(field ->
                        formLayout.getElement().appendChild(field.getElement()));

I have used

        List<HasValueAndElement> fields = buildFields(operation, domainObject, readOnly);
        fields.stream()
                .forEach(field ->
                        formLayout.addFormItem(field.getElement().getComponent().orElse(null),"Need to get actual field label"));

This works in principle.

  • Do you think there could be problems with the rest of your implementation?
  • How can I read and remove the original caption on the field?
  • The solution with addFormItem is not 100% responsive. The labels of the second column slide over the fields of the first column when the browser window is collapsed. Do you know better approaches to achieve the goal?

Thanks!
Knut

Is it possible to customize the button-tool-tips “Refresh list”, “Add”, “Update” and “Delete” ?

Jan Klatt:
Is it possible to customize the button-tool-tips “Refresh list”, “Add”, “Update” and “Delete” ?

18534349.png

Jan Klatt:

Jan Klatt:
Is it possible to customize the button-tool-tips “Refresh list”, “Add”, “Update” and “Delete” ?

Yes. For example: GridCrud::getFindAllButton().getElement().setAttribute("title", "Custom tooltip").

Alejandro, your CRUD-UI is areal life-safer… It makes things so easy…

But there is just one thing I’m missing or I’m just too blind to see it…

I’d like to have some fields to span multiple coloumns, so I can have a “two column layout” for for my “short” fields and under those “short properties” I’d like to have some properties that span th two columns because they usually contain long text…

If this is possible I don’t get how…

Thanks for any hint,
Michael

Michael, thanks for the feedback. I opened a new issue for this. Please follow it to get notified when the functionality is implemented.

https://github.com/alejandro-du/crudui/issues/94

I’ve found everything super simple to customise with apart from the cases of domain objects with nested objects with multiple fields (e.g. CRUD for a person object who has many addresses.

1 Like