JetCracker

Life-time learner's blog

[JSF 2] [Overriding Renderer] How to add custom attributes to component

I’ve been working with JSF for almost a year, and sometimes standard components are not enough for rendering rich  HTML5  pages.

For example, if we need to have a placeholder attribute on a particular HTML5 input element, we just write:

<input type="text" name="login" value="" placeholder="User name"/>

Unfortunately, standard JSF 2 library’s component InputText was designed for HTML4 and doesn’t support attribute ‘placeholder’ (as well as other non-standard attributes, such as ‘autofocus’, ‘autocomplete’). So, the following code won’t work because the ‘placeholder’ attribute will be ignored by standard JSF TextRenderer.

<h:inputText value="#{loginBean.login}" placeholder="User name"/>

What if we really need to have placeholder in our input components? One solution would be using third-party libraries, such as PrimeFaces, or RichFaces. The provide placeholder (they call it ‘Water Mark’) and a lot of other features out of the box.

This solution has its drawbacks. Third-party libraries (like PrimeFaces) are quite difficult to cusomize, for the one thing. Besides, they have got their own issues and may not work as we expect. Also, sometimes in some projects we are not able to use these libraries.

We used PrimeFaces 3.1.1 library in our project Reshaka.Ru, and the library really disappointed me. File Upload component doesn’t work properly, dialogs are incorrectly rendered in most browsers. There are many bugs, and it seems to me that PrimeFaces team is not going to fix them. That’s why in our future projects, I won’t use it.

Let’s get back to the main point. 🙂 In order to add custom attribute, we can override standart JSF component Renderer.

An example how to do it you can find here: http://stackoverflow.com/questions/6859520/adding-custom-attribute-html5-support-to-jsf-2-0-uiinput-component. Here is what I did in my project.

Firstly, create custom renderer classes.

package com.jetcracker.web.jsf.extensions;

import com.sun.faces.renderkit.html_basic.TextRenderer;
import java.io.IOException;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.context.ResponseWriterWrapper;

/**
 * A simple renderer for h:inputText
 * which allows to add some custom attributes.
 */
public class InputTextRenderer extends TextRenderer {

    private static final String[] attributes = new String[] {
                                "placeholder", "type"
                            };

    @Override
    protected void getEndTextToRender(FacesContext context,
                                      UIComponent component,
                                      String currentValue) throws IOException
    {
        final ResponseWriter originalResponseWriter = context.getResponseWriter();

        context.setResponseWriter(new ResponseWriterWrapper() {

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

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

                if("input".equalsIgnoreCase(name)){
                    for(String attr : attributes) {
                        final String value = (String)component.getAttributes().get(attr);
                        if (value!=null) {
                            super.writeAttribute(attr, value, attr);
                        }
                    }
                }
            }
        });

        super.getEndTextToRender(context, component, currentValue);
        context.setResponseWriter(originalResponseWriter); // Restore original writer.
    }

}

 

package com.jetcracker.web.jsf.extensions;

import com.sun.faces.renderkit.html_basic.SecretRenderer;
import java.io.IOException;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.context.ResponseWriterWrapper;

/**
 * A simple renderer for h:inputSecret
 * which allows to add some custom attributes.
 */
public class InputSecretRenderer extends SecretRenderer {

    private static final String[] attributes = new String[] {
                                "placeholder"
                            };

    @Override
    protected void getEndTextToRender(FacesContext context,
                                      UIComponent component,
                                      String currentValue) throws IOException
    {
        final ResponseWriter originalResponseWriter = context.getResponseWriter();

        context.setResponseWriter(new ResponseWriterWrapper() {

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

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

                if("input".equalsIgnoreCase(name)){
                    for(String attr : attributes) {
                        final String value = (String)component.getAttributes().get(attr);
                        if (value!=null) {
                            super.writeAttribute(attr, value, attr);
                        }
                    }
                }
            }
        });

        super.getEndTextToRender(context, component, currentValue);
        context.setResponseWriter(originalResponseWriter); // Restore original writer.
    }

}

 

package com.jetcracker.web.jsf.extensions;

import com.sun.faces.renderkit.html_basic.TextareaRenderer;
import java.io.IOException;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.context.ResponseWriterWrapper;

/**
 * A simple renderer for h:inputTextarea
 * which allows to add some custom attributes.
 */
public class InputTextareaRenderer extends TextareaRenderer {

    private static final String[] attributes = new String[] {
                                "placeholder"
                            };

    @Override
    protected void getEndTextToRender(FacesContext context,
                                      UIComponent component,
                                      String currentValue) throws IOException
    {
        final ResponseWriter originalResponseWriter = context.getResponseWriter();
        context.setResponseWriter(new ResponseWriterWrapper() {

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

            @Override
            public void startElement(String name, UIComponent component) throws IOException {
                super.startElement(name, component);
                System.err.println(name);
                if("textarea".equalsIgnoreCase(name)){
                    for(String attr : attributes) {
                        final String value = (String)component.getAttributes().get(attr);
                        if (value!=null) {
                            super.writeAttribute(attr, value, attr);
                        }
                    }
                }
            }
        });

        super.getEndTextToRender(context, component, currentValue);
        context.setResponseWriter(originalResponseWriter); // Restore original writer.
    }

}

