How to make runtime changes to a Vaadin component after loading - vaadin

How can I change a Vaadin h1 element dynamically using a timer after the page is navigated to from another Vaadin page?
navigate
start.addClickListener(e->{
ActorCallsHandler.ach.startGame();
UI.getCurrent().navigate(Playboard.class);
});
class
public class Playboard extends VerticalLayout
{
private H1 timer;
public Playboard() throws ExecutionException, InterruptedException{
generateGUI();
}
private void generateGUI(){
//h1 element that should alter using a timer after page load
H1 timer = new H1();
}
}

For this to work, you need to use either the Push or Polling functionality of Vaadin so that changes can be sent to the client at any time instead of only in the response to a request that is triggered by some user action.
I would recommend that you check out the documentation for the Push feature at https://vaadin.com/docs/v13/flow/advanced/tutorial-push-configuration.html.

Related

Vaadin 14.2: Intercept logout (invalidate session) with BeforeLeaveListener

in my Vaadin 14.2.0 application there is a BeforeLeaveListener to show a confirmation dialog when the input is dirty (= there are unsaved changes in the input fields) to cancel (or intentionally proceed) the navigation:
BeforeLeaveListener listener = new BeforeLeaveListener() {
#Override
public void beforeLeave(BeforeLeaveEvent event) {
if (dirtyFlag.isDirty()) {
ContinueNavigationAction postponeAction = event.postpone();
// show confirmation dialog and maybe execute a proceed
[...] () -> postponeAction.proceed();
}
}
};
UI.getCurrent().addBeforeLeaveListener(listener);
This works fine for everything but for the logout.
This is how my logout button looked like in the start (what works as long as I do not want to postpone/cancel the logout):
Anchor link = new Anchor();
link.getElement().addEventListener("click", e -> {
VaadinSession.getCurrent().getSession().invalidate();
UI.getCurrent().navigate(LoginView.class);
});
Now I want to postpone the logout until the user confirms that the unsaved changes should get discarded. (Switching both lines in the EventListener seems to be a good idea, but did not work because of the following.)
Within the navigate-call the BeforeLeaveListener is called (good), but the logout is done nevertheless, because the code after the navigate()-call is executed (of course, because it is no blocking call), the session is invalidated and the user is logged out though the confirmation dialog just popped out (but the user had no chance to confirm/deny).
I tried to move the session invalidation into the LoginView (into a BeforeEnterObserver), but the result is that the login view is reloaded in an endless loop.
Question: is there something like "navigate to LoginView and if navigation is not postponed and not cancelled, then invalidate session"?
A workaround is to navigate to an intercepting LogoutView that just invalidates the session and redirects/forwards to the LoginView:
#Route(value = "logout")
public class LogoutView implements BeforeEnterObserver {
#Override
public void beforeEnter(BeforeEnterEvent event) {
VaadinSession.getCurrent().getSession().invalidate();
event.forwardTo(LoginView.class);
}
}
But this seems to me to be just a bad workaround with some overhead (creating a view just to forward to another view)...
I know, you didnt mention Spring. But that's actually what I believe Spring Security is doing under the hood.
My Logout Button (using Spring Security) is looking like this:
new Button("Logout", click -> {
UI.getCurrent().getPage().executeJs("location.assign('logout')");
});
Once you click it, you are logged out and redirected to the login view (more details from Baeldung). I think it will be just fine if you do your LogoutView redirection detour.
The fact that you can even use the Navigator to go to your LogoutView is even better here, since this is caught by BeforeLeaveObserver (in contrast to completely new requests by assigning a new location or refreshing the page. See BeforeLeaveObserver vs. beforeunload for more details on that)
I would still like to propose another idea for your usecase. I think it will be much simpler, but requires that the logout link knows of the dirtyFlag:
Anchor link = new Anchor();
link.getElement().addEventListener("click", e -> {
if(dirtyFlag.isDirty()){
// show Dialog with
// - a confirm btn that calls doLogout() on click
// - a cancel btn that closes the Dialog
} else {
doLogout();
}
});
...
private void doLogout(){
VaadinSession.getCurrent().getSession().invalidate();
UI.getCurrent().navigate(LoginView.class);
}

Register session to User

