Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for browser tab scope #13468

Open
mvysny opened this issue Apr 8, 2022 · 5 comments
Open

Add support for browser tab scope #13468

mvysny opened this issue Apr 8, 2022 · 5 comments

Comments

@mvysny
Copy link
Member

mvysny commented Apr 8, 2022

Describe your motivation

I'm building an app which performs bookings; the user can open the app in multiple tabs and launch a booking wizard in every tab. Each wizard instance may have a different booking holder configured, and this booking holder needs to be preserved even when the wizard is navigated away, or when the tab is reloaded via F5.

Describe the solution you'd like

I need to bind certain information (a bean or a string) to a Browser Tab "scope" (using the word "scope" loosely here since I'm not using Spring). The information needs to survive F5 and all sorts of navigation: via UI.navigate(), via the back/forward button and via the user typing a new URL manually into the browser bar. The bean doesn't need to survive session close.

Having an API from Vaadin similar to BrowserTab.getCurrent().setAttribute("foo", "bar") would be excellent. The values would survive all sorts of navigations and also F5 page reload. When the tab is closed, the values would be removed and garbage-collected, either immediately or at unspecified time in the future. When the session is closed, the values would be removed and garbage-collected, either immediately or at unspecified time in the future.

Alternatively, the UI object could be able to provide a tab ID which would persevere even if UI is destroyed and re-created; that would allow me to create a HashMap from tab ID to my object in VaadinSession. The UI.getId() is null and UI.getUiId() increases with new UI instances and therefore can not be used as tab identifiers.

Describe alternatives you've considered

  • I tried to tie the information via ComponentUtil.setData(UI.getCurrent()) but this doesn't survive F5.
  • I tried having a @PreserveOnRefresh-annotated parent layout which does survive F5 but it doesn't survive browser back/forward button nor user typing a new URL manually.
  • There's no tab identifier, so I can't create a HashMap from tab ID to my object in VaadinSession.

The abovementioned can be replicated with the following example app, just paste these classes into Skeleton Starter for Vaadin 14.8:

@PreserveOnRefresh
public class MyLayout extends VerticalLayout implements RouterLayout {
    public MyLayout() {
        System.out.println("CONSTRUCTOR CALLED");
    }
}

@Route(value = "", layout = MyLayout.class)
public class MainView extends VerticalLayout implements HasUrlParameter<String> {
    public MainView() {
        add(new Button("Navigate - doesn't re-create MyLayout", e -> UI.getCurrent().navigate(MainView.class, "foobar")));
    }

    @Override
    public void setParameter(BeforeEvent event, @OptionalParameter String parameter) {
        System.out.println("Called with parameter " + parameter);
        System.out.println(UI.getCurrent());
        System.out.println(VaadinSession.getCurrent());
    }
}

The test scenarios:

  1. Run the app, click the "Navigate - doesn't re-create..." Button and go back: both the UI and the MyLayout is re-created, so I can't tie my bean as a data to those things.
  2. Run the app, click the "Navigate - doesn't re-create..." Button and go manually to a different URL, say http://localhost:8080/baz . Both the UI and MyLaoyut are re-created.
@mvysny mvysny changed the title Add support for tab scope Add support for browser tab scope Apr 8, 2022
@Artur-
Copy link
Member

Artur- commented Apr 8, 2022

At least earlier Vaadin used to set window.name to a generated string so that the server could find old relevant instances. This should stay the same also on back-forward but maybe @PreserveOnRefresh does not use this. The name even indicates that it is only for refresh

@mvysny
Copy link
Member Author

mvysny commented Apr 8, 2022

Yeah, on client-side there's window.name with the value of something like "ROOT-2521314-0.6047083771634619" (the value differs for every tab, and the value is preserved on page reloads and all navigations), but how can I transfer that value to server-side before views/layouts are instantiated?

@expls
Copy link

expls commented Jul 4, 2022

Did u found a way to preserve tab data? I have a similar use case (with the difference of using Spring Boot) and can't find a solution. There has to be a default way in Vaadin 14+. Using window.name in combination with a map to manage tab data seems too complex and hacky.

@mvysny
Copy link
Member Author

mvysny commented Jul 4, 2022

I don't think there's any other way, judging from the fact that vaadin itself uses the window.name+hashmap solution to preserve extended client details

@archiecobbs
Copy link
Contributor

Is there a reason there can't be a method Page.getId() which returns on the server the same thing as window.name on the client?

Otherwise it appears impossible in Vaadin to distinguish browser tabs.

Related to that, it's impossible to know whether to preserve or clean up state when onDetach() is invoked in a scenario where you want to preserve state on a reload but also cleanup state on a "real" close.

Here's why: I've observed the following behavior when reloading tabs:

  • Without @PreservOnRefresh:
    • Component onDetach()
    • New view beforeEnter() (isRefreshEvent() = false)
    • Component onAttach() - with a new UI instance
  • With @PreservOnRefresh:
    • Component onDetach()
    • Same view beforeEnter() (isRefreshEvent() = true)
    • Component onAttach() - also with a new UI instance

Side note: You might expect in the @PreservOnRefresh case that the same UI would be used on re-attach, but that changed in Vaadin 10.

So you can see the onDetach() is invoked (first) the same way in both cases. For a proper preserve/cleanup lifecycle your state would need to live in the view (which it normally does) and for that the only reliable "end of lifecycle" event is onDetach() (you don't get beforeLeave() events when tabs are closed) but if you use that then you can't preserve state on reloads, at least not without some hacks like "wait a few seconds to see if you get another beforeEvent() before cleaning up your state", etc. Even trickier would be preserving state when someone closes and the re-opens the same tab later (must be enough later that the keep-alive has timed out) - a solution for both would be remembering "unattached" state in the session and then when views are loaded finding and "attaching" the state back to the view.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

No branches or pull requests

4 participants