Monday, 5 March 2018

Topics to study for Sharing and Visibility Designer exam

I've recently taken Sharing & Visibility exam which happens to be the second and final step towards the completion of my Application Architect journey. Phew!

Although, I took this exam way back in November, but given my laziness (oh wait, I was traveling and that's kinda fair) I'm posting my key-learning from it only now.

So before any further a due, here are the topics which you can study in order to master this certification:

Access Grants:

Salesforce uses access grants to define how much access a user or group has to that object’s records. There are 4 types of access grants:

Implicit Grants:

There are a number of sharing behaviors that are built into Salesforce applications. This kind of sharing is called implicit because it is not configured by administrators; it is defined and maintained by the system to support collaboration among members of sales teams, customer service representatives, and clients or customers. Details here
  1. Read-only access to the parent account for a user with access to a child record
  2. Access to child records for the owner of the parent account
  3. Access to records owned by or shared to portal users for internal users 
Explicit Grants:

Salesforce uses explicit grants when records are shared directly to users or groups. Specifically, Salesforce uses explicit grants when:
  1. A user or a queue becomes the owner of a record.
  2. A sharing rule shares the record to a personal or public group, a queue, a role, or a territory.
  3. An assignment rule shares the record to a user or a queue.
  4. A territory assignment rule shares the record to a territory.
  5. A user manually shares the record to a user, a personal or public group, a queue, a role, or a territory.
  6. A user becomes part of a team for an account, opportunity, or case.
  7. A programmatic customization shares the record to a user, a personal or public group, a queue, a role, or a territory
Group Membership Grants:

Grants that occur when a user, personal or public group, queue, role, or territory is a member of a group that has explicit access to
the record.

Inherited Grants:

Grants that occur when a user, personal or public group, queue, role, or territory inherits access through a role or territory hierarchy,
or is a member of a group that inherits access through a group hierarchy.

Object Record Tables

Tables that store the records of a specific object, and indicate which user, group, or queue owns each record.

Object Sharing Tables: 

Store access grants to individuals and groups. Each of rows (called sharing rows) grants a user or group access to a particular record

Group Maintenance Tables:

Store the list of users or groups that belong to each group, indicating group membership. Single group membership or inherited access grant can give several users and groups multiple ways to access a record. Also includes including system-defined group. System-defined groups are groups of users that Salesforce creates and manages internally to support various features and behaviors, such as queues and hierarchies. For every node in role hierarchy, 2 types of system-defined groups is used : Role groups and RoleAndSubordinates groups

Salesforce matching process in sequence: 


Visibility of Custom Settings

Protected:

If the custom setting is contained in a managed package, subscribing organizations can't see the custom setting: it doesn't display as part of the package list. In addition, subscribing organizations can't access the custom setting using either Apex or the API, however, developer organizations can. If the custom setting is contained in an unmanaged package, the custom setting is available through the Enterprise WSDL like any custom object (as if the Visibility was Public.)

Public:

The custom setting is available through the Enterprise WSDL like any custom object. You can package custom settings defined as public. The subscribing organizations can edit the values, as well as access them using Apex and the API, regardless of the type of package (either managed or unmanaged).

Defer Sharing Rule: 

Defer Sharing Calculation lets an administrator suspend and resume sharing calculations.Administrators should plan to suspend/resume sharing calculations during maintenance windows to have minimal impact on users. Details here

Useful links:














All the best for your exam. Feel free to shout out if there's anything which you want to know more.

Monday, 7 August 2017

Topics to study for Data Architecture and Management Designer Exam

Hi folks,

I have passed my Data Architecture and Management Designer exam couple of months back and was keen to share my learnings but thanks to my procrastination it was delaying. Finally, I got a chance to post something in a structured way which might help you to pass this credentials.

Data Architect is the domain exam which falls under the Application Architect tree which leads to Technical Architect (its a long way but getting there) I won't go into the details of the credentials overview since all the instructions are mentioned here  rather I highlight the key areas which you need to focus on, in order to fully understand the large data volume considerations and become an expert in Data management. Roll up your sleeves and lets dig in:

Multi-tenancy & Metadata Overview:

Multitenancy is a means of providing a single application to multiple organizations such as different companies or departments within a company, from a single hardware-software stack.

To ensure that tenant-specific customizations do not breach the security of other tenants or affect their performance, Salesforce uses a runtime engine that generates application components from those customizations. By maintaining boundaries between the architecture of the underlying application and that of each tenant, Salesforce protects the integrity of each tenant’s data and operations.

When any updates are being made related to organization, the platform tracks metadata. Salesforce stores the application data for all virtual tables in a few large database tables, which are partitioned by tenant (organization) and serve as heap storage. The platform’s engine then materializes virtual table data at runtime by considering the corresponding metadata. Details here 



Search Architecture:

Search is the capability to query records based on free-form text.For data to be searched, it must first be indexed. The indexes are created using the search indexing servers, which also generate and asynchronously process queue entries of newly created or modified data. After a searchable object’s record is created or updated, it could take about 15 minutes or more for the updated text to become searchable.

Salesforce performs indexed searches by first searching the indexes for appropriate records, then narrowing down the results based on access permissions, search limits, and other filters. This process creates a result set, which typically contains the most relevant results. After the result set reaches a predetermined size, the remaining records are discarded. The result set is then used to query the records from the database to retrieve the fields that a user sees. Details here

Force.com query optimizer:

The Force.com query optimizer helps the database system’s optimizer produce effective execution plans for Salesforce queries. Force.com query optimizer works on the queries that are automatically generated to handle reports, list views, and both SOQL queries. Details here 

Skinny Tables:

Salesforce creates skinny tables to contain frequently used fields and to avoid joins, and it keeps the skinny tables in sync with their source tables when the source tables are modified. To enable skinny tables, contact Salesforce Customer Support. For each object table, Salesforce maintains other, separate tables at the database level for standard and custom fields. This separation ordinarily necessitates a join when a query contains both kinds of fields. A skinny table contains both kinds of fields and does not include soft-deleted records.

This table shows an Account view, a corresponding database table, and a skinny table that would speed up Account queries. Details here 



Indexes:

Salesforce supports custom indexes to speed up queries, and you can create custom indexes by contacting Salesforce Customer Support. Nulls in the criteria prevented the use of indexes. Details here

Index Tables

The Salesforce multitenant architecture makes the underlying data table for custom fields unsuitable for indexing. To overcome this limitation, the platform creates an index table that contains a copy of the data, along with information about the data types. By default, the index tables do not include records that are null (records with empty values)


Standard and Custom Indexed Fields:

The Force.com query optimizer maintains a table containing statistics about the distribution of data in each index. It uses this table to perform pre-queries to determine whether using the index can speed up the query.

Standard Indexed Fields:

Used if the filter matches less than 30% of the total records, up to one million records.
For example, a standard index is used if:
• A query is executed against a table with two million records, and the filter matches 600,000 or fewer records.
• A query is executed against a table with five million records, and the filter matches one million or fewer records.

Custom Indexed Fields

Used if the filter matches less than 10% of the total records, up to 333,333 records.
For example, a custom index is used if:
• A query is executed against a table with 500,000 records, and the filter matches 50,000 or fewer records.
• A query is executed against a table with five million records, and the filter matches 333,333 or fewer records.

Two-column Custom Indexes 

Two-column indexes are subject to the same restrictions as single-column indexes, with one exception. Two-column indexes can have nulls in the second column by default, whereas single-column indexes cannot, unless Salesforce Customer Support has explicitly enabled the option to include nulls.

PK Chunking: 

Use the PK Chunking request header to enable automatic primary key (PK) chunking for a bulk query job. PK chunking splits bulk queries on very large tables into chunks based on the record IDs, or primary keys, of the queried records. Each chunk is processed as a separate batch that counts toward your daily batch limit, and you must download each batch’s results separately. Details here

Big Objects for data archiving:

BigObjects is a new capability to let you store and manage data at scale on the Salesforce platform. This feature helps you engage directly with customers by preserving all your historical customer event data. If you have a large amount of data stored in standard or custom objects in Salesforce, use BigObjects to store historical data.

Best Practices for Data archiving

Big Objects 


Data Skew: 

“Data skew” refers to the condition of having the ratio of records to a parent record or to an owner out of balance. As a basic rule of thumb, you want to keep any one owner or parent record from “owning” more than 10,000 records.  If you find that is happening a lot, revisit the idea of archiving your data, or create a plan to spread the wealth by assigning the records to more owners, or to more parent records.

Data Governance

Data Governance – refers to the overall management of the availability, usability, integrity, and security of the data employed in an enterprise. A sound data governance program includes a governing body or council, a defined set of procedures, and a plan to execute those procedures

Salesforce governance simplified

Best Practice for Data Governance

Data Stewardship

Management and oversight of an organization’s data assets to help provide business users with high-quality data that is easily accessible in a consistent manner

Data Stewardship/ Data governance

Techniques for Optimizing Performance

1) Mashups - External Website and Callouts

