MyFaces Tomahawk
  1. MyFaces Tomahawk
  2. TOMAHAWK-1579

Wrong source parameter for JSF AJAX onchange event code since there is no DOM element for HtmlSelectOneRadio

    Details

    • Type: Bug Bug
    • Status: Closed
    • Priority: Major Major
    • Resolution: Duplicate
    • Affects Version/s: 1.1.10
    • Fix Version/s: None
    • Component/s: selectOneRadio / radio
    • Labels:
      None
    • Environment:
      Tomahawk 1.1.10 for JSF 2, Mojarra 2.1

      Description

      Hello,

      I'm using Tomahawk's HtmlSelectOneRadio (of Java package org.apache.myfaces.component.html.ext) with 'spread' layout
      and an attached AjaxBehavior for the default event ('valueChange').

      The script code rendered for the event does not work. No AJAX request is submitted.

      In org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlRadioRendererBase.renderRadio(FacesContext,UIInput,String,boolean,boolean,boolean,Integer) the passed UIInput component is the HtmlSelectOneRadio. HtmlRenderUtils.renderBehaviorizedxxx() is used to render the event handler code.
      The generated JavaScript code passes the clientId of the HtmlSelectOneRadio as the value of the JS function parameter 'source', for example:

      <input type="radio" onchange="mojarra.ab('j_id13:j_id51',event,'valueChange','@form',0,

      {'com.os.bellevue.faces.ajax.single':'true'}

      )" value="0" name="j_id13:j_id51" id="j_id13:j_id51:0">

      where 'j_id13:j_id51' is the clientId of HtmlSelectOneRadio. But, of course, there is no HTML DOM element with that ID.
      The renderer renders HTML elements for the HtmlRadio components attached to HtmlSelectOneRadio, but not for the HtmlSelectOneRadio.

      Now, when a radio button is selected and the value changes, the JSF 2.0 JavaScript stuff cannot find an element whose ID matches the clientId of HtmlSelectOneRadio. As a result, no AJAX request is submitted.

      I fixed that bug locally by re-implementing renderRadio(FacesContext,UIInput,String,boolean,boolean,boolean,Integer) in a way so the
      HtmlSelectOneRadio's clientId is replaced by 'this' (the this-reference to the <input type="radio">.
      Of course, I just tested that fix in my application, where I do not use any other events, but 'valueChange'.
      Also, the fix is probably not optimal in terms of performance and it is an ugly workaround:

      protected String renderRadio(FacesContext facesContext,
      UIInput uiComponent,
      String value,
      boolean disabled,
      boolean checked,
      boolean renderId,
      Integer itemNum) throws IOException
      {
      String clientId = uiComponent.getClientId(facesContext);

      String itemId = (itemNum == null)? null : clientId + UINamingContainer.getSeparatorChar(facesContext) + itemNum;

      ResponseWriter writer = facesContext.getResponseWriter();

      writer.startElement(HTML.INPUT_ELEM, uiComponent);

      if (itemId != null)

      { writer.writeAttribute(HTML.ID_ATTR, itemId, null); }

      else if (renderId)

      { writer.writeAttribute(HTML.ID_ATTR, clientId, null); }

      writer.writeAttribute(HTML.TYPE_ATTR, HTML.INPUT_TYPE_RADIO, null);
      writer.writeAttribute(HTML.NAME_ATTR, clientId, null);

      if (disabled)

      { writer.writeAttribute(HTML.DISABLED_ATTR, HTML.DISABLED_ATTR, null); }

      if (checked)

      { writer.writeAttribute(HTML.CHECKED_ATTR, HTML.CHECKED_ATTR, null); }

      if (value != null)

      { writer.writeAttribute(HTML.VALUE_ATTR, value, null); }

      Map<String, List<ClientBehavior>> behaviors = null;
      if (uiComponent instanceof ClientBehaviorHolder && JavascriptUtils.isJavascriptAllowed(facesContext.getExternalContext()))
      {
      behaviors = ((ClientBehaviorHolder) uiComponent).getClientBehaviors();

      // L. Ulrich:
      // original code:
      //HtmlRendererUtils.renderBehaviorizedOnchangeEventHandler(facesContext, writer, uiComponent, behaviors);
      //HtmlRendererUtils.renderBehaviorizedEventHandlers(facesContext, writer, uiComponent, behaviors);
      //HtmlRendererUtils.renderBehaviorizedFieldEventHandlersWithoutOnchange(facesContext, writer, uiComponent, behaviors);
      //HtmlRendererUtils.renderHTMLAttributes(writer, uiComponent, HTML.INPUT_PASSTHROUGH_ATTRIBUTES_WITHOUT_DISABLED_AND_STYLE_AND_EVENTS);
      // end of original code
      //
      // replaced by the following code.
      // It replaces the client ID in the script code rendered for ClientBehaviors.
      // Note that the script code rendered by
      // HtmlRendererUtils.renderBehaviorizedxxx()
      // uses the clientId of the passed uiComponent (which is
      // a HtmlSelectOneRadio, not a HtmlRadio) as value for the
      // source parameter of AJAX scripts
      // (like for jsf.ajax.request(source, event, options) ).
      // But since no HTML element is rendered for HtmlSelectOneRadio,
      // the HTML DOM does not contain an element with that ID.
      // As a result, script code fails to find an element with that ID
      // (JavaScript: Document.getElementById(source)) )
      // which in turn results in no AJAX request being send.
      //
      // I fixed that problem by using a temporary ResponseWriter
      // to write the event attributes.
      // After the attributes have been written,
      // the generated HTML fragment is parsed and
      // the HtmlSelectOneRadio's clientId is replaced by 'this',
      // that is: the DOM element of the HTML <input type="radio">.
      //
      // The fix also switches the value of CURRENT_COMPONENT
      // attribute of facesContext to the
      // org.apache.myfaces.component.html.ext.HtmlSelectOneRadio
      // which is the master for the org.apache.myfaces.custom.radio.HtmlRadio component
      // whose HTML is rendered by this method.
      // The switch was needed so RichFaces'
      // org.ajax4jsf.component.EventValueExpression.getComponent()
      // can find the AjaxComponent for HtmlSelectOneRadio.
      // Since we do no longer combine HtmlSelectOneRadio with
      // RichFaces, the fix for that problem might be obsolete.
      Object originalCurrentComp = facesContext.getAttributes().get(UIComponent.CURRENT_COMPONENT);

      try
      {
      facesContext.getAttributes().put(UIComponent.CURRENT_COMPONENT, uiComponent);

      StringWriter buff = new StringWriter();
      ResponseWriter originalWriter = facesContext.getResponseWriter();
      ResponseWriter myWriter = originalWriter.cloneWithWriter(buff);

      // L. Ulrich, 27.04.2011:
      // enclose the attributes in a dummy element.
      // Otherwise, the ResponseWriter may not write the
      // attributes in buff (at least, the implementation of
      // ResponseWriter in Mojarra 2.1.0 does not write the
      // attribute text to buff as long as there is no open+closed element).
      myWriter.startElement("dummy", uiComponent);

      HtmlRendererUtils.renderBehaviorizedOnchangeEventHandler(facesContext, myWriter, uiComponent, behaviors);
      HtmlRendererUtils.renderBehaviorizedEventHandlers(facesContext, myWriter, uiComponent, behaviors);
      HtmlRendererUtils.renderBehaviorizedFieldEventHandlersWithoutOnchange(facesContext, myWriter, uiComponent, behaviors);

      myWriter.endElement("dummy");

      myWriter.flush();

      String allBehaviorHtml = buff.toString();
      if (allBehaviorHtml.length() > 14)
      {
      int end = allBehaviorHtml.lastIndexOf('"') +1;
      String withoutDummy = allBehaviorHtml.substring(7, end);
      String modifiedHtml = withoutDummy.replaceAll("'" + clientId + "'", "this");

      for (int attrStart = modifiedHtml.indexOf('='); attrStart > 0; attrStart = modifiedHtml.indexOf('='))

      { String attrName = modifiedHtml.substring(0, attrStart).trim(); modifiedHtml = modifiedHtml.substring(attrStart +1); int valueStart = modifiedHtml.indexOf('"'); modifiedHtml = modifiedHtml.substring(valueStart +1); int valueEnd = modifiedHtml.indexOf('"'); String attrValue = modifiedHtml.substring(0, valueEnd); modifiedHtml = modifiedHtml.substring(valueEnd +1); originalWriter.writeAttribute(attrName, attrValue, null); }

      }

      HtmlRendererUtils.renderHTMLAttributes(writer, uiComponent, HTML.INPUT_PASSTHROUGH_ATTRIBUTES_WITHOUT_DISABLED_AND_STYLE_AND_EVENTS);
      }
      finally

      { facesContext.getAttributes().put(UIComponent.CURRENT_COMPONENT, originalCurrentComp); }

      // end of code replacement
      }
      else

      { HtmlRendererUtils.renderHTMLAttributes(writer, uiComponent, HTML.INPUT_PASSTHROUGH_ATTRIBUTES_WITHOUT_DISABLED_AND_STYLE); }

      if (isDisabled(facesContext, uiComponent))

      { writer.writeAttribute(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.DISABLED_ATTR, Boolean.TRUE, null); }

      writer.endElement(HTML.INPUT_ELEM);

      return itemId;
      }

        Issue Links

          Activity

          Hide
          Leonardo Uribe added a comment -

          This is a duplicate of TOMAHAWK-1551, which was already fixed. Could you try with the latest code to see if it is working well? I'll close this one as duplicate. Thanks for report it.

          Show
          Leonardo Uribe added a comment - This is a duplicate of TOMAHAWK-1551 , which was already fixed. Could you try with the latest code to see if it is working well? I'll close this one as duplicate. Thanks for report it.

            People

            • Assignee:
              Leonardo Uribe
              Reporter:
              Lutz Ulruch
            • Votes:
              0 Vote for this issue
              Watchers:
              0 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:

                Development