0 Replies Latest reply on May 24, 2007 3:23 PM by dunks80

    How to update a TR with a4j:outputPanel

    dunks80 Newbie

      This 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.