2) Using SOQL and SOSL
SOQL - You know in which objects or fields the data resides. (if multiple object are related to eachother)

SOSL - You don’t know in which object or field the data resides, and you want to find it in the most efficient way possible. (if multiple objects aren't related to eachother) It is generally faster than SOQL if the search expression uses a CONTAINS term.

3) Deleting Data:

While the data is soft deleted, it still affects database performance because the data is still resident, and deleted records have to be excluded from any queries.

We recommend that you use the Bulk API’s hard delete function to delete large data volumes.

4) Defer Sharing Rules: 

It allows users to defer the processing of sharing rules until after new users, rules, and other
content have been loaded.

Best Practice for achieving good performance in deployments

1) Loading Data from the API
  • Use the fastest operation possible—insert() is fastest, update() is next, and upsert() is next after that. If possible, also break upsert() into two operations: create() and update().
  • When updating, send only fields that have changed (delta-only loads)
  • For custom integrations, authenticate once per load, not on each record.
  • Use Public Read/Write security during initial load to avoid sharing calculation overhead
  • When changing child records, group them by parent—group records by the field ParentId in the same batch to minimize locking conflict

2) Extracting Data from API 
  • Use the getUpdated() and getDeleted() SOAP API to sync an external system with Salesforce at intervals greater than 5 minutes
