[SalesForce] Apex Repeat inside PageBlockTable Headers aren’t Displayed

I have a map of String(key) and Wrapper object(value) called TableView. The TableView has public property of list of a wrapper class object called DataViewRow.

The DataView wrapper class has a map of column names and their values (Map of String to Object).

I try to render the columns dynamically based on the Map inside the DataViewRow wrapper object but the headers are not displayed in this case.

enter image description here

It looks like this in the visualforce page:

<apex:pageblocktable value="{!map_tableToData['Adjustments'].Rows}" var="row">
    <apex:repeat value="{!row.Values}" var="col">
        <apex:column headervalue="{!col}">
            {!row.Values[col]}
        </apex:column>
    </apex:repeat>
</apex:pageblocktable>

TableView class:

public class TableView 
{
    public TableView(List<DataViewRow> rows)
    {
        this.Rows = rows;
    }

    public List<DataViewRow> Rows { get; set; }
}

DataViewRow class:

public virtual class DataViewRow 
{
    protected DataViewRow() { }

    public DataViewRow(List<String> lst_columns, String title)
    {
        //Intialize a new map.
        this.Values = new Map<String,Object>();

        for (String columnName : lst_columns)
        {
            this.Values.put(columnName, null);
        }

        this.Title = title;
    }

    public String Title { get; set; }

    public Map<String,Object> Values { get; set; }

    public Set<String> ColumnNames 
    {
        get
        {
            return this.Values.keySet();
        }
    }

    public virtual Boolean IsEmptyData()
    {
        return false;
    }
}

The values in the map are populated correctly. I've tried to render the column names in the column value and the values are as expected, but the headers are not displayed at all.

I would appreciate your help.

UPDATE:

It seems that there is a problem binding the map in the repeat and use its key inside the headerValue attribute of the column. I've assigned map_tableToData['Adjustments'].Rows[0].Values.keySet() to a different property in the controller because it is the same for all the rows in my case.
I would be happy to get any better solution.

Best Answer

Can confirm - you will not get the headers from the Map keys when using the <apex:repeat> tag to auto-generate columns from a List<Map> structure.

I have tried it with <apex:facet> tags, on both the table and the column; I have tried it with various formulas in the value="" and var="" attributes of the <apex:repeat> tag; I've even tried giving all the headers the same fixed value: <apex:column headervalue="Fred>. The headers just don't display.

This makes sense if you think about it - there is no guarantee that every Map in a List<Map> structure will have the same keys, so how does VF know which ones to use as the headers? To get around this, you will have to provide your own headers in a separate property of the VF page controller. These can be the actual keys from List<Map>[0] or whatever others you might choose.

Sample code is below for situation where I use the first row's key values as the headers.

Relevant controller code:

public List<Map<string, string>> myTable
{
    get
    {
        List<Account> accts = [select id, name, shippingcity, phone, fax from Account];
        List<Map<string, string>> aTable = new List<Map<string, string>>();
        for(account a : accts)
        {
            // Add account fields as strings.  The "field==null ? '' : field" ternary
            // is necessary to avoid a "Map Key Not Found" page error.
            // Not sure why that is, but it's irrelevant to current discussion.
            Map<string, string> aMap = new Map<string, string>();
            aMap.put('ID', a.id);
            aMap.put('Name', a.name);
            aMap.put('Shipping City', a.shippingcity == null ? '' : a.shippingcity);
            aMap.put('Phone', a.phone == null ? '' : a.phone);
            aMap.put('Fax', a.fax == null ? '' : a.fax);
            aTable.add(aMap);
        }
        return aTable;
    }        
}

public List<string> tableHeaders
{
    get { return new List<string>(myTable[0].keySet()); }   
}

Relevant page tags:

<apex:pageblocktable value="{!myTable}" var="rowMap" >
    <apex:repeat value="{!tableHeaders}" var="headerKey">
        <apex:column headerValue="{!headerKey}" value="{!rowMap[headerKey]}" />
    </apex:repeat>
</apex:pageblocktable>

You could also create a second Map<string,string> to obtain custom headers for your columns. The relevant code for that would be:

public Map<string, string> custHeaders
{
    get 
    { 
        return new Map<string, string> { 
            tableHeaders[0]=>'Fred', 
            tableHeaders[1]=>'Daphne', 
            tableHeaders[2]=>'Velma', 
            tableHeaders[3]=>'Shaggy', 
            tableHeaders[4]=>'Scooby'}; 
    }
}

and

<apex:pageblocktable value="{!myTable}" var="rowMap" >
    <apex:repeat value="{!tableHeaders}" var="headerKey">
        <apex:column headerValue="{!custHeaders[headerKey]}" value="{!rowMap[headerKey]}" />
    </apex:repeat>
</apex:pageblocktable>

Hope this helps!

Doug