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 r
egion – 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).
Great article, but got me a little confused. Region is used very similar to page fragment.
But what is the difference between them?
Hi,
I am also facing the access denied issue. My scenario is like this :
template and consuming application both using the View objects. system fetches the data for the VO on the consuming JSFF but fires “access denied”
for the VO on the template page.
here data is gettng populated into VO but display is an issue. I have tried using varioys refresh properties but not helping much.
Does anyways has any idea about it ?
Hi,
Im new to JSF / ADF.
Im now have a problem to make PanelPage componen as a template.
So when i drag panel page component, it already have company logo, application title, copyright, etc
Is this article can solve my problem ?
Thanks to reply
For those who are facing ‘Access Denied’ problem, add value=”#{bindings}” in af:region tag.
example:
Very nice idea! Thanks! Like the others, I also have the same problem of not being able to display a data bound table (for view iterator binding) . eg.: Placing a data bound table in the /regions/BooksPrettyRegion.jspx page …I get Access Denied …Is there a work around to this problem? Thanks!
Attempt 2 because tags got killed in the first…
Very nice article.
Concerning the not working example <af:region id=”content” regionType=”#{attrs.contentRegion}”/>, there might be another trick working only under JSF 1.1, not 1.2. I didn’t test it though, so use at your own risk. Since JSF 1.1 does not use unified EL, using <af:region id=”content” regionType=”${attrs.contentRegion}”/> has a good chance to work, forcing the EL evaluation at an earlier moment, which is needed for the example to work. As of JSF 1.2, ${} has the same effect as #{} (the deferred option is set within the TLD), so my solution, even if it work now, won’t hold too long, but it’s kind of sad to find a use case where the Unified EL EG’s choice to use TLD to define evaluation time rather than the operator break something that would have worked in previous versions.
Regards.
(This comment should replace my previous one)
Great article! If like me you want to see it in action rapidly in JDeveloper 10.1.3.2.0, you just have to: 1- add 4 tag libraries to the project (ADF Faces Components 10_1_3_2_0, ADF Faces HTML 10_1_3_2_0, JSF Core 1.0, JSF HTML 1.0). 2- Replace the region whose id is “content†with the commented tag just above in the file MainTemplate.jspx 3- Run the page AlsHomePage.jspx
Great article! I like me you want to see it in action rapidly in JDeveloper 10.1.3.2.0, you just have to:
1- add 4 tag libraries to the project (ADF Faces Components 10_1_3_2_0, ADF Faces HTML 10_1_3_2_0, JSF Core 1.0, JSF HTML 1.0).
2- Replace the region whose id is “content” with the commented tag just above in the file
3- Run the page AlsHomePage.jspx
I’m having the same issue as Sam, getting an “access denied” error when using data bindings in a regionDef. Has anyone figured out this issue?
Hi,
This is a great article. I am assuming this article is talking about Oracle ADF 10G. In ADF 11G, they have Oracle has implemented page templates. So, to make pages with ’tiles’ is fairly easy. However, the region concept is still available and is a very powerful component for refreshing regions (e.g. based on the type display the table content differenly). Do you have a sample for regions for ADF 11G? I would like to see that and see what changes I need to make to my code.
Thanks,
–aj
Hi,
Great article … I am having some difficulties with the downloaded source. At runtime, I am receiving the following error:
“oracle.adf.share.config.ADFConfigFactory No META-INF/adf-config.xml found”
I put together the adf-config.xml file and placed it in there, but there appears to be problems with it … can the original be made available?
cheers in advance,
Declan
Great article, really useful. It would be nice to see it organised in a flow so that newbies could follow it. For example:
1. Create a new Application
… enumerate the steps to create a new application.
2. Create the Template
… enumerate the steps to create the template.
3. Create the Page
… enumerate the steps for creating a page.
4. Link the Page to the Template
… enumerate the steps to link the page.
etc.
If a regionDef page contains data bindings in its pageDef, I will get “Access Denied” messages on my final display page. I wonder how you resolve this problem.
Hi Anwar,
If you create a new Application in JDeveloper, using the Web Technology Template (ADF BC & JSF), the adf-face-impl.jar is added to the WEB-INF/lib directory of the ViewController project. Alternatively, look in the JDEV_HOME\jlib directory for adf-faces-impl.jar.
Lucas
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).
That above 2 lines mentioned in this article under Resource section.
I am running the application under Jdeveloper 10.1.3.2.0.4066 version, but page not displaying.
May I know where can i get faces-impl.jar file?
Please give me the link. I serached the net but not got.
Greate article. I was able to get this up and running pretty quickly but I have run into an important snag. I have not been able to consume a regionDef page that contains a data control (bindings). It seems that the regionDef does not resolve to its corresponding pageDef file at runtime. Would you expect this to work?
Great article Lucas, dynamic templating is a must have feature for ADF Faces web applications.
Have you ever tried Sitemesh ? I plan to work on it and regions to make use of both worlds. The real issue there is to keep the visual editor working.
See you later,
Seb.
This is really useful – thanks for posting!
One thing that occurred to me reading through it is that there may be a potential race condition when multiple browser windows are open on a session and each page is doing a getObject() to set the content region and the template is then doing a call to getContentRegion() to retrieve it. If there is a race condition here then maybe declaring these beans as request scoped, rather than session scoped, would fix it.