Wrong source parameter for JSF AJAX onchange event code since there is no DOM
element for HtmlSelectOneRadio
--------------------------------------------------------------------------------------------------------------
Key: TOMAHAWK-1579
URL: https://issues.apache.org/jira/browse/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
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;
}
--
This message is automatically generated by JIRA.
For more information on JIRA, see: http://www.atlassian.com/software/jira