Subforms in Nested Bean Validation

Hi All,

I made a dummy application to test vaadin framework. I wanted to tes Nested Form. For which i followed this code
http://demo.vaadin.com/book-examples/book/1_7/#component.form.subform.nestedforms

I have following simple entities

Qualification
----- String descriptor [Not NULL]

Address
----- String city [Not NULL]

PersonAddress (table in between)
----- Integer personid [Not NULL]

----- Integer addressid [Not NULL]

Person
----- String name; [Not NULL]

----- Qualification qualid; [Not NULL]

----- List addressList;

Here is my Person Editor

public class PersonEditor extends VerticalLayout {

    private final PersonRepository repository;

    private TextField name  = new TextField("name");
    private ComboBox qualid = new ComboBox("Qualification");
    private AddressFormTable personAddressList = new AddressFormTable();
    

    Button save = new Button("Save", FontAwesome.SAVE);
    Button cancel = new Button("Cancel");
    Button delete = new Button("Delete", FontAwesome.TRASH_O);
    CssLayout actions = new CssLayout(save, cancel, delete);
    
    private BeanFieldGroup<Person> personFG = new BeanFieldGroup<>(Person.class);

    @Autowired
    public PersonEditor(PersonRepository repository,  QualificationRepository qualRepos, final ChangeHandler h) {
        this.repository = repository;
        
        personFG.setBuffered(false);
        name.setNullRepresentation("");
        name.setRequired(true);
        addComponents(name, qualid, personAddressList, actions);

        qualid.setContainerDataSource(new BeanItemContainer(Qualification.class, qualRepos.findAll()));
        
        setSpacing(true);
        actions.setStyleName(ValoTheme.LAYOUT_COMPONENT_GROUP);
        save.setStyleName(ValoTheme.BUTTON_PRIMARY);
        save.setClickShortcut(ShortcutAction.KeyCode.ENTER);

        save.addClickListener(new ClickListener() {
            
            @Override
            public void buttonClick(ClickEvent event) {
                if(personFG.isValid())
                {
                    repository.save(personFG.getItemDataSource().getBean());
                    h.onChange();
                } else {
                    Notification.show("validation error!",Type.TRAY_NOTIFICATION);
                    
                }
            }
        });
        delete.addClickListener( new ClickListener() {
            
            @Override
            public void buttonClick(ClickEvent event) {
                repository.delete(personFG.getItemDataSource().getBean()); 
                h.onChange();
                
            }
        });
        cancel.addClickListener(e -> editPerson(personFG.getItemDataSource().getBean()));
        setVisible(false);
    }
    

    public interface ChangeHandler {

        void onChange();
    }

    // ENTRY POINT
    public final void editPerson(Person c) {
        final boolean persisted = c.getId() != null;
        Person person; 
        if (persisted) {
            person = repository.findOne(c.getId());
        }
        else {
            person = c;
        }
        
        BeanItem<Person> personBeanItem = new BeanItem<Person>(person);
        cancel.setVisible(persisted);

        personFG.setItemDataSource(personBeanItem);
        personFG.bindMemberFields(this);

        setVisible(true);

        save.focus();
        name.selectAll();
    }

    class AddressFormTable extends CustomField {

        Table table = new Table();
        [b]
BeanItemContainer<PersonAddress> personAddContainer = new BeanItemContainer<PersonAddress>(PersonAddress.class);
[/b]
        VerticalLayout layout = new VerticalLayout();
        
        @Override
        protected com.vaadin.ui.Component initContent() {
[b]
            table.addContainerProperty("form", FormLayout.class, null);
[/b]
            table.setColumnHeaderMode(ColumnHeaderMode.HIDDEN);
            layout.addComponent(table);
            
            Button newMoon = new Button ("New Address");
            newMoon.addClickListener(new  ClickListener() {
                
                @Override
                public void buttonClick(ClickEvent event) {
                    [b]
table.removeAllItems();
                    for (PersonAddress itemId: personAddContainer.getItemIds())
                        addItemToTable(itemId);
[/b]
                    PersonAddress pa = new PersonAddress();
                    Address add = new Address();
                    pa.setAddressid(add);
                    
                    pa.setPersonid(personFG.getItemDataSource().getBean());
                    personFG.getItemDataSource().getBean().getPersonAddressList().add(pa);
                    BeanItem<PersonAddress> itemId = personAddContainer.addBean(pa);
                    addItemToTable(itemId.getBean());
                    
                    table.setPageLength(personAddContainer.size() == 0 ? 1: personAddContainer.size());
                    
                }
            });
            table.setPageLength(1);
            table.setHeightUndefined();
            layout.setWidth("100%");
            layout.addComponent(newMoon);
            layout.setComponentAlignment(newMoon, Alignment.BOTTOM_LEFT);
            return layout;
        }

