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

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);
}
}

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);
}
}

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);
}
}

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.

Related

Vaadin flow, trying to display Horizontal layout in Grid

I have written below code, where I want to add HorizontalLayout with TextBox and Button in Vaadin Grid. But when I run the code it is showing the output as
com.vaadin.flow.component.orderedlayout.HorizontalLayout#145a03ea
com.vaadin.flow.component.orderedlayout.HorizontalLayout#1f7acb46
package com.packagename.myapp;
import java.util.ArrayList;
import java.util.List;
import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
#Route("table")
public class TestTable extends VerticalLayout {
private Grid<TblValue> grid = new Grid<>();
public TestTable() {
init();
}
private void init() {
grid.addColumn(TblValue::getLayout);
grid.setItems(createData());
add(grid);
}
private List<TblValue> createData(){
List<TblValue> data = new ArrayList<>();
data.add(new TblValue());
data.add(new TblValue());
return data;
}
private class TblValue {
HorizontalLayout layout = new HorizontalLayout();
private Text txt = new Text("test string");
private Button btn = new Button("Submit");
public TblValue() {
layout.add(txt, btn);
}
public HorizontalLayout getLayout() {
return layout;
}
public void setLayout(HorizontalLayout layout) {
this.layout = layout;
}
}
}
As mentioned in the comments by Tatu Lund, the solution to your problem is to use grid.addComponentColumn(...) instead of grid.addColumn(...).
The addColumn(...) method will always show a String. If the valueprovider returns any Object, it will show its string value with String.valueOf(object). This is what you are seeing. com.vaadin.flow.component.orderedlayout.HorizontalLayout#145a03ea is what you get when you call String.valueOf(myHorizontalLayout).
addComponentColumn(...) on the other hand will take the returned HorizontalLayout of the valueProvider, and put the actual component into the cell which is what you wanted.

How to bind Enum Values In vaadin flow?

In my vaadin flow project with springboot I faced a problem that enum values with combo box cannot be bind.
I provided my code below. Anyone here who can help me out?
Combobox instantiation:
private ComboBox<Country> nationality = new CompoBox<>("Nationality");
Binding code:
binder.forField(nationality)
.bind(Coach::getNationality,Coach:setNationality);
I think you are missing the setItems call. Here is an example based on the Project Base for Vaadin Flow
package com.vaadin.starter.skeleton;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.PWA;
/**
* The main view contains a button and a click listener.
*/
#Route("")
#PWA(name = "Project Base for Vaadin Flow", shortName = "Project Base")
public class MainView extends VerticalLayout {
public MainView() {
ComboBox<ENUM> comboBox = new ComboBox<ENUM>("Number");
comboBox.setItems(ENUM.values());
Binder<Bean> binder = new Binder<>();
binder.setBean(new Bean());
binder.bind(comboBox,Bean::getField,Bean::setField);
Button button = new Button("Check value",
e-> Notification.show("Value in bean is " + binder.getBean().getField()));
add(button,comboBox);
}
public enum ENUM {
ONE,TWO,TREE
}
public static class Bean {
private ENUM field;
public ENUM getField() {
return field;
}
public void setField(ENUM field) {
this.field = field;
}
}
}

Whitelisting application properties in Spring Cloud Data Flow

