Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to render a composite component using a custom renderer?

I would like to know how to render a composite component, through Java, I mean I have:

<myowntags:selectOneRadio>
  <f:selectItem itemValue="value0" itemLabel="This is the value 0" />
  <f:selectItem itemValue="value1" itemLabel="This is the value 1" />
  <f:selectItem itemValue="value2" itemLabel="This is the value 2" />
</myowntags:selectOneRadio>

or

<myowntags:selectOneRadio>
  <f:selectItems  value="#{controller.items}"  />
</myowntags:selectOneRadio>

and I would like to create a Java class to render it.

I know how to render a custom component without using composite, but since, to render a component I have to specify some values on the block:

<renderer>
    <component-family>javax.faces.SelectBoolean</component-family>
    <renderer-type>javax.faces.Checkbox</renderer-type>
    <renderer-class>com.myapp.CustomCheckboxRenderer</renderer-class>
</renderer>

then I get lost, because I don't know the values of those parameters inside the render tag for a composite component.

Thanks in advance, Angel.

like image 982
Angel Avatar asked Dec 07 '22 12:12

Angel


1 Answers

First, you need to have a backing component which implements NamingContainer and returns "javax.faces.NamingContainer" as component family. This is required by composite components, you can't change that part. The UINamingContainer implementation already does that, so if you can just extend from it.

@FacesComponent("mySelectOneRadio")
public class MySelectOneRadio extends UINamingContainer {
    // ...
}

Or if you rather want to extend from UISelectOne, then you'd have to implement the NamingContainer interface and make sure that you return UINamingContainer.COMPONENT_FAMILY in the getFamily() override.

Then, you need to specify it in <cc:interface componentType>.

<cc:interface componentType="mySelectOneRadio">

Note that at this step you can already perform the rendering (encoding) through Java. Just override the encodeChildren() method.

@FacesComponent("mySelectOneRadio")
public class MySelectOneRadio extends UINamingContainer {

    @Override
    public void encodeChildren(FacesContext context) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        writer.startElement("div", this);
        writer.writeText("hello world", null);
        writer.endElement("div");
    }

}

Coming back to your concrete question, you'd thus like to have a standalone Renderer class for this. That's fine. For that you need to extend Renderer:

@FacesRenderer(componentFamily=UINamingContainer.COMPONENT_FAMILY, rendererType=MySelectOneRadioRenderer.RENDERER_TYPE)
public class MySelectOneRadioRenderer extends Renderer {

    public static final String RENDERER_TYPE = "com.example.MySelectOneRadio";

    @Override
    public void encodeChildren(FacesContext context, UIComponent component) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        writer.startElement("div", component);
        writer.writeText("hello world", null);
        writer.endElement("div");
    }

}

The backing component should be changed as follows in order to properly register this renderer as the default renderer (don't override getRendererType() method! otherwise you or anyone else would be unable to change this by <renderer> in faces-config.xml):

@FacesComponent("myComposite")
public class MyComposite extends UINamingContainer {

    public MyComposite() {
        // Set default renderer.
        setRendererType(MySelectOneRadioRenderer.RENDERER_TYPE);
    }

}

Note that thanks to the @FacesRenderer, you don't need to hassle with faces-config.xml.


Whatever way you choose to encode the children, you can get component's children just by UIComponent#getChildren(). When you're inside MySelectOneRadio component:

if (getChildCount() > 0) {
    for (UICompnent child : getChildren()) {
        // ...
    }
}

Or when you're inside MySelectOneRadioRenderer renderer:

if (component.getChildCount() > 0) {
    for (UICompnent child : component.getChildren()) {
        // ...
    }
}

To delegate to the component's own default rendering, invoke super.encodeChildren() or component.encodeChildren(). To delegate to child's own default rendering, invoke child.encodeAll().

See also:

  • What is the relationship between component family, component type and renderer type?
like image 139
BalusC Avatar answered Dec 21 '22 10:12

BalusC