Crud UI Add-on

Hello everyone!

I’ve implemented a new Vaadin add-on to generate CRUD-like interfaces at runtime:


https://vaadin.com/directory#!addon/crud-ui-add-on

This add-on defines a set of interfaces that are useful to implement CRUD-like UIs. There are at least one implementation for each interface, but I’m planing to implement new ones. You can also implement your own (and
contribute
them?).

Supose you have a
User
class (a Java bean) and you want to show a CRUD UI for it. All you have to do is something like this:

GridBasedCrudComponent<User> crud = new GridBasedCrudComponent<>(User.class);
layout.addComponent(crud);

// use lambda expressions or method references to delegate CRUD operations to your backend:
crud.setAddOperation(user -> backend.add(user));
crud.setUpdateOperation(backend::update);
crud.setDeleteOperation(backend::delete);
crud.setFindAllOperation(backend::findAll);

See the examples provided in the add-on page for more advanced use cases.

Issues and code contributions are welcome on GitHub:
https://github.com/alejandro-du/crudui

Hope you find it useful!

Looks cool. I got to check it out.

Hi Alejandro, I used the updated version of crudui add-on. Thank you for the improvements. I would like some recommendations to :

  • prevent adding an empty record;
  • send a confirmation request before a delete operation;
  • treat a relationship type fields 1: n as CustomerStatus whose selection comboBox is fed from another table;
  • specify the date format (dd / mm / yyyy) to the grid and the form.

regards,

Hi, thanks for the feedback! I uploaded a new version (1.3) of the add-on with some nice improvements.

Eric,

“- prevent adding an empty record;”

I’m not sure what you mean. Would you mind elaborating?


“- send a confirmation request before a delete operation;”

The window that is shown when the “x” button is clicked is the confirmation request. Otherwise the user would have to click “x”, then click “Delete”, then click “Yes, I confirm”, which sounds like too much. Maybe using a “Confirm deletion” title for the window would make things clearer.


“- treat a relationship type fields 1: n as CustomerStatus whose selection comboBox is fed from another table;”

With version 1.3 this is doable by using the
setFieldType
and
setFieldCreationListener
methods. For example:


crud.setFieldType(“mainGroup”, ComboBox.class);
crud.setFieldCreationListener(“mainGroup”, field → {
ComboBox comboBox = (ComboBox) field;
comboBox.setContainerDataSource(new BeanItemContainer<>(Group.class, groups));
comboBox.setItemCaptionPropertyId(“name”);
});


"- specify the date format (dd / mm / yyyy) to the grid […]
"

This is doable by setting a
DateRenderer
for the corresponding column in the
Grid
. For example:


crud.getGrid().getColumn(“birthDate”).setRenderer(new DateRenderer(“%1$te/%1$tm/%1$tY”));


“[…]
and the form.”

With version 1.3 you can use the
setFieldCreationListener
method to configure the fields. For example:


crud.setFieldCreationListener(“birthDate”, field → ((DateField) field).setDateFormat(“yyyy-MM-dd”));

Happy coding!

Hi Alejandro. Thanks for a really useful add-on.

Works great. How would this Crud UI work or a java bean that has a collection of objects and would allow me to edit the collection of items in the form popup?

Thank you kindly,
Jason

Hi Alejandro and thanks thanks for the enhancements in crudui add-on version 1.3.

Date format now is working greats!



crud.getGrid().getColumn(“birthDate”).setRenderer(new DateRenderer(“%1$te/%1$tm/%1$tY”));
crud.setFieldCreationListener(“birthDate”, field → ((DateField) field).setDateFormat(“dd/MM/yyyy”));



“- prevent adding an empty record;”
Alejandro: “I’m not sure what you mean. Would you mind elaborating?”

In Add form, there’s no required field thus when you clic “Save” with any value, empty record is added in the Grid with only Id! (See attached images).


“- send a confirmation request before a delete operation;”

Alejandro, we know that deletion is a critical operation, and we must care about that by requiring a confirmation by a clear message from the user. You may provide it even as optional;


“- treat a relationship type fields 1: n as CustomerStatus whose selection comboBox is fed from another table;”

For 1.3 version, when i use:




crud.setFieldType(“status”, ComboBox.class);
crud.setFieldCreationListener(“status”, field → {
ComboBox comboBox = (ComboBox) field;
comboBox.setContainerDataSource(new BeanItemContainer<>(CustomerStatus.class, EnumSet.allOf(CustomerStatus.class)));
comboBox.setItemCaptionPropertyId(“status”);
});


Combobox adds lines the right number lines but does not display any value. When you edit a customer for example, the corresponding line is display but not item showed! (see attached image).

