ADF Faces can do Tiles or SiteMesh like Push-Style templating

Posted: August 23, 2008 in HowTo, Oracle, Tips, WebProgramming
Tags: , , , ,

How ADF Faces can do Tiles or SiteMesh like Push-Style templating using Regions with Dynamic Attributes by Lucas Jellema

Source: AMIS’s Blog

JSF has in built in facility for templating, apart from the subview component. And the Subview does not give us the kind of push-style templating that has us define templates and apply them to page-specific content, such as demonstrated in this illustration:

Here we push page specific content and place holder values to a generic template that meshes the two together to produce a page that has both the generic, centrally maintained, template as well as the page specific stuff.

There are several ways of using Tiles and JSF together (see for example http://www.jsftutorials.net/tiles/jsf-tiles.html ).

In this article, I will focus on the Region component in ADF Faces. This component also allows us to do something very similar to the above example. But let’s first introduce Regions, as they are typically used. ....
A Region in ADF Faces is a parametrized reusable page fragment. A region is very much like a regular JSF page, that defines a piece of content that can easily be reused in many different pages. Additionally, the region can make use of dynamically defined parameters whose values are passed along with the region inclusion.

This Find Books page includes two Regions:

1. the Global Menu region is a static fragment that returns three global buttons that are to be displayed in every page in the application.

2. the AlsMainMenu region, an almost static fragment, that displays the application wide menu (it is a small application we are talking about) and highlights the currently selected menu-tab. This last piece is dynamic: every page that includes the AlsMainMenu will have to specify which tab in the menu should be highlighted.

In order to use regions like this, the FindBooks-page has these two region elements:

<af:region id="menuGlobal"
           regionType="nl.amis.als.region.globalMenu"/>
<af:region id="mainmenu"
           regionType="nl.amis.als.region.AlsMainMenu">
   <af:attribute name="selectedTab" value="findbooks"/>
</af:region> 

The second region element contains an af:attribute that allows us to pass a pagespecific value.

The two regions are defined in separate pages, that can be named anything and be located anywhere, as we use a metadata file where all regions are registered, with their page name and location. The AlsMainMenu region for example is defined as follows:

Note how the region AlsMainMenu can refer to the dynamic values passed into it from the <af:region> tag (attribute child element). The Find Books tab is only selected when the EL expression evaluates to true, which means only when the attrs.selectedTab value equals findbooks.

In order for the region reference in the FindBooks.jspx to be evaluated correctly by ADF Faces, we need to add the proper region registration to a file called region-metadata.xml located in the WEB-INF directory. The region meta-data for GlobalMenu and AlsMainMenu look like:

<?xml version="1.0" encoding="windows-1252"?>
<!DOCTYPE faces-config PUBLIC
  "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"
  "http://java.sun.com/dtd/web-facesconfig_1_1.dtd"[
<!ELEMENT region-jsp-ui-def (#PCDATA)>
<!ELEMENT required (#PCDATA)>
]>
<faces-config xmlns="http://java.sun.com/JSF/Configuration">
  <component>
    <component-type>nl.amis.als.region.globalMenu</component-type>
    <component-class>oracle.adf.view.faces.component.UIXRegion</component-class>
    <component-extension>
      <region-jsp-ui-def>/regions/globalMenu.jspx</region-jsp-ui-def>
    </component-extension>
  </component>
  <component>
    <component-type>nl.amis.als.region.AlsMainMenu</component-type>
    <component-class>oracle.adf.view.faces.component.UIXRegion</component-class>
    <attribute>
      <attribute-name>selectedTab</attribute-name>
      <attribute-class>java.lang.String</attribute-class>
      <attribute-extension>
        <required>false</required>
      </attribute-extension>
    </attribute>
    <component-extension>
      <region-jsp-ui-def>/regions/AlsMainMenu.jspx</region-jsp-ui-def>
    </component-extension>
  </component>
</faces-config>

Now we have been introduced to basic way of working with regions. Let’s turn next to page wide templates.

ADF Faces page templates

ADF Faces does not really have the concept of Page Templates. A Page Template is a page definition that provides the generic elements that return on every page in the application. These elements by and large determine the look & feel of the application (along of course with the stylesheets). Look for example at two pages in our application, and it becomes soon clear what would be part of the template for this application:

The overall page template is consists of:

  • Fixed elements such as Logo, Application Title, Global menu and Application menu, Copyright Message, Side Menu Bar
  • Page specific content (the yellow area that differs per page)
  • Dynamic elements whose appearance and position is fixed but whose values/settings are page-specific, such a Page Title, Tip and Selected MenuTab

If we do nothing special, working with ADF Faces Regions does not really help us here. Since the page template is all over the page, we cannot include a single region to apply a default template. The page specific content somehow would have to be injected into a generic template that is also customized in certain elements with page specific values. Besides, each page still needs its own JSPX file to cater for proper navigation and support for ADF Faces Model and Page Definitions. So at the moment, the page template is copied for every new page that gets created. The generic template is applied initially for every page. But changes in the generic template can only be applied globally by revisiting and changing every individual page that was once created from the template file.

Inside-out  Template usage

What we would like to do is somehow turn the concept of the template inside-out. The page specifies the template to be applied, provides page specific values but does not copy the template but only references it. Then we can still make changes to the overall appearance of the application in a single location.

It took a little puzzling, but I managed to do this using Regions in ADF Faces. The set up is as follows:

Every page in the application has its own JSPX file with the page definition. This definition consists of nothing more than the inclusion of one or more regions, passing to each region a number of page specific details. These details include the values for a number of placeholders in the referenced template and a reference to a second region that contains the page specific content. This latter reference is used in the main template file to include another region – the yellow area in the picture below – with page specific content. The key to this approach is that the region reference in the main template is dynamic: each page provides its own value for this region reference.

The three pages in our application now have very simple page definitions – as all their content is either provided by the main template or their page specific region counterparts. The FindBookPage.jspx is defined like this:

<?xml version='1.0' encoding='windows-1252'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.0"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:af="http://xmlns.oracle.com/adf/faces" >
  <jsp:output omit-xml-declaration="true" doctype-root-element="HTML"
              doctype-system="http://www.w3.org/TR/html4/loose.dtd"
              doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN"/>
  <jsp:directive.page contentType="text/html;charset=windows-1252"/>
  <f:view>
    <afh:html>
      <afh:head title="AMIS Library  System - Find Book">
        <meta http-equiv="Content-Type" content="text/html; charset=windows-1252"/>
      </afh:head>
      <afh:body>
        <af:form>
          <af:region id="mainTemplate" regionType="nl.amis.als.region.mainTemplate">
            <af:attribute name="contentRegion" value="nl.amis.als.region.FindBookPage"/>
            <af:attribute name="title" value="Find Books"/>
            <af:attribute name="selectedMenuTab" value="findbooks"/>
            <af:attribute name="pageTip" value="Specify the search criteria for finding selected books."/>
          </af:region>
        </af:form>
      </afh:body>
    </afh:html>
  </f:view>
</jsp:root>

That is, the page is nothing more than a parametrized usage of the MainTemplate region.

The MainTemplate is registered in the region-metadata.xml file as follows:

  <component>
    <component-type>nl.amis.als.region.mainTemplate</component-type>
    <component-class>oracle.adf.view.faces.component.UIXRegion</component-class>
    <attribute>
      <attribute-name>selectedTab</attribute-name>
      <attribute-class>java.lang.String</attribute-class>
      <attribute-extension>
        <required>false</required>
      </attribute-extension>
    </attribute>
    <attribute>
      <attribute-name>contentRegion</attribute-name>
      <attribute-class>java.lang.String</attribute-class>
      <attribute-extension>
        <required>false</required>
      </attribute-extension>
    </attribute>
    <attribute>
      <attribute-name>title</attribute-name>
      <attribute-class>java.lang.String</attribute-class>
      <attribute-extension>
        <required>false</required>
      </attribute-extension>
    </attribute>
    <attribute>
      <attribute-name>pageTip</attribute-name>
      <attribute-class>java.lang.String</attribute-class>
      <attribute-extension>
        <required>false</required>
      </attribute-extension>
    </attribute>
    <component-extension>
      <region-jsp-ui-def>/regions/MainTemplate.jspx</region-jsp-ui-def>
    </component-extension>
  </component>

It has four attributes, for the dynamic values to be passed into the template, to populate the three place holders – selected menu tab, title and tip – and to refer to the content region.

Here are a few fragments from the MainTemplate.jspx file.

<?xml version='1.0' encoding='windows-1252'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.0"
          xmlns:h="http://java.sun.com/jsf/html"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:af="http://xmlns.oracle.com/adf/faces"
          xmlns:afh="http://xmlns.oracle.com/adf/faces/html">
  <jsp:output omit-xml-declaration="true" doctype-root-element="HTML"
              doctype-system="http://www.w3.org/TR/html4/loose.dtd"
              doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN"/>
  <jsp:directive.page contentType="text/html;charset=windows-1252"/>
  <af:regionDef var="attrs">
     <af:panelPage title="#{attrs.title}">
       <f:facet name="menu1"/>
       <f:facet name="menuGlobal">
         <af:region id="menuGlobal"
                    regionType="nl.amis.als.region.globalMenu"/>
       </f:facet>
       <f:facet name="infoFootnote">
         <af:panelTip>
           <af:outputFormatted value="#{attrs.pageTip}"></af:outputFormatted>
         </af:panelTip>
       </f:facet>
       <f:facet name="menu1">
         <af:region id="mainmenu"
                    regionType="nl.amis.als.region.AlsMainMenu">
           <af:attribute name="selectedTab" value="#{attrs.selectedMenuTab}"/>
         </af:region>
       </f:facet>
       …
       <af:region id="content" regionType="#{attrs.contentRegion}" />
     </af:panelPage>
   </af:regionDef>
</jsp:root>

This pages defines the main layout of the pages in our application, using an ADF Faces PanelPage component, setting up for example the Application Branding (logo and application title), the Copyright message, the global menu (by including the globalmenu region) and the application specific menu (through yet another region). The really novel part is the region with id=”content”. That is where we import the page content. And the region to load is specified by the page that includes the MainTemplate region.

The region-metadata.xml file contains region definitions for all pages in our application:

  <!-- regions with the real page content -->
  <component>
    <component-type>nl.amis.als.region.AlsHomePage</component-type>
    <component-class>oracle.adf.view.faces.component.UIXRegion</component-class>
    <component-extension>
      <region-jsp-ui-def>/regions/AlsHomeRegion.jspx</region-jsp-ui-def>
    </component-extension>
  </component>
  <component>
    <component-type>nl.amis.als.region.FindBookPage</component-type>
    <component-class>oracle.adf.view.faces.component.UIXRegion</component-class>
    <component-extension>
      <region-jsp-ui-def>/regions/FindBookRegion.jspx</region-jsp-ui-def>
    </component-extension>
  </component>
  <component>
    <component-type>nl.amis.als.region.BooksTablePrettyPage</component-type>
    <component-class>oracle.adf.view.faces.component.UIXRegion</component-class>
    <component-extension>
      <region-jsp-ui-def>/regions/BooksTablePrettyRegion.jspx</region-jsp-ui-def>
    </component-extension>
  </component>

These regions contain the real content for all pages.

Our web application now is structured like this:

where three pages each have their own jspx file, referenced in the faces-config.xml file for navigation purposes, but otherwise consisting of nothing more than a reference to the MainTemplate region that is specified in region-metadata.xml and refers to the regions\MainTemplate.jspx file. This file in turn includes the regions GobalMenu and AlsMainMenu, and is parameterized with four page specific values. The MainTemplate.jsxp page has a dynamic region reference; the page that includes the MainTemplate tells the MainTemplate which region to refer in this dynamic reference. The region reference thus passed is resolved via the region-metadata.xml and results in including the page specific region – AlsHomeRegion.jspx, FindBookRegion.jspx or BooksTablePrettyRegion.jspx.

If we want to change the overall appearance and structure of the pages in our application, we change the MainTemplate.jspx file and nothing but this MainTemplate. We do not need to visit each and every page individually.

Unfortunately, it does not quite work like this

It turns out that the evaluation of the regionType attribute in the af:region element is performed too late. If we pass in a value, evaluation of #{attrs.contentRegion} is done at the wrong moment and we get fairly nasty error:

So we have to resort to a workaround, that allows us to specify the name of the region to be included by the MainTemplate in the page while keeping it dynamic. It turns out that we can refer to a managed bean in the regionType attribute without having this too-late-evaluation issue that we have with the attrs ‘value container’.

What proves to work for me, though it may sound a little complex at first, is the following:

<af:region id="content" regionType="#{Helper.contentRegion}">

in the MainTemplate.jspx. The regionType is set to an EL Expression that does not refer to attrs but instead to a managed bean, lamely called Helper.

In the pages, we have to do the following to pass the value of the region to be injected in the MainTemplate:

<af:region id="mainTemplate" regionType="#{DynamicPageRegionHolder['nl.amis.als.region.FindBookPage']}">
   <af:attribute name="title" value="Find Books"/>
   <af:attribute name="selectedMenuTab" value="findbooks"/>
   <af:attribute name="pageTip" value="Specify the search criteria for finding selected books."/>
</af:region>

This is uses another trick: in order to pass the value of the region we want to include at the right time we use an EL Expression for the mainTemplate region’s regionType attribute. The managed bean referenced here – DynamicPageRegionHolder – implements the Map interface. The EL expression #{DynamicPageRegionHolder[’nl.amis.als.region.FindBookPage’]}  will have the get(Object key) method in this bean invoked. The value of the key parameter will be the region identification for the region to be injected by the MainTemplate region. The get() method currently always returns the region identification for the MainTemplate and stores the page specific contentRegion identification in the Helper bean that is subsequently consulted by the MainTemplate region.

The get() method in the DynamicPageRegionHolder bean:

    public Object get(Object key) {
      helper.setContentRegion((String)key);
      return "nl.amis.als.region.mainTemplate";
    }

The configuration of the two beans in the faces-config.xml file:

  <managed-bean>
    <managed-bean-name>Helper</managed-bean-name>
    <managed-bean-class>nl.amis.Helper</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
  </managed-bean>
  <managed-bean>
    <managed-bean-name>DynamicPageRegionHolder</managed-bean-name>
    <managed-bean-class>nl.amis.MapAdaptor</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
    <managed-property>
      <property-name>helper</property-name>
      <property-class>nl.amis.Helper</property-class>
      <value>#{Helper}</value>
    </managed-property>
  </managed-bean>

It is a somewhat nasty trick. But it does the job.

Resources

Download the JDeveloper 10.1.3.2 project with sources for this article.  NestedRegions_ADFFaces.zip (note: you have to add the adf-faces-impl.jar and the faces-impl.jar libraries to the WEB-INF\lib directory (saves 4 Mb on download).

Comments
  1. Kiran says:

    Hi Ami

    After deploying your code, I am getting this error, Can you please help me out on this

    javax.servlet.jsp.JspException: Cannot find parent tag. at oracle.adfinternal.view.faces.taglib.RegionDefTag.doStartTag(RegionDefTag.java:45) at _regions._globalMenu_jspx._jspService(_globalMenu_jspx.java:48) [/regions/globalMenu.jspx] at com.orionserver[Oracle Containers for J2EE 10g (10.1.3.1.1) ].http.OrionHttpJspPage.service(OrionHttpJspPage.java:59) at oracle.jsp.runtimev2.JspPageTable.service(JspPageTable.java:462) at oracle.jsp.runtimev2.JspServlet.internalService(JspServlet.java:598) at oracle.jsp.runtimev2.JspServlet.service(JspServlet.java:522) at javax.servlet.http.HttpServlet.service(HttpServlet.java:856) at com.evermind[Oracle Containers for J2EE 10g (10.1.3.1.1) ].server.http.ResourceFilterChain.doFilter(ResourceFilterChain.java:64) at oracle.adf.model.servlet.ADFBindingFilter.doFilter(ADFBindingFilter.java:162) at com.evermind[Oracle Containers for J2EE 10g (10.1.3.1.1) ].server.http.ServletRequestDispatcher.invoke(ServletRequestDispatcher.java:622) at com.evermind[Oracle Containers for J2EE 10g (10.1.3.1.1) ].server.http.ServletRequestDispatcher.forwardInternal(ServletRequestDispatcher.java:369) at com.evermind[Oracle Containers for J2EE 10g (10.1.3.1.1) ].server.http.HttpRequestHandler.doProcessRequest(HttpRequestHandler.java:865) at com.evermind[Oracle Containers for J2EE 10g (10.1.3.1.1) ].server.http.HttpRequestHandler.processRequest(HttpRequestHandler.java:447) at com.evermind[Oracle Containers for J2EE 10g (10.1.3.1.1) ].server.http.HttpRequestHandler.serveOneRequest(HttpRequestHandler.java:215) at com.evermind[Oracle Containers for J2EE 10g (10.1.3.1.1) ].server.http.HttpRequestHandler.run(HttpRequestHandler.java:117) at com.evermind[Oracle Containers for J2EE 10g (10.1.3.1.1) ].server.http.HttpRequestHandler.run(HttpRequestHandler.java:110) at oracle.oc4j.network.ServerSocketReadHandler$SafeRunnable.run(ServerSocketReadHandler.java:260) at oracle.oc4j.network.ServerSocketAcceptHandler.procClientSocket(ServerSocketAcceptHandler.java:239) at oracle.oc4j.network.ServerSocketAcceptHandler.access$700(ServerSocketAcceptHandler.java:34) at oracle.oc4j.network.ServerSocketAcceptHandler$AcceptHandlerHorse.run(ServerSocketAcceptHandler.java:880) at com.evermind[Oracle Containers for J2EE 10g (10.1.3.1.1) ].util.ReleasableResourcePooledExecutor$MyWorker.run(ReleasableResourcePooledExecutor.java:303) at java.lang.Thread.run(Thread.java:595)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s