When I create a custom app for SCDF I can, according to the reference define relevant properties that are visible through the dashboard when creating a new stream/task. I created a spring-configuration-metadata-whitelist.properties file like this:
configuration-properties.classes=com.example.MySourceProperties
configuration-properties.names=my.prop1,my.prop2
When I create a new stream definition through the dashboard all properties defined in com.example.MySourceProperties are displayed in the properties dialog, but my.prop1 and my.prop2 are not. Both properties aren't optional and must always be set by the user. How can I include them in the properties dialog?
This tells it which class to pull these properties from Task1 properties class
That we can use with "#EnableConfigurationProperties(Task1Properties.class) declaration
configuration-properties.classes=com.shifthunter.tasks.Task1Properties
Task1Properties.java
package com.shifthunter.tasks;
import org.springframework.boot.context.properties.ConfigurationProperties;
#ConfigurationProperties("pulldata-task")
public class Task1Properties {
/**
* The path to get the source doc from
*/
private String sourceFilePath;
/**
* The path to put the destination doc
*/
private String destinationFilePath;
/**
* Property to drive the exit code
*/
private String controlMessage;
public String getSourceFilePath() {
return sourceFilePath;
}
public void setSourceFilePath(String sourceFilePath) {
this.sourceFilePath = sourceFilePath;
}
public String getDestinationFilePath() {
return destinationFilePath;
}
public void setDestinationFilePath(String destinationFilePath) {
this.destinationFilePath = destinationFilePath;
}
public String getControlMessage() {
return controlMessage;
}
public void setControlMessage(String controlMessage) {
this.controlMessage = controlMessage;
}
}
ShiftHunterTaskPullDataApp.java
package com.shifthunter.tasks;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.task.configuration.EnableTask;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#EnableTask
#EnableConfigurationProperties(Task1Properties.class)
#SpringBootApplication
public class ShiftHunterTaskPullDataApp {
public static void main(String[] args) {
SpringApplication.run(ShiftHunterTaskPullDataApp.class, args);
}
#Bean
public Task1 task1() {
return new Task1();
}
public class Task1 implements CommandLineRunner {
#Autowired
private Task1Properties config;
#Override
public void run(String... strings) throws Exception {
System.out.println("source: " + config.getSourceFilePath());
System.out.println("destination: " + config.getDestinationFilePath());
System.out.println("control message: " + config.getControlMessage());
if(config.getControlMessage().equals("fail")) {
System.out.println("throwing an exception ...");
throw new Exception("I'm ANGRY");
}
System.out.println("pulldata-task complete!");
}
}
}
Sream Dataflow task-pull-data
app register --name task-pull-data --type task --uri maven://com.shifthunter.tasks:shifthunter-task-pulldata:jar:0.0.1-SNAPSHOT
task-pull-data - Details

How can I use dependency injection in a Gluon Desktop app?