3) Searching
  • Keep searches specific and avoid using wildcards, if possible. For example, search with Michael instead of Mi*
  • Use single-object searches for greater speed and accuracy.
4) Deleting 
  • When deleting records that have many children, delete the children first.
  • When deleting large volumes of data, a process that involves deleting one million or more records, use the hard delete option of the Bulk API
Event Monitoring

Event monitoring is one of many tools that Salesforce provides to help keep your data secure. It lets you see the granular details of user activity in your organization. We refer to these user activities as events. You can view information about individual events or track trends in events to swiftly identify abnormal behavior and safeguard your company’s data. Details here

Field Audit Trail

Field Audit Trail lets you define a policy to retain archived field history data up to ten years, independent of field history tracking Details here 

Setup Audit Trail 

Setup Audit Trail tracks the configuration and metadata changes that you and other admins have made to your org. Details here

Merge accounts 

Merging multiple accounts into one helps you keep your data clean so you can focus on closing deals. Details here 

I would be highly suggest to go through the guide for best practices of deployments with large data volume.

Best of luck for your exam. Hope this information helps. Feel free to comment below if there's anything which need you understand more.

Cheers ears


Sunday, 1 May 2016

Image mapping in Salesforce using jQuery

I had interesting requirement in one of my assignment couple of months back, thought I should share with the community.

The requirement came from construction industry client for managing the injuries of labors to track the health and safety. We used image-mapster jQuery library to demonstrate the human body so that support agent can create the injury details after clicking the body part and send the information directly to health and safety department.

