Reputation: 81
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.
// 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;
}
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);
}
}
Upvotes: 1
Views: 930
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
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
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