Vaadin 6: Fighting with Table and Bean binding (again)

Hi, I’m still using Vaadin 6, for a project. As I understand, Vaadin 7’s table is the same.


The Problem:

I’ve got a table with bean objects, in a BeanContainer. The table displays fine. However, I need to edit one of the items, and the user needs to be able to choose whether to save or cancel the edits.

I’m using a form for the content editing. I’ve tried conditional binding (using commit() & discard() ) and they don’t help much, because the bean still does not refresh properly in the table after saving.


Current Design:

The user clicks on a table item, then the bean content is copied (using a shallow copy) and the copy is passed to a Form object. If the user clicks “Save” the modified bean is returned, otherwise null is returned.

This all works fine, I’ve got all the “automation” I want with the forms popping up correctly, the data model, form fields and visible columns are all great.


Table Drama

In the past I had to use the (very) kludgy work around of table.setContainerDataSource(table.getContainerDataSource) ;

All I want to do is update ONE row in the container. Not the whole thing.


Attempted Solutions (which don’t work)

  1. Update the beanitem as follows:

table.getContainerDataSource().getItem(table.getValue())
                   .getItemProperty(table.getValue()).setValue(data) ;

Where data is the bean object (of the same type as the BeanContainer instance)

2a. I’ve also tried this:

BeanItem<?> changed = new BeanItem<Object>(data) ;
         
         for (Object id : table.getContainerPropertyIds())
             table.getItem(table.getValue()).getItemProperty(id)
                      .setValue(changed.getItemProperty(id)) ;

2b. and this minor variant:


         BeanItem<?> changed = new BeanItem<Object>(data) ;
         
         for (Object id : table.getContainerPropertyIds())
             table.getItem(table.getValue()).getItemProperty(id)
                        .setValue(changed.getItemProperty(id)[b]
.getValue()
[/b]) ;

There are lots of posts on the forums regarding this issue, but none of them solve
the simple question of how to update a bean in a BeanContainer and have the table refresh that row
.

Any help much appreciated.

Hi,

I’ve knocked up a (working!) example of a simple bean container select-and-edit, with a buffered form. I wrote it in Vaadin 7 - cos that’s what was in my IDE at the time - but I’ve just used the Vaadin 6 API where possible (and commented where the V6 is different). It should be simple to change the code to use a Vaadin 6 Window as opposed to a Vaadin 7 UI.

The key point is that the item set on the form must be the same item as on the table - i.e. exactly the same BeanItem instance as on the table. The ValueChangeListener in the code does this.

I hope this helps.

Cheers,

Charles.

public class FormEditExample extends UI {
  @Override
  protected void init(VaadinRequest request) {
    /* Lets create some data */
    Person[] people = new Person[]
{
        new Person("Fred Boggins", 20),
        new Person("Doris Day", 98),
        new Person("Freddy Flintoff", 38),
        new Person("Boris van Clinkenthorp", 52)
    };

    /* For simplicity, the itemId of the bean is the bean itself */
    BeanContainer<Person, Person> container = new BeanContainer<Person, Person>(Person.class);
    for (Person person : people) {
      
      container.addItem(person, person);
    }

    final Form form = new Form();
    form.setCaption("Person");
    form.setImmediate(true);
    form.setBuffered(true); // Different in v6 - setReadThrough/WriteThrough
    form.setEnabled(false);

    /* Build the table */
    final Table table = new Table("People", container);
    table.setSelectable(true);
    table.setImmediate(true); // I always forget this : needed so that selection events occur immediately
    table.setNullSelectionAllowed(true);
    table.addValueChangeListener(new Property.ValueChangeListener() {
      @Override
      public void valueChange(Property.ValueChangeEvent event) {
        // Set the form's item to be the same item as the currently selected one
        form.setItemDataSource(table.getItem(table.getValue()));
        form.setEnabled(table.getValue() != null);
      }
    });

    /* Create and add some commit/discard buttons to the form */
    Button save = new Button("Save", new Button.ClickListener() {
      @Override
      public void buttonClick(Button.ClickEvent event) {
        form.commit();
      }
    });
    Button cancel = new Button("Cancel", new Button.ClickListener() {
      @Override
      public void buttonClick(Button.ClickEvent event) {
        form.discard();
      }
    });
    HorizontalLayout buttons = new HorizontalLayout();
    buttons.setSpacing(true);
    buttons.addComponent(save);
    buttons.addComponent(cancel);
    form.setFooter(buttons);

    /* Set up the window/ui with the table and form */
    HorizontalLayout mainLayout = new HorizontalLayout();
    mainLayout.addComponent(table);
    mainLayout.addComponent(form);
    mainLayout.setSpacing(true);
    mainLayout.setMargin(true);

    setContent(mainLayout); // Different in Vaadin 6
  }


  public class Person {
    protected String name;
    protected int age;

    public Person() {
    }

    public Person(String name, int age) {
      this.name = name;
      this.age = age;
    }

