How can an object listen to any data changes on a BeanItem?

I have a bean object wrapped in a BeanItem, and the BeanItem is successfully hooked up as the data source behind some GUI components (TextField, OptionGroup, etc.).

Now I need to write that bean’s data to disk. I created a persistence object to write the bean’s data to a disk file. The actual writing disk was easy to do, using
XStream
to serialize the bean to an XML file. The trick is,
how do I invoke that persistence object every time the bean’s data changes
?

One approach is add a line of code to each of setter methods on the bean class, to call the persistence object to write each change to disk. But I have hundreds of members on that bean. It seems silly to paste a line of code to each of hundreds of setter methods. Surely there is a better way.

The BeanItem wrapper around that bean is already aware of each data change through the GUI. Seems like I should be able to make my persistence object a listener of the BeanItem. But I don’t see how in the API.

Questions:
(a)
Is there some way to
make an object a listener to any data changes
inside a JavaBean or inside a BeanItem?

(b)
Am I barking up the wrong tree, and
is there a different approach
to persisting a bean on every change by the user?

–Basil Bourque

Hi,

It’s difficult to tell without context, but, do you really want to change every time a property changes - or do you actually want to bundle up those changes and persist the bean/item when the user presses save/OK?

The latter is more normal, and typically done with a form.

However, the former is possible - essentially, by adding a valueListener to every property in the bean item. I’ll knock up a little bit of generic code in a while.

Cheers,

Charles.

I would recommend using a customized MyBeanItem class which uses MyMethodProperty instances that know the item and notify its listener. You might need to copy the classes, although probably not much needs to be changed apart from using a suitable custom VaadinDescriptor implementation that knows the MyBeanItem to notify. This approach saves you from registering a large number of listeners separately.

Listening to JavaBean changes directly (not just changes via Vaadin BeanItems etc.) would have been covered by
JSR-295 - BeansBinding
, which has been withdrawn.

Hi,

For what it’s worth, I’ve knocked together a Watcher class that can monitor one more items (any type of Vaadin Item), and notify one or more listeners of any property changes.

Apache 2 code published on GitHub here
https://github.com/canthony/vaadin-item-watcher

If anyone thinks it would be helpful, I’ll can (try to) turn it into an Add-On and publish it in the directory.

Cheers,

Charles.


Yes to the first part
, we really want to persist the data bean to a disk file any time a property changes in its wrapper BeanItem.

This app is not the usual business-style app where basically a detail form is used to edit a database row. This app asks a series of questions, a few hundred questions spread across dozens of Vaadin Layouts. As the user progresses through each question, we want to persist the data of their answer.

You can think of it as a single big database row or bean with the column/member values spread across many Layouts. Imagine possible apps such as:

I am about to take a look at your coding effort. In the mean time I wanted to clarify the question.

–Basil Bourque

So, if the Item classes do not already have Listener support built in, then how does a Vaadin Table’s row get automatically updated when a detail Layout edits the value of a BeanItem in the container backing that table?

I see. Years ago I looked at the BeansBinding JSRs and other such efforts, but frankly could not make heads-or-tails out of how to practically make use of them.

–Basil

Holy cow, wow! That is exactly what I was looking for when I perused the Item interfaces & classes.

I copied your “org.vaadin.itemwatcher” package of
three classes
into my project. I even used your example code to take my project for a spin.
ItemWatcher works perfectly.
Thank you so much! I will go forward using it. If I can help with further testing or such, let me know.

An aside:
My only problem is unrelated to your itemwatcher code, but I was reminded of it when I used your test code with System.out.println: OptionGroup sporadically saves data 2 or 3 times rather than once per user-click. In the past, I’ve built a simple test app in Vaadin 6.7 showing this problem with OptionGroup, where it sporadically gets or sets its property multiple times rather than once. Sometimes 2, 3, 7, or more times. But I could never see a pattern.

–Basil Bourque

I’m glad it’s useful! One thing I forgot to mention - remember to “unwatch” the item afterwards, otherwise the listeners will hang around and could possibley cause a bit of a memory leak.

The other thing worth mentioning is that MethodProperty (which is used, indirectly, by BeanItem) fires a valueChange event every time it is invoked - even if the value “set” is the same. Depending on your requirements, you might want extend ItemWatcher, cache property value, and only propogate when the property value changes. That might reduce the number of times OptionGroup causes the “Save”.

Cheers,

Charles.

Apparently you already solved your problem, but in case someone wonders about this:

The Table component registers itself directly as a listener for each property of each item in its “display buffer” which contains the rows shown to the user as well as some rows around that area that act as a scroll buffer. Unregistration is performed (by the Table) when an item leaves the buffer e.g. as the result of scrolling. This is somewhat complicated and inefficient but at least reuses the listener instance etc.

The idea of registering a ValueChangeListener for an Item to monitor all its properties has come up from time to time. This would simply be a few short utility methods but would bloat the API slightly. It probably will not happen if nobody
creates an enhancement request
for it, though.

The watcher works perfect. It saved my day as it seems there is still no built-in solution for that requirement.
Thanks a lot for the time you spent on that 3 years ago :wink:

Daniel