Regards
28920.jpg
28921.jpg
28922.jpg

Hi Jason,

If by “edit the collection” you mean that the end user can modify the “selected values” then it’s quite easy to do:


crud.setFieldType(“groups” OptionGroup.class);
crud.setFieldCreationListener(“groups”, field → {
OptionGroup optionGroup = (OptionGroup) field;
optionGroup.setMultiSelect(true);
optionGroup.setContainerDataSource(new BeanItemContainer<>(Group.class, groups));
optionGroup.setItemCaptionPropertyId(“name”);
return optionGroup;
});

BTW, in version 1.5, I’ll add a more convenient
setFieldProvider
method.

Many thanks Alejandro. That answer worked well.

How about using a custom field for an entity with a nested object?

I tried:

        GridBasedCrudComponent<Person> crud = new GridBasedCrudComponent<>(Person.class);
        crud.setFieldType("address", AddressPopup.class); 
public class Person {

    private String firstName;
    private String lastName;
    private Address address;
//getters and setters
public class Address {

    private String street;
    private String zip;
//getters and setters//

Taken from https://vaadin.com/wiki/-/wiki/Main/Creating+a+custom+field+for+editing+the+address+of+a+person
public class AddressPopup extends CustomField<Address> {

    private static final long serialVersionUID = 2721197776543658373L;
    private FieldGroup fieldGroup;

    @Override
    protected Component initContent() {
        FormLayout layout = new FormLayout();
        final Window window = new Window("Edit address", layout);
        TextArea street = new TextArea("Street address:");
        TextField zip = new TextField("Zip code:");
        TextField city = new TextField("City:");
        TextField country = new TextField("Country:");
        layout.addComponent(street);
        layout.addComponent(zip);
        layout.addComponent(city);
        layout.addComponent(country);

        fieldGroup = new BeanFieldGroup<Address>(Address.class);
        fieldGroup.bind(street, "street");
        fieldGroup.bind(zip, "zip");
        fieldGroup.bind(city, "city");
        fieldGroup.bind(country, "country");
        Button button = new Button("Open address editor", new ClickListener() {

            /**
             * 
             */
            private static final long serialVersionUID = 2410419047939583633L;

            public void buttonClick(ClickEvent event) {
                getUI().addWindow(window);
            }
        });
        window.addCloseListener(new CloseListener() {
            /**
             * 
             */
            private static final long serialVersionUID = -1936217938466658922L;

            public void windowClose(CloseEvent e) {
                try {
                    fieldGroup.commit();
                } catch (CommitException ex) {
                    ex.printStackTrace();
                }
            }
        });

        window.center();
        window.setWidth(null);
        layout.setWidth(null);
        layout.setMargin(true);
        return button;
    }

    @Override
    public Class<Address> getType() {
        return Address.class;
    }

    @Override
    protected void setInternalValue(Address address) {
        super.setInternalValue(address);
        fieldGroup.setItemDataSource(new BeanItem<Address>(address));
    }
}

But received the following exception:

com.vaadin.data.Buffered$SourceException: null
    at com.vaadin.ui.AbstractField.setPropertyDataSource(AbstractField.java:667) ~[vaadin-server-7.7.3.jar:7.7.3]

    at com.vaadin.data.fieldgroup.FieldGroup.bind(FieldGroup.java:278) ~[vaadin-server-7.7.3.jar:7.7.3]

    at com.vaadin.data.fieldgroup.BeanFieldGroup.bind(BeanFieldGroup.java:155) ~[vaadin-server-7.7.3.jar:7.7.3]

    at com.vaadin.data.fieldgroup.FieldGroup.buildAndBind(FieldGroup.java:1186) ~[vaadin-server-7.7.3.jar:7.7.3]

    at com.vaadin.data.fieldgroup.BeanFieldGroup.buildAndBind(BeanFieldGroup.java:162) ~[vaadin-server-7.7.3.jar:7.7.3]

    at org.vaadin.crudui.AbstractAutoGeneratedCrudFormFactory.addFields(AbstractAutoGeneratedCrudFormFactory.java:26) ~[crudui-1.4.jar:1.4]

    at org.vaadin.crudui.impl.form.GridLayoutCrudFormFactory.buildNewForm(GridLayoutCrudFormFactory.java:33) ~[crudui-1.4.jar:1.4]

    at org.vaadin.crudui.impl.crud.GridBasedCrudComponent.buildForm(GridBasedCrudComponent.java:218) ~[crudui-1.4.jar:1.4]

    at org.vaadin.crudui.impl.crud.GridBasedCrudComponent.addButtonClicked(GridBasedCrudComponent.java:165) ~[crudui-1.4.jar:1.4]

