Sample project attached to edit order and order lines in a single master-detail form.
When editing a line, changing price from 50 to 100, I was expecting that the global amount of the order is recalculated.
But it does not happen, the total amount remains to 50.
If I save the order and reopen it, I can see that under the hood, the amount was correctly computed and saved.
The recalcItem method called below recompute Order amount by summing amounts of each OrderLine.
linesGrid.addEditorPostCommitListener(event -> {
UUID id = (UUID) event.getItemId();
OrderLine l = linesDs.getItem(id);
if (l.getQty() == null)
l.setQty(1d);
linesDs.commit();
recalcItem();
linesGrid.setSelected((OrderLine) null);
});
I’m surprised because I was thinking that a Datasource was observing the underlying object properties to actualize itself when they change (OrderDs in that case), and thus be able to manage a dirty state.
With CollectionDatasource there is the excludeItem/includeItem trick (which I’m not very fond of but it works), but how should I do with simple Datasource ?
More generally, when you have an entity model behind datasources with actual functional methods that change entity properties (like in this case), which UI programming model would you recommend ?
@mortozakhan Thanks, but we are in 6.10. Moreover you are computing total in the Editor, we don’t foster that. In our model, Editor UI does not know about Order internals an just ask it to recalc itself when needed.
Your expectations are correct: the datasource listens to changes in the entity and notifies bound components. And you did everything right except one thing: you set the value to amount field directly prior to setting it via setter, see amount += line.getAmount():
public void recalc() {
if (lines == null)
return;
amount = 0d; // the field itself
int i = 1;
for (OrderLine line : lines) {
line.setOrdinal(i++);
line.recalc();
amount += line.getAmount();
}
setAmount(amount);
}
So when the setter is invoked, the field value is already new, and change listeners are not invoked, because nothing is changed at this moment.
Simply use a local variable for calculations before setting the value to the field:
public void recalc() {
if (lines == null)
return;
double amount = 0d; // local variable
int i = 1;
for (OrderLine line : lines) {
line.setOrdinal(i++);
line.recalc();
amount += line.getAmount();
}
setAmount(amount);
}
For some (wrong) reason, I was thinking there was a previous value stored somewhere for change listeners. This model is more simple and consume less memory, that’s great.
In editors now, I tend more and more to use PropertyChangeListener on item instead of observing the data-bound UI component through a ValueChangeListener.
I find it more suitable to coordonnate data update flow in UI especially with objects that have functional rules to change/compute some of their attributes, like Order in the example.
Is this a model that you would foster ?
I’ll give an example. We have a Product entity. In editor, when supply model change to manual then minimal stock should be set to 0.
Supply model property is bound to a FieldGroup
There are 2 ways of doing that in editor, through PropertyChangeListener like below, or using ValueChangeListener on FieldGroup field. What would you recommend ?
Hi Michael,
As Alex suggested in his comment and issue, we recommend using ItemPropertyChangeEvent listeners on data containers (or legacy datasources).
Thank you both for the hints, I completely converge.
I had a struggling experience with ValueChange listeners similar to the OP. When an entity updates itself in some way, ItemPropertyChange listener is the way to go.
Having had a look on cuba 7 (we are on the edge of upgrading) I did not see a subscribe mechanism for one property, that would be an interesting small addition.
Sounds reasonable, but it would require adding a quite specific property attribute to the general-purpose @Subscribe annotation, which I would like to avoid.
Understandable. So selecting the property could be the result of a more general selection like condition parameter of @EventListener annotation in spring events since 4.2.
But maybe that’s overkill for a subscription mechanism that finally just react on uncommitted changes.
However that could allow routing to a single method from several properties like e.g @Subscribe([...], condition = "#property in {name, address}")
I’ll probably create for our project a mixin or a derived annotation for controllers that suscribes on ItemPropertyChangeEvent and routes through reflection to an existing controller method like onXxxxPropertyChange(ItemPropertyChangeEvent).
e.g onNamePropertyChange if name property changes for a Customer , or onPaymentTermRuleDateMethodPropertyChange if paymentTermRule.dateMethod changes.