Approach for designing View in vaadin 7 - vaadin

I am newbie to vaadin. I have to develop PoC on vaadin. Service layer is already written using spring. As a part of Poc I have to develop a screen below.
When request comes to my UI class, it will call my View using navigator. This view consists of one tabsheet and each tab have its own functionality and depends on other tab values too. First tab is search tab. It displays all the records came from db in the tab content area(Table/Grid addon. I dont know what to use). Each record have access to other two tabs. The other two tabs has fields to map each record's property. As of now, i have taken dummy data to display.
I wrote the view like this . But I am confused weather this approach is correct or not.
#VaadinView(UserView.NAME)
public class UserView extends VerticalLayout implements View {
public static final String NAME = "user";
public UserView(){
// For Tabs
TabSheet tabs = new TabSheet();
// first tab component
VerticalLayout layout = new VerticalLayout();
// for search fields
HorizontalLayout searchArea = new HorizontalLayout();
FormLayout searchAreaName = new FormLayout();
TextField name = new TextField("name");
FormLayout searchAreaEmail = new FormLayout();
TextField email = new TextField("email");
searchAreaName.addComponent(name);
searchAreaEmail.addComponent(email);
searchArea.addComponent(searchAreaName);
searchArea.addComponent(searchAreaEmail);
// for search table
BeanContainer<String, test.User> users = new BeanContainer<String, User>(
User.class);
users.setBeanIdProperty("userId");
users.addBean(new User("sudheer", "sudheer#kewil.com", "1"));
users.addBean(new User("sridhar", "sridhar#kewil.com", "2"));
users.addBean(new User("ranga", "ranga#kewil.com", "3"));
Table table = new Table("", users);
table.setSizeFull();
table.setPageLength(6);
layout.addComponent(searchArea);
layout.addComponent(table);
Tab tabOne = tabs.addTab(layout, "User Search", null);
// second tab component
VerticalLayout userLayout = new VerticalLayout();
userLayout.addComponent(new TextField("user name"));
userLayout.addComponent(new TextField("email"));
tabs.addTab(userLayout, "main details", null);
// tab change event
addComponent(tabs);
tabs.setHeight("50%");
tabs.setWidth("50%");
setComponentAlignment(tabs, Alignment.MIDDLE_CENTER);
}
#Override
public void enter(ViewChangeEvent event) {
}
}
I haven't implemented pagination also. Before going forward, I would like to know any other best approaches to go ahead.
Any suggestions would help me very much. Thanks in advance.
Anybody.. please help me out. I am going blindly with my appproach

Here is what I do in such cases:
Use the Blackboard event bus to fire events. These events carry a payload that essentially is the id of the record clicked/selected.
The other tabs or views are registered as a listener of this event. When the event is fired, the listeners extract the record/entity id from the payload, fetch the entity object from the back-end, and display it accordingly.
This approach ensures loosely-coupled components.
I hope it helps.

Related

Using DockPanelSuite, how do you get context menu for tab strip separate from document tab?

