DataGrid master-detail edit form, master entity not refreshed after line commit

Hi

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.

image

But it does not happen, the total amount remains to 50.

image

If I save the order and reopen it, I can see that under the hood, the amount was correctly computed and saved.

image

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 ?

Best Regards
Michael

Hi
You can use the followings:

@Subscribe
protected void onBeforeCommit(BeforeCommitChangesEvent event) {

    calculateAmount();

    if (PersistenceHelper.isNew(getEditedEntity())) {
        getEditedEntity().setDocNo((int)uniqueNumbersService.getNextNumber("invoice"));
    }

}


private void calculateAmount(){
    getEditedEntity().setTotalAmount(getEditedEntity().getInvoiceLine().stream().map(InvoiceLine::getAmount)
            .reduce(BigDecimal.ZERO, BigDecimal::add));

}

please see a demo-cuba.zip (104.5 KB)
that may help further.

Hi Michael
You have mentioned a test project attached but I don’t see it. Would be great to have it.

My bad here you are

datagrid.zip (124.3 KB)

@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);
}

Thanks @knstvk.

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.

Michael

Hi @knstvk,

Another question on the same topic.

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 ?

    protected void postInit() {
        super.postInit();
        [...]
        getItem().addPropertyChangeListener(this::propertyChanged);
    }

    protected void propertyChanged(Instance.PropertyChangeEvent ev) {
        if("stockManaged".equals(ev.getProperty()))
            stockManagedChanged(ev);
        else if("supplyModelChanged".equals(ev.getProperty()))
            supplyModelChanged(ev);
    }

    protected void stockManagedChanged(Instance.PropertyChangeEvent ev) {
        if(Boolean.FALSE.equals(ev.getValue())) {
            getItem().setSupplyModel(ProductSupplyModel.NONE);
            if(getItem().getStock() != null) {
                getItem().getStock().setMinQtyNew(null);
            }
        } else if(Boolean.TRUE.equals(ev.getValue())) {
            getItem().setSupplyModel(ProductSupplyModel.STOCK_MINI_AUTO); // default
        }
    }

    protected void supplyModelChanged(Instance.PropertyChangeEvent ev) {
        if(ev.getValue() == ProductSupplyModel.NONE || ev.getValue() == ProductSupplyModel.JUST_IN_TIME) {
            if(getItem().getStock() != null) {
                getItem().getStock().setMinQtyNew(null);
            }
        }
    }


Best Regards
Michael

@michael.renaud You may find this ticket useful regarding listeners: Describe difference between ValueChangeListener and datasource ItemPropertyChangeListener · Issue #136 · cuba-platform/documentation · GitHub
It’s from my experience.

1 Like

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.

Taken from the doc:

private void onCustomerDcItemPropertyChange(
        InstanceContainer.ItemPropertyChangeEvent<Customer> event) {
    Customer customer = event.getItem();
    String changedProperty = event.getProperty();
    Object currentValue = event.getValue();
    Object previousValue = event.getPrevValue();
    // ...
}

Could be written as something similar to:

@Subscribe(id = "customerDc", target = Target.DATA_CONTAINER, property="ranking")
private void onCustomerDcItemPropertyChange(
        InstanceContainer.ItemPropertyChangeEvent<Customer> event) {
    String changedProperty = event.getProperty(); // we know that it is 'ranking'
    // ...
}

When you want to observe 9 or 10 properties (several cases in our project), it could avoid a long list of ‘if’ for routing like:

if("ranking".equals(event.getChangedProperty())) {...}
else if("paymentMethod".equals(event.getChangedProperty())) {...}
else if("address".equals(event.getChangedProperty())) {...}
[...]

When the code evolves and you want to observe one new property, you just add a method and don’t touch the rest, that’s a bit more safe.

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.

Would be great to see the code if you implement it.
Also, you are welcome to create a GH issue with your suggestions.