myfaces-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "Lutz Ulruch (JIRA)" <>
Subject [jira] [Created] (TOMAHAWK-1579) Wrong source parameter for JSF AJAX onchange event code since there is no DOM element for HtmlSelectOneRadio
Date Wed, 27 Apr 2011 14:58:03 GMT
Wrong source parameter for JSF AJAX onchange event code since there is no DOM element for HtmlSelectOneRadio

                 Key: TOMAHAWK-1579
             Project: MyFaces Tomahawk
          Issue Type: Bug
          Components: selectOneRadio / radio
    Affects Versions: 1.1.10
         Environment: Tomahawk 1.1.20 for JSF 2, Mojarra 2.1
            Reporter: Lutz Ulruch


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,
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 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);
                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);
myWriter, uiComponent, behaviors);

                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);
                facesContext.getAttributes().put(UIComponent.CURRENT_COMPONENT, originalCurrentComp);
            // end of code replacement
            HtmlRendererUtils.renderHTMLAttributes(writer, uiComponent, HTML.INPUT_PASSTHROUGH_ATTRIBUTES_WITHOUT_DISABLED_AND_STYLE);

        if (isDisabled(facesContext, uiComponent))
Boolean.TRUE, null);


        return itemId;

This message is automatically generated by JIRA.
For more information on JIRA, see:

View raw message