I'm building a prototype using Vaadin8 starting from a single-module template.
I'm trying to assign a unique UI instance (a session) to each authenticated user, so that each user is presented with a particular type of content according to their own settings within the app. Here's my configuration:
#WebListener
public class Market implements ServletContextListener {
public static ArrayList<User>users;
public void contextInitialized(ServletContextEvent sce) {
users=new ArrayList<User>();
User hau=new User("hau");
users.add(hau);
User moc=new User("moc");
users.add(moc);
}
public void contextDestroyed(ServletContextEvent sce){}
}
public class User {
public String username;
public user(String username){this.username=username;}
}
public class MyUI extends UI {
User us3r;
#Override
protected void init(VaadinRequest vaadinRequest) {
final VerticalLayout layout = new VerticalLayout();
String username;
if (this.us3r==null) {username="Guest";}else {username=us3r.username;}
Label who=new Label(username);
TextField userfield=new TextField();
Button login=new Button("login");
login.addClickListener(new ClickListener() {
#Override
public void buttonClick(ClickEvent event) {
for (User user:Market.users) {
if (userfield.getValue().equals(user.username)) {
us3r=user;Page.getCurrent().reload();return;
}
}Notification.show("No user "+userfield.getValue());
}
});
Button logout=new Button("logout");
logout.addClickListener(new ClickListener() {
public void buttonClick(ClickEvent event) {
if(us3r!=null) {us3r=null; Page.getCurrent().reload();}
}
});
layout.addComponent(userfield);
layout.addComponent(login);
layout.addComponent(who);
layout.addComponent(logout);
setContent(layout);
}
After inputting one of the two usernames registered in the Database, I'd like the Label object to display the name of the authenticated user, instead of "Guest". Another effect I'm trying to achieve is if a user is logged in and there is another request to the server, it should generate a fresh UI with the uninstantiated us3r attribute.
Caveats: I have been using Vaadin Flow lately rather than Vaadin 8. So my memory is hazy, and my code may be wrong. And I have kept all the examples overly simple, not ready for production. Lastly, I am sure others would take a different approach, so you may want to do some internet searching to see alternatives.
UI is malleable
The UI of Vaadin is more plastic and malleable than you may realize. You can entirely replace the initial VerticalLayout with some other widget-containing-view.
The way I have handled logins with Vaadin is that my default UI subclass checks for an object of my own User class in the web session. Being based on Jakarta Servlet technology, every Vaadin web app automatically benefits from the Servlet-based session handling provided by the Servlet container. Furthermore, Vaadin wraps those as a VaadinSession.
If the User object is found to be existing as an "attribute" (key-value pair) in the session, then I know the user has already logged-in successfully. So I display the main content in that initial UI subclass object. By "main content", I mean an instance of a particular class I wrote that extends VertialLayout, or HoriontalLayout or some such.
If no User object is found, then my initial UI subclass object displays a login view. By "login view" I mean an instance of some other particular class I wrote that extends VertialLayout, or HoriontalLayout or some such.
When you switch or morph the content within a UI subclass instance, Vaadin takes care of all the updating of the client. The change in state of your UI object on the server made by your Java code is automatically communicated to the Vaadin JavaScript library that was initially installed in the web browser. That Vaadin JS
library automatically renders your changed user-interface by generating the needed HTML, CSS, JavaScript, and so on. There is no need for you to be reloading the page as you seem to be doing in your example code. As a single-page web app, the web page only loads once. In Vaadin, we largely forget about the HTTP Request/Response cycle.
Example app
First we need a simple User class for demonstration purposes.
package work.basil.example;
import java.time.Instant;
import java.util.Objects;
public class User
{
private String name;
private Instant whenAuthenticated;
public User ( String name )
{
Objects.requireNonNull( name );
if ( name.isEmpty() || name.isBlank() ) { throw new IllegalArgumentException( "The user name is empty or blank. Message # b2ec1529-47aa-47c1-9702-c2b2689753cd." ); }
this.name = name;
this.whenAuthenticated = Instant.now();
}
#Override
public boolean equals ( Object o )
{
if ( this == o ) return true;
if ( o == null || getClass() != o.getClass() ) return false;
User user = ( User ) o;
return name.equals( user.name );
}
#Override
public int hashCode ( )
{
return Objects.hash( name );
}
}
The starting point of our app, our subclass of UI checks the session and switches content. Notice how we segregated the check-and-switch code to a named method, ShowLoginOrContent. This allows us to invoke that code again after login, and again after logout.
package work.basil.example;
import com.vaadin.annotations.Theme;
import com.vaadin.annotations.VaadinServletConfiguration;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinServlet;
import com.vaadin.server.VaadinSession;
import com.vaadin.ui.UI;
import javax.servlet.annotation.WebServlet;
import java.util.Objects;
/**
* This UI is the application entry point. A UI may either represent a browser window
* (or tab) or some part of an HTML page where a Vaadin application is embedded.
* <p>
* The UI is initialized using {#link #init(VaadinRequest)}. This method is intended to be
* overridden to add component to the user interface and initialize non-component functionality.
*/
#Theme ( "mytheme" )
public class MyUI extends UI
{
#Override
protected void init ( VaadinRequest vaadinRequest )
{
this.showLoginOrContent();
}
void showLoginOrContent ( )
{
// Check for User object in session, indicating the user is currently logged-in.
User user = VaadinSession.getCurrent().getAttribute( User.class );
if ( Objects.isNull( user ) )
{
LoginView loginView = new LoginView();
this.setContent( loginView );
} else
{
CustomerListingView customerListingView = new CustomerListingView();
this.setContent( customerListingView );
}
}
#WebServlet ( urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true )
#VaadinServletConfiguration ( ui = MyUI.class, productionMode = false )
public static class MyUIServlet extends VaadinServlet
{
}
}
Here is that LoginView, a VerticalLayout. We have our username & password, with a "Sign in" button. Notice how on successful authentication we:
Instantiate a User and add to the automatically-created session as an "attribute" key-value pair. The key is the class User, and the value is the User instance. Alternatively, you can choose to use a String as the key.
Invoke that showLoginOrContent method on MyUI to swap out our login view with a main content view.
In real work, I would locate the user-authentication mechanism to its own class unrelated to the user-interface. But here we ignore the process of authentication for this demonstration.
package work.basil.example;
import com.vaadin.server.VaadinSession;
import com.vaadin.ui.*;
public class LoginView extends VerticalLayout
{
private TextField userNameField;
private PasswordField passwordField;
private Button authenticateButton;
public LoginView ( )
{
// Widgets
this.userNameField = new TextField();
this.userNameField.setCaption( "User-account name:" );
this.passwordField = new PasswordField();
this.passwordField.setCaption( "Passphrase:" );
this.authenticateButton = new Button( "Sign in" );
this.authenticateButton.addClickListener( ( Button.ClickListener ) clickEvent -> {
// Verify user inputs, not null, not empty, not blank.
// Do the work to authenticate the user.
User user = new User( this.userNameField.getValue() );
VaadinSession.getCurrent().setAttribute( User.class , user );
( ( MyUI ) UI.getCurrent() ).showLoginOrContent(); // Switch out the content in our `UI` subclass instance.
}
);
// Arrange
this.addComponents( this.userNameField , this.passwordField , this.authenticateButton );
}
}
Lastly, we need our main content view. Here we use a "customer listing" that is not yet actually built. Instead, we place a couple pieces of text so you know the layout is appearing. Notice how in this code we look up the user's name from our User object in the session attribute.
We include a "Sign out" button to show how we reverse the authentication simply by clearing our User instance as the value of our "attribute" on the session. Alternatively, you could kill the entire session by calling VaadinSession::close. Which is appropriate depends on your specific app.
package work.basil.example;
import com.vaadin.server.VaadinSession;
import com.vaadin.ui.Button;
import com.vaadin.ui.Label;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import java.time.Duration;
import java.time.Instant;
public class CustomerListingView extends VerticalLayout
{
Button logoutButton;
public CustomerListingView ( )
{
// Widgets
this.logoutButton = new Button( "Sign out" );
this.logoutButton.addClickListener( ( Button.ClickListener ) clickEvent -> {
VaadinSession.getCurrent().setAttribute( User.class , null ); // Pass null to clear the value.
( ( MyUI ) UI.getCurrent() ).showLoginOrContent();
}
);
User user = VaadinSession.getCurrent().getAttribute( User.class );
Duration duration = Duration.between( user.getWhenAuthenticated() , Instant.now() );
Label welcome = new Label( "Bonjour, " + user.getName() + ". You’ve been signed in for: " + duration.toString() + "." );
Label placeholder = new Label( "This view is under construction. A table of customers will appear here.\"" );
// Arrange
this.addComponents( this.logoutButton , welcome , placeholder );
}
}
The effect of the "Sign out" button is to remove the main content, and take the user back to the login view.
Separation of concerns
One of the aims of the approach to logins is separation of concerns. The concern of building an interactive user-interface (Vaadin widgets and code) should be kept largely separate from the business logic of how we determine if a user is who they claim to be (authentication code).
Our UI subclass knows almost nothing about user-authentication. We moved all the mechanics of logging-in to other non-Vaadin-specific classes. The Vaadin-related code only has two connection points to authentication: (a) Passing collected credentials (username, password, or such), and (b) Checking for the presence of a User object in the session’s key-value store.
Multi-window web apps
By the way, you should know that Vaadin 8 has amazing support for multi-window web apps. You can write links or buttons to open additional windows/tabs in the browser, all working within the same web app and the same user session. Each tab/window has its own instance of a UI subclass you wrote. All of these UI subclass instances share the same VaadinSession object.
So using the logic seen above applies to all such tab/windows: Multiple windows all belonging to one session with one login.
Fake dialog boxes are not secure
You might be tempted to put your login view inside a dialog box appearing over your main content. Do not do this. A web dialog box is “fake”, in that it is not a window created and operated by the operating-system. A web app dialog window is just some graphics to create the illusion of a second window. The pretend dialog and the underlying content are actually all one web page.
A hacker might gain access to the content on the page, and might be able to defeat your login dialog. This is mentioned in the Vaadin manual, on the page Sub-Windows.
In my example above, we have no such security problem. The sensitive main content arrives on the user’s web browser only after authentication completes.
Web app lifecycle hooks
By the way, your use of ServletContextListener is correct. That is the standard hook for the lifecycle of your web app launching. That listener is guaranteed to run before the first user’s request arrives, and again after the last user’s response is sent. This is an appropriate place to configure resources needed by your app in general, across various users.
However, in Vaadin, you have an alternative. Vaadin provides the VaadinServiceInitListener for you to implement. This may be more convenient than the standard approach, though you need to configure it by creating a file to make your implementation available via the Java Service Implementation Interface (SPI) facility. Your VaadinServiceInitListener as another place to setup resources for your entire web app. You can also register further listeners for the service (web app) shutting down, and for user-session starting or stopping.
Browser Reload button
One last tip: You may want to use the #PreserveOnRefresh annotation.
Vaadin Flow
In Vaadin Flow (versions 10+), I take the same approach to logins.
Well, basically the same. In Vaadin Flow, the purpose of the UI class was dramatically revamped. Actually, that class should have been renamed given how differently it behaves. We no longer routinely write a subclass of UI when starting a Vaadin app. A UI instance is no longer stable during the user's session. The Vaadin runtime will replace the UI object by another new instance (or re-initialize it), sometimes quite rapidly, for reasons I do not yet understand. So I do not see much practical use for UI for those of us writing Vaadin apps.
Now in Flow I start with an empty layout, instead of a UI subclass. Inside that layout I swap nested layouts. First the login view. After authentication, I swap the login view for the main content view. On logout, the opposite (or close the VaadinSession object).

InvokeScriptAsync not launching script

I have an UWP app and I'm trying to get a script invoked from a WebView. The NavigationCompleted event raises and my javascript is included (I think) in the page but the script isn't executed.
Here's my C# code
public sealed partial class MainPage : Page
{
readonly WebView _webView = new WebView();
public MainPage()
{
this.InitializeComponent();
Facebook.Navigate(new Uri("http://www.facebook.com/"));
_webView.NavigationCompleted += WebView_OnNavigationCompleted;
_webView.ScriptNotify += WebView_OnScriptNotify;
}
private async void WebView_OnNavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)
{
await _webView.InvokeScriptAsync("eval", new[]
{
"alert('HEY');" +
"window.external.notify('%%' + location.href);"
});
}
private void WebView_OnScriptNotify(object sender, NotifyEventArgs e)
{
var url = e.Value;
}
}
Also, I added http://www.facebook.com and https://www.facebook.com to my package manifest Content Uris list (include with all WinRT access).
I can't get the alert popping, same for the notify.
Thanks
As noted in the WebView docs, alert doesn't work in a WebView control.
For Script Notify to work you need to add the page to the app manifest as well:
To enable an external web page to fire the ScriptNotify event when calling window.external.notify, you must include the page's Uniform Resource Identifier (URI) in the ApplicationContentUriRules section of the app manifest.
And:
The URIs in this list must use HTTPS
So, try navigating to the https site instead.
Worst case, you can use a web allowed object in order to communicate back to your app as that can be injected in all cases.

