user2959065
user2959065

Reputation: 81

Vaadin 18 | Need to pass a message from client to server using Lit-Template and HTML text

I am trying to call server side function from client using littemplate. I have one problem for which I need help. I am adding a HTML fromatted text on server side which has a custom component 'hello-world2'.

I am passing one attribute (name="Vaadin") in this custom component. I expect that when user clicks on 'helloButton' then the value 'Vaadin' should be sent to server and invoke 'acceptMessage'. But as of now I am getting error as displayed on attached screenshot.

I am doing this because in my current application I already have generated HTML Table. And for few columns I am trying to incorporate and use <vaadin-button> HTML tag. So when user clicks on this button I expect a message/value on server side for further handling.

Please guide my how to do this.

Lit-Template

// hello-world2.ts
import { customElement, html, LitElement, property } from "lit-element";

import "@vaadin/vaadin-button/vaadin-button";
import "@vaadin/vaadin-text-field/vaadin-text-field";
import "@vaadin/vaadin-ordered-layout/vaadin-vertical-layout";
import { showNotification } from "@vaadin/flow-frontend/a-notification";


@customElement('hello-world2')
export class HelloWorld extends LitElement {

    public $server1?: HelloWorldServerInterface;

    @property({ type: String })
    name = '';

    render() {
        return html`<vaadin-vertical-layout theme="padding spacing">
        <vaadin-button id="helloButton" @click="${this.sendMessage}">Message Button</vaadin-button>
    </vaadin-vertical-layout>`;
    }

    sendMessage() {
        showNotification("Hello : " + this.name); //Works well, displays the notification
        this.$server1!.acceptMessage(this.name); // Issue here.

    }


}
interface HelloWorldServerInterface {
    greet(): void;
    acceptMessage(arg0: String): void;
}

Java

package com.example.application.views.helloworld;

import com.example.application.views.main.MainView;
import com.vaadin.flow.component.ClientCallable;
import com.vaadin.flow.component.Html;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.dependency.CssImport;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.littemplate.LitTemplate;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.template.Id;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;

@CssImport("./views/helloworld/hello-world-view.css")
@Route(value = "hello", layout = MainView.class)

@PageTitle("Hello World")
//@Tag("hello-world2") -- Commented, if I uncomment this then I can see two buttons on browser.
@JsModule("./views/littemplate/hello-world2.ts")
public class HelloWorldView extends HorizontalLayout {

     public HelloWorldView() {   
        
         Html html = new Html("<hello-world2 name=\"Vaadin\"></hello-world2>");
         add(html);
        
     }
     
          @ClientCallable 
          public void acceptMessage(String message) {
          System.out.println("Message from client : " + message); 
          }
          
} 

enter image description here

enter image description here

Upvotes: 1

Views: 930

Answers (3)

Haprog
Haprog

Reputation: 853

Here's another working example which might be closer to what you want.

hello-world2.ts:

import { customElement, html, LitElement, property } from 'lit-element';

import '@vaadin/vaadin-button/vaadin-button';
import '@vaadin/vaadin-ordered-layout/vaadin-vertical-layout';
import { showNotification } from '@vaadin/flow-frontend/a-notification';

@customElement('hello-world2')
export class HelloWorld2 extends LitElement {
  @property({ type: String })
  name = '';

  render() {
    return html`
      <vaadin-vertical-layout theme="padding spacing">
        <vaadin-button id="helloButton" @click="${this.sendMessage}">Message Button</vaadin-button>
      </vaadin-vertical-layout>
    `;
  }

  sendMessage() {
    const $server = (this.parentElement as HtmlElementWithMyViewServerInterface).$server;
    showNotification("Hello : " + this.name);
    $server!.acceptMessage(this.name);
  }
}

interface MyViewServerInterface {
  acceptMessage(message: String): void;
}

interface HtmlElementWithMyViewServerInterface extends HTMLElement {
  $server?: MyViewServerInterface;
}

MyView.java:

@Route(value = "myview", layout = MainLayout.class)
@PageTitle("My View")
@JsModule("./views/littemplate/hello-world2.ts")
public class MyView extends HorizontalLayout {
  public MyView() {
    Html html = new Html("<hello-world2 name='Vaadin'></hello-world2>");
    add(html);
  }

  @ClientCallable
  public void acceptMessage(String message) {
    System.out.println("Message from client : " + message);
  }
}

Though ideally you'd also have a Java component class for <hello-world2> and use that in the view instead of the Html component. That could look like this:

HelloWorld2.java:

@Tag("hello-world2")
@JsModule("./views/littemplate/hello-world2.ts")
public class HelloWorld2 extends Component {
    public HelloWorld2() {
        setName("");
    }

