1

I want to use a pageBlockTable to display a grid of customers and their products, with each cell of the grid showing the quantity bought.

As the number of products may change, the grid needs to be dynamic. The only way that I've found to display and save the data for each row is to create a subclass acting as a data structure to display the customer name for each row along with a List to hold the products for that row.

Unfortunately, when I use this subclass the headers don't display. I have created a couple of examples pages which get their data from the same controller.

public with sharing class TestController {

    // Data for the first example
    public List<Account> accounts {get; set;}
    public List<Product2> products {get; set;}

    // Data for the second example
    public List<GridRow> gridRows {get; set;}

    public TestController() {
        accounts = new List<Account>();
        accounts.add(new Account(Name='One'));
        accounts.add(new Account(Name='Two'));
        accounts.add(new Account(Name='Three'));

        products = new List<Product2>();
        products.add(new Product2(Name='Product One'));
        products.add(new Product2(Name='Product Two'));
        products.add(new Product2(Name='Product Three'));

        gridRows = new List<GridRow>();

        for(Integer cust = 0; cust < 3; cust++) {
            GridRow row = new GridRow();
            row.acc = new Account(Name='Customer '+cust);
            List<Product2> prods = new List<Product2>();
            for(Integer p = 0; p < 3; p++) {
                prods.add(new Product2(Name='Product '+p));
            }
            row.products = prods;
            gridRows.add(row);
        }
    }

    public class GridRow {

        public Account acc {get; set;}
        public List<Product2> products = new List<Product2>();

        public List<Product2> getProducts() {
            return products;
        }       

    }
}

The first page uses lists that are members of the controller. The headers display fine for this page.

<apex:page controller="TestController">

    <apex:pageBlock >

        <apex:pageBlockTable value="{!accounts}" var="a">

            <apex:column headerValue="Customer">
                <apex:outputField value="{!a.Name}" />
            </apex:column>

            <apex:repeat value="{!products}" var="p">
                <apex:column headerValue="{!p.Name}">
                    <input type="text" />
                </apex:column>
            </apex:repeat>

        </apex:pageBlockTable>

    </apex:pageBlock>
</apex:page>

The second uses a list of the subclass object to display the rows of data. The headers won't display for this page.

<apex:pageBlock >

    <apex:pageBlockTable value="{!gridRows}" var="row">

        <apex:column headerValue="Customer">
            <apex:outputField value="{!row.acc.Name}" />
        </apex:column>

        <apex:repeat value="{!row.products}" var="p">
            <apex:column headerValue="{!p.Name}">
                <input type="text" />
            </apex:column>
        </apex:repeat>

    </apex:pageBlockTable>

</apex:pageBlock>

I have read this salesforce stackexchange post, but I'm not sure why it says that:

If you are using a List, there is an important gotcha with dynamic tables: header cells are not rendered.

What is the difference between using the List of Accounts and List of Product2s in the first working example and using the List of subclass instances with their Product2 Lists in the second example?

2
  • Did you try adding the bit of css to the VF Page like the post recommends ? Commented Dec 14, 2012 at 11:33
  • I did yes, but that deleted my columns.
    – Joe
    Commented Dec 14, 2012 at 11:38

3 Answers 3

1

I'm not sure what's going on behind the scenes that allows it to work differently depending on what's in the List.

It isn't exactly elegant, but you could use JavaScript to create the row headers, if you really need to.

 <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"> </script>
 <script type="text/javascript">
     var $j = jQuery.noConflict();
     var colHeaders = [];
     var i = 0;
     <apex:repeat value="{!gridRows[0].products}" var="p">
         colHeaders[i] = '{!p.Name}';  
         i++
     </apex:repeat>

     $j(document).ready(function() {
         var headerRow = $j('.theTable thead tr');
         for (i = 0; i < colHeaders.length; i++) {
             headerRow.append('<th>' + colHeaders[i] + '</th>');
         }
     });
 </script>

Add a styleClass to your table so that it is easier to select with jQuery.

 <apex:pageBlockTable value="{!gridRows}" var="row" styleClass="theTable">
1

I hit the problem of headers not displaying yesterday and found this post. But it took me a little while to understand the above explanation.

I'd summarize the problem as:

the type of the value field of the apex:pageBlockTable must be a collection of the SObject type for the headers to render; it cannot be a collection of arbitrary Apex objects

So my fix was to refactor my code with this constraint in mind.

0

Apologies. I hadn't fully understood the post I referred to in my question. Now I understand that adding another repeat block adds the headers, like so:

<style type="text/css">
    .empty-column-content {
        display: none;
    }
</style>

<apex:pageBlockTable value="{!gridRows}" var="row">

    <apex:column headerValue="Customer">
        <apex:outputField value="{!row.acc.Name}" />
    </apex:column>

    <apex:repeat value="{!row.products}" var="p">
        <apex:column headerValue="{!p.Name}">
            <input type="text" />
        </apex:column>
    </apex:repeat>
    <apex:repeat var="header" value="{!headers}">
        <apex:column headerValue="{!header}" styleClass="empty-column-content" />
    </apex:repeat>

</apex:pageBlockTable>

where headers is a list of the product names.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .