Search code examples
javajsftagsfaceletsuicomponents

Custom Facelets-Tag with multiple Child-Components in h:panelGrid


I have written a custom tag extending UIComponentBase.
It adds multiple Child-Components (UIComponent) during the encodeBegin method.

For layouting purposes, I'd like to nest this Child-Components in a h:panelGrid,
but the tag gets in the way here.

ExampleTag.java

private ExampleTag extends UIComponentBase {

    public void encodeBegin(FacesContext context) throws IOException {
        getChildren().add(new HtmlLabel());
        getChildren().add(new HtmlOutputText();
    }
}

ExampleOutput.xhtml

<html>
    <h:panelGrid columns="2">
       <foo:exampleTag />
       <foo:exampleTag />
    </h:panelGrid>
</html>

The generated output would have the HtmlLabel and HtmlOutput components in the same cell,
but I'd like to have them in one row, i.e. two cells.


Solution

    1. h:panelGrid only controls the layout of its own children (and not the children of its children)
    2. each <foo:exampleTag /> creates one composite control (with its own children)

    If you want to add multiple controls to a h:panelGrid, use one of the other template mechanisms.

    For example, this h:panelGrid uses a ui:include:

        <h:panelGrid columns="2">
          <ui:include src="gridme.xhtml">
            <ui:param name="foo" value="Hello,"/>
            <ui:param name="bar" value="World!"/>
          </ui:include>
          <ui:include src="gridme.xhtml">
            <ui:param name="foo" value="Hello,"/>
            <ui:param name="bar" value="Nurse!"/>
          </ui:include>
        </h:panelGrid>
    

    The included composition file:

    <!-- gridme.xhtml -->
    <ui:composition xmlns="http://www.w3.org/1999/xhtml"
        xmlns:ui="http://java.sun.com/jsf/facelets"
        xmlns:h="http://java.sun.com/jsf/html">
      <h:outputText value="#{foo}" />
      <h:outputText value="#{bar}" />
    </ui:composition>
    

    A subset of the view output:

    <table>
    <tbody>
    <tr>
    <td>Hello,</td>
    <td>World!</td>
    </tr>
    <tr>
    <td>Hello,</td>
    <td>Nurse!</td>
    </tr>
    </tbody>
    </table>
    

    Take care with the above implementation - you cannot set IDs explicitly on anything in gridme.xhtml as there is no composite control and therefore no NamespaceContainer to ensure children are namespaced uniquely.


    A component is not a tag.

    public void encodeBegin(FacesContext context) throws IOException {
      getChildren().add(new HtmlLabel());
      getChildren().add(new HtmlOutputText();
    }
    

    This is not an acceptable way to build a composite control. If you do this, new controls will be added to the component every time it is rendered. You should also not do this in the constructor; that would lead to problems too. There is no good way to add child controls within a control; it should be done externally by the view (see above) or a tag.