Combine hardcoded PrimeFaces resources using CombinedResourceHandler

Bauke Scholtz edited this page Apr 18, 2014 · 2 revisions

Introduction

PrimeFaces has a few CSS/JS resources which are not declared via @ResourceDependency annotation and therefore cannot be combined by CombinedResourceHandler. Those resources are hardcoded in PrimeFaces own HeadRenderer class.

The PrimeFaces theme.css file has a dynamic library name (representing the theme name) and therefore cannot be declared as @ResourceDependency because the library/resource name must be compiletime constants.

The since PrimeFaces 4.0 introduced validation.js and beanvalidation.js files needs to be conditionally included (depending on whether <p:clientValidator> is present in the view and whether JSR303 bean validation is supported) and therefore cannot be declared as @ResourceDependency because it cannot be conditionally disabled.

Solution

Basically, you need to create a custom HeadRenderer and explicitly declare those PrimeFaces resources as @ResourceDependency.

Assuming that you're using the PrimeFaces default "aristo" theme, here's an example:

package com.example;

import java.io.IOException;

import javax.faces.application.ResourceDependencies;
import javax.faces.application.ResourceDependency;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.render.Renderer;

@ResourceDependencies({
    @ResourceDependency(library="primefaces-aristo", name="theme.css"),
    @ResourceDependency(library="primefaces", name="primefaces.js"), // Only necessary when at least one validation JS files needs to be included.
    @ResourceDependency(library="primefaces", name="validation/validation.js"), // Only necessary when you need <p:clientValidator>.
    @ResourceDependency(library="primefaces", name="validation/beanvalidation.js") // Only necessary when you use JSR303 bean validation.
})
public class HeadRenderer extends Renderer {

    @Override
    public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
        context.getResponseWriter().startElement("head", component);
    }

    @Override
    public void encodeChildren(FacesContext context, UIComponent component) throws IOException {
        // NOOP.
    }

    @Override
    public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
        for (UIComponent resource : context.getViewRoot().getComponentResources(context, "head")) {
            resource.encodeAll(context);
        }

        context.getResponseWriter().endElement("head");
    }

}

To get it to run, register it as follows in faces-config.xml:

<render-kit>
    <renderer>
        <component-family>javax.faces.Output</component-family>
        <renderer-type>javax.faces.Head</renderer-type>
        <renderer-class>com.example.HeadRenderer</renderer-class>
    </renderer>
</render-kit>

This way those PrimeFaces resources will be combined by CombinedResourceHandler as well, as first resource.

Note that the primefaces.js is only necessary when at least one of those JS files are declared, because they need to be included after the primefaces.js.

Note that you can this way not make use of the PrimeFaces specific <facet name="first|middle|last"> facets in <h:head> to render resources at a specific location in the HTML <head>. But this !PrimeFaces-specific feature is already not recognized by CombinedResourceHandler anyway.