Secondly, add configuration setting to your faces-config.xml (it is in the same diretory as web.xml, usually WEB-INF). If it doesn’t exist, create it. It should look like this:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_1.xsd"
    version="2.1">
    <application>

        <render-kit>
            <renderer>
                <component-family>javax.faces.Input</component-family>
                <renderer-type>javax.faces.Text</renderer-type>
                <renderer-class>com.jetcracker.web.jsf.extensions.InputTextRenderer</renderer-class>
            </renderer>
            <renderer>
                <component-family>javax.faces.Input</component-family>
                <renderer-type>javax.faces.Secret</renderer-type>
                <renderer-class>com.jetcracker.web.jsf.extensions.InputSecretRenderer</renderer-class>
            </renderer>
            <renderer>
                <component-family>javax.faces.Input</component-family>
                <renderer-type>javax.faces.Textarea</renderer-type>
                <renderer-class>com.jetcracker.web.jsf.extensions.InputTextareaRenderer</renderer-class>
            </renderer>
        </render-kit>

    </application>

</faces-config>

It should work. If it won’t, add the following context parameter to your web.xml:

    <context-param>
        <param-name>javax.faces.CONFIG_FILES</param-name>
        <param-value>
            /WEB-INF/faces-config.xml, /faces-config.xml
        </param-value>
    </context-param>

That’s it! Enjoy!

Advertisements

13 responses to “[JSF 2] [Overriding Renderer] How to add custom attributes to component

  1. Pingback: [Java Web] [QapTcha] How to add captcha to JSF page « JetCracker

  2. jetcracker March 6, 2013 at 00:16

    Reblogged this on EasyTag and commented:

    A simple and clean way of how to enable “placeholder” and “type” HTML5 attributes in JSF components!

  3. himadri.dandapat@wipro.com June 19, 2013 at 19:16

    Hi,

    I have added your pice of code in my app but still im getting error that

    Attribute placeholder invalid for tag inputText according to TLD

    i have try with adding the faces-config.xml location in web.xml but im getting the same error. i have added the placeholder attribute in field and have added “InputTextRenderer.java” in the source code folder and have mapped the path in faces-config.xml as you mentioned above,

    One more query i have is that you have wrote the tag inside . but in my case im not able to declare inside

    the DTD of faces-config.xml is

    Thanks in advance. 🙂

    • jetcracker August 15, 2013 at 03:52

      NetBeans and other IDEs still don’t recognize new tags. But if you deploy your app and open the page you will see that the tag you added now works (you will see that placeholder tag is not ignored any more and should be rendered properly).

      But again, in editors of IDE this tag is still underlined and marked as error (because technically the source xhtml page is not valid)

      Also, the latest NetBeans and JSF support HTML5 (there are special tags for HTML5 in JSF 2.2).

  4. himadri June 19, 2013 at 19:22

    I have added your pice of code in my app but still im getting error that

    Attribute placeholder invalid for tag inputText according to TLD

    i have try with adding the faces-config.xml location in web.xml but im getting the same error. i have added the placeholder attribute in field and have added “InputTextRenderer.java” in the source code folder and have mapped the path in faces-config.xml as you mentioned above,

    One more query i have is that you have wrote the tag “render-kit” inside “application” . but in my case im not able to declare inside “application”

    the DTD of faces-config.xml is “web-facesconfig_2_0.xsd”

    Thanks in advance.

    • jetcracker March 1, 2014 at 21:22

      I get the same warning message in NetBeans, but I am able to run the app (I just ignore it).

  5. Keijo October 12, 2013 at 09:36

    Does the new response writer need to be closed?

    • jetcracker October 13, 2013 at 20:07

      I don’t think so. But I am not sure.

  6. Bashan April 6, 2014 at 13:49

    Unbelievable. SO MUCH WORK for such a simple thing. It shows how deeply some of the concepts of JSF are damaged.

    • jetcracker May 3, 2014 at 01:29

      I agree. And also it has some known bugs! However, JSF is slowly getting better (they added native support of HTML5 attributes in v2.2)… maybe it will be very simple and transparent in version 3,0…

  7. Pingback: [PrimeFaces] Due approcci per customizzare i tag di PrimeFaces | Francesco Ficetola

  8. kpalchemist October 29, 2014 at 20:03

    BTW, belongs OUTSIDE {{}}, not inside it. See https://github.com/primefaces-extensions/core/blob/master/src/main/resources/META-INF/faces-config.xml

  9. kpalchemist October 29, 2014 at 20:05

    BTW, the <render-kit> element belongs OUTSIDE <application>, not inside it.

    See https://github.com/primefaces-extensions/core/blob/master/src/main/resources/META-INF/faces-config.xml

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

%d bloggers like this: