dooApp/FXForm2

Adding the capability of deactivate the binding between model properties and controls view properties

Closed this issue · 19 comments

In some case we wait the confirmation of users to save the change that has been made on fxform controls . So I want to add some option of persistence between the source bean properties and Form controls properties, like deactivate automatic update between source bean properties and Form controls properties. and the capability of reload the value of source bean properties to Form controls properties.

Maybe you could use two instances of your model bean: the first one is used as model by the form and the second one is your business object. Then you just need to map the model bean to your business object once the user indicates he wants to save the changes and at this steps you can perform all operations you want. On the other side, you could map the business object to the form model bean to reload the business object value in the form.

Would this fit your need? If not, could you give us more details on the scenario you want to reach?

Hi Antoine excuse me for my English. Yes It's possible to use two beans, but this means some works to do, create two beans and binding them if user want to save, and cancel changes and reload the ancients values if the user want to cancel the changes. I think that the simple way to do this is in presentation layer then on FXFrom. I made some modification on PropertyEditorController and on AbstractFXForm and it's work.

Ok, I understand. Could you share your modifications using a pull request for example to discuss it?

yes of course

but how? I did not use this site before!

If you have cloned the project on Github, you can submit a pull request

hi amishler. I have create a pull request , can we discuss it new?

Thanks for your pull request. I will have a look at it.
I think there a some points that need to be clarified. Using your implementation, cross field validation won't work, since the value of the modified fields won't be set.
Another approach could be to use a temporary bean cloned from the original source. This would allow to add a commit mechanism without losing features.

hi amishler.
In my implementation the field validation works normally without problem.

The issue is not with single field validation, but with validators that involves several fields to perform the validation (e.g. checking that a password confirmation field matches the password field). In the first case, hibernate can perform a check without setting the value to the field. In the second case it requires that all fields are set to perform the validation.

xylo commented

Any updates on this? I really, really need this feature.

in Vaadin this feature is implemented by adding another abstraction layer on top of the Java bean. They call this layer "Item" and such an Item provides an API to all the properties of a bean or whatever. They implemented very different Item classes, but the most important are:

  • BeanItem: wraps a Java bean by accessing the properties of the bean directly (direct reads/writes)
  • BufferedBeanItem wraps a Java bean by creating a copy of all its bean properties so that (temporary) changes in the form do not change the Java bean immediately but only if you call form.commit()

This abstraction layer has the advantage that the source does not necessarily need to by a Java bean. Instead it could be for instance a database row or whatever depending on the implementation of the Item. But that's not important for me.

On the other hand, this would mean that the cross field validation has to be performed on the Item object instead of the Java bean. So the API would change. :( But IMO this would be the cleanest solution.

Another solution which @amischler already mentioned is to make a copy of the bean. However, I think the overhead for the data buffering should be hidden from the developer as far as possible. What about the following API:

  • FXForm provides a method enableBuffering(T sourceCopy) which takes an (empty) instance of the source type which is used as model of the form
  • if buffering is enabled (sourceCopy is set) the method FXForm.setSource(T soruce) just copies all properties from source to sourceCopy and updates the form
  • when form.commit() is called all properties from sourceCopy are copied to source again

With the second solution there is no need to change the existing API. Only some additional function will be added. Moreover, it's definitely faster to implement than the first one.

What do you think about it?

Since this is a show stopper for me, I have to implement this feature next week somehow. The only question is how it fits best into this project and how will be accepted when i create a pull request.

PS: Very nice project BTW!

Thanks for waking up this thread ! I agree, the cleanest solution is to add another abstract layer on the top of the Java Bean. This is also the solution used in TornadoFX ViewModel. With this approach all the rollback/commit and state checking is delegated to this additional abstraction layer. I did not know Vaadin "Item" implementation but I think this is very similar.

Such an approach would require very few changes in FXForm2 code itself. We need a ViewModel implementation that handle the commit and rollback features. Some reflection magic might help to make it very easy to use for the developer. Then, a specific ElementProvider should be able to easily extract the Elements from the ViewModel to be used in the form.

What do you think about this approach ?

After some more reflection about it, I realized that the abstract layer that we need for this feature is already available throught the ElementProvider/Element interface. So what we need to do, is to implement a BufferedElementProvider which provides BufferedElement. Those BufferedElement will act as a buffer between the bean property and the property edited by the form. Commit and rollback method can be implemented at the BufferedElement level and triggered for all elements throught commit/rollback methods on the Form/BufferedElementProvider.

I won't have time to work on this implementation in the next days, but I would be happy to accept a PR !

xylo commented

@amischler
Thanks for your hints. I implemented the buffering by wrapping the Elements created by the ElementFactory. So I have

  • BufferedElement which wraps Element (read only)
  • BufferedPropertyElement which wraps PropertyElement (read/write)
  • BufferedElementFactory which wraps an ElementFactory so that it automatically creates buffered elements

The FXForm got two new methods:

  • commit() for writing all changes to the source bean
  • reload() for reloading the state from the source bean

I though form.reload() might be better than form.rollback(), since reload() does not only undo changes but reads the current state of the source bean again (which might have been changed in the meanwhile). What do you think?

There is still one open issue I want to solve: commit() should write everything or nothing back to the source bean. In other words: When all field values are valid, all values are written to the source bean and form.commit() returns true. When one of the fields has an invalid value, the whole commit should be rejected and form.commit() should return false. I looked for a function like form.isValid() to check whether the form content is valid or not, but could not find one. If you know a simple solution for this, please let me know.

Nice ! You can check whether the form content is valid by checking if FXForm.getConstraintViolations() list is empty. BTW, adding an isValid() method on the form doing exactly this could be interesting !

xylo commented

@amischler
Thanks a lot for the tip with FXForm.getConstraintViolations(). Didn't see that at all. FXForm.isValid() has been added and FXForm.commit() works now as expected.

Deployed in 8.0.19-SNAPSHOT

Deployed in 8.1.0