Adding Sorting Capability to PageBlockTable Component

In one of my previous posts I demonstrated how to add paging feature to your Visualforce Pages, in this article we will explore how we can add sorting option to the PageBlockTable component.

Challenges:
  • How to replace standard header text with a link that calls a controller method for sorting the data
  • How to implement the link in such a way to support sort direction Ascending and Descending features
  • How to view Ascending and Descending state of the columns
And this is one way of meeting the above challenges:
I first replace the standard header of the Column tag with a facet tag to include a commandLink component in it.

Then I select the column name to be pasted as parameter to the controller once the commandLink header is clicked on.

Then I add some condition to the Label of header to show "Ascending" and "Descending" icons.



<apex:page controller="PageBlockTableSortingCon" tabStyle="Account">
<apex:sectionHeader title="Accounts List with Sorting"></apex:sectionHeader>
<apex:form >
<apex:pageBlock title="" id="pageBlock">
<apex:pageBlockButtons location="top">
<apex:commandButton value="View" action="{!ViewData}" id="theButton" rerender="pageBlock"></apex:commandButton>
</apex:pageBlockButtons>
<apex:pageMessages ></apex:pageMessages>
<apex:pageBlockTable value="{!accounts}" var="a" rendered="{!NOT(ISNULL(accounts))}">
<apex:column>
<apex:facet name="header">
<apex:commandLink action="{!ViewData}" value="Account Name{!IF(sortExpression=='name',IF(sortDirection='ASC','▼','▲'),'')}" id="cmdSort">
<apex:param value="name" name="column" assignTo="{!sortExpression}" ></apex:param>
</apex:commandLink>
</apex:facet>
<apex:outputLink value="/{!a.Id}" target="_blank">{!a.Name}</apex:outputLink>
</apex:column>
<apex:column value="{!a.Phone}">
<apex:facet name="header">
<apex:commandLink action="{!ViewData}" value="Phone{!IF(sortExpression=='Phone',IF(sortDirection='ASC','▼','▲'),'')}">
<apex:param value="Phone" name="column" assignTo="{!sortExpression}" ></apex:param>
</apex:commandLink>
</apex:facet>
</apex:column>
<apex:column value="{!a.BillingCity}">
<apex:facet name="header">
<apex:commandLink action="{!ViewData}" value="Billing City{!IF(sortExpression=='BillingCity',IF(sortDirection='ASC','▼','▲'),'')}">
<apex:param value="BillingCity" name="column" assignTo="{!sortExpression}" ></apex:param>
</apex:commandLink>
</apex:facet>
</apex:column>
<apex:column value="{!a.BillingCountry}">
<apex:facet name="header">
<apex:commandLink action="{!ViewData}" value="Billing Country{!IF(sortExpression=='BillingCountry',IF(sortDirection='ASC','▼','▲'),'')}">
<apex:param value="BillingCountry" name="column" assignTo="{!sortExpression}" ></apex:param>
</apex:commandLink>
</apex:facet>
</apex:column>

</apex:pageBlockTable>
</apex:pageBlock>
</apex:form>
</apex:page>





And this how the controller look like:




public class PageBlockTableSortingCon {

private List<Account> accounts;
private String sortDirection = 'ASC';
private String sortExp = 'name';

public String sortExpression
{
get
{
return sortExp;
}
set
{
//if the column is clicked on then switch between Ascending and Descending modes
if (value == sortExp)
sortDirection = (sortDirection == 'ASC')? 'DESC' : 'ASC';
else
sortDirection = 'ASC';
sortExp = value;
}
}

public String getSortDirection()
{
//if not column is selected
if (sortExpression == null || sortExpression == '')
return 'ASC';
else
return sortDirection;
}

public void setSortDirection(String value)
{
sortDirection = value;
}

public List<Account> getAccounts() {
return accounts;
}


public PageReference ViewData() {
//build the full sort expression
string sortFullExp = sortExpression + ' ' + sortDirection;

//query the database based on the sort expression
accounts = Database.query('Select id, Name, BillingCity, BillingCountry, Phone from Account order by ' + sortFullExp + ' limit 1000');
return null;
}

}



A Utility Apex Class to Convert All Types Into String

If you have been in the programming world long enough, you already know that casting types from one to another specially string representation of the variables is always part the job.

Whether you want to show the data to user or form the variables in different formats, incorporate them in messages, etc.

In this article I will present you one of the Apex classes I have written to help me faster develop Salesforce Applications.

This class helps me in number of ways, for example I do not need to worry about the underlying Apex code to convert a primitive type to string anymore, for all types I just need to call one method "ToString" and it will take care of for me.
Also the methods provide me with formatting capabilities, so I can not only convert to string but also format the string in many ways I need.

Look at the below code and see how the results are:



