How to get a reference to the main window from anywhere?

Hello guys,

I use the ThreadLocal pattern (with the TransactionListener) to get the Application instance corresponding to the request, from anywhere and it works fine.

// in MyApplication
	static protected ThreadLocal<MyApplication> currentApplication = new ThreadLocal<MyApplication>();

	@Override
	public void transactionStart(Application application, Object transactionData) {
		if (getCurrent() == null) {
			currentApplication.set(this);
		}
	}

	@Override
	public void transactionEnd(Application application, Object transactionData) {
		currentApplication.remove();
		currentMainWindow.remove();
	}

My application has a main window (of type MainWindow) that I set in the Application.

// In MyApplication.init()
this.setMainWindow(  my new MainWindow )

In my overridden MyApplication.getWindow(name), I do create other instances of MainWindow, one for each browser’s tab that the user opens.

Its means that I cannot use:

Window win = MyApplication.getCurrent().getMainWindow();

because it does not take the browser’s tab into account, it always returns the window of the first tab.

I need the MainWindow instance of the tab from which the request has been sent, of course.

To do that, I applied the ThreadLocal pattern to MainWindow.

// In Myapplication
	static protected ThreadLocal<MainWindow> currentMainWindow = new ThreadLocal<MainWindow>();

	public static MainWindow getCurrentMainWindow() {
		return currentMainWindow.get();
	}

	@Override
	public void transactionEnd(Application application, Object transactionData) {
		currentApplication.remove();
		currentMainWindow.remove();
	}

As far as I know, I cannot initialize it in the TransactionListener.transactionStart() method, because at that level, it does not tell which window/tab is concerned.

I initialize the threadLocal in MyApplication.getName(name), because there, I’ve a reference to the MainWindow corresponding to the current request.

And it works fine, except …

I’ve a login sub-window (as many applications) containing com.vaadin.ui.LoginForm (as I guess it’s the best practice from Vaadin). That form is very special. When the submit button is pressed, a “unusual” request is sent. Application.getWindow(name) is called with a strange name (“loginHandler”), but not with the main’s window name => I cannot initialize the MainWindow’s thread local, I don’t know the value.
Then com.vaadin.ui.LoginForm.LoginListener.onLogin() method is called.
Inside that method, I obviously check if the user exist and if the password is correct.
Still inside that method, I decide that the login corner (top right part of the page as most web applications) should be updated with the user’s name.

Aaaaargh! I need a reference to the correct MainWindow for doing that.
In fact, it’s ok, because I better update the top right corner of every tab => I user Application.getWindows() and update every window’s login corner.

But then I need to refresh the current window (probably not the same data is shown for a logged in user). How to get a reference to the MainWindow in that case?
I’d like to call my home made method WindowUitl.refreshCurrentWindow(). That method would retreive the main window instance “out of the blue”.

For the moment, I see no other idea than cluttering my code with references of (main)Window that I pass from methods to methods; from classes to inner classes.


Questions

  1. Is there a better way (than the ThreadLocal) to know “out of the blue” the main window corresponding to the current request?
  2. If not, is there a better way to initialize the threadLocal than MyApplication.getWindow()?

I know it’s difficult, I’m sorry; it comes from common requirement.
Thank you in advance.
John.

I am sorry for the confusion. Here is a short summary on Vaadin windowing:

  • All windows are equal - they are just instances of Window class
  • Each window has a name (string)
  • Each window can either be added to Application (lets call them “browser windows”) or to a browser window (lets call them “subwindows”)
  • As windows can not be added to subwindows - there is only two level of windows “browser windows” and “subwindows”
  • One (and only one) of the browser windows can be set to be the main window Application.setMainWindow()
  • Both Application has a URL (application.getUrl()) and each window has a URL (window.getURL()). If application URL is http://localhost:8080/myapp/ and window name is “foo”, then window URL is http://localhost:8080/myapp/foo/
  • While as all browser windows can be accessed from window url, the main window can ALSO be accessed from application url

My guess on what you are looking after is a way to get reference to the window where the changes occur. This should actually be fairly easy - in any component you can just do getWindow() to get the reference to the correct window.

In fact you can also use getApplication() in any component to get reference to your application.

The problem is that when your component does not yet belong to any window or any application, the above mentioned methods return null. This is typically faced only when calling these from constructor. Two typical solutions include:

  • passing application or window as a constructor parameter
  • moving the initialization code from constructor to attach(). This can also make your application to start faster and consume less memory as the initialization is done in lazy manner

LoginForm is special. Actually its only purpose is to allow browsers to save password. If this is not a key requirement in your application there is no need to use LoginForm really. You could for example use something like
http://dev.vaadin.com/svn/incubator/paymate-security-demo/src/com/paymate/vaadin/LoginDialog.java

Also one possibility is to do login form outside vaadin. Create a JSP -page that logs in and redirects to Vaadin application when done.

And just to follow up - this confusion originates from API I wrote in 2002 - when the framework did not support any “subwindows”, ajax or anything like that. I hope we will be able to (deprecate and) replace that API with less confusing one in next major version of the framework:
http://dev.vaadin.com/ticket/3067

Thank you for your replies, Joonas.

I was hoping to get another solution than passing Window references from the attach method, but it’s for one (login) window anyway, it does not hurt.

Thank you for this clear and useful reap.
Another main element that helped the confusion is the header and footer. I guess that most web applications have a header and a footer. That’s why frameworks like sitemesh have been invented.
Because of the header and footer, I’ve only one application/browser level window class (that I call MainWindow). That window class defines the header and footer. When the URI changes, I only change the central component between the header and footer. You suggested me to do it that way in another thread. So, I do not use the window names in the URL (because it’s always the same class name). And because I don’t want an URL to look different for the same page just because it’s in another browser tab, I’m not going to give different URL names to different application level window instances. So, I manage the “pages names” myself in the URI fragment.

http://localhost:8080/ApplicationName/#pageName/pageParameters

It’s probably no coincidence if the Sampler application works like that, isn’t it?
It looks like a pattern to me. Probably some reusable code/API on top of the existing Vaadin API will implement it once for everybody, one day?


class MyApplication extends PatternApplication<MyMainWindow> {
   ...
}

@CentralFragment(url="foo")    // if we want "foo" to override the default. http://localhost:8080/AppName/#foo
class MyCentralWidget23 extends VerticalLayout {
   ....
}
  • a mechanisme to manage the parameters behind “foo/”. Another pattern: these parameters are probably entity PKs. Give-me an entity/parameter reader ouf of the box:

   JpaParameterReader<BankAccount> paramReader = new JpaParameterReader<BankAccount>();
   BankAccount ba = paramReader.getMandatoryParameter():  // looks for "#foo/bankaccountid=123",

On a philosophical point of view, I would also introduce the notion of thread local scoped variables in the framework, as Spring or JPA ended by doing. Probably with some annotations too. But it’s probably another discussion :wink:


@Configurable(preConstruction=true)    // AspectJ / Spring.
class MyWidget extends VerticalLayout {

    @ApplicationWindow   // Auto injection of a thread local scoped instance (like a JPA EntityManager)
    MyApplicationWindow myApplicationWindow;

    @Application    // idem.
    MyApplication myApplication;

    @Autowired     // this is a spring one.
    MyBankService myBankService;
    ....

}

I’ll stop here, else I’ll start writing a course or a framework on Vaadin. It’s too early for that, I need to learn more.