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.