    public int getAge() {
      return age;
    }

    public void setAge(int age) {
      this.age = age;
    }

    public String getName() {
      return name;
    }

    public void setName(String name) {
      this.name = name;
    }
  }

}

Thanks Charles!
I’ve already done something similar, but in this case, there is a logic requirement that means I need to update the table bean from outside the scenario you’ve covered.

Is there no way to update the table bean reference without using Form binding?

Thanks for the very thorough reply and example.
Regards,
Anthony

If you work through the container APIs, the table should be notified automatically. The solution 2b looks correct to me based on a quick glance (what didn’t work?) for copying values inside the item. Alternatively, you could replace the whole item in the container using removeItem(…) and add*().

Some months ago, I did write a prototype of a BeanItem that allows replacing the bean and does send notifications to listeners including UI components, but I never got around to testing it enough to publish it. It is a bit more tricky than one might think (listeners, data types, metadata, …) and there are a few limitations (e.g. no nested properties from the container), but I did it in half a day and it was just under 1000 lines of code (whence not published).

Well, the only way to update a table row based on an Item is to use the item/container property binding, yes; the only way to get the table to notice a change to the item is to use the item to make the change (IYSWIM). Basically, it registers PropertyListeners for all of the rendered items, and hence refreshes the rendered rows when they change.

A quick “hamfisted” work-around might be to simply extend Table and add a public method does the following

public void refreshAllCells() { resetPageBuffer(); refreshRenderedCells(); } Essentially, that’ll cause the table to repaint all of it’s already-rendered rows. Typically, that’s not all of the rows in the container, but just the current “page”, plus a buffer either side. Of course, you would need to explicitly call that method when appropriate.

Another approach would be to use your own extension to BeanItem and ObjectProperty to allow you to fire
AbstractProperty#fireValueChange
for all properties on the given item.

e.g. (pseudo code - realistically, move stuff to RefreshBeanContainer,RefreshingBeanItem and RefreshingProperty classes) Object beanThatHasChanged = ... RefreshingBeanItem item = (RefreshingBeanItem) container.getItem(beanThatHasChanged); if(item!=null){ Collection ids = item.getPropertyIds(); for(Object id: ids){ RefreshingProperty property = ( RefreshingProperty) item.getItemProperty(id); property.refresh(); } } That way, you could say “Yeah, I know this bean instance has changed - fire propertychanged for all of the items in it”.

HTH a little,

Cheers,

Charles.

Edited to add: Ahh, I missed the Shallow Copy bit. Ultimately, you’ll need to find the item for the orig bean, change it’s contents to the modified bean and fire all of the valueChanged events/call refreshAllCells.

Thanks Henri and Charles for your replies. I’ll try these ideas out and see how it works. All the best.

If you’re willing, I’d be happy to take the code on and tinker with it some more. I think it is a big gap in the Container model that you can add or remove but not replace.

I understand the design goals of the Container trying to abstract, well, a container. But at the same time, there is an implied "
software contract
", if I can add or remove, I ought to be able to replace, since the container is already mutable.

Here is the version from back then - not sure if it works with the latest Vaadin versions but that should not require much work.
12987.java (29 KB)

Thanks Henri. I’ll fiddle with it and see where I get with it. Thanks a lot.

Finally got to work on this part of the application again. This is going to be a really silly question- but frankly, I still find the whole container concept very opaque. The multiple uses of the concept “Item” in different ways is confusing.

How would I use this? I’ve got a sub-class of BeanItemContainer which is what I want to be able to modify the bean content of.

Perhaps I am using this wrong, but I am adding the bean directly to the container and not wrapping it in a BeanItem. Is that how I would use your mutable version?

Currently my code is working fine (except altering the bean part) and I add beans as follows: container.addItem (someBean) ;
Should I add beans like this: container.addItem (new MutableBeanItem<BeanClass>(someBean) ) ;

None of the standard containers supports adding a pre-created Item instance.

In theory it would be possible to create a container that does support that, but it might have some memory overhead compared with the standard containers and there could be complications with the set of supported properties if it varies from item to item etc.

Then again, also BeanItem already has quite a bit of extra overhead. I have tried to address that to some extent in the project
mcont
, which is effectively a BeanContainer that supports a mix of beans of different types. I haven’t released it as an add-on yet, though.

If MutableBeanItem is a subclass of BeanItem (I can’t recall right now whether it is), you could use a subclass of BeanContainer or BeanItemContainer and override AbstractBeanContainer.createBeanItem(BEANTYPE). See the superclass implementation of the method.

Thanks Henri,
It is a stand alone implementation, implementing interfaces for Item and one other I can’t recall right now.

I am subclassing BeanItemContainer, but I don’t really follow your answer. I am using {beanitemcontainer}.addItem(…) and to add the List results, I’m using addAll(…) on the values passed in the constructor. BeanItemContainer allows for instantiation with a Collection<?> derived list, which is the issue in my other post.