views:

1644

answers:

3

Hi all,

I need to create a rich:dataTable (or even extended) with the following features:

I have a class Company having a collection of Product objects. I want to show the following table:

alt text

I still have not figured out how to do this with a subtable (in all the examples I found the subTable has the exact same columns as the master table). Presumably, I need to play with rowspans in the first two columns, but I still have not found the way.

Could someone provide a pseudo-code for this?

Cheers!

UPDATE 1: I tried set the rowspan of the columns in the left as the size of the list or products, and then :

  • if the products is empty (no products for the company yet), I print two columns. I do this conditionally by setting their rendered attribute to #{myFuncs:sizeOf(company.products)}
  • If the products are >= 1 then I iterate over them with and inside that loop I insert two columns (one for product name and one for description), and for each product name column except the first one I set the breakBefore attribute to #{ !myFunc:firstProduct(company.products, product)}, which evaluates to true for all product names except the first one.

Unfortunately, this did not work for me, because the columns inside the a4j:repeat do not appear at all - not because of the rendered tag. The loop is correct because if I print standard text else, it appears.

Is there a way to achieve rowspan, or am I banging my head on the wall?

UPDATE 2: The issue is probably related to this article, indicating the differences between iteration components such as < a4j:repeat> and the tag < c:forEach>. The first takes place at rendering time, while the second one operates earlier, when JSF components are placed onto the component tree of the page.

I tried to get the rich:columns outside the a4j:repeat and they get rendered (of course, not as expected, but they do).

+2  A: 

There's unfortunately no rowspan support in JSF UIData components. Best what you could do is to just display the collection of products in the same row. You can iterate over it using another UIData component such as h:dataTable (renders <table>), t:dataList (renders <ul>) or a4j:repeat (renders nothing, you need to use e.g. <br/> after each item).

Semi-pseudo based on the basic JSF components:

<h:dataTable value="#{bean.companies}" var="company">
    <h:column>
        <h:outputText value="#{company.name}" />
    </h:column>
    <h:column>
        <h:outputText value="#{company.email}" />
    </h:column>
    <h:column>
        <h:dataTable value="#{company.products}" var="product">
            <h:column>
                <h:outputText value="#{product.name}" />
            </h:column>
        </h:dataTable>
    </h:column>
    <h:column>
        <h:dataTable value="#{company.products}" var="product">
            <h:column>
                <h:outputText value="#{product.description}" />
            </h:column>
        </h:dataTable>
    </h:column>
</h:dataTable>

Use CSS the smart way so that it look like rowspans.

BalusC
Thanks man. I think that perhaps I could use the rich:extendedDataTable for what I need to do. It has a feature they name "Group Rows by Column", but the downside is that I can only group by one column. Do you think that another components library (i.e. Icefaces) has a datatable that does what I need? Cheers!
Markos Fragkakis
Sorry, can't tell much about it. I only have strong hands on experience with Mojarra and Tomahawk and just a little bit with RichFaces/Ajax4jsf.
BalusC
Also, RichFaces datatable _does_ have rowspan attribute for columns. I have not found the way to use it to do the specific thing I want to do.
Markos Fragkakis
Received similar answer in Richfaces forum. Thanks BalusC!
Markos Fragkakis
As reference, this was the topic: http://community.jboss.org/thread/146555
BalusC
A: 

OK, based on the last update, created the page doing the iterations using c:forEach (while building the component tree). The solution I am providing works, but something feels wrong about it because:

  • It takes too much time (~3 seconds 100% CPU for about 20 companies and 200 products). I suspect that this is because the for c:forEach loop essentially builds a huge component tree, which has to be rendered, instead of the initial approaches where the component tree was much smaller.
  • I think I will have to re-build the whole component tree for every change in the data, instead of just re-rendering it.

Anyways, to the code. What I did is something like this (have not tested the one below, but you will get the picture. Notice that the iteration inside the rich:dataTable is essentially ignored):

<rich:dataTable width="70%" id="applicantsTable" rows="100"
rowClasses="applicant_row" columnClasses="col"
value="#{backingBean.companyList}" var="company">
<f:facet name="header">
        <rich:column>
            <h:outputText styleClass="headerText" value="Company Name" />
        </rich:column>
        <rich:column>
            <h:outputText styleClass="headerText" value="Company Email" />
        </rich:column>
        <rich:column>
            <h:outputText styleClass="headerText" value="Product Name" />
        </rich:column>
        <rich:column>
            <h:outputText styleClass="headerText" value="Product Email" />
        </rich:column>

</f:facet>

<c:forEach items="#{backingBean.companyList}" var="c_company">

    <c:if test="#{prs:collectionSize(c_company.products)> 0}">

        <rich:column breakBefore="true"
            rowspan="#{prs:collectionSize(c_company.products)}">
            <h:outputText value="#{c_company.name}" />
        </rich:column>

        <rich:column
            rowspan="#{prs:collectionSize(c_company.products)}">
            <h:outputText value="#{c_company.email}" />
        </rich:column>

        <c:forEach items="#{c_company.products}" var="c_product">
            <!-- This if clause is just to determine the breakBefore attribute -->
            <c:if test="#{c_company.products[0] == c_product}">
                <rich:column>
                    <h:outputText value="#{c_product.name}" />
                </rich:column>
            </c:if>

            <c:if test="#{c_company.products[0] != c_product}">
                <rich:column breakBefore="true" styleClass="internal_cell">
                    <h:outputText value="#{c_product.name}" />
                </rich:column>
            </c:if>

            <rich:column styleClass="internal_cell">
                <h:outputText value="#{c_product.email}" />
            </rich:column>

        </c:forEach>

    </c:if>

</c:forEach>

Markos Fragkakis
You can also just do basic EL `#{not empty bean.collection}` and JSTL's `#{fn:length(bean.collection)}` instead of that `prs:collectionSize`.
BalusC
+1  A: 

You can do this without those complex forEachs. You just need to take advantage of subTable and the rowKeyVar.

For example:

<rich:dataTable
    value="#{backingBean.companyList}"
    rows="100"
    var="company">
    <f:facet name="header">
        <rich:columnGroup>
            <rich:column>Company Name</rich:column>
            <rich:column>Company Email</rich:column>
            <rich:column>Product Name</rich:column>
            <rich:column>Product Email</rich:column>
        </rich:columnGroup>
    </f:facet>
    <rich:subTable value="#{company.products}" var="product" rowKeyVar="rowKey">
        <rich:column rowspan="#{company.products.size()}" rendered="#{rowKey eq 0}">
            #{company.name}
        </rich:column>
        <rich:column rowspan="#{company.products.size()}" rendered="#{rowKey eq 0}">
            #{company.email}
        </rich:column>
        <rich:column>
            #{product.name}
        </rich:column>
        <rich:column>
            #{product.email}
        </rich:column>
    </rich:subTable>
</rich:dataTable>

Renders perfectly for me. Note that I'm using Seam which has Jboss Extended EL that allows me to call size() on the collection. If you aren't using this you can use that prs:collectionSize() or fn:length() as a substitute.

This also works nicely with the Richfaces datascroller.

Hope this helps.

D.

Damo
I am trying this out. **But** it only shows the companies that **have** products. There are some companies that do not have products, which I would like to appear (the remaining product columns should be empty).Is there support for this?
Markos Fragkakis
Not using this approach. That is the limitation. You can however fake up the rows by post-processing the Companies and adding a blank product where none exist.
Damo
I continued discussion on Richfaces community: http://community.jboss.org/message/541744 . There, we have found a workaround even for companies with **no** products.
Markos Fragkakis