    at org.vaadin.crudui.impl.crud.GridBasedCrudComponent.lambda$new$63983b74$2(GridBasedCrudComponent.java:57) ~[crudui-1.4.jar:1.4]

What am i doing wrong? Many thanks

Hi Jason, it should work by calling
formFactory.setFieldProvider(“address”, () → new AddressPopup());
in version 1.5. which I’ll publish later this week.

Hi Alejandro, I tried the crudui add-on 1.4 and it works very well for me, thanks.

“- treat a relationship type fields 1: n as CustomerStatus whose selection comboBox is fed from another table;”
For 1.4 version, i used:

[i]
[size=2]
[font=comic sans ms]
crud.getGridContainer().addNestedContainerBean(“country”);
crud.getGrid().setColumns(“firstName”, “lastName”, “birthDate”, “email”, “gender”, “country.name”);

// Rename column grid headers
crud.getGrid().getColumn(“birthDate”).setHeaderCaption(“Date of birth”);
crud.getGrid().getColumn(“gender”).setHeaderCaption(“Gender”);
crud.getGrid().getColumn(“country.name”).setHeaderCaption(“Country”);

    // customize fields:
    crud.getGrid().getColumn("birthDate").setRenderer(new DateRenderer("%1$te/%1$tm/%1$tY"));
    crud.setFieldCreationListener("birthDate", field -> ((DateField) field).setDateFormat("dd/MM/yyyy"));

    // enum set for gender {Male, Female}
     crud.setFieldType("gender", ComboBox.class);
    crud.setFieldCreationListener("gender", field -> {
        ComboBox comboBox = (ComboBox) field;   
        comboBox.setContainerDataSource(new BeanItemContainer<>(CustomerGender.class, EnumSet.allOf(CustomerGender.class)));
    });   

[/font]
[/size]
[/i]
[i]
[size=2]
[font=comic sans ms]

    // entity class for CustomerCountry       

[/font]
[/size]
[/i]



CountryService countryService = CountryService.getInstance();
crud.setFieldType(“country”, ComboBox.class);
crud.setFieldCreationListener(“country”, field → {
ComboBox comboBox = (ComboBox) field;
comboBox.setItemCaptionPropertyId(“name”);
comboBox.setContainerDataSource(new BeanItemContainer<>(Country.class, countryService.findAll(“”)));
});


It works greats! (see attached image).

Still have theses requests
“- prevent adding an empty record;”
Alejandro: “I’m not sure what you mean. Would you mind elaborating?”
In Add form, there’s no required field thus when you clic “Save” with any value, empty record is added in the Grid with only Id! (See attached images).

“- send a confirmation request before a delete operation;”
Alejandro, we know that deletion is a critical operation, and we must care about that by requiring a confirmation by a clear message from the user. You may provide it even as optional;

28921.jpg
28922.jpg
28925.png

Eric, thanks for the feedback.

The empty record could be a legit data entry, so I cannot add such validation to the add-on. However in 1.5 you’ll be able to throw a
CrudOperationException
from the CRUD listeners to indicate an invalid data entry. The exception’s message is shown as an error on the form.

Regarding the delete confirmation, as I said, the window shown when the “x” button is clicked is the confirmation request itself. I’ll change the default title of the form/window and button in the delete forms for version 1.5 so that the confirmation request is clear. However, if you still want to have two confirmations, you can extend the
VerticalCrudFormFactory
or
GridLayoutCrudFormFactory
classes and override the
buildButton()
to build a button that handles the additional confirmation (this method will be available in 1.5).

Hello everybody,

I just published version 1.5 with the promised enhancements :smiley: The API has changed so, please have a look at the examples on the
add-on page
. Any further ideas and feedback on how to improve it are apreciated.

Hi Alejandro, I tried the crudui add-on 1.5 and thanks for the work done. I’ve encountered some difficulties while migrating theses codes:




// 1. enum set for gender {Male, Female}

Replacing
crud.setFieldType(“gender”, ComboBox.class);
crud.setFieldCreationListener(“gender”, field → {
ComboBox comboBox = (ComboBox) field;
comboBox.setContainerDataSource(new BeanItemContainer<>(CustomerGender.class, EnumSet.allOf(CustomerGender.class)));
});
By
formFactory.setFieldType(“gender”, ComboBox.class);
formFactory.setFieldCreationListener(“gender”, field → {
ComboBox comboBox = (ComboBox) field;
comboBox.setContainerDataSource(new BeanItemContainer<>(CustomerGender.class, EnumSet.allOf(CustomerGender.class)));
});
Do not fill data anymore!





// 2. entity class for CustomerCountry

Replacing
crud.setFieldType(“country”, ComboBox.class);
crud.setFieldCreationListener(“country”, field → {
ComboBox comboBox = (ComboBox) field;
comboBox.setItemCaptionPropertyId(“name”);
comboBox.setContainerDataSource(new BeanItemContainer<>(Country.class, countryService.findAll(“”)));
});
By
formFactory.setFieldType(“country”, ComboBox.class);
formFactory.setFieldCreationListener(“country”, field → {
ComboBox comboBox = (ComboBox) field;
comboBox.setItemCaptionPropertyId(“name”);
comboBox.setContainerDataSource(new BeanItemContainer<>(Country.class, countryService.findAll(“”)));
});
Raised error:
GRAVE: com.vaadin.data.fieldgroup.FieldGroup$BindException: Unable to build a field of type com.vaadin.ui.ComboBox

for editing com.example.tutorial.backend.Country




// 3. How to use setFieldProvider for ComboBox field ?

formFactory.setFieldProvider(“groups”, () → {
OptionGroup optionGroup = new OptionGroup();
optionGroup.setMultiSelect(true);
optionGroup.setContainerDataSource(new BeanItemContainer<>(Group.class, groups));
optionGroup.setItemCaptionPropertyId(“name”);
return optionGroup;
});

What am i doing wrong? Many thanks

Check that you are setting the
CrudFormFactory
to the
CrudComponent
. Also, use
setFieldProvider
to create custom fields. When adding enum values, use
Arrays.asList(TheEnum.values())
insted of
EnumSet
.

See this example:
https://github.com/alejandro-du/crudui/blob/master/src/test/java/TestUI.java

Thanks Alejandro, it s now working!

[i]
[size=2]
[font=arial]
[b]

