Implementing your own JSF tabview component without Primefaces

At my current company we are finally transitioning away from our well-aged Primefaces 3.0 jsf library and have implemented our own (composite) components for all use cases where we used primefaces thus far. As I learned a few things while implementing the components, I thought I could share it with others as good tutorials for composite components are quite rare.

Our composite component which we will be developing here is a tabview component just like the one you see on this screenshot of primefaces showcase. The tabview needs to render 1-n different tabs which the user can switch through freely with a click to the tab header. It’s a standard widget found in every operating system and many widget frameworks.

We will develop it based on twitter bootstrap markup and you can use it without any additional client javascript, you don’t even need the bootstrap.js as the switching of tabs is entirely done on the server side (we are talkng about JSF here!). The main advantages of it over the primefaces tabview are that you have learned something about jsf and that you can freely change the html markup as you need it. You can use any CSS styling framework you like.

The component should work with every JSF 2.x version.

Download

You can download the complete source code in my tabview github repository. You have to copy the templates and classes to your project manually. Don’t forget to adjust the package name of the class and the component name inside the class. If you have any question or suggestion just leave a comment.

What does our tabview component do and how is it structured?

The markup of the tabview is based on twitter’s bootstrap tabview and consists of an unordered list element (ul) with nested li-Elements for the tab headers. The tab content needs to be placed inside an independent tab-content element which is holding a tab-pane element for every tab. So it should look something like this:

<!-- Nav tabs -->
  <ul class="nav nav-tabs" role="tablist">
    <li role="presentation" class="active"><a href="#home" aria-controls="home" role="tab" data-toggle="tab">Home</a></li>
    <li role="presentation"><a href="#profile" aria-controls="profile" role="tab" data-toggle="tab">Profile</a></li>
    <li role="presentation"><a href="#messages" aria-controls="messages" role="tab" data-toggle="tab">Messages</a></li>
    <li role="presentation"><a href="#settings" aria-controls="settings" role="tab" data-toggle="tab">Settings</a></li>
  </ul>

  <!-- Tab panes -->
  <div class="tab-content">
    <div role="tabpanel" class="tab-pane active" id="home">...</div>
    <div role="tabpanel" class="tab-pane" id="profile">...</div>
    <div role="tabpanel" class="tab-pane" id="messages">...</div>
    <div role="tabpanel" class="tab-pane" id="settings">...</div>
  </div>

The component should of course be able to work with any number of tab pages and as tab-content in bigger applications tends to be quite big, the component should only render one tab page at a time, so that the browser only loads the necessary HTML.

Because of this the tabview needs to know which tab page is currently active. If we would use our tabview component now, it would look something like this:

<myComponents:tabview activeTab="#{bean.activeTab}">
   <myComponents:tab id="tab1">Content of Tab1</myComponents:tab>
   <myComponents:tab id="tab2">Content of Tab2</myComponents:tab>
   <myComponents:tab id="tab3">Content of Tab3</myComponents:tab>
</myComponents:tabview>

