Thursday, August 5, 2010

Replacement of h:head in order to fix resources rendering

I came up against need to reimplement JSF 2 head component by reason of an issue. I have implemented my custom head tag and achieved more flexibility. I'm going to begin with a history before I will show the code. I'm working with PrimeFaces component library. PrimeFaces comes with predefined CSS settings. Developers often want to overwrite these predefined styles. I do that quite simple - include PrimeFaces resources inside of the head tag and include my own resources close to the body tag. Including of PrimeFaces resources (by p:resource) is actually not necessary for regular requests with full page refresh. But it's necessary if you have partial updates via ajax. An output looks like
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><head>
<link rel="stylesheet" type="text/css" href="/icManagement/primefaces_resource/2.0.2/yui/tabview/assets/skins/sam/tabview.css" />
<link rel="stylesheet" type="text/css" href="/icManagement/primefaces_resource/2.0.2/yui/accordion/assets/skins/sam/accordionview.css" />
<link rel="stylesheet" type="text/css" href="/icManagement/primefaces_resource/2.0.2/yui/menu/assets/skins/sam/menu.css" />
<script type="text/javascript" src="/icManagement/primefaces_resource/2.0.2/yui/menu/menu-min.js"></script>
<script type="text/javascript" src="/icManagement/primefaces_resource/2.0.2/yui/json/json-min.js"></script>
...
<link type="text/css" rel="stylesheet" href="/icManagement/javax.faces.resource/sky/css/ip-jsftoolkit.css.jsf?ln=themes" />
<link type="text/css" rel="stylesheet" href="/icManagement/javax.faces.resource/sky/css/incursa-management.css.jsf?ln=themes" />
<script type="text/javascript" src="/icManagement/javax.faces.resource/ip-jsftoolkit.js.jsf?ln=js"></script>
<script type="text/javascript" src="/icManagement/javax.faces.resource/incursa-management.js.jsf?ln=js"></script>
Custom CSS, JavaScript files are places after PrimeFaces' one. Well. Mojarra 2.0.2 had an issue, that CSS selectors placed in h:head cannot override styles in implicitly added resources. This issue was fixed in Mojarra 2.0.3. But this bugfixing is not enough because it fixed one issue and caused another one. Why? Often we need <meta> tags to prevent page caching or make sites search engine friendly. Using the <link> tag you can add a "canonical" url to your page and a favicon. The order of these additional tags can be very important. Internet Explorer introduced e.g. a special <meta> tag
<meta http-equiv="X-UA-Compatible" content="..." />
Content of the X-UA-Compatible <meta> tag helps to control document compatibility. We can specify the rendering engine. For example, inserting this:
<meta http-equiv="X-UA-Compatible" content="IE=8" />
into the head of a document would force IE8 renders the page using the new standards mode. I often heard "IE8 breaks my site". Add this <meta> tag or HTTP header which can fix the site:
<meta http-equiv="X-UA-Compatible" content="IE=7" />
It fixes your site, at least until you can sort out the IE8 issues. I use on my pages some great features which are supported by IE8 only. For instance, IE6 / IE7 don't support border color for select elements. You cannot set border color for h:selectOneMenu or h:selectManyLisbox. Unfortunately, IE8 was rendering my pages in IE7 mode. DOCTYPE was ok, but maybe Apache settings were fault or something else. I decided to put content="IE=8" on all my XHTML pages. But it didn't have any effect. The reason: X-UA-Compatible must be the first child of the head! Internet Explorer doesn't accept this <meta> tag if it's placed after <link> or <script> tags. Many people also encountered the same problem and trey tried making the <meta> tag X-UA-Compatible as the first child of the head to solve this problem. But how could I place this <meta> tag quite at the beginning? The standard h:head tag of the last Mojarra implementation doesn't have this possibility. It always renders at first resources added by JSF 2 facility (don't matter what is placed within head). My output was similar this:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><head>
<link rel="stylesheet" type="text/css" href="/icManagement/primefaces_resource/2.0.2/yui/tabview/assets/skins/sam/tabview.css" />
<link rel="stylesheet" type="text/css" href="/icManagement/primefaces_resource/2.0.2/yui/accordion/assets/skins/sam/accordionview.css" />
<link rel="stylesheet" type="text/css" href="/icManagement/primefaces_resource/2.0.2/yui/menu/assets/skins/sam/menu.css" />
<script type="text/javascript" src="/icManagement/primefaces_resource/2.0.2/yui/menu/menu-min.js"></script>
<script type="text/javascript" src="/icManagement/primefaces_resource/2.0.2/yui/json/json-min.js"></script>
...
<meta http-equiv="X-UA-Compatible" content="IE=8"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta http-equiv="pragma" content="no-cache"/>
<meta http-equiv="cache-control" content="no-cache"/>
<meta http-equiv="expires" content="0"/>
You see, the standrad h:head is not flexible enough. Oracle ADF has a special <af:document> tag where you can use the facet "metaContainer" of the <af:document> to output the tags in the right place. Eureka! We need facets in the head component for right order controling of rendered resources. Let's write a custom head component:
public class Head extends UIOutput
{
 public static final String COMPONENT_TYPE = "ip.client.jsftoolkit.components.Head";
 public static final String COMPONENT_FAMILY = "ip.client.jsftoolkit.components";
 private static final String DEFAULT_RENDERER = "ip.client.jsftoolkit.components.HeadRenderer";

 /**
  * Properties that are tracked by state saving.
  */
 protected enum PropertyKeys
 {
  title, shortcutIcon;

  String toString;

  PropertyKeys(String toString)
  {
   this.toString = toString;
  }

  PropertyKeys()
  {
  }

  public String toString()
  {
   return ((this.toString != null) ? this.toString : super.toString());
  }
 }

 public Head()
 {
  setRendererType(DEFAULT_RENDERER);
 }

 public String getFamily()
 {
  return COMPONENT_FAMILY;
 }

 public String getTitle()
 {
  return (String) getStateHelper().eval(PropertyKeys.title, null);
 }

 public void setTitle(String title)
 {
  getStateHelper().put(PropertyKeys.title, title);
 }

 public String getShortcutIcon()
 {
  return (String) getStateHelper().eval(PropertyKeys.shortcutIcon, null);
 }

 public void setShortcutIcon(String shortcutIcon)
 {
  getStateHelper().put(PropertyKeys.shortcutIcon, shortcutIcon);
 }
}
I took "title" and "shortcutIcon" attributes to the head component in order to achieve more convenience by using. Now the renderer:
import java.io.IOException;
import java.util.ListIterator;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.Renderer;

public class HeadRenderer extends Renderer
{
 public void encodeBegin(FacesContext facesContext, UIComponent component) throws IOException
 {
  ResponseWriter writer = facesContext.getResponseWriter();
  writer.startElement("head", component);

  UIComponent first = component.getFacet("first");
  if (first != null) {
   first.encodeAll(facesContext);
  }

  UIViewRoot viewRoot = facesContext.getViewRoot();
  ListIterator<UIComponent> iter = (viewRoot.getComponentResources(facesContext, "head")).listIterator();
  while (iter.hasNext()) {
   UIComponent resource = (UIComponent) iter.next();
   resource.encodeAll(facesContext);
  }
 }

 public void encodeChildren(FacesContext facesContext, UIComponent component) throws IOException
 {
 }

 public void encodeEnd(FacesContext facesContext, UIComponent component) throws IOException
 {
  ResponseWriter writer = facesContext.getResponseWriter();
  Head head = (Head) component;

  UIComponent last = component.getFacet("last");
  if (last != null) {
   last.encodeAll(facesContext);
  }

  if (head.getTitle() != null) {
   writer.startElement("title", null);
   writer.write(head.getTitle());
   writer.endElement("title");
  }

  if (head.getShortcutIcon() != null) {
   writer.startElement("link", null);
   writer.writeAttribute("rel", "shortcut icon", null);
   writer.writeAttribute("href", head.getShortcutIcon(), null);
   writer.endElement("link");
  }

  writer.endElement("head");
 }
}
I introduced two facets called "first" and "last". The resource rendering order with this approach:
  1. Resources placed into "first" facet.
  2. Resources added by JSF 2 facility.
  3. Resources placed into "last" facet.
  4. Page title and shortcut icon.
The new component and its tag have be registered as usual in faces-config and taglib XMLs.
<renderer>
 <component-family>ip.client.jsftoolkit.components</component-family>
 <renderer-type>ip.client.jsftoolkit.components.HeadRenderer</renderer-type>
 <renderer-class>ip.client.jsftoolkit.components.head.HeadRenderer</renderer-class>
</renderer>
<component>
 <component-type>ip.client.jsftoolkit.components.Head</component-type>
 <component-class>ip.client.jsftoolkit.components.head.Head</component-class>
</component>

<tag>
 <tag-name>head</tag-name>
 <component>
  <component-type>ip.client.jsftoolkit.components.Head</component-type>
  <renderer-type>ip.client.jsftoolkit.components.HeadRenderer</renderer-type>
 </component>
</tag>
Using on page:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
   xmlns:f="http://java.sun.com/jsf/core"
   xmlns:h="http://java.sun.com/jsf/html"
   xmlns:ui="http://java.sun.com/jsf/facelets"
   xmlns:jtcomp="http://ip.client/ip-jsftoolkit/components">
<f:view contentType="text/html" locale="#{userSettings.locale}">
<ui:insert name="metadata"/>
<jtcomp:head title="#{messageParameters['frameworkTitle']}" shortcutIcon="#{request.contextPath}/favicon.ico">
  <f:facet name="first">
    <meta http-equiv="X-UA-Compatible" content="IE=8"/>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <meta http-equiv="pragma" content="no-cache"/>
    <meta http-equiv="cache-control" content="no-cache"/>
    <meta http-equiv="expires" content="0"/>
  </f:facet>
  <f:facet name="last">
    <style type="text/css">
      html, body {
        width: 100%;
        height: 100%;
        margin: 0px;
      }
    </style>
    <script type="text/javascript">...</script>
  </f:facet>
  <ui:insert name="resources-pf"/>
</jtcomp:head>
<h:body>
...
 <ui:insert name="resources-app"/>
</h:body>
</f:view>
</html>
Generated output in the right defined order:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><head>
  <meta http-equiv="X-UA-Compatible" content="IE=8" />
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <meta http-equiv="pragma" content="no-cache" />
  <meta http-equiv="cache-control" content="no-cache" />
  <meta http-equiv="expires" content="0" />
  
  <link rel="stylesheet" type="text/css" href="/icManagement/primefaces_resource/2.0.2/yui/tabview/assets/skins/sam/tabview.css" />
  <link rel="stylesheet" type="text/css" href="/icManagement/primefaces_resource/2.0.2/yui/accordion/assets/skins/sam/accordionview.css" />
  <link rel="stylesheet" type="text/css" href="/icManagement/primefaces_resource/2.0.2/yui/menu/assets/skins/sam/menu.css" />
  <script type="text/javascript" src="/icManagement/primefaces_resource/2.0.2/yui/menu/menu-min.js"></script>
  <script type="text/javascript" src="/icManagement/primefaces_resource/2.0.2/yui/json/json-min.js"></script>
  ...
  <link type="text/css" rel="stylesheet" href="/icManagement/javax.faces.resource/sky/css/ip-jsftoolkit.css.jsf?ln=themes" />
  <link type="text/css" rel="stylesheet" href="/icManagement/javax.faces.resource/sky/css/incursa-management.css.jsf?ln=themes" />
  <script type="text/javascript" src="/icManagement/javax.faces.resource/ip-jsftoolkit.js.jsf?ln=js"></script>
  <script type="text/javascript" src="/icManagement/javax.faces.resource/incursa-management.js.jsf?ln=js"></script>
  <style type="text/css">
   html, body {
    width: 100%;
    height: 100%;
    margin: 0px;
   }
  </style>
  <script type="text/javascript">...</script>
  <title>Management Client</title>
  <link rel="shortcut icon" href="/icManagement/favicon.ico" />
  </head>
...
Cool? :-)

5 comments:

  1. Hi Oleg! Nice post! Your solution solves my problem. =D

    My problem is a little differente from yours. I activated SSL for my AS and the jsf.js file was corrupted. Therefore, some functionalities using the 'jsf' variable didn't work because of 'jsf is not defined' error.

    Thanks.
    Renato Herebia

    ReplyDelete
  2. I was not able to execute my program in the IE 8 also..
    i have use only "meta http-equiv="X-UA-Compatible" content="IE=8" " Tag in my Head .."it is not at all functioning i guess " So what to DO ?

    ReplyDelete
  3. Hi Oleg,

    PrimeFaces has native support for first/last facet. In addition, you can also specify a middle facet, which PrimeFaces will use to inject content between the PF CSS and the PF JS.

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. Hi Oleg,

    when I write this code in my faces-config

    < tag > ... < / tag>


    the faces-config sends me the next error:
    cvc-complex-type.2.4.a: Invalid content was found starting with element 'tag'. One of '{"http://java.sun.com/xml/ns/javaee":application, "http://java.sun.com/xml/ns/javaee":ordering, "http://java.sun.com/xml/ns/
    javaee":absolute-ordering, "http://java.sun.com/xml/ns/javaee":factory, "http://java.sun.com/xml/ns/javaee":component, "http://java.sun.com/xml/ns/javaee":converter, "http://java.sun.com/xml/ns/javaee":managed-bean,
    "http://java.sun.com/xml/ns/javaee":name, "http://java.sun.com/xml/ns/javaee":navigation-rule, "http://java.sun.com/xml/ns/javaee":referenced-bean, "http://java.sun.com/xml/ns/javaee":render-kit, "http://java.sun.com/xml/
    ns/javaee":lifecycle, "http://java.sun.com/xml/ns/javaee":validator, "http://java.sun.com/xml/ns/javaee":behavior, "http://java.sun.com/xml/ns/javaee":faces-config-extension}' is expected.

    Could you helpme please?

    ReplyDelete

Note: Only a member of this blog may post a comment.