12

I am trying to write a renderer which would process the placeholder attribute on an <h:inputText> component. I headed to this path after reading JSF 2.0 strips out needed HTML5 attributes and it seems correct. Here's my custom renderer

public class InputRenderer extends com.sun.faces.renderkit.html_basic.TextRenderer{

    @Override
    public void encodeBegin(FacesContext context, UIComponent component) 
    throws IOException {
        System.out.println("Rendering :"+component.getClientId());

        String placeholder = (String)component.getAttributes().get("placeholder");
        if(placeholder != null) { 
            ResponseWriter writer = context.getResponseWriter();
            writer.writeAttribute("placeholder", placeholder, "placeholder");
        }

        super.encodeBegin(context, component);

    }


    @Override
    public void decode(FacesContext context, UIComponent component) {
        super.decode(context, component);
    }

    @Override
    public void encodeEnd(FacesContext context, UIComponent component) 
    throws IOException {
        super.encodeEnd(context, component);
    }
}

And this renderer is registered in faces config as

 <render-kit>
    <renderer>
        <component-family>javax.faces.Input</component-family>
        <renderer-type>javax.faces.Text</renderer-type>
        <renderer-class>com.example.renderer.InputRenderer</renderer-class>
    </renderer>
</render-kit>

This gets registered fine, no issues there.

My intention is to process the placeholder attribute, insert it, and then delegate the processing to super. My above code doesn't work because I'm inserting the attribute at a wrong place. It must be inserted after writer.startElement('input') has executed. However, the startElement must be happening somewhere in the super's encodeBegin() method. So how do I insert a custom attribute ('placeholder' in this case) and then continue the execution flow?

NB: The above code does add a placeholder attribute but not to the input component that I intend to, It writes it to the parent of the Input (since I'm trying to write an attribute before the component itself is actually written in the stream, it applies the attribute to the current component)

Community
  • 1
  • 1
Niks
  • 4,802
  • 4
  • 36
  • 55

3 Answers3

22

This is my way. I added placeholder and data-theme attributes. If you want to add more attributes, you should just add its name to attributes array.

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import com.sun.faces.renderkit.html_basic.TextRenderer;

public class InputRender extends TextRenderer {

    @Override
    protected void getEndTextToRender(FacesContext context,
            UIComponent component,
            String currentValue)
     throws java.io.IOException{

        String [] attributes = {"placeholder","data-theme"};

        ResponseWriter writer = context.getResponseWriter();

        for(String attribute : attributes)
        {
            String value = (String)component.getAttributes().get(attribute);
            if(value != null) {                             
                writer.writeAttribute(attribute, value, attribute);
            }
        }

        super.getEndTextToRender(context, component, currentValue);

    }

}

You should add this to faces-config.xml file.

 <render-kit>
    <renderer>
        <component-family>javax.faces.Input</component-family>
        <renderer-type>javax.faces.Text</renderer-type>
        <renderer-class>your.package.InputRenderer</renderer-class>
    </renderer>
</render-kit>
mb.akturk
  • 301
  • 2
  • 7
  • This is by far the most practical and best answer! :) – Niks Nov 07 '12 at 13:06
  • 1
    Thanks for the response and while this is a fair answer the code has a bug. The return of get(attribute) is an Object, which may be a Boolean (consider the `required` attribute for example). Remove the (String) casting entirely and set return-type as Object. – Darrell Teague Jan 21 '13 at 19:52
  • 1
    Note that this does not work with `` parent with `` children. JSF puts the `required` attribute in the `` element instead of the `` element. Did I say I love JSF? – Darrell Teague Jan 21 '13 at 19:54
  • 1
    @DarrellTeague It also puts the placeholder to div element when the inputText is a child of a div. Is there a way to solve that issue? – cubbuk Jan 23 '13 at 09:32
  • This was immensely useful information. It saves a lot of headaches on the view side and is extremely simple. – Daniel B. Chapman May 11 '13 at 23:24
  • Best answer, But i want to know how to do the same for custom component which is declared inside the taglib.xml facelets – Sasi Dhivya Jun 06 '17 at 11:20
7

You can just override ResponseWriters startElement method, that method is only called once and then you can restore to the original responsewriter object.

import javax.faces.context.*;
import java.io.IOException;

public class InputRenderer extends com.sun.faces.renderkit.html_basic.TextRenderer{

      // Put all of the attributes you want to render here...
      private static final String[] ATTRIBUTES = {"required","placeholder"};

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

            @Override
// As of JSF 1.2 this method is now public.
            public ResponseWriter getWrapped() {
                return originalResponseWriter;
            }   

            @Override
            public void startElement(String name, UIComponent component)
                    throws IOException {
                super.startElement(name, component);
if ("input".equals(name)) {
  for (String attribute : ATTRIBUTES)
  {
    Object value = component.getAttributes().get(attribute);
    if (value != null)
    {
      super.writeAttribute(attribute,value,attribute);
} 
  }
}   
        });
        super.getEndTextToRender(context, component, currentValue);
        context.setResponseWriter(originalResponseWriter); // Restore original writer.
    }



}
Will Vousden
  • 32,488
  • 9
  • 84
  • 95
dileks
  • 71
  • 1
  • 1
  • The above works better (though missing some imports, I corrected getWrapped() as in JSF 1.2 is now public, etc). I think this is the best answer. It also is probably better to use if ("constant".equals(value)) as that handle the null-check as the constant will never equal null but won't throw a NPE. I now officially despise the convolution of JSF as a somehow better alternative but appreciate all of the good feedback here (thanks to Joel for backing StackOverflow). – Darrell Teague Jan 31 '13 at 22:27
3

And to override for MyFaces 2.0.8+

package com.hsop.abc.eld;

import java.io.IOException;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import org.apache.myfaces.renderkit.html.HtmlTextRenderer;

public class InputRenderer extends HtmlTextRenderer
{
    @Override
    protected void renderInputBegin(FacesContext context, UIComponent component)
            throws IOException
    {
        // TODO Auto-generated method stub
        super.renderInputBegin(context, component);

    Object placeholder = component.getAttributes().get("placeholder");
    if(placeholder != null) { 
        ResponseWriter writer = context.getResponseWriter();
        writer.writeAttribute("placeholder", placeholder, "placeholder");
    }

    }
}
Madhuri
  • 31
  • 2