Vaadin 7 and browser url update

My Vaadin 7 application doesn't react on browser url changing. For example I entering from keyboard a new url parameters and pressing Enter key and after that nothing is changing.
Application only reacts on F5 or page refresh button.
How to also make Vaadin 7 application to respond to Enter key after url update ?
UPDATED
I'm using Vaadin com.vaadin.navigator.Navigator object.
For example I have an url: http://example.com/#!products/30970
When I change the url in browser address bar (for example to http://example.com/#!products/34894) and press enter key I would like to change information at my page in order to show info about product with id 34894 instead of product with a previous id 30970.
Vaadin Navigator and UriFragmentChangedListener
Right now I'm using Vaadin Navigator in order to define views:
Navigator navigator = new Navigator(this, viewDisplay);
navigator.addView("products", ProductView.class);
First time in web browser I'm successfully able to access this view with product id parameter for example by the following url:
http://example.com/#!products/30970
ProductView is constructed first time and in its public void enter(ViewChangeListener.ViewChangeEvent event) method I'm able to get uri parameters.
But after that when I change product id in web browser address bar in this url to another one(for example to 30971 in order to display information for another product):
http://example.com/#!products/30971
and press Enter key the view is not refreshed and doesn't react on these changes..
As suggested in the comments I have added UriFragmentChangedListener listener and now at least able to handle URL fragment changes(after Enter key presing).
Now, my logic have to react on these changes and I'm looking for a correct way how it should be implemented in Vaadin 7.
So, If I understood correctly - in additional to Navigator logic I also have to use this listener and inside of this listener logic I have to get a reference on appropriate view(navigator.getCurrentView() ?) object and invoke some method on this object in order to change internal view state without full view rebuild ? If I'm correct - is there some standard mechanism in Vaadin in order to simplify this job ?
i can not think of another way than pass the UriFragmentChangeEvent manually to your View. I guess the Vaadin API can not do it automatic.
public class MyUI extends UI{
#Override
protected void init(final VaadinRequest request) {
/*
* UriFragmentChangedListener
* when URL+Parameter manuell eingegeben werden
*/
getPage().addUriFragmentChangedListener(new UriFragmentChangedListener() {
#Override
public void uriFragmentChanged(UriFragmentChangedEvent event) {
View currentView = getNavigator().getCurrentView();
if(currentView != null){
if(currentView instanceof UriFragmentChangedListener){
((UriFragmentChangedListener)currentView).uriFragmentChanged(event); //custom method
}
}
}
});
}
}
To make this work add UriFragmentChangedListener to your ProductView:
public class ProductView extends CustomComponent implements View, UriFragmentChangedListener {
}
take a look at the Parameters in the ViewChangeEvent of your ProductView
By using this url structure "http://example.com/#!products/30970", you can read the product id as following:
#Override
public void enter(ViewChangeEvent event) {
String productId = event.getParameters();
}

initially load a page with the BrowserField and then have links clicked in that open up in the BB browser instead of the BrowserField?

I want to initially load a page (stored html page) with the BrowserField and then have links clicked in that open up in the BB browser instead of the BrowserField?
My current code is as following,
BrowserFieldConfig.setProperty(BrowserFieldConfig.CONTROLLER, new BrowserFieldController()
{
public InputConnection handleResourceRequest(BrowserFieldRequest request) throws Exception {
return (InputConnection)Connector.open(request.getURL());
}
public void handleNavigationRequest(BrowserFieldRequest request) throws Exception
{
BrowserSession b = Browser.getDefaultSession();
b.displayPage(request.getURL());
}
});
And I want to load the html page stored in resources in browserfield and then open the links from the page in BB Browser which I'm doing using
browserfield.requestContent("local:///test.html");
But application tries to open the html file in browser, which is not desirable.
Please suggest me a workaround,
Thanks,
Aniket
This should be quite easy to achieve.
Firstly you will need to use the BrowserField object instead.
Extend the browser field's javascript engine, by using BrowserField.extendScriptEngine(String name, Scriptable scriptable)
Within the Scriptable you will open the native browser.
In the html, make the buttons execute the extended javascript function you created.
The handleNavigationRequest(BrowserFieldRequest request) method is called each time the browserfield requests content.
Add a count inside the method.
Increment the count by 1 each time the method is called.
If the count is greater then 0, it means the Browserfield has already loaded the first time. Subsequent calls to the method should then open a browser session instead of requesting content inside the Browserfield.
public void handleNavigationRequest(BrowserFieldRequest request) throws Exception
{
if(click<1){
// request for content inside Browserfield
}
else {
BrowserSession b = Browser.getDefaultSession();
b.displayPage(request.getURL());
}
click++;
}

Resources