Vaadin call create a component throught another component - vaadin

I have a Custom component with a textField and a button, I want to onClick (of the Button), get the text from the textField and call another Component.
The error I get when I call mainLayout.addComponent(new A(textField.getValue()); is an
java.lang.IllegalStateException: Composition root must be set to non-null value before the com.example.vaadpro.

As the exception says, you have to call setCompositionRoot for your CustomComponent:
public class A extends CustomComponent {
HorizontalLayout layout = new new HorizontalLayout();
public A() {
layout.add(new Label("Hello world!"));
layout.add(new Button("Click me!"));
setCompositionRoot(layout); // This is needed!
}
}

Related

Vaadin checkbox is not correctly rendered

Using Vaadin 14.0.13 without compatibility mode.
I use a view to create a Dialog with dynamic content:
#Route("")
public class MainView extends VerticalLayout {
public MainView(DialogContentProvider contentProvider) {
this.add(new Button("Click me!", event -> new Dialog(contentProvider.create()).open()));
}
}
The contentProvider is an interface
public interface DialogContentProvider {
Component create();
}
with this implementation:
public class CheckBoxContentProvider implements DialogContentProvider {
#Override
public Component create() {
return new Checkbox("My checkbox", true);
}
}
instantiated by Spring Boot (version 2.2.1.RELEASE) with a bean:
#Bean
public DialogContentProvider contentProvier() {
return new CheckBoxContentProvider();
}
When I click on the button, the dialog is opened but the checkbox haven't the box:
The source code is on github: https://github.com/gronono/bug-vaadin-checkbox
I don't understand why and how I can fix it. If I include the checkbox creation inside the main view, it works fine:
#Route("")
public class MainView extends VerticalLayout {
public MainView(DialogContentProvider contentProvider) {
// this.add(new Button("Click me!", event -> new Dialog(contentProvider.create()).open()));
this.add(new Button("Click me!", event -> new Dialog(new Checkbox("My checkbox", true)).open()));
}
}
This sound an awful lot like this (related github issue)
Basically, this happens when you don't have any View that uses a Checkbox directly, but through other means like reflection or in your case the contentProvider, because in no view of your app there is any import statement of Checkbox (--> therefore, vaadins scan during the installation will not detect usages of Checkbox, so it will not download npm stuff for checkbox).
in the github it says this will be fixed in 14.1
If you need a fix now, for me it worked when I declared a field of that type in any view with a #Route. That field doesn't have to be used.
#Route("")
public class MainView extends VerticalLayout {
private Checkbox unusedCheckbox; // this line fixes it.
public MainView(DialogContentProvider contentProvider) {
this.add(new Button("Click me!", event -> new Dialog(contentProvider.create()).open()));
}
}
Addendum: This is not related to the Checkbox component specifically, it happens with any vaadin component that isn't initially scanned in a route, but used anyway through reflective-, provider-, or generic means.
Edit: You can also work around this currently by adding a #Route(registerAtStartup = false) to your provider that uses the Checkbox directly. This will make vaadins scan see the checkbox usage (therefore importing its npm package), but will not actually register the provider as a real route..
Another way which I prefer if you need this for multiple components is to create a new View with a #Route(registerAtStartup = false) which only defines private variables for each component that you'll need in the application (and arent already used directly in some view of yours). This has the advantage of all these component usage definitions in one place, and once the official fix is released, you need only to delete one class and the deprecated workaround is gone.
#Route(registerAtStartup = false)
public class ComponentImportView extends VerticalLayout {
private Checkbox checkBox;
private Upload upload;
private ProgressBar progressBar;
}

VaadinSession attribute and updating session-bound components

I have a Vaadin Navigator with multiple View elements. Each view has a different purpose however some also contain common traits that I have put inside custom components.
One of those custom components is the menu - it is positioned at the top and allows navigation between the different views. I create and add this component inside the constructor of each view (if you are interested in the menu's implementation see the end of this post). Here is a skeleton for each custom view:
class MyViewX implements View {
MenuViewComponent mvc;
public MyViewX() {
mvc = new MenuViewComponent();
addComponent(mvc);
}
#Override
public void enter(ViewChangeEvent event) {
}
}
So far, so good. In order to make things simple I will explain my problem using a simple label and not one of my other custom components but the dependency that I will describe here is the same for those components just like with the label.
Let's say I have a label which sole purpose is to display a greeting with the user's username. In order to do that I use VaadinSession where I store the attribute. This is done by my LoginController, which validates the user by looking into a database and if the user is present, the attribute is set and one of the views is opened automatically. The problem is that VaadinSession.getCurrent().getAttribute("username") returns null when called inside the constructor. This of course makes sense omho because a constructor should not be bound by a session-attribute.
So far I have managed to use the enter() method where there is no problem in retrieving session attributes:
class MyViewX implements View {
MenuViewComponent mvc;
public MyViewX() {
mvc = new MenuViewComponent();
addComponent(mvc);
}
#Override
public void enter(ViewChangeEvent event) {
String username = (String)VaadinSession.getCurrent().getAttribute("username");
Label greeting = new Label("Hello " + username);
addComponent(greeting);
}
}
The issue that comes from this is obvious - whenever I open the view where this label is present, a new label is added so if I re-visit the view 10 times, I will get 10 labels. Even if I move the label to be a class member variable the addComponent(...) is the one that screws things up. Some of my custom components really depend on the username attribute (in order to display user-specific content) hence I also have to place those in the enter(...) method. The addComponent(...) makes a mess out of it. I even tried the dirty way of removing a component and then re-adding it alas! in vain:
class MyViewX implements View {
MenuViewComponent mvc;
Label greeting;
public MyViewX() {
mvc = new MenuViewComponent();
addComponent(mvc);
}
#Override
public void enter(ViewChangeEvent event) {
String username = (String)VaadinSession.getCurrent().getAttribute("username");
greeting = new Label("Hello " + username);
// Remove if present
try { removeComponent(greeting); }
catch(Exception ex) { }
// Add again but with new content
addComponent(greeting);
}
}
but it's still not working. So my question is: what is the simplest way of updating a component that requires session-bound attributes?
The navigation via the menu custom component is omho not the issue here since all components of the menu are loaded in it's constructor. That's why it's also load that component in particular in a view's own constructor. Here is an example of a button in my menu that opens a view:
#SuppressWarnings("serial")
#PreserveOnRefresh
public class MenuViewComponent extends CustomComponent {
public MenuViewComponent(boolean adminMode) {
HorizontalLayout layout = new HorizontalLayout();
Label title = new Label("<h2><b>Vaadin Research Project</b></h2>");
title.setContentMode(ContentMode.HTML);
layout.addComponent(title);
layout.setComponentAlignment(title, Alignment.TOP_LEFT);
Button personalDashboardButton = new Button("Personal dashboard", new Button.ClickListener() {
#Override
public void buttonClick(ClickEvent event) {
getUI().getNavigator().navigateTo(MainController.PERSONALDASHBOARDVIEW);
}
});
personalDashboardButton.setStyleName(BaseTheme.BUTTON_LINK);
layout.addComponent(personalDashboardButton);
layout.setComponentAlignment(personalDashboardButton, Alignment.TOP_CENTER);
// Add other buttons for other views
layout.setSizeUndefined();
layout.setSpacing(true);
setSizeUndefined();
setCompositionRoot(layout);
}
}
PERSONALDASHBOARDVIEW is just one of the many views I have.
It may be worth considering how long should your view instances "live", just as long they're displayed, until the session ends or a mix of the two. With this in mind and depending on what needs to happen when you enter/re-enter a view, you have at least the following 3 options:
1) Recreate the whole view (allowing for early view garbage-collection)
first register a ClassBasedViewProvider (instead of a StaticViewProvider) which does not hold references to the created views:
navigator = new Navigator(this, viewDisplay);
navigator.addProvider(new Navigator.ClassBasedViewProvider(MyView.NAME, MyView.class));
simple view implementation
public class MyView extends VerticalLayout implements View {
public static final String NAME = "myViewName";
#Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
// initialize tables, charts and all the other cool stuff
addComponent(new SweetComponentWithLotsOfStuff());
}
}
2) Keep some already created components and replace others
public class MyView extends VerticalLayout implements View {
private MySweetComponentWithLotsOfStuff mySweetComponentWithLotsOfStuff;
public MyView() {
// initialize only critical stuff here or things that don't change on enter
addComponent(new MyNavigationBar());
}
#Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
// oh, so the user does indeed want to see stuff. great, let's do some cleanup first
removeComponent(mySweetComponentWithLotsOfStuff);
// initialize tables, charts and all the other cool stuff
mySweetComponentWithLotsOfStuff = new SweetComponentWithLotsOfStuff();
// show it
addComponent(mySweetComponentWithLotsOfStuff);
}
}
3) Lazy creating and updating (or not) the content when entering
public class MyView extends VerticalLayout implements View {
private boolean isFirstDisplay = true;
private MySweetComponentWithLotsOfStuff mySweetComponentWithLotsOfStuff;
public MyView() {
// initialize only critical stuff here, as the user may not even see this view
}
#Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
// oh, so the user does indeed want to see stuff
if (isFirstDisplay) {
isFirstDisplay = false;
// lazily initialize tables, charts and all the other cool stuff
mySweetComponentWithLotsOfStuff = new SweetComponentWithLotsOfStuff();
addComponent(mySweetComponentWithLotsOfStuff);
} else {
// maybe trigger component updates, or simply don't do anything
mySweetComponentWithLotsOfStuff.updateWhateverIsRequired();
}
}
}
I'm sure (and curious) that there may be other options, but I've mainly used a variation of 1) using spring with prototype views and component tabs.

