Lit: How do I get data from the client?

I am creating my own lit wrapper for cropper.js. So far it’s working, but I have one major headache I haven’t been able to figure out. In my Java class I need a method which when called should return data which is in the lit template. How do I go about this?
Putting a button into the lit template, calling the server and passing the data when this button is clicked does work, but I need to initiate this from the server, not from the client.

https://vaadin.com/docs/latest/create-ui/web-components/java-api-for-a-web-component
Does this help you?

So if you have a ‘discrete’ property in your lit template, you could create a java component for it, with methods:

public void setDiscrete(boolean discrete) {
    getElement().setProperty("discrete", discrete);
}
public boolean isDiscrete() {
    return getElement().getProperty("discrete", false);
}

If you want to call some method in lit-template, then you’d probably want to call
getElement().executeJsFunction(...) as described here https://vaadin.com/docs/latest/create-ui/web-components/java-api-for-a-web-component#calling-element-functions

Thank you. I tried the property before but it was always null on the server side.
I then read this “It [executeJs] also returns a server-side promise for the JavaScript function’s return value.” from the docs you sent me and that worked after I changed my .executeJs(“foo()”) to .executeJs(“return foo()”).

One more question: The method I call from the server “lives outside” of the lit template so to say:

class LitCropper extends LitElement {
// stuff
}
window.methodCalledFromServer = function (parentNode) {
}

Is there a way to put methodCalledFromServer in the template class to be able to access the objects in that scope? Right now I define a variable outside of that scope, set it inside the template class with listeners and then access it in methodCalledFromServer. This is a bit cumbersome however and not particularly pretty.
I hope this makes sense, I’m not too experienced with JS.

If you put it inside the class, you should be able to access it with the getElement().callJsFunction("methodCalledFromServer", parentNode); I think.

If that’s not working, (just like the properties), I wonder if you have created the serverside component correctly:
https://vaadin.com/docs/latest/create-ui/templates/basic#creating-the-java-template-class

Sadly it’s not working. If I define the method with window. I get a syntax error and using it without window. results in nothing.
I have created it like the docs suggest, the only difference is my template file is javascript and not typescript. Could that be it?

within the class you wouldn’t use a window. , you would just have a function like this:

class MyClass {
  methodCalledFromServer(parentNode) {
    ...
  }
}

I’m not sure if typescript is required, but I’ve always used it in our projects.
I’m also wondering whether there are any browser side errors in the browsers console, that might indicate that the JS part is not loaded / configured properly.

Nope, there aren’t any warnings/errors in the console.
When I put the method in the class and then try to call it nothing happens, all I get is this:

Sending xhr message to server: [...] "args":["ReferenceError: methodCalledFromServer is not defined"]

So far the workaround (while not ideal) does work. When I have some more time I will try to make the template into a typescript file and create a reproducible example.

It would be easier with a sample of your code. Normally on the server side you need a function annotated with @clientcallable and on the client side this.$server.myfunction. If it’s not working it’s probably an issue with a wrong class or element.

Okay, this is the lit template:

import {html, LitElement} from 'lit-element';
import Cropper from "cropperjs";

let canvasData = {data: "", mimeType: ""};

class LitCropper extends LitElement {

    static get properties() {
        return {
            mimeType: {type: String},
            imageSrc: {type: String},
        };
    }

    static get styles() {
        // styles
    }

    async firstUpdated(changedProperties) {
        this.currentImage = this.shadowRoot.getElementById("image");
        this.initializeCropper();
    }

    initializeCropper() {
        this.currentImage.src = `data:${this.mimeType};base64,` + this.imageSrc;
        this.currentImage.addEventListener('cropend', () => {
            this.setCanvasData();
        })
        this.currentImage.addEventListener('ready', () => {
            this.setCanvasData();
        });
        this.cropper = new Cropper(this.currentImage, {
            // settings
        });
    }

    setCanvasData() {
        canvasData.data = this.cropper.getCroppedCanvas().toDataURL(this.mimeType);
        canvasData.mimeType = this.mimeType;
    }

    render() {
        return html`
            <div id="img-container" style="height: 100%; width: 100%">
                <img id="image"/>
            </div>`;
    }
}

window.getImageCanvasData = function (parentNode) {
    return canvasData;
}

window.customElements.define('lit-cropper', LitCropper);

And the java class:

@Tag("lit-cropper")
@JsModule("./src/cropper/lit-cropper.js")
@NpmPackage(value = "cropperjs", version = "1.6.1")
public class Cropper extends LitTemplate {

    public Cropper(byte[] imageData) {
    var encoded = Base64.encodeBase64(imageData);
    setImageSrc(new String(encoded, StandardCharsets.US_ASCII));
    setMimeType("image/jpeg");
    }

    public InputStream getImageCanvas() {
    getElement().executeJs("return getImageCanvasData($0)", getElement()).then(res -> {

    });
    return null;
    }

    private void setImageSrc(String src) {
    getElement().setProperty("imageSrc", src);
    }

    private void setMimeType(String mimeType) {
    getElement().setProperty("mimeType", mimeType);
    }
}

If i put the getImageCanvasData in the template class as such:

class LitCropper extends LitElement {

    getImageCanvasData (parentNode) {
        return canvasData;
    }
 
}

The call from the server is not received and I get a ReferenceError:

Uncaught ReferenceError: getImageCanvasData is not defined

If your function is in your component then you can call it like this:
getElement().executeJs("return $0.getImageCanvasData()", getElement())
or simply like this getElement().executeJs("return this.getImageCanvasData()")

Thank you, this works!
It would be great if the docs regarding lit templates and web components could be a bit more comprehensive. While the solution is now completely obvious I wouldn’t have figured it out and this topic is probably the one that’s most alien for developers used to java primarily.

I’ll forward this to our team,I think that could be a great blog post or an article in our documentation.

@flexible-fox That could be a good idea for a blog post, what do you think?