36

I would like to be able to dynamically control the columns displayed in a page block table. This would mean that if new fields are added to the object the controller and page do not have to be updated.

I understand that a standard list view could used, be the data model is:

Parent_c -< Child_c -< Grandchild__c

And I will be building a page displaying all the grandchildren for a selected Parent, and creating a list view for each parent is not ideal.

2 Answers 2

42

Page Block Tables actually support <apex:repeat> tags (This was not originally supported). The following example demonstrates 2 ways of accomplishing dynamic columns in a table depending on the type of data you supply.

Using fieldsets is the simplest way to control dynamic columns. This is the best choice if displaying a table of SObjects

<apex:pageBlock id="block">
    <apex:pageBlockTable id="table" var="account" value="{!accounts}">
        <apex:column headerValue="Account Name" value="{!account.Name}" />
        <apex:repeat var="f" value="{!$ObjectType.Account.FieldSets.Example_Fieldset}">
            <apex:column headerValue="{!f.label}">
                <apex:inputField value="{!account[f]}" />
            </apex:column>
        </apex:repeat>
    </apex:pageBlockTable>
</apex:pageBlock>

If you are using a List, there is an important gotcha with dynamic tables: header cells are not rendered. The fix is to declare the following css snippet and apply the style as outlined below

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

<apex:pageBlock id="block2">
    <apex:pageBlockTable id="table" var="row" value="{!rows}">
        <apex:repeat var="cell" value="{!row.cells}">
            <apex:column headerValue="No matter how this is defined, it results in an empty cell">
                <apex:outputText value="{!cell}" />
            </apex:column>
        </apex:repeat>
        <!--Define the headers here and hide the empty cells that are created -->
        <apex:repeat var="header" value="{!headers}">
            <apex:column headerValue="{!header}" styleClass="empty-column-content" />
        </apex:repeat>
    </apex:pageBlockTable>
</apex:pageBlock>

The example controller for the visualforce snippets above:

public with sharing class PageBlockTableFieldSetExample {
    public List<Account> accounts { get; private set; }
    public List<String> headers { get; private set; }
    public List<RowExample> rows { get; private set; }

    public PageBlockTableFieldSetExample() {
        String queryFields = '';
        for(Schema.FieldSetMember f : SObjectType.Account.FieldSets.Example_Fieldset.getFields()) {
            if(queryFields.length() > 0) {
                queryFields += ', ';
            }
            queryFields += f.getFieldPath();
        }
        //Query the records you want to display
        this.accounts = Database.query(
            ' SELECT ' +
                queryFields +
            ' FROM ' +
                ' Account ' +
            ' WHERE ' +
                ' AccountNumber = null ' +
            ' LIMIT 10 ');


        //Setup the row of classes example
        this.headers = new List<String> { 'Monday', 'Tuesday', 'Wednesday' };
        this.rows = new List<RowExample>();
        for(Integer i = 0; i < 3; i++) {
            this.rows.add(new RowExample(this.headers.size()));
        }
    }

    public class RowExample {
        //A variable number of cells in different rows will result in 
        public final List<String> cells { get; private set; }

        public RowExample(Integer length) {
            this.cells = new List<String>();
            for(Integer i = 0; i < 3; i++) {
                this.cells.add('Cell ' + (math.random() * 10).intValue());
            }
        }
    }
}
1
  • Thanks for this - been chasing the lack of output using headerValue. In the end I also tried using a header apex:facet and that did render (at least with API 29) so that is another work-around.
    – Keith C
    Commented Jan 26, 2016 at 1:23
8

You could using Dynamic Visualforce Components to construct the pageBlockTable, which avoids the limitation re headings when adding columns dynamically in VF alone.

Controller:

public List<Account> getAccountList()
{
    String fieldList = '';
    for(Schema.FieldSetMember fsm : SObjectType.Account.FieldSets.MyFieldset.getFields())
        fieldList += fieldList.length()>0 ? ','+fsm.getFieldPath() : fsm.getFieldPath();
    return (List<Account>) Database.query('select ' + fieldList + ' from Account');
}

public Component.Apex.PageBlockTable getMyPageBlockTable()
{
    Component.Apex.PageBlockTable table = new Component.Apex.PageBlockTable(var='acc');
    table.expressions.value='{!AccountList}';
    for(Schema.FieldSetMember fsm : SObjectType.Account.FieldSets.MyFieldset.getFields())
    {
        Component.Apex.OutputField outputField = new Component.Apex.OutputField();
        outputField.expressions.value = '{!acc.' + fsm.getFieldPath() + '}';
        Component.Apex.Column column = new Component.Apex.Column(headerValue=fsm.getLabel());
        column.childComponents.add(outputField);
        table.childComponents.add(column);
    }
    return table;
}

Visualforce page:

<apex:pageBlock >
    <apex:dynamicComponent componentValue="{!MyPageBlockTable}"/>
</apex:pageBlock>
5
  • This is best way to solve this problem, I will say more robust solution Commented Apr 4, 2013 at 5:04
  • I upvoted many months ago, but now having seen the wild abominations that are possible with Dynamic Components, I no longer like this answer very much. Use of Dynamic Components should always be discouraged as strongly as possible. Here is someone who agrees with me: tehnrd.com/… Commented Apr 10, 2013 at 6:05
  • 2
    Yes, I have read Jason's great blog post. Of course it is easier to mess-up MVC with DVCs than without them. Check out Stephan Morais commented on Jason's post - with DVCs theres no longer a simple view=VisualforcePage controller=ApexController model, but you certainly can maintain MVC while using DVCs, you just need to develop an appropriate pattern. Admittedly there's no evidence of this kind of pattern in my answer! Commented Apr 12, 2013 at 13:47
  • On patterns - I think there's an important principle here - many patterns which predate Force.com can be useful to the Force.com developer, but need varying amounts of adaptation to fit; its actually something I really enjoy about working on this platform. But... yes, care should be taken when using of DVCs; you should most likely use an Apex pattern to protect MVC; the question at hand (Dynamic number of columns in a pageBlockTable) is not necessarily the best, "bang on targed" use case for DVCs Commented Apr 12, 2013 at 13:48
  • Whatever about patterns, the dynamic components will leave you with broken pages if you are using them for input fields because "by design" (according to salesforce support) they are not serialized into the view state. So if you have a form with several fields and a user enters data into each field, an error such as an invalid date value on one field will cause all the field data to be thrown away which is not what your end user expects. (These dynamic components are not participating correctly in the underlying framework - probably JSF - lifecycle.)
    – Keith C
    Commented Aug 8, 2013 at 16:55

You must log in to answer this question.

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