How to create a composite component for a datatable column?
Asked Answered
E

2

12

Given this datatable (naturally working):

<rich:dataTable var="var" value="#{values}">
<rich:column>
  <f:facet name="header">
   HEADER
  </f:facet>
  <h:outputText value="#{var}" />
</rich:column>
</rich:dataTable>

If I define a custom component (also ok in the syntax and at the right place under resources/components):

   <?xml version="1.0" encoding="UTF-8"?>
    <html xmlns="http://www.w3.org/1999/xhtml"
        xmlns:ui="http://java.sun.com/jsf/facelets"
        xmlns:f="http://java.sun.com/jsf/core"
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:a4j="http://richfaces.org/a4j"
        xmlns:rich="http://richfaces.org/rich"
        xmlns:composite="http://java.sun.com/jsf/composite">

    <!-- INTERFACE -->
    <composite:interface>
        <composite:attribute name="val" />
    </composite:interface>

    <!-- IMPLEMENTATION -->
    <composite:implementation>
        <rich:column>
            <f:facet name="header">
       HEADER
       </f:facet>
       <h:outputText value="#{cc.attrs.val}" />
       </rich:column>
    </composite:implementation>
    </html>

Why does the following does not work?

<?xml version="1.0" encoding="UTF-8"?>
<ui:composition template="/WEB-INF/templates/default.xhtml"
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:a4j="http://richfaces.org/a4j"
    xmlns:rich="http://richfaces.org/rich"
    xmlns:my="http://java.sun.com/jsf/composite/components">
    <ui:define name="content">
        <rich:dataTable var="var" value="#{values}">
                 <my:mycolumn val="#{var}"/>
                </rich:dataTable>
    </ui:define>
</ui:composition>

Do you know how could I let it work (I want to define my own column and save code)? Thanks a lot! Bye

Ento answered 11/7, 2012 at 19:21 Comment(0)
J
26

The <my:mycolumn> element must be an instance of UIColumn as that's the only valid child of a UIData component during the render response phase. All other UIComponent types will be ignored, thus not rendered. A composite component is implicitly a UINamingContaner component, which isn't a UIColumn and therefore ignored.

A PrimeFaces <p:dataTable> with a backing component that extends UIColumn also won't work due to the wrong lifecycle of a composite component. The column has to be created during the view build time, while the composite component's body is created during view render time.

The solution is to create a tag file instead, which means an extra .taglib.xml file, yet it works flawlessly.

/WEB-INF/tags/column.xhtml:

<ui:composition
    xmlns="http://www.w3.org/1999/xhtml" 
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets" 
    xmlns:rich="http://richfaces.org/rich">
    <rich:column>
        <f:facet name="header">HEADER</f:facet>
        <h:outputText value="#{val}" />
    </rich:column>
</ui:composition>

/WEB-INF/my.taglib.xml:

<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib 
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd"
    version="2.0">
    <namespace>http://example.com/my</namespace>
    <tag>
        <tag-name>column</tag-name>
        <source>tags/column.xhtml</source>
        <attribute>
            <description>Column value</description>
            <name>val</name>
        </attribute>
    </tag>
</facelet-taglib>

Note: The <attribute> entries are not mandatory, but are nice for documentation purposes, such as generated docs and IDE autocomplete.

/WEB-INF/web.xml:

<context-param>
    <param-name>javax.faces.FACELETS_LIBRARIES</param-name>
    <param-value>/WEB-INF/my.taglib.xml</param-value>
</context-param>

Usage:

<ui:composition
    xmlns="http://www.w3.org/1999/xhtml" 
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets" 
    xmlns:rich="http://richfaces.org/rich"
    xmlns:my="http://example.com/my">
    <rich:dataTable value="#{values}" var="value">
        <my:column val="#{value}" />
    </rich:dataTable>
</ui:composition>
Justus answered 12/7, 2012 at 2:45 Comment(8)
Thanks a lot BalusC. But with that method you can't add children with some <cc:insertChildren/> can you ? Is there a way to use jsf/composite tags and say the root UIComponent must be javax.faces.component.UIColumn ?Kial
@Anthony: Just use <ui:insert>. No, there's no way with composites, as already answered.Justus
This workaround seems like a huge oversight in the JSF specification. The above solution works for me as well; however, how would I emulate a cc:renderFacet with a newly defined facet? Ui:define/ui:insert with a name works for me. But, is there a way to still provide another f:facet child and use some equivalent of cc:renderFacet in column.xhtml?Brigand
It is all good and fine, however, why do I need to write a "my.tablib.xml" and declare it in web.xml while this sort of mechanics is not necessary defining composites??Espadrille
I'm also wondering if there is some possibility to include facets like with <cc:renderFacet> in composite components. I couldn't find anything. Anybody any idea?Jessie
@MartinHöller In your definition of column.xhtml you can use <ui:insert /> to define where child tags will be put. It appears that you also can define named insertion points just like with templates. I tested adding <ui:insert name="foo" /> into column.xhtml, and referred to it from the outside using <my:column><ui:define name="foo">hello quasi facet</ui:define></my:column> - this appears to work, even in combination with an unnamed <ui:insert /> for direct children.Aluminium
@whaefelinger - for historic reasons. Taglibs are older than composite components and have a more manual setup. On the other hand, they come with far less overhead on the server side, being fairly simple templating as opposed to cc's naming container magic.Aluminium
@Aluminium I employed your advice and modified it like <my:column><ui:define name="foo"><p:commandLink value="#{object.entity}" action="#{someBean.someFunc(object.entity)}"></ui:define></my:column>, this also worked, the commandLink and its action linked, without this method the link will just direct me to error page. <p:commandLink> doesn't work in template file if it is placed inside the custom tag as a children of the tag, but <h:outputText> is available in that way. Using <ui:define> and <ui:insert>, <p:commandLink> and <h:outputText> looks having no difference.Aftersensation
Q
-1

I ran into the same question (primefaces), my compromise: exclude the column tag from the composite

<rich:dataTable var="var" value="#{values}">
    <rich:column rendered=#{yesOrNo}">
       <my:mycolumn val="#{var}">
       .. with child nodes if you want ...
       </my:mycolumn>
    </rich:column>
</rich:dataTable>
Quietude answered 21/3, 2020 at 7:55 Comment(2)
This Is A Very weird and illogical answer If contains code that is factually not correct. And you did not exclude a column but include it, weird.Steelmaker
The OP's composite contained the rich:column tag itself. This compromise works, because the <rich:column> is outside of the composite (thats why it can be created during the view build time, while the composite component's body is created during view render time, as BalusC told) If the composite contains a big code (as in my case), this could help. Syntax: it uses the variables and structure from the question.Quietude

© 2022 - 2024 — McMap. All rights reserved.