How to attach more than one bean in a form

Hi there,

I’m a newbie in vaadin. And due to my practice, I have to create a form to insert, update data for 2 table. Two table will look like this:
SUBJECT_TABLE
ID Name Description
1 MyName MyDescription

DETAILS_TABLE
ID | SubjectID | ParentID | Content

1 | 1 | Null | Content1
2 | Null | 1 | SubContent1.1
3 | Null | 1 | SubContent1.2
4 | 1 | Null | Content2
5 | Null | 4 | SubContent2.1
6 | Null | 4 | SubContent2.2

Are there anyway to input all of these data in only one form with bean attach? I can’t find any thread that help me with this problem (or may be I have low ability of searching :frowning: )
Please help or guide me to the right thread.

Thanks in advanced
TamNguyen

Hi Tam,

take a look to this
post
.

Hope it helps.

Javi

Hi Javier,

Thanks for your help but my problem is more complicated. T_T
Since I have to work with two table at a time, so the input form will look like the picture that I attached.

I already have two beans Subject and Details

@Entity
@Audited
@Table
public class Subject extends GeneratedKeyBaseEntity
{
    private static final long serialVersionUID = 4563943969726213794L;

    @Column(name = "name", nullable = false)
    private String name;
    
    @Column(name = "description", nullable = false)
    private String description;
    
    @OneToOne(fetch = FetchType.LAZY, mappedBy = "subject")
    private Details detail;
...
@Entity
@Audited
@Cacheable(true)
public class Details extends GeneratedKeyBaseEntity
{
    private static final long serialVersionUID = 1413136371395313591L;
    
    @NotAudited
    @Column(name = "parentId")
    private Long parentId;
    
    @Column(name = "content", nullable = false)
    private String content;
    
    @OneToOne(fetch = FetchType.LAZY)
    private Subject subject;
    
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "parentDetail")
    private List<Details> subDetails;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "parentId", insertable = false, updatable = false)
    private Details parentDetail;
....

But I don’t know how to put them in same form

 
String SUBJECT_NAME = "name";
String SUBJECT_DESCRIPTION = "description";
String DETAIL_CONTENT = "detail.content";
String[] PROPS = new String[]
{SUBJECT_NAME, SUBJECT_DESCRIPTION, DETAIL_CONTENT};
subjectFrm = new Form()
        {
            private static final long serialVersionUID = -7468890841917310653L;
            
            private FormLayout subjectFormLayout = new FormLayout();
            
            {
                this.setLayout(subjectFormLayout);
            }
            
            @Override
            protected void attachField(final Object propertyId, final Field field)
            {
                if (SUBJECT_NAME.equals(propertyId))
                {
                    subjectFormLayout.addComponent(field);
                }
                else if (SUBJECT_DESCRIPTION.equals(propertyId))
                {
                    subjectFormLayout.addComponent(field);
                }
                else if (DETAIL_CONTENT.equals(propertyId))
                {
                    subjectFormLayout.addComponent(field);
                }
            }
            
            @Override
            public void commit() throws SourceException, InvalidValueException
            {
                
            }
            
        };
        
        subjectFrm.setFormFieldFactory(new FormFieldFactory()
        {
            private static final long serialVersionUID = -5152824267335436654L;

            @Override
            public Field createField(final Item item, final Object propertyId, final Component uiContext)
            {
                Field field = null;
                if (SUBJECT_NAME.equals(propertyId))
                {
                    field = subjectNameTxt;
                }
                else if (SUBJECT_DESCRIPTION.equals(propertyId))
                {
                    field = subjectDescriptionTxt;
                }
                else if (DETAIL_CONTENT.equals(propertyId))
                {
                    field = detailContentTxt;
                }
                return field;
            }
        });
        
        Subject subject = new Subject();
        Details detail = new Details();
        subject.setName("");
        subject.setDescription("");
        subject.setDetail(detail);
        BeanItem<Subject> subjectBean = new BeanItem<Subject>(subject);
        
        subjectFrm.setWriteThrough(false);
        subjectFrm.setItemDataSource(subjectBean);
        subjectFrm.setVisibleItemProperties(PROPS);
        subjectFrm.setCaption("Subject");
        subjectFrm.getFooter().addComponent(saveButton);
        subjectFrm.getFooter().addComponent(discardButton);

Do you have any idea about this situation ?

Thanks and Best regards
Tam Nguyen
12162.jpg

