I try to create a table with rowspan, but get a wrong table, could somebody help to resolve this?

This is my UPD VF Page:

 <apex:page controller="FinancialProductsController" showHeader="false" sidebar="false" action="{!getData}">
     <meta name="viewport" content="width=device-width, initial-scale=1"/>
     <apex:stylesheet value="{!URLFOR($Resource.BootstrapMincss)}"/>
     <apex:includeScript value="{!$Resource.JQueryMinJs}"/>
     <apex:includeScript value="{!$Resource.BootstrapMinJs}"/>              
 <apex:form id="Form">
     <apex:messages />              
            <table class="table table-bordered">
                 <tr class="headerRow">
                     <th>Business Division</th>
                     <th>Managed By</th>
                     <th>Legal Entity</th>
                     <th>Launch Date</th>
                     <th>Sales Guru</th>
                     <th>PMO version (attachment)</th>
                     <th>Internal Presentation (attachment)</th>
                     <th>External Presentation(attachment)</th>
                 <apex:repeat id="myRepeatHeader" value="{!divisionList}" var="key">         
                     <apex:variable var="count" value="{!1}"/>            
                     <apex:repeat id="contactDetails" value="{!mapDivision[key]}" var="att">
                            <apex:outputPanel layout="none" rendered="{!count==1}">
                                <td rowspan="{!(divisioncountMap[key])}" style="vertical-align:middle">
                                    <apex:outputText value="{!att.BD}"/>
                            <td><apex:outputText value="{!att.MB}"/></td>
                            <td><apex:outputText value="{!att.Name}"/></td>
                            <td><apex:outputText value="{!att.LE}"/></td>
                            <td><apex:outputText value="{!att.LD}"/></td>
                            <td><apex:outputText value="{!att.SG}"/></td>

                            <apex:variable var="count" value="{!count+1}"/>

And this is my UPD controller:

public class FinancialProductsController {

//map to capture Division Name and List of Financial Products
public Map<String, List<FinProdWrapper>> mapDivision {get; set;} 
//map to capture Division Name and count of Financial Products which will be used to define rowspan.
public Map<String, Integer> divisionCountMap {get; set;}
//to capture list of Division Names
public List<String> divisionList {get; set;}

public FinancialProductsController(){
    mapDivision = new Map<String, List<FinProdWrapper>>();
    divisionCountMap = new Map<String, Integer>();
    divisionList = new List<String>();

//prepare data to display in Visualforce.
public void getData(){
    List<Financial_Product__c> fd = [Select Business_Devision__c, Launch_Date__c, Name, Legal_Entities__c, Link_to_Confluence__c, Managed_by__c, Responsible_PM__c, Sales_Guru__c from Financial_Product__c];

    //grouped into division and prepare the map
    for(Financial_Product__c fdObj : fd){
        List<FinProdWrapper> fpWrapperList = new List<FinProdWrapper>();
        //verify map already contains same Division Name
            //retrieve the list of existing Financial Products
            fpWrapperList = mapDivision.get(fdObj.Business_Devision__c);
            //put the new Financial Product to the list
            fpWrapperList.add(new FinProdWrapper(fdObj));

            mapDivision.put(fdObj.Business_Devision__c, fpWrapperList);
            divisionCountMap.put(fdObj.Business_Devision__c, fpWrapperList.size());
            //create a new map of Division Name
            fpWrapperList.add(new FinProdWrapper(fdObj));
            mapDivision.put(fdObj.Business_Devision__c, fpWrapperList);
            divisionCountMap.put(fdObj.Business_Devision__c, fpWrapperList.size());                
    //create a list of Division Names which will be helpful to iterate
    divisionList = new List<String>(mapDivision.keySet());

public Class FinProdWrapper {

    public FinProdWrapper(Financial_Product__c fn){
        this.Name = fn.Name;
        this.BD = fn.Business_Devision__c;
        this.LD = fn.Launch_Date__c.format();
        this.LE = fn.Legal_Entities__c;
        this.LTC = fn.Link_to_Confluence__c;
        this.MB = fn.Managed_by__c;
        this.RPM = fn.Responsible_PM__c;
        this.SG = fn.Sales_Guru__c;

    public String Name {get; set;}
    public String BD {get; set;}
    public String LD {get; set;}
    public String LE {get; set;}
    public String LTC {get; set;}
    public String MB {get; set;}
    public String RPM {get; set;}
    public String SG {get; set;}        


As You recomend I try to create a wrapper class, and I get first column dinamically makes rowspan. But I don't get how to make the second column make rowspan as the first column.

Now I Have:

  A  |  B  |  C  |  D  |
     |  B1 |  C1 |  D1 |
  A1 |  B1 |  C2 |  D2 |
     |  B1 |  C3 |  D3 |

But I need:

  A  |  B  |  C  |  D  |
     |     |  C1 |  D1 |
  A1 |  B1 |  C2 |  D2 |
     |     |  C3 |  D3 |

2 Answers 2


Examples such as this one HTML rowspan Attribute reduce the number of td elements in the following rows (as rowspan is effectively saying use the content from the previous row).

So you need to modify your code to only output these td elements once in every 3 rows:

                 <td rowspan="3">{!fd.Business_Devision__c}</td>
                 <td rowspan="3">{!fd.Managed_by__c}</td>

That is probably going to take the introduction of an apex:variable that you use to count. (Or you could introduce a wrapper class where you pre-calculate a true/false for the output on the server-side.) Plus an apex:outputPanel of layout="none" to do the conditional rendering of the pair of td elements.

  • 2
    I don't believe apex:variable works inside of apex:repeat tags for counting anymore (as it states in the second paragraph of the article you linked).
    – gNerb
    Commented Mar 26, 2018 at 18:18
  • @gNerb Thanks - didn't know that. Will change the answer.
    – Keith C
    Commented Mar 26, 2018 at 19:44
  • @KeithC Please look at my updated code
    – Viktor
    Commented Mar 27, 2018 at 9:37
  • @Victor Best not to rely on the counter approach - see the warning that gNerb pointed out.
    – Keith C
    Commented Mar 27, 2018 at 9:46
A    |    B    |   C   |   D   |
     |         |   C1  |   D1  |
A1   |    B1   |   C2  |   D2  |
     |         |   C3  |   D3  |

Based on the above given requirement, i had a similar code which shows Accounts and its related contacts in the same way and sharing it below if it may help you in any way.

VF Page:

<apex:page controller="SampleController" showHeader="false" sidebar="false">
    <apex:form id="Form">
        <apex:messages />  
        <apex:pageBlock >
                <table class="list">
                    <tr class="headerRow">
                        <th>Account Id</th>
                        <th>Account Name</th>
                        <th>Account Created Date</th>
                        <th>Account Last Modified Date</th>
                    <apex:repeat value="{!AllFinProd}" var="fd">
                        <apex:repeat value="{!fd.Contacts}" var="fd1">
                                <td colspan="2"></td>


Apex Class:

public class SampleController {
    public List<Account> getAllFinProd(){
        List<Account> fd = [SELECT Id,Name,CreatedDate,LastModifiedDate, (SELECT FirstName, LastName from Contacts) from Account];
        return fd;

I would also recommend using wrapper class to group the result based on the Business Division, suggested by Keith.

  • Please look at my updated code
    – Viktor
    Commented Mar 27, 2018 at 9:37

You must log in to answer this question.

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