I have a simple UI class
public class HelloWorldUI extends UI {
#Override
protected void init(VaadinRequest request) {
System.out.println("Initialized !");
final VerticalLayout layout = new VerticalLayout();
layout.addComponent(new Label("Hello World !"));
setContent(layout);
}
#Override
public void detach() {
System.out.println("Detach !");
super.detach();
}
#Override
public void attach() {
System.out.println("Attach !");
super.attach();
}
}
When first time my UI was loaded , I see outputs at my console as
Attach !
Initialized !
It is OK and this is what I expected. But when I refresh the browser , my console outputs were
Attach !
Initialized !
Detach !
Amazing ! I think Detach ! may be produce first because (as I think) when browser was refreshed , detach() method should be call and attach() , init() should be follow . But actually detach() method will call after attach() method. What's wrong my thinking ?
Browser Refresh = New UI Instance
When you refresh a browser window or tab, a new UI instance is created. So you see an attach message of a new UI instance. The old UI instance will be detached later.
This is default behavior in Vaadin 7. You may change that behavior with an annotation.
#PreserveOnRefresh
Adding #PreserveOnRefresh annotation to the UI changes the behavior: No new UI instance won't be created on refresh.
To quote the doc for this annotation:
Marks a UI that should be retained when the user refreshed the browser window. By default, a new UI instance is created when refreshing, causing any UI state not captured in the URL or the URI fragment to get discarded. By adding this annotation to a UI class, the framework will instead reuse the current UI instance when a reload is detected.
Related
Context: In a Vaadin 23 application there is a form that is reachable directly via URL. It registers a BeforeLeaveListener with UI.getCurrent().addBeforeLeaveListener(bll);. The implementation of the BeforeLeaveListener is this:
#Override
public void beforeLeave(BeforeLeaveEvent event) {
ContinueNavigationAction action = event.postpone();
askAndProceedIfOk(() -> {action.proceed();});
}
It explicitly postpones the BeforeLeaveEvent and explicitly proceeds the event.
In the application there's a second form that is opened in a modal dialog with the option to close it.
Dialog modalDialog = new Dialog();
modalDialog.setCloseOnEsc(true);
modalDialog.setCloseOnOutsideClick(true);
modalDialog.setModal(true);
The BeforeLeaveListener doesn't work here, but a ComponentEventListener<Dialog.DialogCloseActionEvent> does:
modalDialog.addDialogCloseActionListener(new ComponentEventListener<Dialog.DialogCloseActionEvent>() {
#Override
public void onComponentEvent(Dialog.DialogCloseActionEvent event) {
myForm.askAndProceedIfOk(() -> {modalDialog.close();});
}
});
Surprise: I am surprised that BeforeLeaveEvent and DialogCloseActionEvent work different:
BeforeLeaveEvent offers postpone() and proceed()
DialogCloseActionEvent seems to be implicitly postponed just by showing another modal Dialog that asks the user whether to save unsaved changes. And I do have to close the modal dialog explicitly.
Question: Is it right that both events (with a similar task) work that different or do I miss some details here?
My question stems from an issue almost identical to the one here (which did not end up getting a satisfactory answer):
https://vaadin.com/forum/thread/13932610
Like this person, I expected that upon closing the browser that my app was open in, a detach event would proc; however, this did not happen. I've tried adding a detach listener, overriding the detach method, and doing both at the same time, but none of them were successful. As for how I know the detach event was not called, my detach event is a simple print statement - that does not show up in the output.
Note that like in the aforementioned thread, I've already set the heartbeat interval (2 seconds in my case) and set closeIdelSessions to be true. So, I thought I would just have to wait six seconds, but that's certainly not been the case.
When I try this (find the essential parts of the code below), the detach() is eventually being called. I run this with Jetty, and I did not touch its default. It tooks some ~45 minutes after closing the Browser, when I saw "Detach called" logged on console. So yes, the time is lengthy. The reason is that the last UI is cleaned up only after HttpSession is expired (which depends on application container etc. settings). If you want to do forced clean up quicker, you need to use https://vaadin.com/directory/component/cleanupservlet-add-on
#Push
#SuppressWarnings("serial")
public class DemoUI extends UI {
#WebServlet(value = "/*", asyncSupported = true)
#VaadinServletConfiguration(productionMode = false, ui = DemoUI.class, heartbeatInterval=5, closeIdleSessions=true)
public static class Servlet extends VaadinServlet {
}
#Override
public void detach() {
System.out.println("Detach called");
}
#Override
protected void init(VaadinRequest vaadinRequest) {
...
}
I have a JavaFX WebView that is being updated with background messages and executes scripts to update the page.
webEngine.getLoadWorker().stateProperty().addListener(
new ChangeListener<State>() {
public void changed(ObservableValue ov, State oldState, State newState) {
if (newState == State.SUCCEEDED) {
try {
webEngine.executeScript("foo1();");
} catch (Exception e) {
// JSException: TypeError: 'undefined' is not a function
}
}
}
});
This normally runs correctly, however when I create a second WebView with it's own WebEngine and try and execute foo2() on that second page, at about the same time as execution foo1 on the first page, I get this:
JSException: TypeError: 'undefined' is not a function
I could have a synchronization block, but it seems it should be unnecessary since the webEngines are suppose to be independent, are there other solutions?
This is a session management error in your program.
2 WebViews compete for the same session, and ... assuming you haven't implemented protocol and session handlers, including cookie storage, etc..., the second webengine which is unable to obtain the connection will run the script over something that doesn't exist ( as far as the webengine is concerned ) hence an UNDEFINED object.. thus the error.
I'm using this stuff over HTTPS protocols, and have experienced this.
In Vaadin, when you scroll down or up in tables (com.vaadin.ui.Table) there is no event that will be fired to tell you that user is now scrolling.
Why would we need scroll event in table?
Let's first take a look at this example of Vaadin (Dashboard Demo) after you open the link just click sign in, and you'll be automatically redirected to the transactions page, this page shows a table of financial transactions, these transactions will show a batch after another while you are scrolling, what actually happened is that the data was loaded on UI initiation (all the transactions).
Now, let's assume that we have thousands or say millions of transactions. Is it logic to load them all together when the UI is initiated? isn't it of wisdom to load them bit by bit to prevent slowing down the UI while it waits for all transactions to load?.
The best solution to this problem is to get the first (say 100 transactions) then get more transactions while scrolling down, but this solution have only one problem, that is Vaadin does not support Scroll Event Handling in com.vaadin.ui.Table !!
According to your question you are looking for lazy loading.
If you connect your table with a container which supports lazy loading your items are loaded from it's data source lazily. This means when you scroll and get out of the buffered items the table "asks" the container for more items. The container then loads more items from it's data source.
For example the JPAContainer from Vaadin supports that feature. This container connects to a data source using JPA. More information here.
Vaadin Table's Scrolling Event
The Dashboard Demo (GitHub repo) project actually depends on Vaadin tables as you see in the code in this file in line 53, what we are going to do is to extend com.vaadin.ui.Table and implement our own behavior that will support scrolling Vaadin tables from now on.
First of all, let's create new simple interface ScrollingTableScrollListener this interface will be responsible for implementing scroll events and its code will look like this:
package com.vaadin.demo.dashboard.scrolling;
public interface ScrollingTableScrollListener {
public void doTableScroll();
}
This interface should be implemented whenever you have a table in your view and you want to add a scroll event handler for it. But wait a minute this is not applicable to any kind of tables, this is only applicable to our own table.
Now, let's create our table, our table's name is (ScrollingTable) and it extends (com.vaadin.ui.Table) this class code is:
package com.vaadin.demo.dashboard.scrolling;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.vaadin.ui.Table;
public class ScrollingTable extends Table {
private static final long serialVersionUID = 5007124121625961567L;
List listeners = new ArrayList();
private void fireSrollEvent() {
for (ScrollingTableScrollListener listener : listeners) {
listener.doTableScroll();
}
}
public void addScrollListener(ScrollingTableScrollListener listener) {
listeners.add(listener);
}
#Override
public void changeVariables(Object source, Map variables) {
super.changeVariables(source, variables);
fireSrollEvent();
}
}
Actually, this table is the same as Vaadin's one, but it has the following differences:
It overrides the method changeVariables() of the super class (Vaadin Table : com.vaadin.ui.Table), actually here lays our core business that will initiate the Scrolling Behavior. What we did here is that we invoked the changeVariables() of the super class and then we invoked the fireSrollEvent() method.
Another difference is that it has two more members:
public void addScrollListener(ScrollingTableScrollListener listener) this method will take care of adding new listeners to table's scrolling event.
private void fireSrollEvent() this method is the one that will be invoked by changeVariables() method and will invoke the method doTableScroll() on every registered listener that this table has added by invoking the method addScrollListener().
Now to make use of the new stuff that we have added, we will change the original code of the aforementioned Dashboard demo (specifically the file TransactionsView.java). In this file there are only few lines to add and modify.
First, we will modify the line 49 by adding new interface that this class will implement, which is our new interface (ScrollingTableScrollListener) and implement its single method by adding the following lines in the end of this class:
#Override
public void doTableScroll() {
// TODO Auto-generated method stub
Notification.show("You are scrolling!\nYou can add your own behavior here!");
}
Then, we will change both lines 53 and 66 to use the new inherited class (ScrollingTable) rather than the super class (Table):
//Line 53 in the original class
Table t;
//Line 55 in our class
ScrollingTable t;
....
....
....
//line 66 in the original class
t = new Table() {
//line 68 in our class
t = new ScrollingTable() {
Finally, we should add the listener to our ScrollingTable's scrolls :) , this is done by invoking the method addScrollListener on the table (t) after defining the table (line 89 of our new class):
t.addScrollListener(this);
this line means that this class (TransactionView) is listening on the ScrollingTable (t) scroll event, and it will invoke the method doTableScroll whenever the user scrolls down/up the ScrollingTable (t).
Here you go, you have now a table that will tell you whenever the user is scrolling, your task now is to do what you want when the table fires scrolling event and put your stuff between the curly brackets {} of the method that we defined in the first step:
#Override
public void doTableScroll() {
// Put your own code here ...
}
Here is the link of the new Dashboard on GitHub.
The answer on my blog
Hm, event fires on EVERY action with table (such as row select, etc.). More appropricate solution is additional checking variable changing in table state.
Here example:
public class ScrollingTable extends Table {
// ...
#Override
public void changeVariables(Object source, Map<String, Object> variables) {
super.changeVariables(source, variables);
handleScrollEvent(variables);
}
private void handleScrollEvent(Map<String, Object> variables) {
if (variables.containsKey("firstvisible")) {
for (TableScrollListener listener : listeners) {
listener.doTableScroll();
}
}
}
We are currently facing one problem in our portlet-environment using JSF2.
The application is creating dynamic portal-pages for the actual user session...think of it as Eclipse editor-views where the user can edit entities. So for now I call the dynamic-views editors :-)
The problem we are facing now, is following. The user navigates to a editor and works on the portlet(s). The displayed views in each portlet change over time of course. Now he wants to have a look on another entity which is displayed in another editor. But when he navigates back to the first editor, the state of portlets changes back to the default views.
In the portlet-world each portlet each portlet gets the view it should display via a parameter which is stored in the PortletSession and I can easily change that parameter as well. I know that this parameter is causing the trouble because when the changes the editors, the portlets will always check this parameter to decide which view to show.
request.getPortletSession().setAttribute("com.ibm.faces.portlet.page.view", "/MyPage.xhtml");
My idea was, to somehow add a callback to each JSF-navigation which will set this parameter to the view the navigation is going to display (possibly including view-params).
Is it possible to have a standard callback? If not, would be possible to execute some kind of EL in the navigation-rule that will set this parameter?
somehow add a callback to each JSF-navigation
You could perform the job in a custom ConfigurableNavigationHandler. Here's a kickoff example:
public class MyNavigationHandler extends ConfigurableNavigationHandler {
private NavigationHandler parent;
public MyNavigationHandler(NavigationHandler parent) {
this.parent = parent;
}
#Override
public void handleNavigation(FacesContext context, String from, String outcome) {
// TODO: Do your job here.
// Keep the following line untouched. This will perform the actual navigation.
parent.handleNavigation(context, from, outcome);
}
#Override
public NavigationCase getNavigationCase(FacesContext context, String fromAction, String outcome) {
return (parent instanceof ConfigurableNavigationHandler)
? ((ConfigurableNavigationHandler) parent).getNavigationCase(context, fromAction, outcome)
: null;
}
#Override
public Map<String, Set<NavigationCase>> getNavigationCases() {
return (parent instanceof ConfigurableNavigationHandler)
? ((ConfigurableNavigationHandler) parent).getNavigationCases()
: null;
}
}
To get it to run, register it as follows in faces-config.xml:
<application>
<navigation-handler>com.example.MyNavigationHandler</navigation-handler>
</application>