ApexPages.addmessage(new ApexPages.message(ApexPages.severity.INFO,'Integer: '+ zConvert.ToString(13434)));
 ApexPages.addmessage(new ApexPages.message(ApexPages.severity.INFO,'Double: '+ zConvert.ToString(1.23)));
 ApexPages.addmessage(new ApexPages.message(ApexPages.severity.INFO,'Boolean: '+ zConvert.ToString(true)));
 ApexPages.addmessage(new ApexPages.message(ApexPages.severity.INFO,'Date: ' + zConvert.ToString(date.newinstance(1960, 2, 17))));
 ApexPages.addmessage(new ApexPages.message(ApexPages.severity.INFO,'Date time: ' + zConvert.ToString(Datetime.now(),'MMM, dd yyyy')));
 ApexPages.addmessage(new ApexPages.message(ApexPages.severity.INFO,'File Size: '+ zConvert.FileSizeToString(6766767)));
 ApexPages.addmessage(new ApexPages.message(ApexPages.severity.INFO,'Money: '+ zConvert.CurrencyToString(Decimal.valueOf(34.99),'$'))




This is the result of running the above code:



Well, very nice, now let's see how the actual class is developed:
Because I would like to directly call my Coverter's class methods without creating a new instance of the class, I have defined all the methods as "static".

The methods for this class are:
  • ToString(Integer)
  • ToString(Double)
  • ToString(Long)
  • ToString(Boolean)
  • ToString(Date)
  • ToString(Date,format)
    sample: zConvert.ToString(mydate,'MM-dd-yy')
  • ToString(Time)
  • ToString(Time,format)
    sample: zConvert.ToString(myTime,'hh:mm:ss')
  • ToString(Datetime)
  • ToString(Datetime,format)
  • ToString(Decimal)
  • ToString(Decimal, ScientificNotaion)
    ScientificNotaion is a Boolean value and if false is passed then the string will not have scientific notations.
  • FileSizeToString(Long)
    Returns values such as "5.5 KB", "8 MB", etc. Parameter passed is in bytes.
  • CurrencyToString(Decimal, CurrencyChar)
    CurrencyChar can be "$", "£", etc




public class zConvert
{
 /* The Initial Developer of the Original Code is Sam Arjmandi.
 * Portions created by the Initial Developer are Copyright (C) 2008
 * the Initial Developer. All Rights Reserved. 
 * 
 * This Code is provided "As Is" without warranty of any kind.
 */
 
  public static String ToString(integer Value)
  {
      /* string representation if an Integer value */
      return Value.format();
  }
 
  public static String ToString(Double Value)
  {
    /* string representation if a Double value */
     return Value.format();
  }
 
  public static String ToString(Boolean Value)
  {
     /* string representation if a Boolean value */
     if (Value)
       return 'true';
     else
       return 'false';
  }
 
  public static String ToString(Long Value)
  {
    /* string representation if a Long value */
    return Value.format();
  }
 
  public static String ToString(Date Value)
  {
     /* string representation if a Date value */
     return Value.format();
  }
 
  public static String ToString(Date Value,String format)
  {
    /* string representation if a Date value with formatting */
    Datetime temp = Datetime.newInstance(Value.year(), Value.month(), Value.day());
    return temp.format(format);
  }
 
  public static String ToString(Datetime Value)
  {
     /* string representation if a Datetime value */
     return Value.format();
  }
 
  public static String ToString(Datetime Value,String format)
  {
     /* string representation if a Datetime value with formatting */
     return Value.format(format);
  }
 
  public static String ToString(Time Value)
  {
    /* string representation if a Time value */
    return String.valueOf(Value);
  }
 
  public static String ToString(Time Value, String format)
  {
    /* string representation if a Time value with formating */
    Datetime temp = Datetime.newInstance(1970, 1, 1, Value.hour(), Value.minute(), Value.second());
    return temp.format(format);
  }

  public static String ToString(Decimal Value)
  {
    /* string representation if a Decimal value */
    return Value.format();
  }
 
  public static String ToString(Decimal Value, Boolean ScientificNotation)
  {
    /* string representation if a Decimal value with or without Scientific Notation */
    if (ScientificNotation)
     return Value.format();
    else
     return Value.toPlainString();
  }
 
  public static String FileSizeToString(Long Value)
  {
     /* string representation if a file's size, such as 2 KB, 4.1 MB, etc */
     if (Value < 1024)
       return ToString(Value) + ' Bytes';
     else
     if (Value >= 1024 && Value < (1024*1024))
     {
       //KB
       Decimal kb = Decimal.valueOf(Value);
       kb = kb.divide(1024,2);
       return ToString(kb) + ' KB';
     }
     else
     if (Value >= (1024*1024) && Value < (1024*1024*1024))
     {
       //MB
       Decimal mb = Decimal.valueOf(Value);
       mb = mb.divide((1024*1024),2);
       return ToString(mb) + ' MB';
     }
     else
     {
       //GB
       Decimal gb = Decimal.valueOf(Value);
       gb = gb.divide((1024*1024*1024),2);
      
       return ToString(gb) + ' GB';
     }
    
  }
 
  public static String CurrencyToString(Decimal Value, String CurrencyChar)
  {
     return CurrencyChar + ToString(Value);
  }
 
}