Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding custom attribute (HTML5) support to Primefaces (3.4)

In trying to implement the simple html5 attribute 'autofocus' in my JSF/Primefaces web-application, I was alerted to the fact that the components do not pass all unknown attributes on to the final markup. I can understand reasonings for this, as components can be complex combinations of html markup and it would not be clear where to place the attributes if they are not already well-defined by the component.

But the best solution for me is to have support for autofocus (and any other possible types of attributes I may want to support in my application that primefaces has not defined).

I have seen Adding custom attribute (HTML5) support to JSF 2.0 UIInput component, but that seems to apply for the basic JSF components and does not work for PrimeFaces components.

How do I extend the component/rendering of Primefaces to support this?

like image 269
Rich Avatar asked Mar 22 '13 16:03

Rich


1 Answers

Instead of homegrowing a custom renderer for every single individual component, you could also just create a single RenderKit wherein you provide a custom ResponseWriter wherein the startElement() method is overriden to check the element name and/or component instance and then write additional attributes accordingly.

Here's a kickoff example of the HTML5 render kit:

public class Html5RenderKit extends RenderKitWrapper {

    private RenderKit wrapped;

    public Html5RenderKit(RenderKit wrapped) {
        this.wrapped = wrapped;
    }

    @Override
    public ResponseWriter createResponseWriter(Writer writer, String contentTypeList, String characterEncoding) {
        return new Html5ResponseWriter(super.createResponseWriter(writer, contentTypeList, characterEncoding));
    }

    @Override
    public RenderKit getWrapped() {
        return wrapped;
    }

}

The HTML5 response writer:

public class Html5ResponseWriter extends ResponseWriterWrapper {

    private static final String[] HTML5_INPUT_ATTRIBUTES = { "autofocus" };

    private ResponseWriter wrapped;

    public Html5ResponseWriter(ResponseWriter wrapped) {
        this.wrapped = wrapped;
    }

    @Override
    public ResponseWriter cloneWithWriter(Writer writer) {
        return new Html5ResponseWriter(super.cloneWithWriter(writer));
    }

    @Override
    public void startElement(String name, UIComponent component) throws IOException {
        super.startElement(name, component);

        if ("input".equals(name)) {
            for (String attributeName : HTML5_INPUT_ATTRIBUTES) {
                String attributeValue = component.getAttributes().get(attributeName);

                if (attributeValue != null) {
                    super.writeAttribute(attributeName, attributeValue, null);
                }
            }
        }
    }

    @Override
    public ResponseWriter getWrapped() {
        return wrapped;
    }

}

To get it to run, create this HTML5 render kit factory:

public class Html5RenderKitFactory extends RenderKitFactory {

    private RenderKitFactory wrapped;

    public Html5RenderKitFactory(RenderKitFactory wrapped) {
        this.wrapped = wrapped;
    }

    @Override
    public void addRenderKit(String renderKitId, RenderKit renderKit) {
        wrapped.addRenderKit(renderKitId, renderKit);
    }

    @Override
    public RenderKit getRenderKit(FacesContext context, String renderKitId) {
        RenderKit renderKit = wrapped.getRenderKit(context, renderKitId);
        return (HTML_BASIC_RENDER_KIT.equals(renderKitId)) ? new Html5RenderKit(renderKit) : renderKit;
    }

    @Override
    public Iterator<String> getRenderKitIds() {
        return wrapped.getRenderKitIds();
    }

}

And register it as follows in faces-config.xml:

<factory>
    <render-kit-factory>com.example.Html5RenderKitFactory</render-kit-factory>
</factory>

The JSF utility library OmniFaces has also such a render kit, the Html5RenderKit (source code here) which should theoretically also work fine on PrimeFaces components. However, this question forced me to take a second look again and I was embarrassed to see that the component argument in ResponseWriter#startElement() is null in <p:inputText> (see line 74 of InputTextRenderer, it should have been writer.startElement("input", inputText) instead). I'm not sure if this is intentional or an oversight in the design of the PrimeFaces renderer or not, but you could use UIComponent#getCurrentComponent() instead to get it.


Update: this is fixed in OmniFaces 1.5.


Noted should be that the upcoming JSF 2.2 will support defining custom attributes in the view via the new passthrough namespace or the <f:passThroughAttribute> tag. See also What's new in JSF 2.2? - HTML5 Pass-through attributes.

Thus, so:

<html ... xmlns:p="http://java.sun.com/jsf/passthrough">
...
<h:inputText ... p:autofocus="true" />

(you may want to use a instead of p as namespace prefix to avoid clash with PrimeFaces' default namespace)

Or:

<h:inputText ...>
    <f:passThroughAttribute name="autofocus" value="true" />
</h:inputText>
like image 61
BalusC Avatar answered Oct 12 '22 23:10

BalusC