How to bind Enum Values In vaadin flow? - vaadin

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

Related

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

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.

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

lombok and guice injection

I'm new to lombok and guice injection, I could get the general concept but I ran into some code which I don't understand and can't search due to the syntax. Following is the code, can someone help me understand this?
import com.google.inject.Inject;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
#AllArgsConstructor(access = AccessLevel.PRIVATE, onConstructor = #__({ #Inject }))
public class SomeClass {
...
}
Thanks!
This is going to add a constructor with all fields as parameters, with #Inject annotation and private modifier, so your code will be expanded to:
import com.google.inject.Inject;
public class SomeClass {
#Inject
private SomeClass() {
}
}
This is assuming there are no fields in the class. If you have some fields, then they will be added to the constructor, for example:
import com.google.inject.Inject;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
#AllArgsConstructor(access = AccessLevel.PRIVATE, onConstructor = #__({ #Inject }))
public class SomeClass {
private String name;
}
Will become:
import com.google.inject.Inject;
public class SomeClass {
private String name
#Inject
private SomeClass(String name) {
this.name = name;
}
}
Please note, that this won't work in Guice anyway, as it requires a constructor that is not private, per this documentation.
Also make sure Lombok retains any #Named annotations you have added!
Otherwise the code below for example will fail to inject:
#AllArgsConstructor(access = AccessLevel.PACKAGE, onConstructor = #__({#Inject}))
public class SomeClass {
#Named("example")
private String exampleString;
}
public class ExampleModule extends AbstractModule {
#Override
protected void configure() {
bind(String.class)
.annotatedWith(Names.named("example"))
.toInstance("Hello, world!");
}
}
See this answer: Lombok retain fields. You want to add
lombok.copyableAnnotations += com.google.inject.name.Named
to your lombok.config file.

Select Menu JSF to CDI differences

I want to use the arquillian warp test framework for a JSF project I am developing. I understand that I need to use the CDI annotations instead of the JSF ones to get this to work. I am using #ViewScoped beans so I have included seam-faces in my project to deal with this (i am running on JBoss 7). I have modified my beans to use #Named and where I was using #PostConstruct I have put this into the constructor which all seems to be okay.
When I access a view with a selectOneMenu it never has any list items. Here is the code form the view and the bean.
View:
<h:selectOneMenu value="#{ngoBean.ngo.country}" >
<f:selectItems value="#{ngoBean.countryValues}" />
</h:selectOneMenu>
Bean:
import com.a.Facade;
import com.a.CountryEnum;
import com.a.GoverningBody;
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.faces.bean.ViewScoped;
import javax.faces.model.SelectItem;
import javax.inject.Named;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* Created with IntelliJ IDEA.
*/
#Named("ngoBean")
#ViewScoped
public class NgoBean implements Serializable {
private GoverningBody ngo = new GoverningBody();
private List<GoverningBody> ngoList;
private boolean edit;
private List<SelectItem> countryValues;
#EJB(beanName = "NgoFacadeImpl")
private Facade<GoverningBody> ngoController;
public NgoBean(){
}
#PostConstruct
public void init(){
//TODO this is a bad way of loading db data i should change it
ngoList = ngoController.findAll();
countryValues = initCountryValues();
}
public void add(){
ngoList.add(ngoController.save(ngo));
//reset the variable
ngo = new GoverningBody();
}
public void edit(GoverningBody item) {
this.ngo = item;
edit = true;
}
public void save() {
ngo = ngoController.update(ngo);
edit = false;
}
public void delete(GoverningBody item) {
ngoController.delete(item);
ngoList.remove(item);
}
public List<GoverningBody> getNgoList() {
return ngoList;
}
public GoverningBody getNgo() {
return ngo;
}
public boolean isEdit() {
return edit;
}
public List<SelectItem> getCountryValues() {
return countryValues;
}
public void setCountryValues(List<SelectItem> countryValues) {
this.countryValues = countryValues;
}
public List<SelectItem> initCountryValues() {
List<SelectItem> items = new ArrayList<>(CountryEnum.values().length);
int i = 0;
for(CountryEnum g: CountryEnum.values()) {
items.add(new SelectItem(g, g.getName()));
}
System.out.println("items = " + items);
return items;
}
}
I tried annotating the method with #Factory("countryValues") but this didn't seem to help.
This problem was unrelated to the symptom. The root cause of the problem was an incorrectly located beans.xml this should have be in the WEB-INF directory of the war not the META-INF directory of the ear.
I also changed the seam-faces dependency to use apache CODI, this is not necessary but this uses #ViewAccessScoped instead of #ViewScoped the different name is less ambiguous I think.

Resources