Vaadin basic layout: fixed header and footer + scrollable content - java

I am new to Vaadin and trying to know if it can suit my needs for a webapp project migration.
Actually I'm already loosing my time on a simple goal: to have a layout with fixed headers and footers, and a scrollable content in the middle.
I made a very basic fiddle with what I want:
jsfiddle
Here is the main Vaadin class I came up with:
public class MyVaadinUI extends UI {
// attributes
#WebServlet(value = "/*", asyncSupported = true)
#VaadinServletConfiguration(productionMode = false, ui = MyVaadinUI.class, widgetset = "testvaadin.aep.com.AppWidgetSet")
public static class Servlet extends VaadinServlet {
}
#Override
protected void init(VaadinRequest request) {
buildMainLayout();
}
private void buildMainLayout() {
final VerticalLayout mainLayout = new VerticalLayout();
mainLayout.setSizeFull();
//HEADER
final VerticalLayout headerLayout = new VerticalLayout();
final Resource res = new ThemeResource("img/logo.png");
final Image image = new Image(null, res);
headerLayout.addComponent(image);
//CONTENT
final VerticalLayout contentLayout = new VerticalLayout();
for(int i=0; i<80; i++){
contentLayout.addComponent(new Button("TEST " + i));
}
//FOOTER
final VerticalLayout footerLayout = new VerticalLayout();
footerLayout.addComponent(new Label("--------------------------- footer --------------------------"));
mainLayout.addComponent(headerLayout);
mainLayout.addComponent(contentLayout);
mainLayout.addComponent(footerLayout);
mainLayout.setExpandRatio(contentLayout, 1);
setContent(mainLayout);
}
}
The displayed page is OK on startup, but when I scroll down, the footer also scrolls (it is not fixed).
On startup:
When scrolled:
I browsed a lot of pages on this topic, but I did never see any correct answer. This seems to be rather complicated in Vaadin, although it is very simple in HTML; Vaadin may not suit my needs.
Anyway, do you know how can I achieve this behaviour?
Thanks!

You can use a Panel to create a scrollable center content area. See the example below.
For the panel to work, everything in the component hierarchy must be setSizeFull (or equivalent) and the content of the panel must not (in the example mainLayout and contentPanel are 100%, but contentLayout is not (implicit))
#Grapes([
#Grab('org.vaadin.spring:spring-boot-vaadin:0.0.3'),
#Grab('com.vaadin:vaadin-client-compiled:7.4.0.beta1'),
#Grab('com.vaadin:vaadin-themes:7.4.0.beta1'),
])
import com.vaadin.ui.*
#org.vaadin.spring.VaadinUI
class MyUI extends UI {
protected void init(com.vaadin.server.VaadinRequest request) {
final headerLayout = new VerticalLayout(new Label('HEADER'))
final footerLayout = new VerticalLayout(new Label('FOOTER'))
final contentLayout = new VerticalLayout()
80.times{ contentLayout.addComponent(new Button("TEST $it")) }
// XXX: place the center layout into a panel, which allows scrollbars
final contentPanel = new Panel(contentLayout)
contentPanel.setSizeFull()
// XXX: add the panel instead of the layout
final mainLayout = new VerticalLayout(headerLayout, contentPanel, footerLayout)
mainLayout.setSizeFull()
mainLayout.setExpandRatio(contentPanel, 1)
setContent(mainLayout)
}
}
(runs standalone with spring run vaadin.groovy)

Related

Vaadin 14,6 AppLayout with StatusBar?

I am happily using the standard Vaadin AppLayout Component as the layout starting point for my application. Now I received the requirement to add a statusbar. The statusbar must span the same width as the NavBar, so it cannot be part of the "content".
Is this at all possible with the default AppLayout?
Originally AppLayout has been purposed to take over the whole space, so it is not really meant for this use case. However I was able to tweak it to behave fit footer bar with these settings.
public class MainLayout extends VerticalLayout implements RouterLayout {
private AppLayout appLayout = new AppLayout();
private FlexLayout childWrapper = new FlexLayout();
public MainLayout() {
... setup appLayout ...
childWrapper.setSizeFull();
appLayout.setContent(childWrapper);
HorizontalLayout statusBar = new HorizontalLayout();
statusBar.setHeight("50px");
statusBar.setWidth("100%");
statusBar.add(new Span("Status Bar"));
statusBar.getElement().getStyle().set("background",
"var(--lumo-tint-30pct)");
appLayout.getElement().getStyle().set("width", "100%");
appLayout.getElement().getStyle().set("height", "500px");
add(appLayout, statusBar);
this.expand(appLayout);
}
#Override
public void showRouterLayoutContent(HasElement content) {
childWrapper.getElement().appendChild(content.getElement());
}
}
If you are interested in the status bar to be on top instead, just switch add(appLayout, statusBar); to add(statusBar, appLayout);

Why can't I set the content again after I have fresh the web page in Vaadin?

I got a strange issue. My web application works as it don't remove text/numbers from text boxes or resetting check boxes when I refresh the web page from the web browser.
But when I refresh the web page then I got the problem:
java.lang.IllegalStateException: Can't move a node from one state tree to another. If this is intentional, first remove the node from its current state tree by calling removeFromTree
java.lang.IllegalStateException: Unregistered node was not found based on its id. The tree is most likely corrupted.
And
Assertion error: No child node found with id 28
Assertion error: Node 3 is already registered
So why can't I set the content again after I have fresh the web page in Vaadin? I'm using Vaadin 14. If I don't refresh the web page, then I can change the content as much as I want. But as long I don't refresh the web page, then I will not get an error.
Notice that I have #PreserveOnRefresh enabled. Without that, I get no error. But then the text/values and all information will disappear when I refresh the page.
Here is my code.
#Route("")
#Viewport("width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes, viewport-fit=cover")
#PreserveOnRefresh
public class MainView extends AppLayout {
/**
*
*/
private static final long serialVersionUID = 1L;
public MainView() {
// Get the components
BuildPredictValidateTemplate buildPredictValidateTemplate = new BuildPredictValidateTemplate();
LoadExportTemplate loadExportTemplate = new LoadExportTemplate();
// Create logo and drawer
Image barImage = new Image("img/barImage.png", "Fisherfaces Logo");
barImage.setHeight("55px");
addToNavbar(new DrawerToggle(), barImage);
// Create tabs and add listeners to them
Tab buildPredictValidate = new Tab("Build & Predict & Validate");
buildPredictValidate.getElement().addEventListener("click", e -> {
setContent(buildPredictValidateTemplate.getBuildButtonPredictButtonValidateButtonTextArea());
});
Tab loadExport = new Tab("Load & Export");
loadExport.getElement().addEventListener("click", e -> {
setContent(loadExportTemplate.getSubjectCounterExportButtonUploaders());
});
// Add them and place them as vertical
Tabs tabs = new Tabs(buildPredictValidate, loadExport);
tabs.setOrientation(Tabs.Orientation.VERTICAL);
addToDrawer(tabs);
}
}
And
#Data
//#Component
public class BuildPredictValidateTemplate {
private VerticalLayout buildButtonPredictButtonValidateButtonTextArea;
public BuildPredictValidateTemplate() {
// Create the complete form layout
buildButtonPredictButtonValidateButtonTextArea = createBuildButtonPredictButtonValidateButtonTextArea();
}
private VerticalLayout createBuildButtonPredictButtonValidateButtonTextArea() {
// Text area that works like a terminal
TextArea textTerminal = new TextArea();
textTerminal.setPlaceholder("");
textTerminal.setWidthFull();
textTerminal.setHeightFull();
// Progressbar
ProgressBar progressBar = new ProgressBar();
progressBar.setValue(0);
// Buttons for Builing, Predicting and Validate
Button build = new Button("Build");
build.addClickListener(e -> {
System.out.println("Building");
});
Button predict = new Button("Predict");
predict.addClickListener(e -> {
System.out.println("Predicting");
});
Button validate = new Button("Validate");
validate.addClickListener(e -> {
System.out.println("Validating");
});
// Uploader for prediction
//Upload upload = new PictureUpload().getUpload();
// Add them all now
HorizontalLayout horizon = new HorizontalLayout(build, validate, predict);
return new VerticalLayout(horizon, progressBar, textTerminal);
}
}
And also
#Data
//#Component
public class LoadExportTemplate {
private VerticalLayout subjectCounterExportButtonUploaders;
public LoadExportTemplate() {
// Create layout for the uploads
VerticalLayout uploadsLayout = new VerticalLayout();
// Create subject counter for how many uploaders we should have
NumberField subjectCounter = createSubjectCounter(uploadsLayout);
// Create layout for holding subject counter, export button and uploaders
subjectCounterExportButtonUploaders = createLayoutForSubjectCounterExportButtonUploaders(subjectCounter, uploadsLayout);
}
private VerticalLayout createLayoutForSubjectCounterExportButtonUploaders(NumberField subjectCounter, VerticalLayout uploadsLayout) {
// Create SubjectCounter and ExportButton on a row
Button exportButton = new Button("Export to MATLAB code");
exportButton.addClickListener(e -> {
System.out.println("Exported to MATLAB code.");
});
HorizontalLayout layoutHorizon = new HorizontalLayout(subjectCounter, exportButton);
// Add the uploaders under the horizontal layout
return new VerticalLayout(layoutHorizon, uploadsLayout);
}
private NumberField createSubjectCounter(VerticalLayout uploadsLayout) {
NumberField subjectCounter = new NumberField();
subjectCounter.setValue(1d);
subjectCounter.setHasControls(true);
subjectCounter.setMin(1);
subjectCounter.addValueChangeListener(e-> {
// First clear, then fill with new uploaders
uploadsLayout.removeAll();
for(Double i = 0.0; i < e.getValue(); i++) {
PictureUpload pictureUpload = new PictureUpload();
uploadsLayout.add(pictureUpload.getUpload());
}
});
// Add one to begin with
//PictureUpload pictureUpload = new PictureUpload();
//uploadsLayout.add(pictureUpload.getUpload());
return subjectCounter;
}
}
I believe this is a bug in Vaadin, at least it is not what I would expect to happen.
I have created an issue for it here https://github.com/vaadin/flow/issues/8286
Edit:
As a workaround, you can toggle visibility instead. But this would require you to add all components to e.g. a Div, and set that as the content.
E.g setContent(new Div(component1, component2));
Then when clicking on a tab, you would have to hide all components except the one clicked, e.g.
getContent().getChildren().forEach(component -> {
boolean visible = component.equals(theComponentIWantToShow);
component.setVisible(visible);
});