How do I add a TextItem to HLayout in Smartgwt 2.4?

I want to add a TextItem component into a HLayout in SmartGwt.
I would just want to avoid adding each component to DynamicForm and then to a Layout
Instead I want to directly add a TextItem to HLayout.
public class Test4 implements EntryPoint {
DynamicForm dynamicForm = new DynamicForm();
TextItem textItem = new TextItem();
HLayout hLayout = new HLayout();
public void onModuleLoad() {
// dynamicForm.setFields(textItem);
hLayout.addMember(textItem);
hLayout.draw();
}
}
HLayout.addMember() method accepts a Widget or Canvas as an argument. TextItem extends the FormItem which doesn't extent any of the above classes. Thus it is imposible to achieve what your code states. Your only solution is to add it through a DynamicForm using the setFields() method. If you have more than one TextItems you can just call something like the below:
dynamicForm.setFields(textItem1, textItem2, textItem3, ..., textItemN);
If you do not wish to be able to change the value of the textItem you can always use a Label or create a custom widget from simpler classes to achieve the look of the TextItem.

Vaadin addStyleName problem

I created a TextField with TextChangeListener. When user types in certain values (in this case 'admin') then addStyleName is invoked on that field and font color becomes red. But afterwards, the value is blank and each entered character is being cleared.
Here is the code of the application. Why after adding new style to TextField its value changes?
public class VaadintestApplication extends Application {
#Override
public void init() {
Window mainWindow = new Window("Vaadintest Application");
setTheme("test");
TextField textField = new TextField("username");
textField.setEnabled(true);
textField.setTextChangeEventMode(TextChangeEventMode.EAGER);
textField.addListener(new TextChangeListener() {
public void textChange(TextChangeEvent event) {
if ("admin".equals(event.getText())) {
((TextField) event.getComponent()).addStyleName("text-error");
} else {
((TextField) event.getComponent()).removeStyleName("text-error");
}
}
});
mainWindow.addComponent(textField);
setMainWindow(mainWindow);
}
}
I would guess that the following happens:
The style name change triggers a repaint on the server, causing the TextField component to be serialized again to the client
The client receives the serialization (the whole bloody thing, not just the changed parts, because that's how things work with Vaadin), and hence it changes the contents of the textfield, while ignoring any changes that are pending from the text change listener
Solutions:
Update the value of the TextField at the same time you add/remove the style name: ((TextField) event.getComponent()).setValue(event.getText())
Create a custom client side widget which extends VTextField and add the functionality there

Vaadin: Sub window

I'm trying to use a sub window but either the components I try to put in it show up in the "mainwindow" or I receive a java.lang.UnsupportedOperationException. I'll show you both cases. I'd like to place a HorizontalLayout in the sub window when I need to put in some real components and not just a Label and a Button.
public class SubWindow extends CustomComponent {
Window mainWindow; // Reference to main window
Window myWindow; // The window to be opened
public SubWindow(Window main) {
mainWindow = main;
createWindow();
}
public void createWindow() {
myWindow = new Window("My Dialog");
HorizontalLayout layout = new HorizontalLayout();
// Add the window inside the main window.
mainWindow.addWindow(myWindow);
layout.addComponent(new Label("A label"));
layout.addComponent(new Button("Ok"));
// The composition root MUST be set
setCompositionRoot(layout);
myWindow.addComponent(layout);
}
}
When I run this and open a new sub window using
window = new Window("Title");
setMainWindow(window);
window.addComponent(new SubWindow(window));
I get
SEVERE: Terminal error:
com.vaadin.event.ListenerMethod$MethodException
Cause: java.lang.UnsupportedOperationException
at com.vaadin.event.ListenerMethod.receiveEvent(ListenerMethod.java:510)
at com.vaadin.event.EventRouter.fireEvent(EventRouter.java:164)
...
Caused by: java.lang.UnsupportedOperationException
at com.vaadin.ui.CustomComponent.removeComponent(CustomComponent.java:248)
at com.vaadin.ui.AbstractComponentContainer.addComponent(AbstractComponentContainer.java:207)
...
On the other hand, if I switch place between setCompositionRoot(layout) and myWindow.addComponent(layout), the Label and the Button just end up in the main window instead of the newly created sub window.
What am I missing?
i suggest that you extend Window directly than go through a CustomLayout. A layout can't contain a window - it goes the other way around.
Change
public class SubWindow extends CustomComponent to public class SubWindow extends Window
myWindow = new Window("My Dialog"); to setCaption("My Dialog");
and
// The composition root MUST be set
setCompositionRoot(layout);
myWindow.addComponent(layout);
to setContent(layout);
That is the standard way of creating a subwindow, exactly the same way you create the main window. I would also move the mainWindow.addWindow(myWindow); outside of the class and not pass the mainwindow object to the subwindow, because that is not really the part of the subwindow object.

Resources