  1. “- send a confirmation request before a delete operation;”
    [/b]
    Alejandro, we know that deletion is a critical operation, and we must care about that by requiring a confirmation by a clear message from the user. You may provide it even as optional;
    [/font]
    [/size]
    [/i]
    It is better now with version 1.5!
    However can I set the caption of the corresponding message <Are you sure you want to delete this item?> ?





2. “- prevent adding an empty record;”







Alejandro: The empty record could be a legit data entry, so I cannot add such validation to the add-on. However in 1.5 you’ll be able to throw a CrudOperationException from the CRUD listeners to indicate an invalid data entry. The exception’s message is shown as an error on the form.



How can I do this? Suppose I want First Name and Birth Date set to required.

[i]
[b]
[size=2]
[font=arial]
3. formFactory.setDisabledPropertyIds(CrudOperation.UPDATE, “id”); not seems to have effect anymore. I want for example to set Id field visible but disabled during UPDATE operation.

  1. How to set refresh list visible, someting like: crud.setXxxxVisible(Boolean); ?
    [/font]
    [/size]
    [/b]
    [/i]



What am i doing wrong? Thanks

Eric,

  1. Captions and window titles are up to the
    FormLayout
    implementation:
    WindowBasedCrudLayout.setWindowCaption
    or
    HorizontalSplitCrudLayout.setFormCaption
    .
  2. Use the Bean Validation API or manually add
    Validator
    s.
  3. Will be
    fixed
    in 1.5.1.

  4. crud.setFindAllOperationVisible(Boolean)
    .

Hi Alejandro,
Could build a “ReportViewer add-on” which will allow to preview a report in a new Form and provides the following boutons/links operations: Print, ExportToPDF, ExportToExcel, ExportToWord and ExportToCSV ?
And the ability to communicate with a backend.

Best regards

Hi, that’s on my to-do list :wink: I don’t know when I’ll have time for it, though. But hey, let’s use this thread to solve things related to the Crud UI add-on :slight_smile:

You’re right, in fact it’s just because I haven’t got any feedback for this request in private message!

Regards

Hi Alejandro. Thanks for the improvements to 1.5

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.

Can you advise on how i would go about creating forms for this sort of relationship:

class Person{
  String name;
  String lastname;
  List<Address> addresses;
}

I’ve tried using the formFactory.setFieldProvider to assign a custom popup window for filling in an address like so:

GridLayoutCrudFormFactory<Person> formFactory = new GridLayoutCrudFormFactory<>(Person.class, 2, 2); GridBasedCrudComponent<Person> crud = new GridBasedCrudComponent<>( Person.class); crud.setCrudFormFactory(formFactory); formFactory.setFieldProvider("address", ()-> new AddressPopup()); addComponent(crud); but the result is a IllegalArgumentException: Window is already attached to an application. So I’m clearly doing something wrong!

Thank you kindly,
Jason