Does anyone know if there is any easy way to use dependency injection within the FXML controllers of a Gluon Desktop (ParticleApplication) app? There is already #Inject used for things like
public class HomeController {
#Inject ParticleApplication app;
#Inject private ViewManager viewManager;
#Inject private StateManager stateManager;
(as part of the framework) but I would like to be able to #Inject my own objects.
EDIT: An answer suggested to use Gluon Ignite, but I'm still having trouble figuring it out. Here is some of my attempted code:
My ParticleApplication class:
package com.gluonapplication;
import com.gluonhq.ignite.guice.GuiceContext;
import com.gluonhq.particle.application.ParticleApplication;
import com.google.inject.AbstractModule;
import javafx.scene.Scene;
import java.util.Arrays;
public class GluonApplication extends ParticleApplication {
private GuiceContext context = new GuiceContext(this, () -> Arrays.asList(new GuiceModule()));
public GluonApplication() {
super("Gluon Desktop Application");
context.init();
System.out.println("From within GluonApplication Constructor: " +
context.getInstance(TestClassToInject.class).testDependancy.testString
);
}
#Override
public void postInit(Scene scene) {
setTitle("Gluon Desktop Application");
}
class GuiceModule extends AbstractModule {
#Override protected void configure() {
// Use just in time injection.
}
}
}
My controller class:
package com.gluonapplication.controllers;
import com.gluonapplication.TestClassToInject;
import com.gluonhq.particle.application.ParticleApplication;
import com.gluonhq.particle.state.StateManager;
import com.gluonhq.particle.view.ViewManager;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javax.inject.Inject;
public class PrimaryController {
#Inject ParticleApplication app;
#Inject private ViewManager viewManager;
#Inject private StateManager stateManager;
#Inject private TestClassToInject testClassToInject;
#FXML
private Label label;
public void initialize() {
}
public void postInit() {
System.out.println("From within controller's postInit() method: " +
testClassToInject.testDependancy.testString
);
}
public void dispose() {
}
}
And then my two classes created as the dependancies:
package com.gluonapplication;
import javax.inject.Inject;
public class TestClassToInject {
#Inject
public TestDependancy testDependancy;
public TestClassToInject() {
}
}
package com.gluonapplication;
public class TestDependancy {
public String testString = "This is a test string";
public TestDependancy() {
}
}
And finally my Gradle file:
apply plugin: 'java'
apply plugin: 'application'
repositories {
jcenter()
maven {
url 'http://nexus.gluonhq.com/nexus/content/repositories/releases'
}
}
mainClassName = 'com.gluonapplication.GluonApplication'
dependencies {
compile 'com.gluonhq:particle:1.1.0'
compile 'com.gluonhq:ignite-guice:1.0.0'
}
The "From within GluonApplication Constructor: " +
context.getInstance(TestClassToInject.class).testDependancy.testString prints out just fine, so I think I have the dependancy classes and Guice configured OK. However, the System.out.println("From within controller's postInit() method: " + testClassToInject.testDependancy.testString); doesn't work from within the controller because the testClassToInject is null at time of use.
You can use Gluon Ignite (http://gluonhq.com/open-source/ignite/) which supports dependency injection with Dagger, Guice and Spring. Gluon Ignite is an open source project, and you can find an example in the test directory on the repository website: https://bitbucket.org/gluon-oss/ignite/src/c85197b33852/src/test/java/com/gluonhq/ignite/?at=default

Using ApplicationMenuItem to override default message sender in RIM API

I was wondering if there was any way to make an ApplicationMenuItem inside the message edit screen that, when selected sends the message to the selected contacts itself, bypassing the default sending program. I am fine with the menu item and have seen how to do similar things by retrieving the 'context' argument but I'm not sure how I would get the message body and contacts that were selected.
In ApplicationMenuItem of message edit context will be an instance of net.rim.blackberry.api.mail.Message
See code:
package so.samples;
import net.rim.blackberry.api.invoke.Invoke;
import net.rim.blackberry.api.invoke.MessageArguments;
import net.rim.blackberry.api.mail.Address;
import net.rim.blackberry.api.mail.Message;
import net.rim.blackberry.api.mail.MessagingException;
import net.rim.blackberry.api.menuitem.ApplicationMenuItem;
import net.rim.blackberry.api.menuitem.ApplicationMenuItemRepository;
import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.component.Dialog;
public class SOComposeMsgMenuApp extends UiApplication {
public static void main(String[] args) {
(new SOComposeMsgMenuApp()).enterEventDispatcher();
}
public SOComposeMsgMenuApp() {
ApplicationMenuItem emailMenuItem = new ApplicationMenuItem(0) {
public Object run(final Object context) {
if (context instanceof Message) {
StringBuffer text = new StringBuffer("Message \nTo:\n");
Message msg = (Message) context;
Address[] to = new Address[] {};
try {
to = msg.getRecipients(Message.RecipientType.TO);
} catch (MessagingException e) {
}
for (int i = 0; i < to.length; i++) {
text.append(to[i].toString());
text.append("\n");
}
text.append("Body:\n");
text.append(msg.getBodyText());
Dialog.inform(text.toString());
}
return context;
}
public String toString() {
return "My Menu Item";
}
};
ApplicationMenuItemRepository amir = ApplicationMenuItemRepository
.getInstance();
amir.addMenuItem(ApplicationMenuItemRepository.MENUITEM_EMAIL_EDIT,
emailMenuItem);
Invoke.invokeApplication(Invoke.APP_TYPE_MESSAGES,
new MessageArguments(MessageArguments.ARG_NEW, "", "testing",
"just trying to test menu item from compose screen"));
}
}

Resources