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.
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()
.