When using DockPanelSuite, is it possible to have a context menu for the tab strip that is different from the one for a document tab? For example, right click an empty space on the tab strip and get one context menu then right click a document tab and get a different context menu specific to the document.
I tried setting the ContextMenuStrip property of the DockPanel. I got a context menu for any empty space on the DockPanel control as well as the document tab strip when visible and all open document tabs. That's a good start but I really only wanted the context menu for the tab strip. Not the main control or any tabs.
I also followed along with the sample project to make a context menu for the document by setting the TabPageContextMenuStrip property of the DockContent form. I discovered that you get a document specific context menu by right clicking the document tab, but it also overrides the DockPanel's ContextMenuStrip. While that is useful, it's still not the desired result.
Edit:
Updating this post in case anyone else is interested in achieving the objective of the question.
After much source code analysis and testing, I concluded that the objective could not be achieved using the available public Properties, Methods, and Events. However, we can achieve the goal by using a bit of reflection.
Discoveries:
DockContent.ContextMenuStrip
This property does nothing for the DockPanel. It will provide a context menu in the client area of the document. However, for some reason, the RichTextBox control set to Fill in the provided sample blocks the context menu from popping up.
DockContent.TabPageContextMenuStrip
This property causes the associated ContextMenuStrip to display when the document is active. However, it displays when you right click anywhere on the tab strip, not just when you right click the document tab.
Solution:
First, add a public property to the DockContent form which will contain a reference to the context menu.
public ContextMenuStrip TabContextMenu { get { return contextMenuTabPage; } }
Next, add an event handler in the MDI main form for the DockPanel.ActiveDocmentChanged event. This will be used to add an event handler to the tab strip after it’s been created.
this.dockPanel.ActiveDocumentChanged += new System.EventHandler(this.dockPanel_ActiveDocumentChanged);
private void dockPanel_ActiveDocumentChanged(object sender, EventArgs e)
{
// Hook into the document pane tabstrip mouse up event
// if we haven't already.
if (dockPanel.ActiveDocumentPane != null
&& dockPanel.ActiveDocumentPane.TabStripControl != null
&& dockPanel.ActiveDocumentPane.TabStripControl.Tag == null)
{
dockPanel.ActiveDocumentPane.TabStripControl.Tag = "MouseUp Hooked";
dockPanel.ActiveDocumentPane.TabStripControl.MouseUp +=
TabStripControl_MouseUp;
}
}
Finally, add the event handler for the TabStripControl.
private void TabStripControl_MouseUp(object sender, MouseEventArgs e)
{
// Capture right click action
if (e.Button == MouseButtons.Right)
{
ContextMenuStrip menu = contextMenuDocumentPane;
Point screenPos = Cursor.Position;
Point tabstripsPos = dockPanel.ActiveDocumentPane
.TabStripControl.PointToClient(screenPos);
// Determine if cursor is over a tab
var tabstrip = dockPanel.ActiveDocumentPane.TabStripControl;
var tabs = tabstrip.GetType()
.GetProperty("Tabs", BindingFlags.Instance |
BindingFlags.NonPublic).GetValue(tabstrip);
foreach (var tab in (IEnumerable)tabs)
{
var bounds = tab.GetType()
.GetProperty("Rectangle")
.GetValue(tab);
if (((Rectangle)bounds).Contains(tabstripsPos))
{
// Display context menu for this document tab
var document = tab.GetType()
.GetProperty("Content")
.GetValue(tab);
menu = ((ContentWindow)document).TabContextMenu;
}
}
// Show appropriate context menu
menu.Show(screenPos);
}
}

Vaadin Dataprovider: how to avoid "auto-fetch"?