        void addItemToTable (PersonAddress itemId) {
            AddressForm form = new AddressForm(itemId);
            [b]
BeanItem<PersonAddress> bi = personAddContainer.getItem(itemId);
            if(bi.getItemProperty("addressid")  != null)
            {
                bi.expandProperty("addressid");
            }
            BeanFieldGroup<PersonAddress> beanFieldGroup = new BeanFieldGroup<PersonAddress>(PersonAddress.class);
            beanFieldGroup.setBuffered(false);
[/b]
            [b]
beanFieldGroup.setItemDataSource(bi);
            beanFieldGroup.bindMemberFields(form);
            table.addItem(new Object[]{form}, itemId);
[/b]

        }
        
        @Override
        public Class<?> getType() {
            return List.class;
        }
        
         @Override
            public void setPropertyDataSource(Property newDataSource) {
                Object value = newDataSource.getValue();
                if (value instanceof List) {
                    @SuppressWarnings("unchecked")
                    List beans = (List) value;
                    [b]
personAddContainer.removeAllItems();
                    personAddContainer.addAll(beans);
                    table.removeAllItems();
                    for (PersonAddress itemId: personAddContainer.getItemIds())
                        addItemToTable(itemId);
[/b]
                    table.setPageLength(beans.size());
                    super.setPropertyDataSource(newDataSource);
                } else
                    throw new ConversionException("Invalid type");
                
            }

        class AddressForm extends FormLayout {

            @PropertyId("addressid.city")
            TextField city ;
            Button delete = new Button("Delete", FontAwesome.TRASH_O);
            
            public AddressForm(final PersonAddress itemId) {
                city = new TextField("City");
                city.setImmediate(true);
                delete.addClickListener( new ClickListener() {
                    
                    @Override
                    public void buttonClick(ClickEvent event) {
                        PersonAddress pa = personAddContainer.getItem(itemId).getBean();
                        pa.getPersonid().getPersonAddressList().remove(pa);
                        pa.setPersonid(null);
                        pa.setAddressid(null);
                        personAddContainer.removeItem(itemId);
                        table.removeItem(itemId);
                    }
                });
                
                addComponent(city);
                addComponent(delete);
                setSizeUndefined();
                city.setNullRepresentation("");
                [b]
city.setRequired(true);
[/b]
            }
            
        }
        
    }

}

I am not a pro in Vaadin, I just started learning it. I am liking it so far.

I feel my code is hacky (specially the pieces of code in bold above), it just works somehow and probably i am not using the correct components. I feel there are better ways to do this simple task.
For instance I
should be using FieldGroup Instead of FormLayout
, But i was having trouble making it work.

Secondly I would like the validators to be automatically picked up from the anotations from my entity. But when I
add a new Address
to the person the
Not Null validator is not fired
for the text field city bound to the property on the beanitem.

Could someone please suggest me improvements and how to fix my code.

thanks in advance
Chahat

27910.png

Hi, first off, if it’s working that’s already good considering you are starting with the framework :slight_smile: Unfortunatelly, there are not
bold
lines in the code you provided (I think this forum doesn’t suport it). I’d recomend you watch
this video
(or even the whole
series
). It explains how to use
BeanFieldGroup
to do the data binding if yo are interested. But in a nutshell:

With BeanFieldGroup, you can create any layout containing input components (such as text fields, check boxes, combo boxes, …) and bind them to a POJO. For example:

public class Person {
    private String firstame;
    privete String lastName;
    ... getters and setters ...
}

public class PersonForm extends FormLayout {
    private TextField firstName;
    private TextField lastName;
    ... configure the componones and add them to the layout ...
}

BeanFieldGroup.bindFieldsUnbuffered(somePersonInstance, somePersonFormInstance);

About the not null validator, if you mean you want to use Java Bean Validation API, take a look at
this example
.

Happy coding! :slight_smile: