Vaadin flow, trying to display Horizontal layout in Grid - vaadin

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.

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.

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

Vaadin, Switching Content via setContent() not displaying

Vaadin 7.6.2
Take the following example:
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Panel;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
public class MyClass extends Panel {
TextField myField = new TextField();
HorizontalLayout hLayout = new HorizontalLayout( myField );
VerticalLayout vLayout = new VerticalLayout( myField );
Button button = new Button( "Press Me" );
public MyClass() {
super();
applySettings();
}
private void applySettings() {
button.addClickListener(new ClickListener() {
#Override
public void buttonClick(Button.ClickEvent event) {
setContent( hLayout );
}
});
vLayout.addComponent( button );
this.setContent( vLayout );
}
}
When I click the button the vLayout disappears but the hLayout (with myField) doesn't appear. What step am I'm missing? Or, is there a different way to do this?
If I add a secondary text field, like so:
TextField myField = new TextField();
TextField myField2 = new TextField(); // tf2
HorizontalLayout hLayout = new HorizontalLayout( myField );
VerticalLayout vLayout = new VerticalLayout( myField2 ); // tf2
It appears to work, however what I'm trying to achieve is the ability to dynamically switch my layouts using the fields (and their data) from the switched-out layout.
One component can not have 2 parents at the same time (hLayout & vLayout in your case), thus if it already has one, Vaadin will remove it from the previous parent and add it as a child to the current one. This is the addComponent method inherited from AbstractComponentContainer:
/**
* This only implements the events and component parent calls. The extending
* classes must implement component list maintenance and call this method
* after component list maintenance.
*
* #see com.vaadin.ui.ComponentContainer#addComponent(Component)
*/
#Override
public void addComponent(Component c) {
// Make sure we're not adding the component inside it's own content
if (isOrHasAncestor(c)) {
throw new IllegalArgumentException(
"Component cannot be added inside it's own content");
}
if (c.getParent() != null) {
// If the component already has a parent, try to remove it
AbstractSingleComponentContainer.removeFromParent(c);
}
c.setParent(this);
fireComponentAttachEvent(c);
markAsDirty();
}
If you're in debug mode, you can somewhat see an image of the composition tree in your browser by adding ?debug to your URL, something like http://localhost:8080/?debug
Well, it looks like I may have solved it.
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Panel;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
public class MyClass extends Panel {
TextField myField = new TextField();
HorizontalLayout hLayout = new HorizontalLayout();
VerticalLayout vLayout = new VerticalLayout();
Button button = new Button( "Press Me" );
public MyClass() {
super();
applySettings();
}
private void applySettings() {
button.addClickListener(new ClickListener() {
#Override
public void buttonClick(Button.ClickEvent event) {
vLayout.removeAllComponents(); // this is optional
hLayout.addComponent( myField );
setContent( hLayout );
}
});
vLayout.addComponents( myField, button );
this.setContent( vLayout );
}
}
If I don't add myField in the layout constructors, but add it later in the code, it seems to work.
UPDATE
After more testing it seems whichever layout LAST called addComponent() for a given field, THAT layout gets the handle to that field. This behavior seems odd and if someone could explain why this is? That would be enlightening.

Refreshing Table Model Adapter on deleting record from it : Blackberry

I m creating a Table Model Adapter in Blackberry.(**This is a sample what i m doing****)I have added button field and two Strings.I m putting data in String from vector.Now on button click i want to delete the row against button.The Data is deleted from the database but not removed from Screen view when button is clicked.When i calld the Retrieve() function to call updated database and draw the Table model adapter again....it is adding new Table below old one....not refreshing it. Is there any solution to show the refreshed data in same table.
package mypackage;
import java.util.Vector;
import net.rim.device.api.system.Display;
import net.rim.device.api.ui.Color;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.Manager;
import net.rim.device.api.ui.XYRect;
import net.rim.device.api.ui.component.ButtonField;
import net.rim.device.api.ui.component.LabelField;
import net.rim.device.api.ui.component.table.DataTemplate;
import net.rim.device.api.ui.component.table.TableController;
import net.rim.device.api.ui.component.table.TableModelAdapter;
import net.rim.device.api.ui.component.table.TableView;
import net.rim.device.api.ui.component.table.TemplateColumnProperties;
import net.rim.device.api.ui.component.table.TemplateRowProperties;
import net.rim.device.api.ui.container.MainScreen;
import net.rim.device.api.ui.decor.BackgroundFactory;
public final class MyScreen extends MainScreen implements FieldChangeListener
{
private DeviceTableModelAdapter _tableModel;
private Vector _cities;
private static final int NUM_ROWS = 1;
private static final int ROW_HEIGHT = 50;
private static final int NUM_COLUMNS = 3;
public ButtonField btn;
public MyScreen(){
super(Manager.NO_VERTICAL_SCROLL);
_cities = new Vector();
_tableModel = new DeviceTableModelAdapter();
Vector sample =new Vector();
sample.addElement("Newyork");
sample.addElement("NewDelhi");
sample.addElement("NewOrleans");
int ik = 0;
while(ik < sample.size())
{
String modelNumber = sample.elementAt(ik).toString();
String modelName = "-Earth-";
String ne = String.valueOf(ik);
Object[] row = {modelName, modelNumber, ne};
_tableModel.addRow(row);
ik++;
}
TableView tableView = new TableView(_tableModel);
tableView.setDataTemplateFocus(BackgroundFactory.createLinearGradientBackground(Color.WHITE, Color.WHITE, Color.BLUEVIOLET, Color.BLUEVIOLET));
TableController tableController = new TableController(_tableModel, tableView);
tableController.setFocusPolicy(TableController.ROW_FOCUS);
tableView.setController(tableController);
// Specify a simple data template for displaying 3 columns
DataTemplate dataTemplate = new DataTemplate(tableView, NUM_ROWS, NUM_COLUMNS)
{
public Field[] getDataFields(int modelRowIndex)
{
Object[] data = (Object[]) (_tableModel.getRow(modelRowIndex));
Field[] fields = {getButtonFieldObject((String)data[0]), new LabelField((String) data[1]), new LabelField((String) data[2])};
return fields;
}
};
dataTemplate.useFixedHeight(true);
// Define regions and row height
dataTemplate.setRowProperties(0, new TemplateRowProperties(ROW_HEIGHT));
for(int i = 0; i < NUM_COLUMNS; i++)
{
dataTemplate.createRegion(new XYRect(i, 0, 1, 1));
dataTemplate.setColumnProperties(i, new TemplateColumnProperties(Display.getWidth() / NUM_COLUMNS));
}
// Apply the template to the view
tableView.setDataTemplate(dataTemplate);
add(tableView);
}
public void fieldChanged(Field arg0, int arg1) {
/*** tableView.DeleteAll(); ****/
/**** calling Class again to draw the table Modal Adapter again with updated value *******/
}
private final static class City
{
private String _name;
private String _region;
private String _image;
City(String name, String region, String image)
{
_name = name;
_region = region;
_image = image;
}
public String getName()
{
return _name;
}
public String getRegion()
{
return _region;
}
public String getImage()
{
return _image;
}
}
private class DeviceTableModelAdapter extends TableModelAdapter
{
public int getNumberOfRows()
{
return _cities.size();
}
public int getNumberOfColumns()
{
return NUM_COLUMNS;
}
protected boolean doAddRow(Object row)
{
Object[] arrayRow = (Object[]) row;
_cities.addElement(new City((String) arrayRow[0], (String) arrayRow[1], (String) arrayRow[2]));
return true;
}
protected Object doGetRow(int index)
{
City city = (City) _cities.elementAt(index);
Object[] row = {city.getImage(), city.getRegion(), city.getName()};
return row;
}
}
public ButtonField getButtonFieldObject(String arg){
btn = new ButtonField(arg,ButtonField.CONSUME_CLICK);
btn.setChangeListener(this);
return btn;
}
}
What you have to consider is the fact that Blackberry table UI follows the MVC design pattern.
So it means that data are updated in the model.
Like for example:
Object yes = (Object)"Yes";
// gets the Model attached to this View
TableModel tm = (TableModel)view.getModel();
// updates the data attached to the row
tm.setElement(rowIndex, columnIndex, yes);
tm.modelReset();

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