Refresh Detail view in Jmix 2.x

Hello everyone,

I have an issue with refreshing a DetailView in our application and hope you can help me out.

We have Customer and Order entities, and in the Customer DetailView, we display the total amount of all orders. When a new order is created and the Order DetailView is closed, the Customer DetailView should automatically refresh so that the total amount is recalculated and displayed without having to close and reopen the entire view.

Here is a snippet of my code:

package com.company.jedr.view.customer;

import com.company.jedr.entity.Customer;
import com.company.jedr.entity.Order;
import com.company.jedr.view.main.MainView;
import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.router.RouteParameters;
import io.jmix.flowui.ViewNavigators;
import io.jmix.flowui.view.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;

@Route(value = "customers/:id", layout = MainView.class)
@ViewController("Customer.detail")
@ViewDescriptor("customer-detail-view.xml")
@EditedEntityContainer("customerDc")
public class CustomerDetailView extends StandardDetailView<Customer> {
    private static final Logger log = LoggerFactory.getLogger(CustomerDetailView.class);
    @ViewComponent
    private Span totalAmountSpan;
    @Autowired
    private ViewNavigators viewNavigators;

    @Subscribe
    public void onAttachEvent(final AttachEvent event) {
        if (CollectionUtils.isEmpty(getEditedEntity().getOrders())) {
            return;
        }

        getEditedEntity().getOrders().stream()
                .map(Order::getAmount).reduce(Double::sum)
                .ifPresent(totalAmount -> totalAmountSpan.setText("Total amount: " + totalAmount));
    }

    @Install(to = "ordersDataGrid.create", subject = "afterSaveHandler")
    private void ordersDataGridCreateAfterSaveHandler(final Order order) {
        closeWithSave()
                .then(() -> viewNavigators.detailView(Customer.class)
                        .withViewClass(CustomerDetailView.class)
                        .withRouteParameters(new RouteParameters("id", getEditedEntity().getId().toString()))
                        .navigate()
                );
    }

    @Subscribe
    public void onAfterClose(final AfterCloseEvent event) {
        log.info("Close Event: " + event.getCloseAction());
    }

}

My problem is that during the first refresh, a blink effect occurs, which works but is not ideal. When trying to add another order, the refresh does not happen, and there is no blink effect anymore.

The logs show different orders of the close actions being fired:

2024-06-11T17:23:52.406+02:00  INFO 91833 --- [nio-8080-exec-3] c.c.j.view.customer.CustomerDetailView   : Close Event: CloseAction{actionId='save'}
2024-06-11T17:25:06.117+02:00  INFO 91833 --- [nio-8080-exec-3] c.c.j.view.customer.CustomerDetailView   : Close Event: CloseAction{actionId='save'}
2024-06-11T17:25:06.145+02:00  INFO 91833 --- [nio-8080-exec-1] c.c.j.view.customer.CustomerDetailView   : Close Event: CloseAction{actionId='navigate'}

Perhaps as a more general question: what would you suggest for re-rendering the UI of a detail view after some interaction happend. Re-rendering could mean something like:

  • statically calculated values that are not two-way data-bound
  • refresh the visibility / enabled settings of buttons

Thanks
Mario

Here is the example application showing this behaviour:
jmix-example-detail-refresh.zip (113.3 KB)

Refreshing things is definitely an interesting topic.
Using CUBA, I did a refresh of a “list” view by adding a timer, that basically triggered a reload of the data. Since there is not much data, it’s acceptable. Not elegant, but, under my control.
If there was a lot of data, I would make it with the timer reading a semaphore, and semaphore value is manually set when a change is made, so reload happens only when there is a real change.

This is not much different than responding to events, because in each case there is a loop checking some semaphore anyway, it’s just that at the time I needed something that would work solid and that I could understand and control.

Using a manual timer, to refresh an already opened list view, without reloading, you would need to then know what exactly has been changed, and use methods such as setText , setValue etc. to manually update the components needing the update, and if there is a grid that’s more pain, for a new entity you would need to add it to the grid’s collectionContainer, and to the grid itself if you are not refreshing the grid completely. For modified entities, you would need to search for it and update the cells and collectionContainer entry… very painful.

For your case, maybe you can add an event listener to the detail view, that would .reload the data loader, or call the recalculation of your values.

I would also like to know the answer to your question, how to best do it, not just for the detail view, but also detail with a datagrid, and list view too, not to mention views containing new Apache Superset dashboards, and charts add-on.

Kind regards,
Mladen

Yes, this would work. But I would want to avoid it as there might be 10 things we have to “refresh”. So if there is a generic way from the framework I would like to rather use this. Otherwise next time I’m adding a button that has some enable / disable setting, I might forget to also add this to this programmatic method.

Basically what I’m search for is a more react-style approach where the re-render automagically happens when some state is changed :slight_smile: I know Vaadin is not made like this, but perhaps there is still a way.