The purpose of this solution is to track the common injuries and reasons to know which safety systems need more investment in order to make them better.

Technically, we have defined the HTML image maps for the body parts. I have used three images which are shown below and added them as static resource in Salesforce

This image is used to show the background which defines the structure of the body

This image is used to highlight the part of the body
This image is used to show the selected part when the mouse is hovered


All the images overlay each other and give the effect of single image. When the user hovers the body part it changes the color and display the name of the part, after clicking it shows the form with the auto populated values based on the selection, on submission of form it creates the injury detail record in Salesforce as shown below:




Code Snippet for Visualforce page:


<apex:page standardController="Case">

    <apex:includeScript value="https://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js" />
    <apex:includeScript value="{!URLFOR($Resource.AbsoluteBeginners, '/AbsoluteBeginners/scripts/jquery.imagemapster.js')}"/>

<style>

@import "https://fonts.googleapis.com/css?family=Raleway";

#caseInjuryForm{
    width:100%;
    height:100%;
    opacity:.95;
    top:0;
    left:0;
    display:none;
    position:fixed;
    background-color:#313131;
    overflow:auto
}

img#close {
    position:absolute;
    right:-14px;
    top:-14px;
    cursor:pointer
}

div#popupCase {
    position:absolute;
    left:50%;
    top:17%;
    margin-left:-202px;
    font-family:'Raleway',sans-serif
}
#caseform {
    max-width:300px;
    min-width:250px; 
    padding:10px 50px;
    border:2px solid gray;
    border-radius:10px;
    font-family:raleway;
    background-color:#fff
}

#casep {
    margin-top:30px
}

#caseh2 {
    background-color:#FEFFED;
    padding:20px 35px;
    margin:-10px -50px;
    text-align:center;
    border-radius:10px 10px 0 0
}

#casehr {
    margin:10px -50px;
    border:0;
    border-top:1px solid #ccc
}

.caseinput {
    width:82%;
    padding:10px;
    margin-top:30px;
    border:1px solid #ccc;
    padding-left:40px;
    font-size:16px;
    font-family:raleway
}

#casename {
    background-image:url(../images/name.jpg);
    background-repeat:no-repeat;
    background-position:5px 7px
}

#caseemail {
    background-image:url(../images/email.png);
    background-repeat:no-repeat; 
    background-position:5px 7px
}

#casetextarea {
    background-image:url(../images/msg.png);
    background-repeat:no-repeat;
    background-position:5px 7px;
    width:82%;
    height:95px;
    padding:10px;
    resize:none;
    margin-top:30px;
    border:1px solid #ccc;
    padding-left:40px;
    font-size:16px;
    font-family:raleway;
    margin-bottom:30px
}


#casesubmit {
    text-decoration:none;
    width:100%;
    text-align:center;
    display:block;
    background-color:#FFBC00;
    color:#fff;
    border:1px solid #FFCB00;
    padding:10px 0;
    font-size:20px;
    cursor:pointer;
    border-radius:5px
}

#casespan {
    color:red;
    font-weight:700
}

#casebutton {
    width:10%;
    height:45px;
    border-radius:3px;
    background-color:#cd853f;
    color:#fff;
    font-family:'Raleway',sans-serif;
    font-size:18px;
    cursor:pointer
}


