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:
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:firstPr开发者_运维技巧oduct(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).
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.
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.
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>
精彩评论