Use Case 1 is answered below, Use Case 2 has been moved to a separate question (Vaadin Flow: Returning to a view, the view should not reload data from the backend)
I'd like to use a Vaadin Flow (v14 LTS/v19) grid component backed by a lazy DataProvider which does not automatically fetch data from the backend when the grid is shown.
There are at least two use cases:
showing grid data does not make sense unless the user provided filter parameters
returning to a #PreserveOnRefresh tagged view should not replace the shown data with current data. (further elaborated in update)
Being pretty new to Vaadin 14+, I could not figure out how to achieve this. Every time my GridView is displayed, the count and fetch callbacks of DataProvider are queried. The call originates from the DataCommunicator of the grid.
So for Use Case 1: How to stop the DataProvider from fetching data as long as it does not make sense?
And for Use Case 2: How to prevent overwriting the grid state when adding a grid to the UI for the second time?
Thanks a lot!
StackTrace to my fetch callback (Vaadin Flow 14):
at org.vaadin.example.GridView.fetch(GridView.java:46)
at org.vaadin.example.GridView.lambda$new$c4b2c115$1(GridView.java:23)
at com.vaadin.flow.data.provider.CallbackDataProvider.fetchFromBackEnd(CallbackDataProvider.java:137)
at com.vaadin.flow.data.provider.AbstractBackEndDataProvider.fetch(AbstractBackEndDataProvider.java:61)
at com.vaadin.flow.data.provider.DataCommunicator.fetchFromProvider(DataCommunicator.java:362)
at com.vaadin.flow.data.provider.DataCommunicator.activate(DataCommunicator.java:647)
at com.vaadin.flow.data.provider.DataCommunicator.collectKeysToFlush(DataCommunicator.java:589)
at com.vaadin.flow.data.provider.DataCommunicator.flush(DataCommunicator.java:461)
at com.vaadin.flow.data.provider.DataCommunicator.lambda$requestFlush$2f364bb9$1(DataCommunicator.java:425)
at com.vaadin.flow.internal.StateTree.lambda$runExecutionsBeforeClientResponse$2(StateTree.java:390)
at [java.util.stream] omitted
at com.vaadin.flow.internal.StateTree.runExecutionsBeforeClientResponse(StateTree.java:387)
at com.vaadin.flow.server.communication.UidlWriter.encodeChanges(UidlWriter.java:411)
at com.vaadin.flow.server.communication.UidlWriter.createUidl(UidlWriter.java:187)
at com.vaadin.flow.server.communication.UidlRequestHandler.writeUidl(UidlRequestHandler.java:122)
at com.vaadin.flow.server.communication.UidlRequestHandler.synchronizedHandleRequest(UidlRequestHandler.java:91)
at com.vaadin.flow.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:40)
at com.vaadin.flow.server.VaadinService.handleRequest(VaadinService.java:1547)
at com.vaadin.flow.server.VaadinServlet.service(VaadinServlet.java:247)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
update 20210430
Here's the code of my GridView which also fakes the backend DataProvider:
#Route(value = "grid", layout = MainView.class)
public class GridView extends VerticalLayout {
public GridView() {
final Grid<Person> g = new Grid(Person.class);
g.setColumns("name");
g.setDataProvider(DataProvider.fromCallbacks(q -> fetch(q), q -> count(q)));
add(g);
// filter omitted
final Button refresh = new Button("refresh");
refresh.addClickListener(e -> {
System.out.println("refresh clicked");
g.getDataProvider().refreshAll();
});
add(refresh);
add(new TextField("State check"));
}
// fake DataProvider
private int count(Query<Person, Void> q) { return 3; }
private Stream<Person> fetch(Query<Person, Void> q) {
q.getLimit(); //vaadin checks these have been called
q.getOffset(); //vaadin checks these have been called
System.out.println("fetching again");
new Exception().printStackTrace(); //figure out who called
return Arrays.asList(new Person("1"), new Person("2"), new Person("3")).stream();
}
}
My MainView is used to switch between GridView and EmptyView
#PreserveOnRefresh
public class MainView extends AppLayout {
private Component emptyBView;
private Component gridBView;
public MainView() {
final Button emptyB = new Button("Btn empty");
emptyB.addClickListener(e -> {
if (emptyBView == null) { emptyBView = new EmptyView();}
setContent(emptyBView);
});
addToNavbar(emptyB);
final Button gridB = new Button("Btn grid");
gridB.addClickListener(e -> {
if (gridBView == null) gridBView = new GridView();
setContent(gridBView);
});
addToNavbar(gridB);
}
}
MainView is an AppLayout used to switch the contents of the AppLayout from GridView to EmptyView and back.
Use Case 2 is: When returning to GridView, the GridView should be exactly same state as before (which works fine with the TextField).
open GridView -> grid should not be filled with data
enter filter params (not shown in code)
click "refresh" to populate the grid
enter "Spiderman" in TextField "stateCheck"
switch to EmptyView
in the real app: do something in EmptyView and potentially other views
return to GridView -> the grid should not reload the data, it should just stay as it was - just like the TextField still displays "Spiderman", the grid should display the same data as before without reloading it.
For Case 1: In the callback check if you have filter parameters, return an empty set if not. Using the new V17+ API it would look like this:
grid.setItems(query -> {
if(filterParameters.isEmpty()) {
// Return an empty stream
} else {
// Fetch from backend
}
});
You can read more in the docs here: https://vaadin.com/docs/latest/flow/binding-data/data-provider (V19) or https://vaadin.com/docs/v14/flow/binding-data/tutorial-flow-data-provider (V14)
I would need more info on what you're currently doing to help out with Case 2. How are you constructing the view, what does your code look like? A full stack trace with the "Caused by" would also help.
I would recommend only setting the DataProvider to the Grid once the first filter parameter is set. The client-side Grid expects to receive the number of items it requires from the fetch query; it might work in some corner case if you don't provide the requested numbers of items from fetch, but it's not designed to behave like that.
Note that this applies specifically to using DataProviders with filters in Vaadin 14 series - Vaadin 17 introduced a new optional simplified way of fetching items, which changes this equation a bit. It's not backported to Vaadin 14 yet (currently planned for 14.7).

