Question

Hello again JSF Gurus.

I am looking for help on custom renderers and ajax handlers. Our project cannot use the normal radio button and check box renderers as they render in tables and this is against our corporate standards. Neither can we use third party component libraries.

We have a custom renderer that we have used in a few projects now that works fine. However, on this particular project, I need to render the ajax click handler for some radio buttons. Following the advice in the answer to this question I have added the call to RenderKitUtils.renderSelectOnclick(); and whilst it appears to render the same javaScript as the standard renderer, and indeed, firebug network panel shows the outgoing request, my value change listener is not being fired and I am looking for help as to why (it fires perfectly well with the standard renderer)

Some code: Firstly, the creation of the ajax handler (the radio button is created programatically)

          AjaxBehavior valueChangeAction = (AjaxBehavior) FacesUtils.getApplication().createBehavior(AjaxBehavior.BEHAVIOR_ID);

        valueChangeAction.addAjaxBehaviorListener(new ProbeQuestionListener(currentQuestion, "probeDiv" + questionNumber,
            probeDiv));

        selectUI.addClientBehavior("valueChange", valueChangeAction);
        valueChangeAction.setRender(Collections.singletonList("probeDiv" + questionNumber));

Wih the standard renderer, this produces:

<table id="answer_1" class="marginLeft1a">
  <tbody>
    <tr>
      <td>
        <input id="answer_1:0" type="radio" onclick="mojarra.ab(this,event,'valueChange',0,'probeDiv2')" value="0" name="answer_1">
        <label for="answer_1:0"> Yes</label>
      </td><td>
         <input id="answer_1:1" type="radio" onclick="mojarra.ab(this,event,'valueChange',0,'probeDiv2')" value="1" name="answer_1">
         <label for="answer_1:1"> No</label>
     </td>
  </tr>

and all is well. The ajax request is posted and my server side listener gets fired.

The relevant parts of my custom renderer:

 public void encodeEnd(final FacesContext p_context, final UIComponent p_component) throws IOException {

    /* set up convenience fields */
    context = p_context;
    component = p_component;
    componentId = component.getClientId(context);

    // rendering divs etc omitted.

    while (iterator.hasNext()) {
        final UIComponent childComponent = iterator.next();
        // irrelevant code omited
        if ("javax.faces.SelectItem".equals(childComponent.getFamily())) {
            renderSelectItem(idIndex, childComponent);
            idIndex++;
        }
     }
 }

private void renderSelectItem(final int p_idIndex, final UIComponent p_childComponent) throws IOException {
        // unrelated code omitted
        writer.startElement("input", component);
        writer.writeAttribute("type", getInputType(), null);
        writer.writeAttribute("id", p_childComponentId, "id");
        writer.writeAttribute("name", componentId, "clientId");
        RenderKitUtils.renderSelectOnclick(context, component, false);
        writer.endElement("input");
        // unrelated code omitted
}

Update: The decode method in the base class for my renderer is:

    @Override
public void decode(final FacesContext p_context, final UIComponent p_component) {

    final Map<String, String> valuesMap = p_context.getExternalContext().getRequestParameterMap();

    final UIInput input = (UIInput) p_component; // NOSONAR

    final Object value = valuesMap.get(input.getClientId(p_context));
    // System.out.println("radio button decode: found " + value);
    input.setSubmittedValue(value);
}

This renders as :

<span class="marginLeft1a" id="answer_1">
    <label for="answer_1:0">
    <input type="radio" value="0" onclick="mojarra.ab(this,event,'valueChange',0,'probeDiv2')" name="answer_1" id="answer_1:0"> 
     Yes
     </label>
     <label for="answer_1:1">
       <input type="radio" value="1" onclick="mojarra.ab(this,event,'valueChange',0,'probeDiv2')" name="answer_1" id="answer_1:1"> 
       No
     </label>
</span>

As you can see, the javaScript click handler is identical, as are the name and id attribute values of the input tags. Although this triggers the Ajax request, the listener is not fired server side as it is with the first version.

Any ideas ?

Was it helpful?

Solution

The decode() method of the Renderer (or UIComponent if there's no renderer) is supposed to decode the ajax request based on javax.faces.behavior.event request parameter and trigger ClientBehavior#decode() on the matching client behaviors.

Basically, the logic is as follows:

Map<String, List<ClientBehavior>> behaviors = ((ClientBehaviorHolder) component).getClientBehaviors();

if (!behaviors.isEmpty()) {
    Map<String, String> params = context.getExternalContext().getRequestParameterMap();
    String behaviorEvent = params.get("javax.faces.behavior.event");

    if (behaviorEvent != null) {
        List<ClientBehavior> behaviorsForEvent = behaviors.get(behaviorEvent);

        if (behaviorsForEvent != null && !behaviorsForEvent.isEmpty()) {
           String behaviorSource = params.get("javax.faces.source");

           if (isBehaviorSource(context, behaviorSource, component.getClientId())) {
               for (ClientBehavior behavior: behaviorsForEvent) {
                   behavior.decode(context, component);
               }
           }
        }
    }
}

Given the fact that you're using RenderKitUtils and the generated HTML output contains mojarra.ab(), I'll assume that you're using Mojarra. In that case, it's perhaps wiser to extend your custom renderer from RadioRenderer instead which has all this logic already implemented in HtmlBasicRenderer and SelectManyCheckboxListRenderer so that you don't need to reinvent the wheel.

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