Wednesday, October 8, 2008

Using af:panelCollection with master-detail tables

af:panelcollection is an aggregation component over components such as tree, table, treeTable for the purpose of standardizing menus, toolbars etc. I recently ran into some specific layout issues when using panelCollection with tables in a master-detail layout, which I feel are worth blogging.

Master-detail tables are typically shown one underneath another inside a panelGroupLayout component with vertical layout. With the default design-time configuration settings, I discovered that the layout for the detail table looked just plain ugly. Irrespective of any width and/or columnStretching attributes specified on the detail table, it looked squished with a forced horizontal scrollbar. My first instinct for fixing this was to surround the panelCollection inside a panelStretchLayout, thinking that its lack of stretchability inside panelGroupLayout was what was contributing to its ugliness. Unfortunately, playing around with all sorts of config settings with a panelStrechLayout thrown in ended up as a futile exercise. I must have spent close to a day poring over various component configuration documentations, trying different permutations and scratching my head wondering why none of them were working. Various conflicting constraints/conditions I was struggling with were:

  • panelStretchLayout should not be included inside panelGroupLayout as a general rule of thumb (unless specifying sizes in fixed pixels which typically shouldn't be done for applications to preserve browser compatibility)
  • panelGroupLayout layout=vertical can be included inside panelStretchLayout
  • panelCollection can be stretched inside a strechable parent, however if inside a panelGroupLayout, it won't be stretchable
  • af:table will fill up inside panelCollection if columnStretching specified

In the end, the solution I ended up with is far simpler, almost of the "duh!" variety. It was told to me by someone with prior knowledge of having worked with this use case. I haven't seen it well documented anywhere I searched, and feel it's worth blogging here, with the hope that it will save few hours for someone in the future. The key to the solution is that it does away with panelStretchLayout altogether, and specifies a width of 100% on both the panelCollection and contained table. That's it, it's that simple!! Here is an example layout:

<?xml version='1.0' encoding='US-ASCII'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:h="http://java.sun.com/jsf/html"
          xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
  <jsp:directive.page contentType="text/html;charset=US-ASCII"/>
  <f:view>
    <af:document>
      <af:form>
        <af:panelGroupLayout layout="vertical">
          <af:panelHeader text="Divisions">
            <f:facet name="context"/>
            <f:facet name="menuBar"/>
            <f:facet name="toolbar"/>
            <f:facet name="legend"/>
            <f:facet name="info"/>
            <af:panelCollection inlineStyle="width:100%;">
              <f:facet name="menus"/>
              <f:facet name="toolbar"/>
              <f:facet name="statusbar"/>
              <af:table var="row" width="100%" columnStretching="last">
                <af:column sortable="false" headerText="col1">
                  <af:outputText value="#{row.col1}"/>
                </af:column>
                <af:column sortable="false" headerText="col2">
                  <af:outputText value="#{row.col2}"/>
                </af:column>
                <af:column sortable="false" headerText="col3">
                  <af:outputText value="#{row.col3}"/>
                </af:column>
                <af:column sortable="false" headerText="col4">
                  <af:outputText value="#{row.col4}"/>
                </af:column>
                <af:column sortable="false" headerText="col5">
                  <af:outputText value="#{row.col5}"/>
                </af:column>
              </af:table>
            </af:panelCollection>
          </af:panelHeader>
          <af:panelHeader text="Product Groups">
            <f:facet name="context"/>
            <f:facet name="menuBar"/>
            <f:facet name="toolbar"/>
            <f:facet name="legend"/>
            <f:facet name="info"/>
            <af:panelCollection inlineStyle="width:100%;">
              <f:facet name="menus"/>
              <f:facet name="toolbar"/>
              <f:facet name="statusbar"/>
              <af:table var="row" columnStretching="last" width="100%">
                <af:column sortable="false" headerText="col1">
                  <af:outputText value="#{row.col1}"/>
                </af:column>
                <af:column sortable="false" headerText="col2">
                  <af:outputText value="#{row.col2}"/>
                </af:column>
                <af:column sortable="false" headerText="col3">
                  <af:outputText value="#{row.col3}"/>
                </af:column>
                <af:column sortable="false" headerText="col4">
                  <af:outputText value="#{row.col4}"/>
                </af:column>
                <af:column sortable="false" headerText="col5">
                  <af:outputText value="#{row.col5}"/>
                </af:column>
                <af:column sortable="false" headerText="col6">
                  <af:outputText value="#{row.col6}"/>
                </af:column>
                <af:column sortable="false" headerText="col7">
                  <af:outputText value="#{row.col7}"/>
                </af:column>
                <af:column sortable="false" headerText="col8">
                  <af:outputText value="#{row.col8}"/>
                </af:column>
              </af:table>
            </af:panelCollection>
          </af:panelHeader>
        </af:panelGroupLayout>
      </af:form>
    </af:document>
  </f:view>
</jsp:root>

I have seen some other proposed solution elsewhere, which has deeply nested panelStrechLayout elements. Instead of using a vertical panelGroupLayout, it uses a panelAccordian and showDetailItem. A visible disadvantage of such approach is that the text for showDetailItem adds yet another unnecessary title to the master-detail layout. Even if such text is omitted, the presence of showDetailItem is waste of unnecessary real estate. From a pure configuration perspective, such solution seems a bit convoluted to me due to the multiple levels of nested panelStretchLayout components. The approach presented here is far simpler.

Check out general guidelines on best practices for layout design.

Also, section 8.2.2 "Nesting Components Inside Components That Allow Stretching" from application developer's guide is worth going over.

Also see couple of latest layout guides:

Layout Pattern: Common Component in Form

Layout Pattern: Table with Header Left, Footer Right


Note that this trick of inlineStyle="width:100%;" cannot be arbitrarily applied to any component to force them to stretch. In particular, pay special attention to following tip from section 8.2.2 from above link:

Tip: Do not attempt to stretch any of the components in the list of components that cannot stretch by setting their width to 100%. You may get unexpected results. As stated previously, if you need one of these components to stretch, you need to wrap them in a component that can be stretched.

Since panelCollection is a component that can stretch, above tip does not apply to it in the presented sample. However, such inlineStyle configuration cannot be applied to other non-stretchable components such as panelBox.

2 comments:

Vitaly Orbidan said...

put panel collection inside of the panelGroupLayout, make panelGroupLayoutlayout scroll and make width on the panelCollection inherit

Hozefa said...

It worked like magic; thanks for blogging it.