The JSF Single Page Applications (SPA) with dynamic tabs

-Professional diary of one team leader-

Svetozar Radojčin
6 min readSep 8, 2020

The problem

The Single Page Application (SPA) is not an unknown topic in the JSF world. There are several resources on the Internet and in books, which suggest certain solutions, but they are mostly reduced to a dynamic change of the content of one page. In doing so, the previous content is replaced with a new one, depending on what the user has chosen. What, however, if new content needs to be displayed next to existing, so as to allow the user to switch from one content to another? It is not difficult to imagine scenarios in the real world: an official working at a public counter, initiated a long process of data entry through the application, which involves the passage of a complex wizard. At some point of time, while the wizard is not yet complete, a client appears that needs to be served through the same application.If there was no possibility of opening a new working area (ie, app module in order to serve client), in addition to the existing one, the only thing left would be: either for the client to wait until the clerk completes the entire wizard, or for the clerk to terminate the work he had been doing to serve the client.

The solution

What the clerk need, is actually something like this:

Each menu item (on the left side) opens the corresponding application module in a new working area, thus allows the user to work on multiple application modules simultaneously

Inspiration and Technology stack used

This is inspired by Oracle’s UIShell Functional Pattern, , I am implemented the same, just by using JavaServer Faces (with PrimeFaces component library), and CDI

Download and User guide

if you want to see how it works, or want to develop such applications, you can download everything you need FROM HERE. What you got, is a simple Eclipse project, which is an example of one such application. There is also ready-to-deploy JakartaEEDemo.war, you can deploy him to GlassFish, Tomcat, .. where you normally place your JSF applications. The browser URL for deployed application is: http://localhost:8080/JakartaEEDemo/dyntab_master_layout.xhtml (of course, change the port if need).

I did not single out 6 Java classes (they are in the demo application) in a separate .jar archive. These are the infrastructure classes that govern the entire mechanism so, it is a good idea to transfer them to a separate archive, and use that archive in your applications. As usual, just put him to the /WEB-INF/lib folder.

faces-config.xml settings

At time this writing (as well as at the time I was developing all this) the actual JSF implementation is 2.3, so I used a couple managed-beans, which need to be configured in faces-config.xml:

<application>
<action-listener>org.primefaces.application.DialogActionListener</action-listener>
<view-handler>org.primefaces.application.DialogViewHandler</view-handler>
<navigation-handler>dyntabs.TabNavigationHandler</navigation-handler>
</application>

<!-- DynTabTracker, manager, etc... -->
<managed-bean>
<managed-bean-name>dynTabTracker</managed-bean-name>
<managed-bean-class>dyntabs.DynTabTracker</managed-bean-class>
<managed-bean-scope>view</managed-bean-scope>
<managed-property>
<property-name>maxNumberOfTabs</property-name>
<property-class>java.lang.Integer</property-class>
<value>10</value>
</managed-property>
<managed-property>
<property-name>initialTabs</property-name>
<property-class>java.util.List</property-class>
<list-entries>
<value-class>dyntabs.DynTab</value-class>
<value>#{HomeDynTab}</value>
</list-entries>
</managed-property>
</managed-bean>

<managed-bean>
<managed-bean-name>dynTabManager</managed-bean-name>
<managed-bean-class>dyntabs.DynTabManager</managed-bean-class>
<managed-bean-scope>view</managed-bean-scope>
<managed-property >
<property-name>tabTracker</property-name>
<property-class>dyntabs.DynTabTracker</property-class>
<value>#{dynTabTracker}</value>
</managed-property>
<managed-property>
<property-name>initialTabs</property-name>
<property-class>java.util.List</property-class>
<list-entries>
<value-class>dyntabs.DynTab</value-class>
<value>#{HomeDynTab}</value>
</list-entries>
</managed-property>
</managed-bean>

Both of dynTabTracker and dynTabManager have initialTabs property, which is a list of application modules, which should be initially opened at the beginning of the application. Usually, it’s just a home page. As you can see, this value references another managed bean, #{HomeDynTab}, which should be also registered in the faces-config.xml :

