Wednesday, February 1, 2017

Display records with rowspan in Visualforce - Simplified Approach


Use Case


Sometimes we receive requirement to display data in tabular format like below where we need to use rowspan attribute of table data.





We need to have specific design upfront on how we can prepare the data and it will dynamically consider rowspan and format the rows accordingly in Visualforce.

For the sake of simplicity I am using Salesforce Contact record to prepare and display those.

Approach in Apex Controller to prepare data

1) Create a ContactWrapper with all the Contact attributes.
2) In getData() method, retrieve the Contact List firing SOQL query and prepare the desired map i.e. mapModule which contains ModuleName as Key and List<ContactWrapper>. In that method, prepare a List of ModuleNames from mapModule.keySet().


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public class ContactController{
 //map to capture Module Name and List of Contacts
 public Map<String, List<ContactWrapper>> mapModule {get;set;} 
 
 //map to capture Module Name and count of Contact which will be used to define rowspan.
 public Map<String, Integer> moduleCountMap{get;set;}
 
 //to capture list of Module Names
 public List<String> moduleList {get;set;}

 public ContactController()
 {
  mapModule = new Map<String, List<ContactWrapper>>();
  moduleCountMap = new Map<String, Integer>();
  moduleList = new List<String>();
 }
 
 //prepare data to display in Visualforce.
 public void getData()
 {
  List<Contact> contactList = [SELECT Name, Phone, Email, Module__c 
         FROM Contact 
         WHERE Module__c !=null 
         ORDER BY Module__C LIMIT 10];
  
  //grouped into module and prepare the map
  for(Contact conObj:contactList)
  {
   List<ContactWrapper> conWrapperList = new List<ContactWrapper>();
   //verify map already contains same Module Name
   if(mapModule.containsKey(conObj.Module__c))
   {
    //retrieve the list of existing contact
    conWrapperList = mapModule.get(conObj.Module__c);
    
    //put the new contact to the list
    conWrapperList.add(new ContactWrapper(conObj));
    
    mapModule.put(conObj.Module__c, conWrapperList);
    
    //store count of rows per Module Name
    modulecountMap.put(conObj.Module__c, conWrapperList.size());
   }
   else
   {
    //create a new map of Module Name
    conWrapperList.add(new ContactWrapper(conObj));
    mapModule.put(conObj.Module__c, conWrapperList);
    
    //store count of rows per Module Name
    modulecountMap.put(conObj.Module__c, conWrapperList.size());
   }
  }
  
  //create a list of Module Names which will be helpful to iterate
  moduleList = new List<String>(mapModule.keySet()); 
 }

 public Class ContactWrapper {
  
  public ContactWrapper(Contact conObj)
  {
   this.Name = conObj.Name;
   this.Phone = conObj.Phone;
   this.Email = conObj.Email;
   this.Module = conObj.Module__c;
  }
  
  public String Name {get;set;}
  public String Phone {get;set;}
  public String Email {get;set;}
  public String Module {get;set;}
 }
}

Approach in Visualforce to display data


1) First, repeat through moduleList which contains all the Module Name as key.

2) To determine, td rowspan get the size by {!modulecountMap[key]}.

3) Create an inner repeat which has List of ContactWrapper which we can easily retrieve by {!mapModule[key]}
4) The main challenge to display tds with rowspan can be handled with proper rendering logic using <apex:outputPanel>, which seems to be crazy.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<apex:page controller="ContactController" action="{!getData}">
  <table border="1">
   <tr>
    <th>Group</th>
    <th>Name</th>
    <th>Phone</th>
    <th>Email</th>
    </tr>    
     <apex:repeat id="myRepeatHeader" value="{!moduleList}" var="key">         
         <apex:variable var="count" value="{!1}"/>            
            <apex:repeat id="contactDetails" value="{!mapModule[key]}" var="att">
                <tr>
                    <apex:outputPanel layout="none" rendered="{!count==1}">
                        <td rowspan="{!(modulecountMap[key])}"><apex:outputText value="{!att.Module}"/></td>
                    </apex:outputPanel>
                    <td><apex:outputText value="{!att.Name}"/></td>
                    <td><apex:outputText value="{!att.Phone}"/></td>
                    <td><apex:outputText value="{!att.Email}"/></td>

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


At my DE, it is showing like this. 

rowspan

To get a help on how to use rowspan in HTML, refer td row span

Conclusion

Overall, it's look like a easy to implement functionality but until and unless we make our hands dirty in writing logic, we cant release whats the pain in developing this.

But, it's a charm, working on this type of requirements.