CODI Client Side WindowHandler Pattern

Historic Considerations

Until the end of the 1990s web browsers are usually single threaded and only had one window. But in the last years browsers supporting multiple windows or even tab became the standard. Since those days lots of efforts went into uniquely identifying a single browser window on the server side. Sadly browser windows still lack of a native windowId, thus maintaining web application data in @SessionScoped backing beans is still used in most of the cases.

How JSF-2 changed the world

The MyFaces Orchestra community did a good summary about the various ways to handle multiple window support in JSF Applications. Those findings are still valid and up to date, but the environmental conditions have changed slightly since then.

It is easy to pass a windowId around with a POST request, but it gets tricky with GET requests. Due to the new JSF-2 ability to use bookmarkable URLs and deep links, a typical JSF-2 application contains much more GET links than we used to see in JSF-1, thus we have far more href links to cope with.

Standard windowId Handling

With a classical approach we would not be able to simply add a windowId parameter to such links because if the user would open the link in a new browser window or tab, we would carry the windowId - and thus the window scope - over to the new browser tab/window. The classic solution was to omit the windowId for all GET links, but by doing this we would now loose the window scope far too often with JSF-2!

Marios summary also contains a method to prevent this problem by storing a value directly in the browser window via JavaScript. Usually this is rendered and executed in the same page as the user form. See the "Post-render window detection" paragraph for a more detailed description.
The major downside of this solution is that we might already pollute 'foreign' beans (and destroy their information) while rendering the page, which means this is not feasible as general solution.

The new Client Side WindowHandler Pattern

Our new approach works around this issue by introducing a small static html page. In our case it gets served via a small lifecycle interceptor (see ClientSideWindowHandler.java) which only gets invoked for GET requests to JSF pages. This windowhandler html page checks via JavaScript if the window.name is set. If not, we are on a new browser tab / window. In this case a freshly generated windowId - which has previously set into the windowhandler.html by replacing '$$windowIdValue$$' - will be used and set into window.name. The request will be reloaded and the tabs windowId will be transferred to the server via a short living cookie. The ClientSideWindowHandler will unpack the cookie and sets the it's windowId for the current request.

The JSF part

In your JSF application, you don't need to change much apart from activating the ClientSideWindowHandler as <alternatives> in your beans.xml. The links remain unchanged.

The windowhandler html part in JavaScript

The windowhandler (for the provided default see 'static/windowhandler.html') is just a small html page which performs the following steps if JavaScript is enabled:

  1. check if the window.name is set in the onload event. If no window.name is present, then we are on a new page. In this case we set the window.name to the value which '$$windowIdValue$$' got replaced to.
  2. set the window.name which now contains the correct windowId into a cookie
  3. reload the destination page by simply setting window.location.reload();

The full page source is a bit more complicated but follows these fundamental principals

Configuration

The ClientSideWindowHandler is implemented as @Alternative. Thus it needs to get activated in a beans.xml:

beans.xml
<beans xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">

    <alternatives>
        <class>org.apache.myfaces.extensions.cdi.jsf.impl.scope.conversation.ClientSideWindowHandler</class>
    </alternatives>

</beans>

Hint for Weld

If that doesn't work in combination with Weld please check AlternativeConfiguration

Downsides

Having the windowhandler.html site rendered between requests sometimes leads to some 'flickering' if the destination page takes some time to load. The browser first renders our windowhandler and only after that the original page will get loaded.

This effect may be minimized by branding the windowhandler.html page and providing an own one with a bgcolor which matches your application.

For html-5 aware browsers we also got rid of this flickering by storing away a 'screenshot' of the first page in onclick() and immediately restore this 'screenshot' on the intermediate windowhandler.html page. Technically we do this by storing away the <body> and css information into the html5 localStorage and restore them on the intermediate page. We also introduced a WindowConfig which is able to parse a request and decide upon the UserAgent or any other information if a client will get an intermediate page or if he gets the result page directly.

  • No labels