I'm having problems with a custom component I'm writing in that it won't render any nested controls. The component is a simple layout control, very loosely adapted from the ApplicationLayout control in the Extension Library. The XPage code looks like this:
<px:exampleControl id="exampleControl1">
<xp:span styleClass="mySpan">Inner Text</xp:span>
</px:exampleControl>
The exampleControl will render fine but the nested span won't. My basic renderer code is:
public class ExampleRenderer extends Renderer {
@Override
public void encodeBegin(FacesContext context, UIComponent component)
throws IOException {
ResponseWriter writer = context.getResponseWriter();
writer.startElement("div", component);
writer.writeAttribute("class", "custom-banner", null);
writer.endElement("div");
writer.startElement("div", component);
writer.writeAttribute("class", "main-body", null);
}
@Override
public boolean getRendersChildren() {
return true;
}
@Override
public void encodeChildren(FacesContext context, UIComponent component) {
try {
super.encodeChildren(context, component);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void encodeEnd(FacesContext context, UIComponent component)
throws IOException {
ResponseWriter writer = context.getResponseWriter();
writer.endElement("div");
writer.startElement("div", component);
writer.writeAttribute("class", "custom-footer", null);
writer.endElement("div");
}
}
I've even attempted to use a specific render function borrowed from the Extension Library renderers (the utility functions in AbstractApplicationLayoutRenderer.java) but component.getChildCount() always returns 0.
So why aren't the nested controls rendering and what am I missing?
I finally figured this out. The Renderer code was a bit of a red herring because, as Stephan (stwissel) commented, Renderer.encodeChildren does nothing. Looking at the source of several ExtLib components, none of them use it.
The change I made was to the extension class. After reading Keith Strickland's blog (http://www.keithstric.com/A55BAC/keithstric.nsf/default.xsp?documentId=82770C11FA7B9B21852579C100581766 ) and examining com.ibm.xsp.extlib.component.layout.UIVarPublisherBase , I implemented the FacesComponent interface. In particular, I wanted to call the buildContents method which invokes the FacesComponentBuilder. The JavaDoc for FacesComponent has this to say about FacesComponent:
buildContents Build the component children and facets, the default implementation is usually: builder.buildAll(context, this, true); // includeFacets=true
My extension class ended up looking like this:
package com.example.component;
import javax.faces.FacesException;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
import com.ibm.xsp.component.FacesComponent;
import com.ibm.xsp.page.FacesComponentBuilder;
public class CustomLayout extends UIComponentBase implements FacesComponent {
public CustomLayout(){
super();
setRendererType("com.example.applicationlayout");
}
@Override
public String getFamily() {
return "com.example.applicationlayout";
}
public void buildContents(FacesContext context, FacesComponentBuilder builder)
throws FacesException {
builder.buildAll(context, this, true);
}
public void initAfterContents(FacesContext context) throws FacesException {
}
public void initBeforeContents(FacesContext context) throws FacesException
}
}
It now seems to work as I wanted.
As an aside, other objects in the ExtLib (UIWidgetContainer and UIList specifically) have different ways of rendering their children without implementing FacesComponent. That probably requires a blog post.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With