1

I want to use bootstrap with JSF 2.1, the most important thing is that data-toggle attribute can be used. The implementation for h:inputText works fine. However, when I tried to do the same with h:selectOneMenu the renderer doesn't seem to be called (I put a breakpoint on startElement method). I used a documentary approach to find the renderer type and the component family.

This is my SelectRenderer.java. I took as a reference dilek's code

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;

/**
 * @link https://stackoverflow.com/a/7886942/4253946
 */
public class InputRenderer extends com.sun.faces.renderkit.html_basic.TextRenderer {

    private static final String[] ATTRIBUTES = { "data-toggle" };

    @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.
    }
}

This is my faces-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<faces-config version="2.1" xmlns="http://java.sun.com/xml/ns/javaee">
    <render-kit>
        <renderer>
            <component-family>javax.faces.Input</component-family>
            <renderer-type>javax.faces.Text</renderer-type>
            <renderer-class>InputRenderer</renderer-class>
        </renderer>
        <renderer>
            <component-family>javax.faces.SelectOne</component-family>
            <renderer-type>javax.faces.Menu</renderer-type>
            <renderer-class>SelectRenderer</renderer-class>
        </renderer>
    </render-kit>
</faces-config>

fragment of index.xhtml

<div class="form-group">
    <h:outputLabel class="col-md-4 control-label" for="mySelect">my Label Select</h:outputLabel>
    <div class="col-md-7">
        <h:selectOneMenu  id="mySelect" name="mySelect" class="form-control" data-toggle='tooltip'>
            <f:selectItem itemValue="0" itemLabel="Dummy Label 1" />
            <f:selectItem itemValue="1" itemLabel="Dummy Label 1" />
        </h:selectOneMenu >
    </div>
</div>

UPDATE I I changed javax.faces.Input to javax.faces.SelectOne

¿Why the attribute doesn't pass to the rendered html?

Community
  • 1
  • 1
Edu
  • 159
  • 1
  • 14

1 Answers1

1

UPDATE II - ANSWER - I chose a mixed approach between dilek's code and Vladiator's code. I just need to create one helper class called AditionalAttributesWritter. Also, is a good idea to check out the code implementation.

import java.io.IOException;

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

/**
 * @link https://stackoverflow.com/a/7886942/4253946
 */
public class AditionalAttributesWritter extends ResponseWriterWrapper {
    private static final String[] ATTRIBUTES = { "data-toggle" };
    private ResponseWriter originalResponseWriter;

    public AditionalAttributesWritter(ResponseWriter originalResponseWriter) {
        super();
        this.originalResponseWriter = originalResponseWriter;
    }

    @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);
        for (String attribute : ATTRIBUTES) {
            Object value = component.getAttributes().get(attribute);
            if (value != null) {
                super.writeAttribute(attribute, value, attribute);
            }
        }
    }
}

and finally the SelectRenderer is like this

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

import java.io.IOException;

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

/**
 *  @link https://stackoverflow.com/questions/27324024/jsf-selectonemenu-extending-menurenderer-for-displaying-a-plain-text-when-there/27337240#27337240
 */
public class SelectRenderer extends MenuRenderer {
    @Override
    protected void renderSelect(FacesContext context, UIComponent component) throws IOException {
        final ResponseWriter originalResponseWriter = context.getResponseWriter();
        context.setResponseWriter(new AditionalAttributesWritter(originalResponseWriter));
        super.renderSelect(context, component);
        context.setResponseWriter(originalResponseWriter); // Restore original writer.
    }
}

and it works. If you think that this approach is not good enough please let me know.

Community
  • 1
  • 1
Edu
  • 159
  • 1
  • 14