How to update a TR with a4j:outputPanel
dunks80 May 24, 2007 3:23 PMThis is sort of a hack but was useful in my case...
1) Extend the AjaxOutputPanelRenderer to allow for rendering of a TR element...
//this was a cut an paste deal since the useful methods were marked private
public class ExtendedAjaxOutputPanelRenderer extends AjaxOutputPanelRenderer
{
private final String[] STYLE_ATTRIBUTES = new String[] { "style", "class" };
/*
* (non-Javadoc)
*
* @see org.ajax4jsf.framework.renderer.RendererBase#doEncodeBegin(javax.faces.context.ResponseWriter,
* javax.faces.context.FacesContext, javax.faces.component.UIComponent)
*/
protected void doEncodeBegin(ResponseWriter writer, FacesContext context, UIComponent component) throws IOException
{
UIAjaxOutputPanel panel = (UIAjaxOutputPanel) component;
if (!"none".equals(panel.getLayout()))
{
writer.startElement(getTag(panel), panel);
getUtils().encodeId(context, component);
getUtils().encodePassThru(context, component);
getUtils().encodeAttributesFromArray(context, component, STYLE_ATTRIBUTES);
}
}
/**
* @param panel
* @return
*/
private String getTag(UIAjaxOutputPanel panel)
{
// TODO Auto-generated method stub
if ("block".equals(panel.getLayout()))
{
return HTML.DIV_ELEM;
}
else if ("tr".equals(panel.getLayout()))
{
return HTML.TR_ELEMENT;
}
else
{
return HTML.SPAN_ELEM;
}
}
/*
* (non-Javadoc)
*
* @see org.ajax4jsf.framework.renderer.RendererBase#doEncodeEnd(javax.faces.context.ResponseWriter,
* javax.faces.context.FacesContext, javax.faces.component.UIComponent)
*/
protected void doEncodeEnd(ResponseWriter writer, FacesContext context, UIComponent component) throws IOException
{
UIAjaxOutputPanel panel = (UIAjaxOutputPanel) component;
if (!"none".equals(panel.getLayout()))
{
writer.endElement(getTag(panel));
}
if (panel.isKeepTransient())
{
markNoTransient(component);
}
}
/**
* Set "transient" flag to false for component and all its children ( recursive ).
*
* @param component
*/
private void markNoTransient(UIComponent component)
{
for (Iterator iter = component.getFacetsAndChildren(); iter.hasNext();)
{
UIComponent element = (UIComponent) iter.next();
markNoTransient(element);
element.setTransient(false);
}
}
}
2) install customer renderer in face-config.xml
<render-kit> <renderer> <description> Renderer for a4j output panel that renders a tr element </description> <component-family>javax.faces.Panel</component-family> <renderer-type>org.ajax4jsf.components.AjaxOutputPanelRenderer</renderer-type> <renderer-class>ExtendedAjaxOutputPanelRenderer</renderer-class> </renderer> </render-kit>
3) include the following overriden version of A4J.AJAX.XMLHttpRequest.prototype.updatePagePart method (b/c internet explorer 7 sucks and won't allow access to outerHTML for tr elements)
A4J.AJAX.XMLHttpRequest.prototype.updatePagePart = function (id) {
var newnode = this.getElementById(id);
if (!newnode) {
LOG.error("New node for ID " + id + " is not present in response");
return;
}
var oldnode = window.document.getElementById(id);
if (oldnode) {
var anchor = oldnode.parentNode;
// need to check for firstChild due to opera 8 bug with hasChildNodes
Sarissa.clearChildNodes(oldnode);
if (oldnode.outerHTML) {
LOG.debug("Replace content of node by outerHTML()");
var tagName = oldnode.tagName;
var newNodeValue = new XMLSerializer().serializeToString(newnode);
if (_SARISSA_IS_IE && "TR" == tagName) {
LOG.debug("oldnode.outerHTML=" + oldnode.outerHTML);
LOG.debug("newNodeValue=" + newNodeValue);
this.updateTable(oldnode, newNodeValue);
} else {
oldnode.outerHTML = newNodeValue;
}
} else {
var importednode;
importednode = window.document.importNode(newnode, true);
LOG.debug("Replace content of node by replaceChild()");
anchor.replaceChild(importednode, oldnode);
}
// re-execute all script fragments in imported subtree...
// TODO - opera 8 run scripts at replace content stage.
if (!A4J.AJAX._scriptEvaluated) {
this.evalScripts(newnode);
}
LOG.debug("Update part of page for Id: " + id + " successful");
} else {
LOG.warn("Node for replace by response with id " + id + " not found in document");
}
};
//this is a slightly modified version of prototype.js Element.Methods.update
A4J.AJAX.XMLHttpRequest.prototype.updateTable = function (element, html) {
element = $(element);
html = typeof html == "undefined" ? "" : html.toString();
var tagName = element.tagName.toUpperCase();
if (["THEAD", "TBODY", "TR", "TD"].include(tagName)) {
var div = document.createElement("div");
switch (tagName) {
case "THEAD":
case "TBODY":
div.innerHTML = "<table>" + html.stripScripts() + "</table>";
depth = 2;
break;
case "TR":
div.innerHTML = "<table><tbody>" + html.stripScripts() + "</tbody></table>";
depth = 3;
break;
case "TD":
div.innerHTML = "<table><tbody><tr>" + html.stripScripts() + "</tr></tbody></table>";
depth = 4;
}
$A(element.childNodes).each(function (node) {
element.removeChild(node);
});
depth.times(function () {
div = div.firstChild;
});
$A(div.childNodes).each(function (node) {
element.appendChild(node);
});
} else {
element.innerHTML = html.stripScripts();
}
setTimeout(function () {
html.evalScripts();
}, 10);
return element;
};
4) use the a4j:outputPanel as the tr element in a normal table....
<table>
<c:forEach items="#{values}" var="val" varStatus="status">
<a4j:outputPanel id="op_#{status.index}" layout="tr">
<td>
val.id
</td>
</a4j:outputPanel>
</c:forEach>
</table>
I used this to do expandable/collapsible table rows. I know i probably could have used component xyz to accomplish the same thing and my answer to that is that I didn't want to.