</style>
    
    <script type="text/javascript" src="/soap/ajax/28.0/connection.js"></script>
    <script type="text/javascript" src="/soap/ajax/28.0/apex.js"></script>
    <script language="JavaScript">
        
        sforce.connection.sessionId = "{!$Api.Session_ID}";
        $(document).ready(function(){
            
            document.getElementById('caseInjuryForm').style.display = "none";
            
            $('#human-body-map').mapster({
                singleselect : true,
                render_highlight: {
                    fillColor: '2aff00',
                    stroke: false,
                    fade: false,
                    altImage: '{!URLFOR($Resource.HumanBodyMap, '/HumanBodyMap2.gif')}'
                    
                },
                render_select: {
                    fillColor: 'ff000c',
                    stroke: false,
                    altImage: '{!URLFOR($Resource.HumanBodyMap, '/HumanBodyMap1.gif')}'  
                },
                mapKey:'datapart',
                fillOpacity : 1,
                areas: [
                {
                    key: 'RightHandPalm',
                    selected: true
                },
                {
                    key: 'LeftHandPalm',
                    selected: true
                },
                {
                    key: 'RightLegKnee',
                    selected: true
                }]
            });
        
        });
        
        function div_show(title) {
            document.getElementById('caseInjuryForm').style.display = "block";
            var res = title.split("-");
            document.getElementById('partOfBody').value=res[0];
            document.getElementById('side').value=res[1];
            document.getElementById('detailPartBody').value=res[2];
        }
        
        function div_hide() {
            document.getElementById('caseInjuryForm').style.display = "none";
        }
        
        
        function InjurySubmit(){
            var createRecord = new Array();     
            
            var createInjuryDetail = new sforce.SObject("Injury_Detail__c");
            createInjuryDetail.Name = 'SalesforceZone Demo'     
            createInjuryDetail.Case__c= '{!Case.Id}';    
            createInjuryDetail.Part_of_body_injured__c = document.getElementById('partOfBody').value;
            createInjuryDetail.Side__c = document.getElementById('side').value;
            createInjuryDetail.Detail_of_part_of_body_injured__c = document.getElementById('detailPartBody').value;
            createInjuryDetail.Nature_of_injury__c = document.getElementById('natureInjury').value;
            createInjuryDetail.Aggravation_Description__c = document.getElementById('description').value;
            
            createRecord.push(createInjuryDetail);        
            sforce.connection.create(createRecord);
            document.getElementById('caseInjuryForm').style.display = "none";
            window.open("/"+'{!Case.id}','_parent');
            
        }
    </script>
        
    <div id="image">
        <div id="mapster_wrap_0" style="display: block; position: relative; padding: 0px; width: 400px; height: 400px;">
            <img class="mapster_el" src="{!URLFOR($Resource.HumanBodyMap,'/HumanBodyMap1.gif')}" style="display: none;"/>
            <img class="mapster_el" src="{!URLFOR($Resource.HumanBodyMap,'/HumanBodyMap1.gif')}"  style="display: none;"/>
            <img class="mapster_el" src="{!URLFOR($Resource.HumanBodyMap,'/HumanBodyMap1.gif')}" style="display: none;"/>
            <img id="human-body-map"  src="{!URLFOR($Resource.HumanBodyMap,'/HumanBodyMap.gif')}" border="0" width="800" height="600" orgWidth="800" orgHeight="600" usemap="#human-body-map" alt="human-body-map" />
        </div>
    </div>
    
    <map name="human-body-map">
        <area href="#" shape="poly" datapart="RightHandPalm" title="Hand-Right-Palm of Hand"  coords="81,332,94,315,106,305,114,307,124,310,125,324,119,336,117,344,114,351,111,357,105,363,103,362,100,368,93,364" onClick="div_show(this.title);return false" alt="" />
        <area href="#" shape="poly" datapart="LeftHandPalm" title="Hand-Left-Palm of Hand"  coords="304,312,322,307,333,311,341,317,346,327,345,333,337,332,336,341,337,356,336,361,334,364,332,366,328,367,325,368,317,361,311,345,307,333"  onClick="div_show(this.title);return false"  alt=""/>
        <area href="#" shape="poly" datapart="RightLegKnee" title="Leg-Right-Knee"  coords="170,398,180,410,192,414,201,418,205,433,203,442,194,441,184,440,175,435,175,434,171,433" onClick="div_show(this.title);return false" alt=""/>
    </map>
    
    <div id="caseInjuryForm">
        
        <div id="popupCase">
        <!-- Case Injury Details Form -->
        <form action="#" id="caseform" method="post" name="caseform">
        <img id="close" src="{!$Resource.close}" onclick ="div_hide()" />
        <h2 id="caseh2">Case Injury Details</h2>
        <hr id="casehr"/>
        <input id="partOfBody" class="caseinput" name="casename" placeholder="Part of body injured" type="text"/>
        <input id="side" class="caseinput" name="caseemail" placeholder="Side" type="text"/>
        <input id="detailPartBody" class="caseinput" name="caseemail" placeholder="Detail of part of body injured" type="text"/>
        <input id="natureInjury" class="caseinput" list="injuryList" name="caseemail" placeholder="Nature of injury" />
        <datalist id="injuryList">
            <option value="Abrasion" />
            <option value="Aggravation" />  
            <option value="Allergy"  />
            <option value="Amputation" />
            <option value="Animal" />
            <option value="Bruise" />
            <option value="Burn" />
            <option value="Chemical" />
            <option value="Choking/Suffocation" />  
            <option value="Concussion" />
            <option value="Crush/Bruise" />
            <option value="Dehydration" />  
            <option value="Dislocation" />      
            <option value="Electric shock" />
            <option value="Foreign body" />
            <option value="Fracture" />
            <option value="Hearing loss" />
            <option value="Heat stroke" />
            <option value="Intercranial Injury" />
        </datalist> 
        
        <input id="description" class="caseinput" name="caseemail" placeholder="Aggravation Description" type="text"/>
        <a onClick="InjurySubmit()" id="casesubmit">Submit Case Injury</a>
        
        </form>
        </div>
        <!-- Popup Div Ends Here -->
    </div>
    