How to set different content for Tabs in Vaadin14?

I have a pretty simple class that basically is just an AppLayout with some Tab.
Now my issue. I am not able to find a smart way to display different contents for the Tabs-class. Is there any interface or something that can be called to differ the content for the Tab?
class MainAppView extends AppLayout {
public MainAppView()
{
createDrawerAndAddToAppView();
}
void createDrawerAndAddToAppView()
{
Tabs tabs = createTabsForDrawer();
tabs.setOrientation(Tabs.Orientation.VERTICAL);
addToDrawer(tabs);
H1 a = new H1("Test"); // Is displayed as content for every Tab
tabs.addSelectedChangeListener(selectedChangeEvent ->
/**
* How to get the specific content of a Tab here?
*/
//selectedChangeEvent.getSelectedTab(). //getContent() and put in super.setContent()?
super.setContent(a)); // Displays 'Test' as content for every Tab
// The Listener shall display the specific content of the getSelectedTab()
}
private Tabs createTabsForDrawer()
{
return new Tabs(
new Tab("Home"),
new Tab("Dummy"),
new Tab("Test"));
}
}
Here is one example, using a map to keep track of which content belongs to which tab. In reality your tab content would be more complicated, and maybe be created in it's own method.
#Route
public class TabTest extends VerticalLayout {
private Map<Tab, Component> tabComponentMap = new LinkedHashMap<>();
public TabTest() {
Tabs tabs = createTabs();
Div contentContainer = new Div();
add(tabs, contentContainer);
tabs.addSelectedChangeListener(e -> {
contentContainer.removeAll();
contentContainer.add(tabComponentMap.get(e.getSelectedTab()));
});
// Set initial content
contentContainer.add(tabComponentMap.get(tabs.getSelectedTab()));
}
private Tabs createTabs() {
tabComponentMap.put(new Tab("Show some text"), new H1("This is the text tab"));
tabComponentMap.put(new Tab("Show a Combo Box"), new ComboBox<String>());
tabComponentMap.put(new Tab("Show a button"), new Button("Click me and nothing happens"));
return new Tabs(tabComponentMap.keySet().toArray(new Tab[]{}));
}
}
You can do something similar with routes also, but then you would probably want your containing component to be a RouterLayout. Also this requires a bit more logic if you want to automatically select the correct tab after navigating from somewhere else.
#Route
public class TabTest extends VerticalLayout implements RouterLayout {
private Map<Tab, String> tabToUrlMap = new LinkedHashMap<>();
private Div contentContainer = new Div();
public TabTest() {
Tabs tabs = createTabs();
Div contentContainer = new Div();
contentContainer.setSizeFull();
add(tabs, contentContainer);
tabs.addSelectedChangeListener(e ->
UI.getCurrent().navigate(tabToUrlMap.get(e.getSelectedTab())));
}
private Tabs createTabs() {
RouteConfiguration routeConfiguration = RouteConfiguration.forApplicationScope();
tabToUrlMap.put(new Tab("View 1"), routeConfiguration.getUrl(TestView1.class));
tabToUrlMap.put(new Tab("View 2"), routeConfiguration.getUrl(TestView2.class));
tabToUrlMap.put(new Tab("View 3"), routeConfiguration.getUrl(TestView3.class));
return new Tabs(tabToUrlMap.keySet().toArray(new Tab[]{}));
}
#Override
public void showRouterLayoutContent(HasElement content) {
getElement().appendChild(content.getElement());
}
}
And an example view
#Route(layout = TabTest.class)
public class TestView3 extends VerticalLayout {
public TestView3() {
add("View 3");
}
}