<managed-bean>
<managed-bean-name>HomeDynTab</managed-bean-name>
<managed-bean-class>dyntabs.DynTab</managed-bean-class>
<managed-bean-scope>view</managed-bean-scope>
<managed-property>
<property-name>uniqueIdentifier</property-name>
<value>Home</value>
</managed-property>
<managed-property>
<property-name>title</property-name>
<value>Welcome to JSF SPA Dynamic tabs demo!</value>
</managed-property>
<managed-property>
<property-name>includePage</property-name>
<value>/WEB-INF/include/home/home.xhtml</value>
</managed-property>
<managed-property>
<property-name>closeable</property-name>
<value>false</value>
</managed-property>
</managed-bean>

The most important properties of this bean, is:

  • includePage — we will use this to tell what should be displayed in the tab, which will be opened from application menu. Because we are in the Single Page App, it is clear that the content can only be a JSF fragment, ie, part of full JSF page. This is the content of the tab. Usually, we place the JSF fragment in the /WEB-INF/ folder of the application, so value for that property will be something like that:
    /WEB-INF/include/home/home.xhtml
  • title — will be used for title of the tab
  • closeable — with this property, we configure whether the tab can be closed or not
  • uniqueIdentifier — a value (String, you can choose whatever you want) that uniquely identifies one application module

IMPORTANT: in the <managed-bean-name> value, keep DynTab as a sufix, the tool uses this assumption in order to find the managed bean, and launch new tab based on bean properties which we are just talking about.

And this is a way: for each menu item in the application, which needs to launch some application module in the new tab (working area), we need to register one managed bean, in the manner described above.
The managed bean class will be the same: dyntabs.DynTab, we will only adjust properties described above.

The Single Page and launching the application module via the menu item

As you can see, the only facelet page in demo application is a dyntab_master_layout.xhtml.
The page is based on /WEB-INF/templates/masterLayout.xhtml template, containing code which allows the application module to be displayed in dynamics tabs:

<h:form id="mainForm">
<p:growl id="msgs" showDetail="false" />
<p:tabView id="mainTabView" activeIndex="#{dynTabManager.activeTabIndex}"
widgetVar="mainTabView">
<p:ajax event="tabChange" listener="#{dynTabManager.onTabChange}"/>
<p:ajax event="tabClose" listener="#{dynTabManager.onTabClose}"/>
<c:forEach items="#{dynTabManager.tabMenuModel}" var="tab">
<p:tab id="#{tab.id}" title="#{tab.title}" closable="#{tab.closeable}" >
<f:subview id="_sub_#{tab.id}">
<ui:include src="#{tab.includePage}">
<ui:param name="cdiBean" value="#{tab.cdiBean}"/>
</ui:include>
</f:subview>
</p:tab>
</c:forEach>
</p:tabView>
</h:form>

You can use the same template, or adjust him to your needs, but this part of the code must be present. Notice that this code uses properties from managed beans we registered in the faces-config.xml, in order to display content in dynamic tabs.

When you want to launch some application module via menu item (for which it was previously registered managed bean in the faces-config.xml), all you have to do is set the menu’s (or buttons’s) action property according to the following rule: if the managed bean name (which must have a DynTab suffix), is, for example, DepartmentsDynTab, the action property should be: “uishell:Departments” :

<p:menuitem value="Departments" action="uishell:Departments"/>

Closing work spaces

There are two ways to close current (active) work space:
The “natural way”, by simply closing appropriate tab component:

The second way is via the code. Consider following button in the demo application:

The source code for

<p:commandButton icon="ui-icon-myCloseCurrent"
title="Close current work space"
actionListener="#{dynTabManager.removeCurrentTab}"
process="@this"/>

so just use the #{dynTabManager.removeCurrentTab} as actionListener

Closing all work spaces

Consider following button from the demo application:

The source code for that button is:

<p:commandButton icon="ui-icon-myCloseAll" 
title="Close all work spaces"
actionListener="#{dynTabManager.closeAllActiveTabs}"
process="@this"/>

so just use the #{dynTabManager.closeAllActiveTabs} as actionListener.

Conclusion

This is a simple, yet powerful tool if you want to give you web application’s user the real power — in this way, users can work on multiple application modules at the same time, without having to close a previously opened module.

Well, that would be it.
I’d like to hear what you think?

Best regards,..

--

--