</apex:page>


Download jQuery plugin from here
http://www.outsharked.com/imagemapster/

Define your HTML image maps here
http://www.image-mapper.com/

Useful link to setup the page
http://members.shaw.ca/sites/AbsoluteBeginners/beginner1.htm

Feel free comment below if anything is over pedantic. Cheers

Sunday, 4 October 2015

Workaround for removing the Save&New button from Standard pages after Winter'16 release

As you all know earlier Salesforce replaced the rich text editor in HTML Area home page components with a new version that supports more markup but doesn’t allow HTML to be manually entered.
So as a workaround, we used custom links in home page components to invoke our scripts, See the below link for details.

http://salesforce.stackexchange.com/questions/38918/end-of-javascript-sidebar-workarounds

Since this workaround was only supported till Summer'15 and we are now in Winter'16 so this wont work because now sidebar script does not automatically invoke and requires a manual click on the link to invoke it. See this link for details http://docs.releasenotes.salesforce.com/en-us/winter16/release-notes/rn_forcecom_general_requirescript.htm

With the new UI, the sidebar is going to go away. So, the less reliance we have on the sidebar components, the better it would be for our transition.

However, there isn't any other way to hide the "Save & New" button but to replace it by using a Visualforce page. Salesforce has been taking security as the highest priority and been striving to avoid any kind of hacking activities (including Javascript workarounds) which could incur potential security thread to customers. That's the reason why Javascript is no longer supported within a home page component.

After discussing with Salesforce, the workaround I've found is to override the "New" button of an object using a Visualforce page which has a Controller to check if the "Save & New" is clicked. If it's clicked then ignore the "New" action and just do "Save". So even the "Save & New" button is still visible but it would just behave like the "Save" button.
The following are the sample code which works on the Account Object. To see it in action simply goto Setup | Customize | Accounts | Buttons, Links and Actions | Edit the "New" button by the following Visualforce page.

Visualforce Page:

1
2
<apex:page standardController="Account" extensions="SObjectNewActionOverrideController" action="{!onLoadPage}">
</apex:page>
Controller:
public class SObjectNewActionOverrideController {

    public String debug{get;Set;}
    private String SObjectPrefix = null;
    public SObjectNewActionOverrideController(ApexPages.StandardController controller){
        this.SObjectPrefix = controller.getRecord().getSObjectType().getDescribe().getKeyPrefix();
    }
    