Buttons are not clickable inside a vertical Layout Vaadin?

Hi everyone I have this layout:
Here is the class MainLayout:
public class MainLayout extends VerticalLayout {
/**
*
*/
private static final long serialVersionUID = 1L;
private VerticalLayout upperSection = new VerticalLayout();
private HorizontalSplitPanel lowerSection = new HorizontalSplitPanel();
private VerticalLayout menuLayout = new VerticalLayout();
private VerticalLayout contentLayout = new VerticalLayout();
public MainLayout() {
upperSection.addComponent(new Label("Header"));
menuLayout.addComponent(new Label("Menu"));
contentLayout.addComponent(new Label("Content"));
lowerSection.addComponent(menuLayout);
lowerSection.addComponent(contentLayout);
addComponent(upperSection);
addComponent(lowerSection);
showBorders();
setSizeFull();
lowerSection.setSizeFull();
// menuLayout.setSizeFull();
contentLayout.setSizeFull();
setExpandRatio(lowerSection, 1);
//lowerSection.setSplitPosition(30);
}
private void showBorders() {
String style = "v-ddwrapper-over";
setStyleName(style);
upperSection.setStyleName(style);
lowerSection.setStyleName(style);
menuLayout.setStyleName(style + "-menu");
contentLayout.setStyleName(style + "-content");
}
#SuppressWarnings("serial")
public void addMenuOption(String caption, final Component component) {
Button button = new Button(caption);
menuLayout.addComponent(button);
button.addClickListener(new ClickListener() {
#Override
public void buttonClick(ClickEvent event) {
contentLayout.removeAllComponents();
contentLayout.addComponent(component);
}
});
}
}
This layout class extends VerticalLayout and constructs the basic structure of the layout, the addMenuOption method adds a button to the left menu column and a click listener to it so that when the user clicks on the button the content layout on the right should switch its content from the current to the one bound with the button, now inside the init method of the UI:
protected void init(VaadinRequest request) {
MainLayout layout = new MainLayout();
layout.addMenuOption("Option 1", new Label("Component 1"));
layout.addMenuOption("Option 2", new Label("Component 2"));
setContent(layout);
}
Actually the result I obtain is this:
But my problem is that neither of the two buttons (Option 1, Option 2) are clickable.
Where is the problem?
Thanks for the attention!
You are right. Adding style "v-ddwrapper-over" to one of the components makes the buttons non-clickable. Lets take a look at definition of this style in style.css file.
.appName .v-ddwrapper-over:before, .so5 .v-ddwrapper-over:after {
content: "";
position: absolute;
z-index: 10;
top: -1px;
right: -1px;
bottom: -1px;
left: -1px;
border: 0 solid #197de1;
}
What's important is the fourth line with z-index. This brings a component (more specifficaly div in DOM) to the front covering all others components with less z-index value (usually they have 0).
If you really need this style to be applied to all your components (seems weird to me) consider adding additional style to the buttons with higher z-index value.
Learn more about z-index property here.

