Question

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?

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top