spring-bootvaadinvaadin4spring

Vaadin - run client side javascript after image fully loaded


I need to print a picture on client side. I used this as a template. My PrintUI looks like this:

@Override
protected void init(VaadinRequest request) {
    Item item = ..get item ..

    StreamResource imageStream = ... build image dynamically ...
    Image image = new Image(item.getName(), imageStream);
    image.setWidth("100%");
    setContent(image);
    setWidth("100%");

    // Print automatically when the window opens
    JavaScript.getCurrent().execute("setTimeout(function() {print(); self.close();}, 0);");
}

This works so far in IE but in chrome it opens the printing preview showing an empty page. The problem is that the image is loaded in some way that chrome does not wait for it and starts the printing preview immideatly.

To verify this, I tried: (setting a 5sec timeout)

JavaScript.getCurrent().execute("setTimeout(function() {print(); self.close();}, 0);");

Then it works in IE and Chrome, but its of course an ugly hack, and if the connection is slower than 5sec, then again it will fail.

In pure JS it would work like this, but Im not sure how to reference the element from vaadin in cient-side js. Any ideas?


Solution

  • You can use AbstractJavascriptExtension.

    Example extension class:

    @JavaScript({ "vaadin://scripts/connector/wait_for_image_load_connector.js" })
    public class WaitForImageLoadExtension extends AbstractJavaScriptExtension {
    
        private List<ImageLoadedListener> imageLoadedListeners = new ArrayList<>();
    
        public interface ImageLoadedListener {
            void onImageLoaded();
        }
    
        public void extend(Image image) {
            super.extend(image);
            addFunction("onImageLoaded", new JavaScriptFunction() {
    
                @Override
                public void call(JsonArray arguments) {
                    for (ImageLoadedListener imageLoadedListener : imageLoadedListeners) {
                        if (imageLoadedListener != null) {
                            imageLoadedListener.onImageLoaded();
                        }
                    }
                }
            });
        }
    
        public void addImageLoadedListener(ImageLoadedListener listener) {
            imageLoadedListeners.add(listener);
        }
    }
    

    and javascript connector (placed in wait_for_image_load_connector.js) with the waiting method you have linked:

    window.your_package_WaitForImageLoadExtension = function() {
            var connectorId = this.getParentId();
            var img = this.getElement(connectorId);
    
            if (img.complete) {
                this.onImageLoaded();
            } else {
                img.addEventListener('load', this.onImageLoaded)
                img.addEventListener('error', function() {
                    alert('error');
                })
            }
    
        }
    

    Then you can do something like that:

    Image image = new Image(item.getName(), imageStream);
    
    WaitForImageLoadExtension ext = new WaitForImageLoadExtension();
    ext.extend(image);
    ext.addImageLoadedListener(new ImageLoadedListener() {
    
        @Override
        public void onImageLoaded() {
            JavaScript.eval("print()");
        }
    });
    

    In your case, when calling print() is the only thing you want to do after the image is loaded, you can also do it without server-side listener by just calling it in the connector:

    if (img.complete) {
        print();
    } else {
        img.addEventListener('load', print)
        img.addEventListener('error', function() {
            alert('error');
        })
    }