    public HelloWorld2(String name) {
        setName(name);
    }

    public void setName(String name) {
        getElement().setProperty("name", name);
    }
}

MyView.java:

@Route(value = "myview", layout = MainLayout.class)
@PageTitle("My View")
public class MyView extends HorizontalLayout {
  public MyView() {
    add(new HelloWorld2("Vaadin"));
  }

  @ClientCallable
  public void acceptMessage(String message) {
    System.out.println("Message from client : " + message);
  }
}

Upvotes: 2

Haprog
Haprog

Reputation: 853

If you want HelloWorldView in Java to represent a Java API for the <hello-world2> client side custom element it should extend Component (or LitTemplate if you want to use @Id annotations) instead of HorizontalLayout and you should uncomment the @Tag annotation and remove the new Html and add(html) parts.

When you have @Tag("hello-world2") it means that by adding HelloWorldView to the page it actually adds <hello-world2></hello-world2> as that is then considered as the client side representation of this Java component. So when you also manually add the HTML for it in constructor you would end up with <hello-world2><hello-world2></hello-world2></hello-world2> (which is why you would see two times the content).

Now when you have the @Tag commented out here and you extend HorizontalLayout it means that HelloWorldView = <vaadin-horizontal-layout> component as it inherits the @Tag("vaadin-horizontal-layout") from HorizontalLayout (instead of being a direct Java API for your <hello-world2> custom element) on the client side. And then the <vaadin-horizontal-layout> instance would get the $server.acceptMessage() method so you can't call it from this in <hello-world2> as it would only be declare on its parent.

Here's a working example (I tested it works) for what I think you're trying to do (or something similar):

hello-world2.ts

import { customElement, html, LitElement, property } from 'lit-element';

import '@vaadin/vaadin-button/vaadin-button';
import '@vaadin/vaadin-ordered-layout/vaadin-vertical-layout';
import { showNotification } from '@vaadin/flow-frontend/a-notification';

@customElement('hello-world2')
export class HelloWorld2View extends LitElement {
  $server?: HelloWorldServerInterface;

  @property({ type: String })
  name = '';

  render() {
    return html`
      <vaadin-vertical-layout theme="padding spacing">
        <vaadin-button id="helloButton" @click="${this.sendMessage}">Message Button</vaadin-button>
      </vaadin-vertical-layout>
    `;
  }

  sendMessage() {
    showNotification("Hello : " + this.name);
    this.$server!.acceptMessage(this.name);
  }
}

interface HelloWorldServerInterface {
  greet(): void;
  acceptMessage(arg0: String): void;
}

HelloWorld2View.java:

package com.example.application.views.helloworld;

import com.vaadin.flow.router.Route;
import com.vaadin.flow.router.PageTitle;
import com.example.application.views.MainLayout;
import com.vaadin.flow.component.ClientCallable;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.dependency.JsModule;

@Route(value = "hello", layout = MainLayout.class)
@PageTitle("Hello World")
@Tag("hello-world2")
@JsModule("./views/littemplate/hello-world2.ts")
public class HelloWorld2View extends Component {
    public HelloWorld2View() {
        setName("Vaadin");
    }

    public void setName(String name) {
        getElement().setProperty("name", name);
    }

    @ClientCallable
    public void acceptMessage(String message) {
        System.out.println("Message from client : " + message);
    }
}

I'm not sure what exactly is the use case for the name property here though or why you'd want to set it in the constructor of the component, but here I also added a setter for it in the Java API. Alternatively you could set the default value for name also in the TS file instead of setting it in the Java constructor.

It seems like you may want this <hello-world2> to be a component that is intended to be added to a view (instead of representing a whole view by itself). In that case you should have a TS and Java file (like above) for this component specifically and then use it in some view and the HelloWorld2View above should probably be named just HelloWorld2 or similar so it's not confused to be a view.

public class SomeOtherView extends Div {
    public SomeOtherView() {
        HorizontalLayout hl = new HorizontalLayout();
        HelloWorld2 helloComponent = new HelloWorld2();
        helloComponent.setName("Vaadin");
        hl.add(helloComponent);
        add(hl);
    }
}

or

public class SomeOtherView extends HorizontalLayout {
    public SomeOtherView() {
        HelloWorld2 helloComponent = new HelloWorld2();
        helloComponent.setName("Vaadin");
        add(helloComponent);
    }
}

Upvotes: 2

Jean-Christophe Gueriaud
Jean-Christophe Gueriaud

Reputation: 1378

Vaadin sets a variable called $server you can't change this name. That's the reason you have an error because $server1 does not exist.

You should rename $server1 to $server and it should work.

Upvotes: 1

Related Questions