As you can see, we already have 2 composite components: one for the tabview and one for the tabs. The tabview would be responsible for the rendering of the tab header elements (and should provide a container element where the currently active tab page can render its children.

If you already have used a composite component you can already guess that this is not easily possible using just a single xhtml file for the tabview component. You can’t access a dynamic list of child elements of a specific component, render just one of them and render HTML of your own too.

Maybe you could do something similar with two or three additional wrapper composite components and using the rendered attribute, but the managed bean of the user would still need to provide a (String) list of all tabs additional to defining them in xhtml because you need a list of all tabs for rendering the header. This would just complicate using the tabview component.

Instead we will be using backing components for our composite components. The backing components (which are just annotated java classes) will contain the necessary logic to switch active tabs and to create the list of tabs dynamically. They will also be responsible for rendering themselves and/or their children. Additionally, they will be responsible for other features too, like calling a user-defined onTabChange method.

The “tabview” composite component

The xhtml for the tabview now looks like this:

<composite:interface type="my.package.components.Tabview">
  <composite:attribute name="activeTab" type="java.lang.String" required="true" />
  <composite:attribute name="onTabChange" method-signature="void listener(java.lang.String)" />
</composite:interface>

<composite:implementation>
  <ul class="nav nav-tabs" role="tablist">
    <ui:repeat var="tab" value="#{cc.tabs}">
      <li role="presentation" class="#{cc.isTabActive(tab) ? 'active':''}">
        <h:commandLink value="#{tab.label}">
          <f:ajax listener="#{cc.makeTabActive(tab)" render="#{cc.clientId}" />
        </h:commandLink
      </li>
    </ui:repeat>
  </ul>
</composite:implementation>

The component is defining that its using a backing component of type “my.package.components.Tabview”. We come to the implementation of this class in just a bit.
Additionally the tabview is defining two attributes: the currently active tab (which is mandatory) and an onTabChange listener method which will be called when the user successfully has switched to another tab. The listener gets the new tab as a parameter. The implementation part of the xhtml is just rendering the list of tabs for the header. Its using a “cc.getTabs()” and a “cc.isTabActive(Tab)” method which we will need to define on its backing component.

Note that we don’t add a container element for the tab content. One could think we could do something like this in the xhtml:

<!-- snip -->
<composite:implementation>
  <ul class="nav nav-tabs" role="tablist">
    <ui:repeat var="tab" value="#{cc.tabs}">
      <li role="presentation" class="#{cc.isTabActive(tab) ? 'active':''}">
        <!-- snip -->
      </li>
    </ui:repeat>
  </ul>

  <div class="tab-content">
    <div class="tab-pane">
      <composite:insertChildren />
    </div>
  </div>
</composite:implementation>

(Note the use of composite:insertChildren)

Unfortunately that doesn’t work for our intended use of the backing component. The backing component needs to access its children to look how many tabs there are, access their labels and create the tab list. But if you use composite:insertChildren inside the xhtml template JSF disables access to the children in the backing component, the call to UIComponent.getChildren() would always return an empty list. So we don’t use insertChildren and render the tab content elements by hand inside the encodeEnd()-method inside the backing component (yes, JSF is that crazy!).

Now, on to the implementation of the tabview backing component. We will need to implement three methods:

  • public List<Tab> getTabs() – returns a list of Tab instances to render the tab header list. The Tab class is the backing component of the tab composite component to which we come later
  • public boolean isTabActive(Tab tab) – returns true if the given Tab is the currently active tab
  • public void makeTabActive(Tab tab) – is called as an ajax listener when user actually clicks on a tab header to set it as active. This needs to call the setter on the JSF managed bean for the activeTab property and call the onTabChange listener method if one is defined
@FacesComponent("com.mareon.jsf.components.TabView")
public class TabView extends UINamingContainer {

   public void makeTabActive(Tab activeTab) {
       FacesContext facesContext = getFacesContext();
       ELContext elContext = facesContext.getELContext();

       //getAttributes().put("activeTab", activeTab); //<-- this should call setter of property on managed bean, at least in my jsf installation it does not work

       //so we use this longer method instead
       ValueExpression valueExpression = facesContext.getApplication().getExpressionFactory()
             .createValueExpression(elContext, "#{cc.attrs.activeTab}", String.class);
       valueExpression.setValue(elContext, activeTab.getTabName());

       MethodExpression onTabChange = (MethodExpression) getAttributes().get("onTabChange");
       if (onTabChange != null) {
          onTabChange.invoke(elContext, new Object[]{activeTab.getTabId()});
       }
   }

   public List<Tab> getTabs() {
      List<Tab> tabs = new ArrayList<>();
      List<UIComponent> children = getChildren();

      if (CollectionUtils.isNotEmpty(children)) {
          for (UIComponent child : children) {
             if (child.isRendered() && child instanceof Tab) {
                tabs.add((Tab) child);
             }
          }
      }

      return tabs;
   }

   public boolean isTabActive(Tab tab) {
      return StringUtils.equals((String) getAttributes().get("activeTab"), tab.getTabId());
   }
}

To render the child elements (aka tabs) now we have to override one or more of the encode*-Methods we are inheriting form UIComponent.

We are overriding encodeBegin to open a wrapper element for our whole component with our clientID so we can update the component with an AJAX update (see the commandLink in the implementation xhtml of tabview.xhtml). If we wouldn’t do this, JSF ajax update logic would not complain as the update ID is known on server side component tree, but the update would still fail as there would be no DOM element inside the browser to actually update.

The overridden encodeEnd method is rendering the contents of the current active tab:

   @Override
   public void encodeBegin(FacesContext context) throws IOException {
      super.encodeBegin(context);
      ResponseWriter writer = context.getResponseWriter();

      writer.startElement("div", this);
      writer.writeAttribute("id", this.getClientId(), null);
   }

   @Override
   public void encodeEnd(FacesContext context) throws IOException {
      List<UIComponent> children = getChildren();
      ResponseWriter writer = context.getResponseWriter();

      writer.startElement("div", this);
      writer.writeAttribute("class", "tab-content", null);

      if (CollectionUtils.isNotEmpty(children)) {
         for (UIComponent child : children) {
            if (child.isRendered()) {
               child.encodeAll(context);
            }
         }
      }

      writer.endElement("div");
      writer.endElement("div"); //the one from encodeBegin 

      super.encodeEnd(context);
   }

encodeEnd does in fact render ALL children not just only tabs. If the user would define other content as direct child elements of tabview it would be rendered too. Of course, our styling would get messed up, but what can you do if your users are using your software in unintended ways? ?
Sure, we could check if the child element is an instance of Tab. But I don’t like that, as there can be use cases where the user really wants that so we leave it as it is.

Thats it. Now onto the tab component (which will be way easier) and we are done.

The “tab” composite component

As the main “business” logic of the tabview itself is implemented inside the tabview composite component, the tab component is way easier. Have a look at the template xhtml:

<composite:interface componentType="my.package.components.Tab">
   <composite:attribute name="label" type="java.lang.String" required="true"/>
   <composite:attribute name="id" type="java.lang.String" required="true" />
</composite:interface>

<composite:implementation>
   <ui:fragment rendered="#{cc.tabActive}" styleClass="tab-pane #{cc.tabActive ? 'active' : ''}">
      <composite:insertChildren/>
   </ui:fragment>
</composite:implementation>
</html>

We are defining that we are using a backing component of type “my.package.components.Tab” and that we need two mandatory attributes:

  • ID – which is a known attribute anyway, but now its mandatory
  • label – this is the label which is displayed in the header and which can be clicked to make this tab active

The implementation is just as simple. We are using a ui:fragment “component” to render our children only when this tab is active. As this is known only inside the user provided managed bean which is known only inside the tabview composite component, we will need to ask our parent exactly this: are we active? So we need to define one method in our backing component: isTabActive (additional to two helper methods). Here it is:

@FacesComponent("my.package.components.Tab")
public class Tab extends UINamingContainer {

   public boolean isTabActive() {
      TabView tabView = (TabView) getCompositeComponentParent(this);
      return tabView.isTabActive(this);
   }

   public String getTabId() {
      return (String) getAttributes().get("id");
   }
   public String getTabLabel() {
      return (String) getAttributes().get("label");
   }
}

isTabActive is accessing its parent and is asking it if its active. Thats it.

I have left out any error handling code in my examples by choice. Of course I advise you to inlcude error handling by yourself. For instance, you should make sure inside the Tab class that your parent is really a tabview instance.

Conclusion

The tabview component(s) should be working out of the box. Except if I made any errors while simplifying them for this tutorial. Please contact me if you find any errors.

You can do almost anything with JSF composite components and I have learned quite a lot using them. Have fun while trying it our yourself. Any feedback is welcome!

Leave a Reply

Your email address will not be published. Required fields are marked *

*