Skip to main content

Custom Renderer Doesn't Render Children on AJAX Postback

1 reply [Last post]
dperriero
Offline
Joined: 2010-03-23
Points: 0

I've encountered a problem when creating my own custom renderer where I refresh a UIData component through a polling AJAX request and no children of the UIData component re-render. On the initial GET, the renderer displays the UIData table perfectly fine, but when the call to the jsf.ajax.request responds back, only the table renders and the UIColumns don't re-render.

Here's my example:

public class TestRenderer extends Renderer {

@Override
public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
super.encodeBegin(context, component);

TestObject obj = (TestObject) component.getAttributes().get("testObject");

component.getChildren().clear();

if (obj != null) {
UIComponent objComp = obj.getObjectComponent();

if (objComp != null) {
component.getChildren().add(objComponent);
}
}
}

@Override
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
super.encodeEnd(context, component);
}
}

I'm calling this renderer through a proxy tag defined through a componentType.
Do I need to recursively encode the children of the base UIData component by overriding the encodeChildren method?

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
dperriero
Offline
Joined: 2010-03-23
Points: 0

I have solved my own problem.

When you implement your own custom renderer and try to update the component within the component tree through AJAX, you must implement a Phase Listener in tandem to update the client ID of the component at the RENDER RESPONSE phase.

You can achieve a partial page update by visiting the phase client IDs through the visitTree method accessible through the FacesContext viewRoot. When calling the visitTree method you will need to set up an inner class VisitCallback to access each component associated with it's client id.

Here is an example: (Feel free to interject any code review comments)

public class TestPhaseListener implements PhaseListener {
@Override
public void afterPhase(PhaseEvent event) { }

@Override
public void beforePhase(PhaseEvent event) {
FacesContext context = FacesContext.getCurrentInstance();
ExternalContext extContext = context.getExternalContext();

if (context.getPartialViewContext.isAjaxRequest()) {
try {
List phaseClientIds = (List) context.getPartialViewContext.getRenderIds();
EnumSet hints = EnumSet.of(VisitHint.SKIP_UNRENDERED);
PartialVisitContext visitContext = (PartialVisitContext) VisitContext.createVisitContext(context, phaseClientIds, hints);

extContext.setResponseContentType("text/xml");
extContext.addResponseHeader("Cache-Control", "no-cache");

PartialResponseWriter writer = context.getPartialViewContext().getPartialResponseWriter();
writer.startDocument();

context.getViewRoot().visitTree(visitContext, RENDER_RESPONSE);

writer.endDocument();
writer.flush();
context.responseComplete();
} catch (Exception e) {
e.printStackTrace();
}
}
}

@Override
public PhaseId getPhaseId() {
return PhaseId.RENDER_RESPONSE;
}

public static final VisitCallback RENDER_RESPONSE = new VisitCallback() {
public VisitResult visit(VisitContext visitContext, UIComponent component) {
FacesContext facesContext = visitContext.getFacesContext();
PartialResponseWriter writer = facesContext.getPartialViewContext().getPartialResponseWriter();
MyComponent myComponent = null;

ValueExpression ve = component.getParent().getValueExpression("myComponent");
if (ve != null) {
myComponent = (MyComponent) ve.getValue(facesContext.getELContext());
}

try {
writer.startUpdate(component.getClientId());

if (myComponent != null) {
UIComponent compositeComponent = myComponent.getCompositeComponent();

if (compositeComponent != null) {
component.getParent().getChildren().add(compositeComponent);
compositeComponent.encodeAll(facesContext);
}
}

writer.endUpdate();
} catch (Exception e) {
e.printStackTrace();
}

return VisitResult.REJECT;
}
};
}