Vaadin Flow: Returning to a view, the view should not reload data from the backend

Split out from Vaadin Dataprovider: how to avoid "auto-fetch"?.
Given a Vaadin Flow 19 app with a MainView extends AppLayout, a GridView and an EmptyView And #PreserveOnRefresh annotation is used on MainView.
When returning to GridView, the GridView should be exactly in the same state as before:
open GridView using button in MainView for the first time -> Grid uses DataProvider to fetch data from backend
enter "Spiderman" in TextField with caption "stateCheck"
switch to EmptyView using button in MainView
in the real app: do something in EmptyView and potentially other views
return to GridView using button in MainView for the 2nd time
Then (1) the TextField with caption "stateCheck" should display the value "Spiderman"
And (2) the grid should still show the same data as before; it should not reload the data from the backend
Observed behaviour:
(1) is ok, but (2) not: the grid always calls fetch method to get data from the backend.
How do I achieve the desired behavior?
Here's the code of my GridView which also fakes the backend DataProvider:
#Route(value = "grid", layout = MainView.class)
public class GridView extends VerticalLayout {
public GridView() {
final Grid<Person> g = new Grid(Person.class);
g.setColumns("name");
g.setDataProvider(DataProvider.fromCallbacks(q -> fetch(q), q -> count(q)));
add(g);
add(new TextField("State check"));
}
// fake DataProvider
private int count(Query<Person, Void> q) { return 3; }
private Stream<Person> fetch(Query<Person, Void> q) {
q.getLimit(); //vaadin checks these have been called
q.getOffset(); //vaadin checks these have been called
System.out.println("fetching again");
return Arrays.asList(new Person("1"), new Person("2"), new Person("3")).stream();
}
}
MainView is used to switch between GridView and EmptyView
#PreserveOnRefresh
public class MainView extends AppLayout {
private Component emptyBView;
private Component gridBView;
public MainView() {
final Button emptyB = new Button("Btn empty");
emptyB.addClickListener(e -> {
if (emptyBView == null) { emptyBView = new EmptyView();}
setContent(emptyBView);
});
addToNavbar(emptyB);
final Button gridB = new Button("Btn grid");
gridB.addClickListener(e -> {
if (gridBView == null) gridBView = new GridView();
setContent(gridBView);
});
addToNavbar(gridB);
}
}
This is actually intentional behavior. The server side dataprovider listener needs to be removed when component is detached and rewired on attaching. The reason is that otherwise there would be listeners accumulating and producing a memory leakage. If you think your users would be using refresh page often, you should consider adding a cache to your application to optimize performance.
Now one could entertain with the idea of having this kind of caching of previous loaded data behavior via API in Grid also in Vaadin framework, as it may or may not be desirable. It is application specific.
If the use case of refreshing is really to get the fresh data of live and active database, it is actually desired that data is loaded when page is refreshed.
If the desire is to avoid extra bombarding of DB as data is known to be static, you want to have caching.

Is it possible to set description to a disabled text field in Vaadin?