If you are using explicitly created BeanItems, the easiest solution is probably to use nested properties. Otherwise, check what the container you are using might provide - if I remember correctly, e.g. BeanContainer and JPAContainer support telling the container to automatically add specified nested properties to their items.

The code could look something like


myBeanItem.addItemProperty("content", new NestedMethodProperty(myBeanItem.getBean(), "detail.content"))

Vaadin 7 will also make adding nested properties directly to BeanItems a little easier with a helper method for the above.

EDIT: As you seem to be using JPA, make sure lazy loading does not cause problems - if necessary, force the loading of lazy fields. Depending on your use of the item, the JPA library used etc. this may or may not be a problem for you.

Hi Henri,

Thanks a lot. My problem is solved now. :D.
Before I mark this thread as Resolved, I would like to have one more question about bean with form. :slight_smile:
Are there anyway (again :slight_smile: ) to put a List of bean to a form?
For example:
I have three Details bean with id 1,2,3. Can I put these beans into only one form with first text area “Content” for content of Details bean1, second text area “Content” for content of Details bean 2, …
I already know that we can display these bean using table


BeanItemContainer<Details> detailsBean = new BeanItemContainer<Details>(Details.class);
List<Details> detailsList = //code to get list of detail objects
detailsBean.addAll(detailsList );
Table table = new Table();
table.setContainerDataSource(detailsBean )

and thanks to Javier, I also know a trick to answer my question : using 3 sub forms to display 3 Details objects. But it would be cooler if we can go directly to one form with these beans, right ? :slight_smile:
If you have time, please share your idea with us about this question.
And once again, thank you for helping me in previous problem.
Best regard,
Tam Nguyen

Hi Tam,

First of all, you don’t have to thank nothing it’s really a pleasure for me help other developers, also when my answer doesn’t fit the needs of them :unsure:

And now, after read your post and review the code you post

I think that the use of a table for your data structure is not bad, you can set editable mode on table and add a TableFieldFactory that shows the “content” property in bean as TextField (or TextArea as you prefer) and then save the changes you made on bean in table to your database, adding a Listener

There is a good example of how to implement this in the
Book of Vaadin
, and also
here
in the Book Examples Application you can view
http://demo.vaadin.com/book-examples/book/#component.table.editable
.

The code snippet could be similar to this one:


BeanItemContainer<Details> detailsBean = new BeanItemContainer<Details>(Details.class);
List<Details> detailsList = //code to get list of detail objects
detailsBean.addAll(detailsList );
Table table = new Table();
table.setContainerDataSource(detailsBean )
table.setTableFieldFactory(new DefaultFieldFactory() {
    @Override
    public Field createField(Container container, Object itemId, Object propertyId, Component uiContext) {
        // Create a TextField if the propertyId is content property of your Detail class
        if ("content".equals(propertyId)) {
            return new TextField();
        }
        // Otherwise use the default field factory 
        return super.createField(container, itemId, propertyId,uiContext);
    }
});
// Add ValueChangeListener to listen changes on table data
table.addListener(new Property.ValueChangeListener() {
    @Override
    public void valueChange(ValueChangeEvent event) {
          // Implement the logic to save your entity here
});
// Make table editable
table.setEditable(true)

Hope this helps, but if Henri has another idea, problably should be better than mine :slight_smile:

Cheers,

Javi

I agree that an editable Table is often a good approach in such situations, and can also be supplemented with controls to add/remove rows etc. if necessary. Note, though, that the value of a Table (also when on a Form) is the selected row id (or a Set of them for a multiselectable table), which can cause some complications if you put the table directly as a field in the form. You can get around this e.g. by wrapping the table in a
CustomField
and returning for instance a list of your beans or some other suitable representation - or by preventing the form from saving the value of that field and just using it for in-place editing.

Another option - in case you want the content fields of the sub-objects directly on the form - would be to add related properties by hand (e.g. looping over a list of sub-beans) to your BeanItem and then displaying the item on the Form. You could use MethodProperty, NestedMethodProperty or your own custom Property implementations (inheriting AbstractProperty) for this, and have a little helper class or a subclass of BeanItem to automate all this.

Hi Javier and Henri,

Thank you all for helping me about this problem. I will try both 2 solutions to improve my vaadin’s knowlegde.

Hope someday I will have an opportunity to work with you two.
Thank you & best regards
Tam Nguyen