UI becomes inert when refreshing grid items

Hello!

I am experimenting with server push on a home project and encountered some logs I’ve never seen until bumping vaadin-core to latest. I can reliably reproduce this:

2024-10-02T20:38:49.956+03:00  INFO 20792 --- [nio-8080-exec-1] c.v.f.i.nodefeature.ElementListenerMap   : Ignored listener invocation for checked-changed event from the client side for an inert vaadin-checkbox element
2024-10-02T20:38:49.956+03:00  INFO 20792 --- [nio-8080-exec-1] c.v.f.i.nodefeature.ElementListenerMap   : Ignored listener invocation for checked-changed event from the client side for an inert vaadin-checkbox element
2024-10-02T20:38:49.956+03:00  INFO 20792 --- [nio-8080-exec-1] c.v.f.i.nodefeature.ElementListenerMap   : Ignored listener invocation for indeterminate-changed event from the client side for an inert vaadin-checkbox element
2024-10-02T20:38:49.956+03:00  INFO 20792 --- [nio-8080-exec-1] c.v.f.i.nodefeature.ElementListenerMap   : Ignored listener invocation for indeterminate-changed event from the client side for an inert vaadin-checkbox element

I’ll further refer to these 4 as “set of logs”.

For more context, I work with “Device” objects. I have a view which contains a Grid. One of the columns is a component column which contains a checkbox (a command button for the device).
On single click select on a row, a dialog pops up. This dialog contains a number field (used for device calibration) which sends a message via server push on value change event. When this sends a message, all listening UIs (meaning all UIs connected to the app) will start refreshing their items and values (I do this for instant refresh without reloading page):

attachEvent.getUI().access(this::updateView)
// where updateView does this:
grid.setItems(...) 
// which creates new checkboxes for all rows again

When the cursor reaches the "attacheEvent.getUI()… " line inside

@Override
protected void onAttach(AttachEvent attachEvent)

All UIs become inert, then the inert status goes downstream until it reaches the checkboxes, then ElementListenerMap logs the lines which I pasted above, from here:

        for (DomEventListenerWrapper wrapper : typeListeners) {
            if (!isNavigationRequest && inert && !wrapper.allowInert) {
                // drop as inert
                LoggerFactory.getLogger(ElementListenerMap.class.getName())
                        .info("Ignored listener invocation for {} event from "
                                + "the client side for an inert {} element",
                                event.getType(), event.getSource().getTag());
                continue;
            }

I read InertData’s javadoc (and this Server-Side Modality | Advanced Topics | Flow | Vaadin Docs which I’m not actively using as far as I know) that the status is passed down from the parent. I found out the UIs get inert first:

UI.getCurrent().getInternals().getStateTree().getRootNode().isInert() // returns true

I’ve tested with multiple concurrent users (and multiple VaadinSession). When I change the value of the number field, the set of logs appear only once per checkbox, no matter how many listeners are present.

I hope this isn’t breaking anything, as the listeners from Checkbox have NO_OP:

getElement().addPropertyChangeListener("indeterminate", "indeterminate-changed", NO_OP);
getElement().addPropertyChangeListener("checked", "checked-changed", NO_OP);

I’m struggling to figure out why this happens, why the UIs get inert, who makes them inert, if this signals an issue and how to solve this.
I’d really appreciate some insight from you on this topic.

There you are using it. A dialog is by default “modal” - use setModal(false) and the “problem” is gone

Dialogs are modal by default and use server-side modality by default. When you open the dialog the UI becomes inert, which means that it ignores any events or other RPC calls from the browser that are outside of that dialog, such as from your grid. My guess would be that when you make the grid load new data in the background it renders new checkboxes, which then dispatch change events for several properties. However, due to server-side modality the server ignores those events, and that’s where you get the log entries from.

If you don’t need server-side modality for this use-case, you can:

  • Either make the dialog non-modal
  • Or disable server-side modality by opening your dialog like this:
add(dialog);
dialog.open();
ui.setChildComponentModal(dialog, false);

Note that you explicitly need to add the dialog to the UI, if you just call dialog.open() it will not work.

As for why this is issue is just surfacing now after a version bump, there was a recent fix to grid that allows it to load data from the server, even if it is part of the UI that is inert. Previously, it should not have been able to do so, or at least not reliably.

Thank you for the info on this topic!

By the way, I just noticed now the dialog doesn’t close anymore when clicking outside of it (after settings modal to false), its color seems to be changed and also less bold (I’m using default Lumo DARK and LIGHT themes).
Do you have any idea why?

That is the current default behavior for modeless dialogs. See also [dialog] Modeless dialog does not honor close on outside click · Issue #7778 · vaadin/web-components · GitHub

1 Like

Sadly that’s “intended” because without “modal” it’s “modeless” and the shadow and clicking outside is gone.

Edit: damn you are quick Sascha :grin:

Understood.

I’m testing on Firefox Desktop, Edge Desktop, Chrome Desktop and it’s the same behavior (I saw this:)

Browsers

Firefox (Have not tested other)

Is it possible to let’s say alleviate this effect via CSS?

As I mentioned above, you can use a modal dialog, and just disable server-side modality.

I can edit my report :wink: it should effect all browser.

It might be possible to apply “with-backdrop” again to the overlay but my JavaScript Kung Fu is kinda limited on mobile

I tried applying ::backdrop on the dialog className and nothing happened yet (with dialog having modal false). Or I didn’t actually re-enable backdrop?
I applied Sascha’s second suggestion on my actual “production” code so we’re talking about .setModal(false) solution now.

“with-backdrop” is an internal state of the overlay which can’t be accessed with CSS - it has to be re-applied with JS