Image is not displayed when src changed and setVisible(true) called

Hi All,
I’ve got a view with an image created as follows :

final Image icon = new Image(); 
icon.setVisible(false);
icon.getElement().setAttribute("onerror", "this.style.display='none'");

Then later I call :

icon.setSrc(getIconPath());
icon.setVisible(true);

If getIconPath returns an URL with valid content on the server, the image is displayed.
If it returns an URL with invalid (no content → 404) then the “broken image” symbol is hidden (that’s what I wanted to achieve). But then if I update the URL again with a valid URL, then the image does not appear.
By inspecting the HTML I see that the style “display: none;” is not removed from the element, despite the call to icon.setVisible(true);
In order for my code to work I’ve had to add :

icon.getElement().executeJs("this.style.display='flex'");```

This happens on both Vaadin 24.4.18 and Vaadin 24.5.6
Am I doing something wrong or is it a known issue ? 
Thanks.

The onerror puts the style on the element, but there’s nothing that removes the style.
The same will happen if you do the same in plain html + js.

There’s probably many different ways to solve this, but the first one that comes to my mind is to use onerror to set a fallback image (e.g. a 1x1 px empty image), instead of setting a style, since that will be overridden when you supply a new src.

Thanks for the reply. I was assuming that setVisible(true) + new src would take care of it.
I’ll stick with my solution since it works :slight_smile:
Thanks !

IIRC there should be also a Java API that can be used, something like icon.getStyle().setDisplay(...)

Thanks for the tip. I did not know. I changed the code since it’s much cleaner, and it works :+1:

Hi all! I’m looking this from the perspective “how should it work in Java API”?

  1. API to check if image was loaded correctly
  2. Always hide missing images (and show valid ones)
  3. Configuration option to hide image if missing

I have my opinion, but what do you think?

@marcoc_753 Please add something like this to the core:

If that was available, I think @Charles-Edouard would have never gotten into this trap and also the code (mixing totally different abstraction layers and languages) I have seen wouldn’t make me that depressed :crazy_face:

1 Like

The reason that setVisible(true) does not help is, that setVisible does not modify the style attribute, but simply adds/removes an hidden attribute to the client side element. But since you added an explicit style.display = 'none', the resulting client side style="display: none" will override any displays set by other css.

What you could do with already available features would be to fire a custom event from the client and listen to that in Flow, for instance

// ... in Java ...
Element element = anchor.getElement();

// register the client side on error listener and fire a custom event, that the anchor will listen to in Java
element.executeJs("this.addEventListener('onerror', e => this.dispatchEvent(new CustomEvent('image-error')))");

// the Java listener for the custom client side event
element.addEventListener("image-error", event -> /*... react on the error */);

This way you can handle for instance the visibility completely with your Java code without any additional manual client side modification.

This combination of a java script listener plus firing a custom event, that then will be listened on in Java can be used for many purposes and many components. It’s a very helpful tool to overcome missing built-in Java events

Hi Stefan,
thanks for the detailed explanation. I was not aware of the ability to easily add events like this.
This is a very good solution that could help solve other problems. I’ll keep that in mind !
Regards

Yes having such event would of course be the cleanest way to solve the issue …

Even though the code snippet by @Stefan.27 works, don’t use it as such. Just take it as a PoC. Reason 1: you are then easily mixing two different abstraction layers. Whatever the anchor class there is, that is the right place where Element API level things should be placed. Reason 2: the JS execution is not needed here at all. You can also listen to standard DOM events, not just CustomEvent’s.

Example:

        // With raw Image component, but working with multiple abstraction levels in the same class
        // which you should NEVER do. Just added here as an example Sorry for our API letting you do this....
        Image rawImageThatFailsToo = new Image("https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.pngX", "Google logo");
        rawImageThatFailsToo.getElement().addEventListener("error", event -> {
            Notification.show("This fails too!");
            // Hide in this case
            rawImageThatFailsToo.setVisible(false);
        });
        add(rawImageThatFailsToo);

        // It is also possible to "piggyback" on the DOM API if you want only one class and don't want to create
        // another for the event. This is better, but not perfect, DomEventListeners and DomEvent are for component impls.
        // Check the VImage class for a proper way to do this, MyImage below
        MyImage myImage = new MyImage();
        myImage.addErrorListener(event -> {
            Notification.show("Yay, this works too!");
        });

    }
    
    private static class MyImage extends Image {
        public Registration addErrorListener(DomEventListener listener) {
            return getElement().addEventListener("error", listener);
        }
    }

True, to simply listen on the java side for the native “error” event slipped through my mind.

Hi @Matti thanks for sharing this improved method.
In my case I need both an event corresponding to onerror=“XXX” and onload=“YYY”. I’ve tried

icon.getElement().addEventListener("load", event ->  icon.setVisible(true) );

and

icon.getElement().addEventListener("onload", event ->  icon.setVisible(true) )

But the onload / load event is never dispatched …
The event for error works though. What name should I use to catch the “onload” event ?

The code below works, but is not as clean as yours … :

icon.getElement().setAttribute("onerror", "this.style.setProperty(\"display\", \"none\",\"important\");");
icon.getElement().setAttribute("onload", "this.style.removeProperty(\"display\");");
     

Thanks