Failure to update JPanels inside CardLayout

I have two JPanels nested inside a cardPanl(with a cardLayout).
When switching between pages I need to have new instance of the page created. For example when I switch from homePage to captchaPage, I will replace the current homePage with a new instance of 'HomePage'. same thing goes when switching from captchaPage to homePage.
I will create the new instances without any problem but what I see on the screen is the old view of the JPanels meaning they do not get repainted.
I've searched for this problem and almost all the solutions suggest calling revalidate(), validate() or repaint() on the panel.
I've did it all and still I get the old view. I'm sure that creating the new instances is done successfully because when printing the capthcha in the console i see that it changes but the view remains the same.
Here is my structure:
BasicPage.java
public class BasePage extends JPanel {
protected JFrame parent;
protected String name;
public BasePage(JFrame parent, String name) {
this.parent = parent;
this.name = name;
// ...
}
}
CaptchaPage.java
public class CaptchaPage extends BasePage {
private String challenge;
public CaptchaPage(JFrame parent, String name) {
super(parent, name);
challenge = new BigInteger(130, new SecureRandom()).toString(32);
challenge = challenge.length() > 5 ? challenge.substring(0, 5) : challenge;
JLabel label = new JLabel(challenge);
this.add(label);
}
}
Dashboard.java
public class Dashboard extends JFrame {
private JPanel cardPanel;
private BasePage homePage;
private BasePage captchaPage;
public Dashboard() {
cardPanel = new JPanel();
cardPanel.setLayout(new CardLayout());
homePage = new HomePage(this, "0");
captchaPage = new CaptchaPage(this, "1");
cardPanel.add(homePage, "0");
cardPanel.add(captchaPage, "1");
this.add(cardPanel);
}
protected void switchPage(String name) {
((CardLayout)cardPanel.getLayout()).show(cardPanel, name);
if (name.equals("1")) {
homePage = new HomePage(this, "0");
homePage.revalidate();
}
else {
captchaPage = new CaptchaPage(this, "1");
captchaPage.revalidate();
}
}
}
Answer
BasePage page = new HomePage(this, "0");
cardPanel.add(page, "0");
cardPanel.revalidate();
homePage = page;
You added panels to the CardLayout with the following code which is correct:
cardPanel.add(homePage, "0");
cardPanel.add(captchaPage, "1");
Now you are trying to update the CardLayout with code like:
homePage = new HomePage(this, "0");
That will not work. All you are doing is changing the reference of the homepage variable. You have not added the component to the CardLayout.
To change the panel then code should be the same as the code you used to add the panel initially:
JPanel homepage = new HomePage(...);
cardPanel.add(...);
Why are you changing the components on the panel? Why does the homepage change. Sounds like a strange design to me.

Categories

Resources