I would like to add a tooltip to a disabled text field.
Do you have any ideas how can I do it?
I'm using Vaadin 6.8.13.
When I select a specific item in combo box, disabled text field will be enabled. Otherwise, text field will be disabled
TextField readonly = new TextField("Read-Only");
readonly.setValue("I am sitting here read only");
readonly.setReadOnly(true);
readonly.setDescription("Not this time Mojojojo");
Is this what you want?
Sure, why should it not work -- unless you have not even tried it first.
Disable the TextField via setEnabled(false) and add a description via setDescription('...'):
#Grapes([
#Grab('org.vaadin.spring:spring-boot-vaadin:0.0.5.RELEASE'),
#Grab('com.vaadin:vaadin-server:7.4.4'),
#Grab('com.vaadin:vaadin-client-compiled:7.4.4'),
#Grab('com.vaadin:vaadin-themes:7.4.4'),
])
import org.vaadin.spring.annotation.VaadinUI
import com.vaadin.server.VaadinRequest
import com.vaadin.ui.*
#VaadinUI
class MyUI extends UI {
protected void init(VaadinRequest request) {
setContent( new TextField().with{
caption = "I have a Caption"
value = "And a Value"
enabled = false
description = "And have a Description"
it
})
}
}
// spring run vaadin.groovy
Try disabling the label and then putting it inside a layout. Then set that layout's description. The user won't know the difference.
VerticalLayout layout = new VerticalLayout();
TextArea textArea = new TextArea("Hello World");
textArea.setEnabled(false);
VerticalLayout textAreaWrapper = new VerticalLayout();
textAreaWrapper.addComponent(textArea);
textAreaWrapper.setDescription("Some description");
layout.addComponent(textAreaWrapper);
this.setContent(layout);
this.setSizeFull();
You could create your own class which extends VerticalLayout and creates a TextArea inside it. That way you don't have to create a layout wrapper each time you just create your "MyTextArea" or whatever you want to call it.
Screenshot
N.B.
From your question I had originally thought the following code wouldn't work but it works fine? I haven't tried it in Vaadin 6.8.13 though. I am using Vaadin 7.1.2.
VerticalLayout layout = new VerticalLayout();
TextArea textArea = new TextArea("Hello World");
textArea.setEnabled(false);
textArea.setDescription("Some description");
layout.addComponent(textArea);
this.setContent(layout);
this.setSizeFull();

Popup Print Window in Vaadin 7 with Table

I am new on Vaadin.
How to pass Table component to new popup screen in Vaadin 7? Assume I already created table using com.vaadin.ui.Table.
Table aaa = new Table();
Currently Vaadin tutorial just show how to create print popup without pass component/data.
Based on below code
public static class PrintUI extends UI {
#Override
protected void init(VaadinRequest request) {
// Have some content to print
setContent(new Label(
"<h1>Here's some dynamic content</h1>\n" +
"<p>This is to be printed.</p>",
ContentMode.HTML));
// Print automatically when the window opens
JavaScript.getCurrent().execute(
"setTimeout(function() {" +
" print(); self.close();}, 0);");
}
}
...
// Create an opener extension
BrowserWindowOpener opener =
new BrowserWindowOpener(PrintUI.class);
opener.setFeatures("height=200,width=400,resizable");
// A button to open the printer-friendly page.
Button print = new Button("Click to Print");
opener.extend(print);
Appreciated if someone could show me how to pass Table aaa into PrintUI class.
Window window = new Window("my test table popup");
window.setContent(table);
window.setModal(true);
getUI().addWindow(window);
The only way that I have found to pass objects to the UI class in popup window is to store them in session:
Table table = new Table();
VaadinSession.getCurrent().setAttribute("table", table);
In the popup window:
Table t = (Table) VaadinSession.getCurrent().getAttribute("table");
You can also initialize your Table in your PrintUI class. If you need to initialize a specific Table instance, you can pass, let's say, your table id parameter to the UI via .getParameter("idTable") (which, in the end, works as adding a GET parameter to the URL) and then retrieve it in your PrintUI's init() method via the request parameter with .getParameter("idTable").

Resources