    public PageReference onLoadPage(){
        this.debug = JSON.serializePretty(ApexPages.currentPage().getParameters());
        String retURL = ApexPages.currentPage().getParameters().get('retURL');

        if(ApexPages.currentPage().getParameters().get('save_new')=='1'
            && retURL != null
            && retURL.startsWith('/'+SObjectPrefix)
            && retURL.indexOf('/', 4) < 0
            && !retURL.contains('?')
            && retURL.length() >= 15){
            PageReference pg = new PAgeReference(retURL);
            pg.setRedirect(true);
            return pg;
        }else{
            PageReference pg = new PAgeReference('/'+this.SObjectPrefix+'/e');
            for(String key : ApexPages.currentPage().getParameters().keyset()){
            if(key == 'save_new' || key == 'sfdc.override') continue;
                pg.getParameters().put(key, ApexPages.currentPage().getParameters().get(key));
        }

        pg.getParameters().put('nooverride','1');
        pg.setRedirect(true);
        return pg;
        }
    }
}

Saturday, 6 June 2015

Using Comparable Interface to sort by Date

I have come across the requirement in which I have to sort the list of Apex Class based on Date.

If you want to add sorting support for Lists that contain non-primitive types, that is, lists of user-defined types. you must implement the Comparable interface with its compareTo method in your class.

To implement the Comparable interface, you must first declare a class with the implements keyword.
For sorting by date, we can use daysBetween(Date) which returns the number of days between that called the method and specified date.

Code Snippet of Apex Class:  
global class PaymentPlanList implements Comparable{
    
    public static List <PaymentPlanList> lstPaymentPlans {get;set;}
    public String Type {get;set;}
    public Decimal Amount {get;set;}
    public Date ToDate {get;set;}
    public Date FromDate {get;set;}
    
    public PaymentPlanList(PaymentPlan__c pp){
        Type = pp.Name;
        Amount = pp.Amount__c;
        ToDate = pp.To_Date__c;
        FromDate = pp.From_Date__c;
    }
    
    public static List<PaymentPlanList> GetPayments(){
        if(lstPaymentPlans == null ){
            lstPaymentPlans = new List<PaymentPlanList>();
            for(PaymentPlan__c payment : [Select Name,Amount__c,From_Date__c,To_Date__c from PaymentPlan__c]){
                lstPaymentPlans.add(new PaymentPlanList(payment));
            }            
        }
       return lstPaymentPlans;
    }
    
    global Integer compareTo(Object compareTo) {
       //assuming if their is no date, it would be TODAY
       Date fromDatee = compareTo != null ? ((PaymentPlanList)compareTo).FromDate : System.today();           
       return fromDatee.daysBetween(this.FromDate); // Sorting by Date
    }
}
Sorting list of Apex Class and binded to VF page:
public class PaymentPlan{
    public List <PaymentPlanList> PaymentPlans {get;set;}
    public PaymentPlan(){
        PaymentPlans = PaymentPlanList.GetPayments(); // Calling Method of Outer Class for populating List of user-defined type
        PaymentPlans.sort(); // Adds sorting support for Lists that contain non-primitive types, that is, Lists of user-defined types.
    }
}
Visuaforce Page:
<apex:page controller="PaymentPlan" >
    <apex:pageBlock >
        <apex:pageBlockTable value="{!PaymentPlans}" var="pp">
            <apex:column headerValue="Payment Type" value="{!pp.Type}" style="width:25%"/>
            <apex:column headerValue="Amount" value="{!pp.Amount}" style="width:25%"/>
            <apex:column headerValue="From Date" style="width:25%">
                <apex:outputText value="{0,date,MM/dd/yy}"> 
                    <apex:param value="{!pp.FromDate}"/> 
                </apex:outputText> 
            </apex:column>
            <apex:column headerValue="To Date" style="width:25%">
                <apex:outputText value="{0,date,MM/dd/yy}"> 
                    <apex:param value="{!pp.ToDate}"/> 
                </apex:outputText> 
            </apex:column>
        </apex:pageBlockTable>
    </apex:pageBlock>
</apex:page>
If you notice the list is sorted by Date.
Feel free to post comments if you have any questions. Cheers !!!