[SalesForce] Visualforce dynamic sObject table column value formatting

I want to allow my team to display some leads and tasks (and any known object) with custom conditions and display all the data on only one screen.
I made some custom objects "blocks" containing SOQL elements :

  • base query ; ex: select * from lead where createdDate = TODAY
  • headers ; ex: Name, CreatedDate, id

I have a controller that get all those blocks then get all the records in wrappers with a list of headers and a list of those sobjects.

Then I have a visualforce page that use repeat vf command :

  • repeat on all wrappers to create tables
  • repeat on all headers to create the table header then fill the rows & 1st column is a link to the record

This way my team can work on only one screen with data we want.

But I would like to be able to format the output value, depending on the type.
Obviously for dates and datetimes.

I am just thinking about one thing : I could use a map of headers instead of a list, something like:

key = header, value = type. GetHeaders => return List<String>(headers.keySet())

So in my columns i could check the type to use some formated output values.


Edit with code :

Controller with an inner class containing headers (=fields) and a list of records.
Some queries are created from values contained in a custom object TodoBlock__c, then they are used to fill the list "records".

public with sharing class ToDoBlockController{
public TodoBlock todoBlock      {get;set;}

public void initComponent(){
    // init todoblock
}

public class TodoBlock{
    // input
    public String        name       {get;set;}
    public String        query      {get;set;}
    public List<String>  headers    {get;set;}
    public Integer       queryLimit {get;set;}

    // calculation
    public List<SObject> records {get;set;}
    public Integer       count   {get;set;}
    public String        objType {get;set;}

    // modification
    public String        sortColumn     {get{ return sortColumn==null ? headers[0] : sortColumn; } set;}
    public boolean       isAscSort      {get{ return isAscSort==null ? true : isAscSort; }  set;}

    public TodoBlock(TodoBlock__c record, Integer queryLimit, String sortColumn, String isAscSortStr){ 
        // attributes initialization
    }
    public void runQueries(){
        // queries records
    }
}

}

Visualforce component containing the table.
I am having trouble displaying it correctly, anyway important part is under the 2 comments. Here i would like to format the value depending on its type. Value is record[header] result. Type can be anything.
I see 2 option :

  • I can get type from VF side => i create tags with conditions in rerender attribute
  • I need to store it somewhere in the controller => instead of String[] header I shoud use Map{String=>String} where key is header/field name and value is type

    0}">

    {!$ObjectType[todoBlock.objType].fields[header].label}

    rendered="{!todoBlock.sortColumn = header}"
    value="/s.gif"/>

                    <!-- 1st col = link -->
                    <apex:outputLink value="/{!rec.Id}" target="_blank" rendered="{!header == todoBlock.headers[0]}">{!rec[header]}</apex:outputLink>
    
                    <!-- other cols = normal -->
                    <apex:outputLabel rendered="{!header != todoBlock.headers[0]}">{!rec[header]}</apex:outputLabel>
                </apex:column>
                </apex:repeat>
    
            </apex:pageBlockTable>
        </apex:pageBlockSection>
    

Best Answer

So I made a Map fieldTypeMap and a component to handle the date/datetime format

Here is what I did, maybe it will be usefull for someone else.

Controller

I added the map as an attribute of my inner class into the controller:

// header/type mapping
public Map<String,String> headerTypeMap {get;set;}

I initialize it at the end of the constructor, thanks to this.headers (array of string header/field name), after the queries, like this:

        // calculation :
        runQueries();

        // mapping
        this.headerTypeMap = new Map<String,String>();
        for(String field : this.headers){
            this.headerTypeMap.put(
                field,
                Schema.getGlobalDescribe().get(objType).getDescribe().fields.getMap().get(field).getDescribe().getType()+''
            );
        }

Visualforce component

Because I often need to format date and datetime I thought it could be a good idea to create a formating component So I made a quick and dirty one:

<apex:component controller="FormatedValueController">
<apex:attribute name="dateValue" 
    type="Date" 
    assignTo="{!inputDateValue}"
    description="input value to formatTodoBlock__c record that contains data" 
/>
<apex:attribute name="datetimeValue" 
    type="Date" 
    assignTo="{!inputDateTimeValue}"
    description="input value to formatTodoBlock__c record that contains data" 
/>

{!formatedvalue}

Controller:

public class FormatedValueController {
    public Date     inputDateValue {get;set;}
    public Datetime inputDateTimeValue {get;set;}

    public String getFormatedValue(){
        String formatedValue = '';
        formatedValue += inputDateValue == null ? '' : inputDateValue.format();
        formatedValue += inputDateTimeValue == null ? '' : inputDateTimeValue.format();
        return formatedValue;
    }
}

And a quick&dirty test class too (for code coverage only):

@isTest
global class FormatedValueController_test{
    static testMethod void FormatedValueController() {
        FormatedValueController c = new FormatedValueController();
        Test.startTest ();
        c.getFormatedValue();
        Test.stopTest();
    }
}

100% you-hou ;)

And finally ...

Main Component

Oddly I need to use "{!todoBlock.headerTypeMap[header]}" everywhere, where todoBlock is the instance of my innerclass and header the var of repeat on headers attribute (table with a dynamic number of columns).

I wanted to use an apex variable for each header, kind of:

<apex:variable var="type" value="{!todoBlock.headerTypeMap[header]}" />

but for an unknown reason it just display the last header value in every loop. For example if I have DATE, DATETIME, STRING, it displays STRING, STRING, STRING. Not a big deal to use the full code, it is just less readable.

So here it is :

<apex:outputLabel rendered="{!todoBlock.headerTypeMap[header]=='DATETIME'}">
    <c:FormatedValue datetimeValue="{!rec[header]}"/>
</apex:outputLabel>

<apex:outputLabel rendered="{!todoBlock.headerTypeMap[header]=='DATE'}">
    <c:FormatedValue dateValue="{!rec[header]}"/>
</apex:outputLabel>

<apex:outputLabel rendered="{!NOT(AND(todoBlock.headerTypeMap[header]=='DATETIME',todoBlock.headerTypeMap[header]=='DATE'))}">
    {!rec[header]}
</apex:outputLabel>