Friday, 3 September 2010

Rotating a VisualForce table

One issue that crops up repeatedly on the SalesForce Developer Discussion Boards is how to rotate a table. E.g. if I have a table that displays account information one account per row:

how can I rotate this to display one account per column:


First, to create a class to model the data that is being passed back to the page. In this case the class wraps a row of data, thus row 1 contains the name of each account, row2 contains the street address etc.

As each cell in the table is a text field, our class can simply wrap a list of String primitives. The  class implementation is shown below:

public class RowWrapper
    {
     // the values (cells) making up this row
     public List<String> values {get; set;}
     
     // constructor
     public RowWrapper()
     {
      values=new List<String>();
     }
     
     // append a value (cell) to the row
     public void addValue(String value)
     {
      values.add(value);
     }
    }

The next step is to write an extension to the Account standard controller that transforms a list of Accounts into a list of RowWrapper classes.

	// retrieves the list of accounts backing the page
    public List<Account> getAccounts()
    {
    	if (null==accs)
    	{
    		accs=[select id, Name, BillingStreet, BillingCity, BillingPostalCode from Account
    	          where BillingCity != null and BillingPostalCode!=null limit 3];
    	}
    	
    	return accs;
    }
    
    // retrieves the list of row wrappers
    public List<RowWrapper> getRowWrappers()
    {
    	if (null==rows)
    	{
    		rows=new List<RowWrapper>();
    		
    		// create a row for each field - there are 4 of these, Name, Street, City and PostCode
    		for (Integer idx=0; idx<4; idx++)
    		{
    			rows.add(new RowWrapper());
    		}
    		
    		// iterate the accounts and populate the rows
    		for (Integer idx=0; idx<getAccounts().size(); idx++)
    		{
    			rows[0].addValue(getAccounts()[idx].Name);
    			rows[1].addValue(getAccounts()[idx].BillingStreet);
    			rows[2].addValue(getAccounts()[idx].BillingCity);
    			rows[3].addValue(getAccounts()[idx].BillingPostalCode);
    		}
    	}
    	
    	return rows;
    }


The final step is to create a VisualForce page to iterate the RowWrapper list and output the table:

<table class="list" border="0" cellpadding="0" cellspacing="0">
    <tr class="headerRow  ">
      <apex:repeat value="{!headWrap.values}" var="heading">
        <th class="headerRow ">
           {!heading}
        </th>
      </apex:repeat>
    </tr>
    <apex:repeat value="{!rowWrappers}" var="row">
       <tr>
         <apex:repeat value="{!row.values}" var="value">
           <td>
             {!value}
           </td>
         </apex:repeat>
       </tr>
    </apex:repeat>
  </table>

The page and controller can be downloaded here. Simply unzip into the src directory of your Force.com project in the IDE.

11 comments:

  1. Bob, Great A+ stuff as usual. You are truly an asset to the SFDC development community.

    Thanks again for your time in both developing and posting this.

    ReplyDelete
  2. Hi Bob, your code was helpful for beginner like me. Wanted to know if it is possible to make this grid editable and add update logic on this. If possible, can you provide some pointers on how to do that. in my case i have 3 columns, 1 column displays data in account within salesforce, second and third column displays similar data extracted from 2 external systems. Client is using the page to compare values in 3 different systems. I have got the logic working based on your code above. But wanted to know if i get a requirement to update the columns, how would i do it.

    Thanks,
    Arvin

    ReplyDelete
    Replies
    1. Basically you'd turn the markup that outputs the values - {!value}, into apex:inputText tags. One problem would be that as the RowWrapper just contains a list of strings, you wouldn't get the enhanced functionality that you would with inputfields (e.g. date picker, lookup etc).

      Delete
    2. Thanks Bob. I got it working.

      Delete
  3. Hi Bob.. Possible to add an additional column in the first position to label the rows and identify the values? You did not rotate the header row.

    ReplyDelete
  4. Hi,

    Actually my object is like String strtemp='Mahesh__'+strFieldName+'__c';
    How to use this like as object in my apex class?

    Please help me......

    ReplyDelete
  5. Hi Bob,
    What if I don't want to change the type of data to string. Is that possible with wrapper class. I want to display Date and status(of type checkbox) horizontally.

    ReplyDelete
  6. Thank you Bob

    ReplyDelete
  7. So what the heck is headWrap or did you mean to write headWarp? I ask because it's a bit of a headwarp to get your headwrap

    ReplyDelete