Force.com Apex and Visualforce Best Practices

Triggers

  • There should only be one trigger for each object.
Apex Trigger Code

trigger ContactTrigger on Contact (after delete, after insert, after undelete, 
after update, before delete, before insert, before update) 
{
        ContactTriggerHandler handler = new ContactTriggerHandler(Trigger.isExecuting, Trigger.size);

        if(Trigger.isInsert && Trigger.isBefore)
        {
		handler.OnBeforeInsert(Trigger.new);
	}
	else if(Trigger.isInsert && Trigger.isAfter){
		handler.OnAfterInsert(Trigger.new);
		ContactTriggerHandler.OnAfterInsertAsync(Trigger.newMap.keySet());
	}

	else if(Trigger.isUpdate && Trigger.isBefore){
		handler.OnBeforeUpdate(Trigger.old, Trigger.new, Trigger.newMap);
	}
	else if(Trigger.isUpdate && Trigger.isAfter){
		handler.OnAfterUpdate(Trigger.old, Trigger.new, Trigger.newMap);
		ContactTriggerHandler.OnAfterUpdateAsync(Trigger.newMap.keySet());
	}

	else if(Trigger.isDelete && Trigger.isBefore){
		handler.OnBeforeDelete(Trigger.old, Trigger.oldMap);
	}
	else if(Trigger.isDelete && Trigger.isAfter){
		handler.OnAfterDelete(Trigger.old, Trigger.oldMap);
		ContactTriggerHandler.OnAfterDeleteAsync(Trigger.oldMap.keySet());
	}

	else if(Trigger.isUnDelete){
		handler.OnUndelete(Trigger.new);	
	}
}


Apex Class Code

public with sharing class ContactTriggerHandler {
	private boolean isHandlerExecuting = false;
	private integer BatchSize = 0;

	public ContactTriggerHandler(boolean isExecuting, integer size){
		isHandlerExecuting = isExecuting;
		BatchSize = size;
	}

	public void OnBeforeInsert(Contact[] newContacts){
		//Example usage
		for(Contact newContact : newContacts){
			if(newContact.Name == null){
				newContact.Name.addError('Missing Name Field');
			}
		}
	}

	public void OnAfterInsert(Contact[] newContacts){

	}

	@future public static void OnAfterInsertAsync(Set newContactIDs){
		//Example usage
		List newContacts = [select Id, Name from Contact where Id IN :newContactIDs];
	}

	public void OnBeforeUpdate(Contact[] oldContact, Contact[] updatedContacts, Map contactMap){
		//Example Map usage
		Map contacts = new Map( [select Id, FirstName, LastName, Email from Contact where Id IN :contactMap.keySet()] );
	}

	public void OnAfterUpdate(Contact[] oldContacts, Contact[] updatedContacts, Map contactMap){

	}

	@future public static void OnAfterUpdateAsync(Set updatedContactIDs){
		List updatedContacts = [select Id, Name from Contact where Id IN :updatedContactIDs];
	}

	public void OnBeforeDelete(Contact[] contactsToDelete, Map contactMap){

	}

	public void OnAfterDelete(Contact[] deletedContacts, Map contactMap){

	}

	@future public static void OnAfterDeleteAsync(Set deletedContactIDs){

	}

	public void OnUndelete(Contact[] restoredContacts){

	}

	public boolean IsTriggerContext{
		get{ return isHandlerExecuting;}
	}

	public boolean IsVisualforcePageContext{
		get{ return !IsTriggerContext;}
	}

	public boolean IsWebServiceContext{
		get{ return !IsTriggerContext;}
	}

	public boolean IsExecuteAnonymousContext{
		get{ return !IsTriggerContext;}
	}
}

  • Use Helper Classes

https://github.com/thysmichels/Force.com-Helper-Classes

https://github.com/thysmichels/base

http://code.google.com/p/apex-lang/

Dynamic Delete Batch Apex Class

global class dynamicDelete implements Database.Batchable{

   global final String Query;

   global dynamicDelete(String q)
   {
      Query=q;
   }

   global Database.QueryLocator start(Database.BatchableContext BC)
   {
      return Database.getQueryLocator(query);
   }

   global void execute(Database.BatchableContext BC, List scope)
   {
      delete scope;
   }
}

Scheduler for Batch Apex Delete

global class dynamicDeleteScheduler implements Schedulable{
   private static final String deleteLeads = 'Select id from Lead where Email like \'%test%\' limit 5000';
   private static final String deleteTasks = 'Select id from Task where WhoId IN (Select id from Contact where email like \'%test%\') limit 50000';
   private static final String deleteOpportunities = 'Select id from Opportunity where id in (Select OpportunityId from OpportunityContactRole where Contact.Email like \'%test%\') limit 50000';
   private static final String deleteCases = 'Select id from Case where ContactId IN (Select id from Contact where Email like \'%test%\') limit 50000';
   private static final String deleteTestAccounts = 'Select id from Account where Email__c like \'%test%\' limit 50000';
   private static final String deleteTestContacts = 'Select id from Contact where Email like \'%test%\' limit 50000';

   global void execute(SchedulableContext SC) 
   {     
      dynamicDelete deleteTask = new dynamicDelete(deleteTasks);
      Database.executebatch(deleteTask);

      dynamicDelete deleteCase = new dynamicDelete(deleteCases);
      Database.executebatch(deleteCase);

      dynamicDelete deleteOpportunity = new dynamicDelete(deleteOpportunities);
      Database.executebatch(deleteOpportunity);

      dynamicDelete deletecontact = new dynamicDelete(deleteTestContacts);
      Database.executebatch(deletecontact);

      dynamicDelete deleteaccount = new dynamicDelete(deleteTestAccounts); 
      Database.executebatch(deleteaccount);

   }
}
    Bulkify Triggers to process up to 200 records for each call. This will help you avoid hitting DML limit per process.
Bulk your DML operations with with List/Maps/Sets

private void bulkUpdateExample(Map updateContactOwner)
{
    List<Contact> listToHoldListOfContactsToBeUpdated = new List<Contact>();
    Id salesForceAdminId = [Select id from User where Name='Salesforce Administrator'].Id;
    for (Id contactIdToUpdateOwner : updateContactOwner.keySet())
    {
          updateContactOwner.get(contactIdToUpdateOwner).OwnerId = salesForceAdminId;
          //Add all records to a List/Map/Set to be updated outside the for loop
          listToHoldListOfContactsToBeUpdated.add(updateContactOwner.get(contactIdToUpdateOwner));
    }
    //Always update outside a for loop
    update listToHoldListOfContactsToBeUpdated;
}


Bulkify your Test classes with at least 200 records

@isTest(SeeAllData=false) static void testContactOwnerReassignmentSuccess()
{
  Account createAccount = new Account(Name='[TEST]');
  insert createAccount;

  List contactList = new List();
  for (int k = 0; k < 200; k++)
      contactList.add(new Contact(FirstName='[Test]' + k, AccountId=createAccount.Id));

  Test.startTest();
  insert contactList;
  Test.stopTest();

  System.assert(200 == [Select count() from Contact where 
Owner.Name = 'Salesforce Administrator']);
}
    Use Collections in SOQL “WHERE” clauses to retrieve all records using a single query. Also use self, semi and anti joins:

 legalDocumentNames = new String[]
         {
            'Employment Application',
            'Release of Liability',
            'Non-Disclosure Agreement',
            '%Employment Agreement'
          }
List legalDocumentQuery = [select ContactId from Asset
where Product2.Name Like :legalDocumentNames];

Apex 

    Use @future annotation for methods that can be executed asynchronously

@future public static void OnAfterInsertAsync(Set newAssetIDs)
{
    new smbNurtureCampaign().insertCampaignMemebersToControlAndTestCampaign(newAssetIDs);
}
    Provide proper exception handling.
Apply try catch to all methods

public static User getSalesRepsForRouting(List getListActiveUser)
    {
        try
        {
          return getListActiveUser.size() == 1 ? getListActiveUser[0] : getListActiveUser[0];
        }
        catch(handlerException e)
        {
            Debugger.createDebugRecord(e, 'ACRoundRobinV5');
            sendErrorMail(e.getMessage());
            return null;
        }
    }    


Extend the Exception Class

      public class handlerException extends Exception{}


Catch Exception and create record in custom Debugger Object

public static void createDebugRecord(handlerException exceptionObject, String methodName)
     {
         Database.insert(new Debugger__c
            (   
                    Message__c = exceptionObject.getMessage(),
                    Line_Number__c = exceptionObject.getLineNumber(),
                    Stack_Trace__c = exceptionObject.getStackTraceString(),
                    Method_Name__c=methodName
            ), false);
     }
     public static void createDebugRecord(DMLException dmlexceptionObject, String methodName)
     {
         Database.insert(new Debugger__c
            (       
                    DML_Record_Id__c = dmlexceptionObject.getDmlId(0),
                    DML_Type__c = dmlexceptionObject.getTypeName(),
                    Message__c = dmlexceptionObject.getDmlMessage(0),
                    Line_Number__c = dmlexceptionObject.getDmlIndex(0),
                    Stack_Trace__c = dmlexceptionObject.getDmlStatusCode(0),
                    Method_Name__c=methodName
            ),false);
     }
    Prevent SOQL and SOSL injection attacks by using static queries, binding variables or the escapeSingleQuotes method.

        private static final String[] QUERY_SELECT_STATEMENT =
        new String[]{'select ',' from ', ' where Id = \''};

        String query = QUERY_SELECT_STATEMENT[0];
        for(Schema.DescribeFieldResult dfr : queryFields)
            query = query + dfr.getName() + ',';

        query = query.subString(0,query.length() - 1);
        query = query + QUERY_SELECT_STATEMENT[1];
        query = query + obj.getName();
        query = query + QUERY_SELECT_STATEMENT[2];
        query = query + theId + '\'';
    When querying large data sets, use a SOQL “for” loop

        for (Account accountToCheckAndUpdateAccountDetails : [Select BillingStreet, BillingCity, BillingState, BillingPostalCode, BillingCountry, ShippingStreet, ShippingCity, ShippingState, ShippingPostalCode, ShippingCountry, Phone, Fax, Website, Industry from Account])
        {
           .....
        }
        update accountToCheckAndUpdateAccountDetails;
    Use Apex Limits Methods to avoid hitting governor exceptions.
In the if statement we check if it is save to proceed without hitting governor limits 

List accountOwnerDetail = [SELECT OwnerId, Owner.Name, Owner.Profile.Name, Owner.isActive from Account];
if (accountOwnerDetail.size() + Limits.getDMLRows() < Limits.getLimitDMLRows())
{
  ... 
}
    No SOQL or SOSL queries inside loops
Create parent-to-child and child-to-parent relationship queries outside for loops

Contact getContactWithAccount = Database.query('Select Name, Email,  Account.Name, Account.BillingStreet from Contact limit 1');
Account getAccountContactOpportunityInOneQuery = Database.query('Select Id, (Select Name from Account.Contacts), (Select Name from Account.Opportunities) from Account limit 1');


Tip: Update an Object without querying for it

List accountFieldsToBeUpdated = new List();
for (Contact updateAccountsFieldFromContact : [Select AccountId from Contact where OtherCity ='San Francisco'])
{ 
    //create new account object with the accountId from contact
     Account newAccountObject = new Account(Id=updateAccountsFieldFromContact.AccountId);
     newAccountObject.ShippingCity = 'SF';
     newAccountObject.BillingCity = 'SF';   
     accountFieldsToBeUpdated.add(newAccountObject);
}
update accountFieldsToBeUpdated ;
    Do not use hardcoded IDs: As ID’s are unique and different between environments.
Don't use ID's rather get Id's by Name

        Opportunity getOpportunity = [Select Name from Opportunity where Owner.Id='00560000001S1eh' limit 1];


Rather do the following as your unit tests will fail in other environments 
 
        Opportunity getOpportunity = [Select Name from Opportunity where Owner.Name='John Black' limit 1];

Visualforce

    Do not hardcode picklists in Visualforce pages; include them in the controller instead.
Picklist Controller Method

public List getFilterByRecordTypeList() 
    {
            List<SelectOption> op = new List<SelectOption>();
            Set<Id> setPPFamily = new Set<Id>();
            setPPFamily.clear();
            setPPFamily.add('All Opportunities');
            List<AggregateResult> aggregateResultsOfMotivatingProductFamily = [SELECT RecordType.Name FROM Opportunity Group By RecordType.Name];

            for (AggregateResult ppfamilyname : aggregateResultsOfMotivatingProductFamily)
            op.add(new SelectOption((String)ppfamilyname.get('RecordType.Name'),(String)ppfamilyname.get('RecordType.Name')));

            op.sort();
            return op;
    }

Calling Picklist Controller in Visualforce

<apex:outputText style="color:#f8f8ff;font-weight:bold" value="Filter By Document Type"/>
<apex:toolbarGroup itemSeparator="line" location="left" id="toobarGroupForm2">
<apex:selectList label="Filter by Record Type" value="{!FilterByRecordType}" size="1" required="false" >
<apex:actionSupport event="onchange" action="{!FilterByRecordTypeAction}" status="filterByRecordType" reRender="innerblock"/>
<apex:outputPanel style="color:#f8f8ff;font-weight:bold">
<apex:actionStatus id="filterByRecordType" startText="Filtering your records..." stopText=""/>
</apex:outputPanel>
<apex:selectOptions value="{!FilterByRecordTypeList}"/>
</apex:selectList>
</apex:toolbarGroup>
    Javascript and CSS should be included as Static Resources allowing the browser to cache them.
Import your JS and CSS to the Static Resources as Zip of single files. Include CSS on top and JS at the bottom for faster loading

<apex:page controller="YourCustomController" sidebar="true" cache="true" showChat="true" >
<head>
<apex:stylesheet value="{!URLFOR($Resource.jQuery1, '/css/ui-lightness/jquery-ui-1.8.23.custom.css')}" />
</head>
<body>
....

<apex:includeScript value="{!URLFOR($Resource.jQuery1, '/js/jquery-1.8.0.min.js')}" />
<apex:includeScript value="{!URLFOR($Resource.jQuery1, '/js/jquery-ui-1.8.23.custom.min.js')}" />
</body>
</apex:page>
 
    Mark controller variables as “transient” if they are not needed between server calls. This will make your page load faster as it reduces the size of the View State.
Transient Example Visualforce Page

<apex:page controller="ExampleController">
<apex:form id="theForm">
<apex:panelBar id="thePanel">
<apex:panelbarItem label="Transient vs Non Transient">
<b>Transient Time: </b> {!t1} <br />
<b>Non Transient Time: </b> {!t2}
</apex:panelbarItem>
<apex:actionPoller interval="5" enabled="true" rerender="thePanel" action="{!refresh}" />
</apex:panelBar>
</apex:form>
</apex:page>

Transient Example Controller

public class ExampleController {

    public PageReference refresh() {
        return null;
    }

    DateTime t1;
    transient DateTime t2;

    public String getT1() {
        if (t1 == null) t1 = System.now();
        return '' + t1;
    }

    public String getT2() {
        if (t2 == null) t2 = System.now();
        return '' + t2;
    }
}
    Use the <repeat> tag to iterate over large collections.

<apex:page standardController="Opportunity" id="thePage">
<table id="EquipmentTable" border="1">
<apex:repeat value="{!Opportunity}" var="line" id="theRepeat">
<tr>
<td valign="top"><apex:outputField value="{!line.Id}" /></td>
<td valign="top"><apex:outputField value="{!line.Name}" /></td>
<td valign="top"><apex:outputField value="{!line.Probability}" /></td>
<td valign="top"><apex:outputField value="{!line.Amount}" /></td>
</tr>
</apex:repeat>
</table>
</apex:page>

Dynamic SOQL Queries packages and security

For managed packaged development dynamic SOQL queries assist developers with creating soql queries on the fly without hard coding field names which may not be there in all orgs. When you install an managed package you may run into trouble is some fields do not exist. This is why dynamic SOQL queries can be beneficial by using the schema describe objects. The code below can help any developer to retrieve the fields from any object by providing the object name. From that you can build a dynamic soql query and also define the data type for each field. Be careful when using Dynamic SOQL queries as it can be prone to SOQL injection attacks. Make sure your return type is static and final.

Return the Schema.SObjectType for the specific Object’s ID we provide

public static Schema.SObjectType getObjectSchema(String objectSObjectName)
    {
        if (sobjectSchemaMap.isEmpty())
        	getSchemaMap();

        return sobjectSchemaMap.get(objectSObjectName);
    }

SchemaMap() method to return the Schema Map for your organization.

private static Map<String, Schema.SObjectType> sobjectSchemaMap;
public static Map getSchemaMap()
    {
        return sobjectSchemaMap == null ? Schema.getGlobalDescribe() : sobjectSchemaMap;
    }

Dynamically creating the SOQL Query when we have the queryFields, Object and Id.

private String query;
public String buildQueryAllString(List queryFields,DescribeSObjectResult obj, String theId)
    {
        query = QUERY_SELECT_STATEMENT[0];
        for(Schema.DescribeFieldResult dfr : queryFields)
            query = query + dfr.getName() + ',';

        query = query.subString(0,query.length() - 1);
        query = query + QUERY_SELECT_STATEMENT[1];
        query = query + obj.getName();
        query = query + QUERY_SELECT_STATEMENT[2];
        query = query + theId + '\'';
        return query;
    }

Putting it all together:
1. We provide and Record ID.
2. find the SObject for the ID provided.
3. Get the SObject Fields for the SObject
4. Create a SOQL Query
5. Run the SOQL Query to retrieve information.

public SObject processSchemaInfo(String id)
    {
    	try
    	{
        schemaMap = DynamicSOQL.getSchemaMap();
        sobjects = schemaMap.values();

        Schema.DescribeSObjectResult objDescribe;
        List tempFields;

        buildQuery = '';
        fields = new List();

        for(Schema.SObjectType objType : sobjects)
        {
            objDescribe = objType.getDescribe(); 
            String sobjectPrefix = objDescribe.getKeyPrefix();
            if(id != null && sobjectPrefix != null && id.startsWith(sobjectPrefix))
            {
                objectType = objDescribe.getLocalName();
                Map<String, Schema.SObjectField> fieldMap = objDescribe.fields.getMap();
                tempFields = fieldMap.values();
                for(Schema.SObjectField sof : tempFields)
                {
                    fields.add(sof.getDescribe());
                }
                buildQuery = buildQueryAllString(fields,objDescribe,id);
            }
        }

        return  Database.query(buildQuery);
       }
       catch(Exception ex)
       {
       		System.debug(ex.getMessage());
       		return null;
       }
    }

Minimize and Optimize Apex Test Classes with SmartFactory

I was writing allot of test code to create SObjects and all the necessary fields. There are some problems when writing test code when specifying all the necessary fields.

Some of the things to consider when creating test fields is required fields and pre-populating lookups. Let us look at some examples:
Let’s start of creating a Account in a Test Class:

Account newAccount = new Account(Name='This is a Test Account');

Now we create the Contact associated to that Account:

Contact newContact = new Contact(AccountId=newAccount.Id);

mmm but Contact has more required fields and will fail if I call the:

 insert newContact;

Let’s take it a bit further and create an Opportunity and select the newAccount.

Opportunity newOpportunity = new Opportunity(AccountId=newAccount.Id);

the same story for opportunity is we need more required fields to be completed, and do we always know the required fields?No for some unknown Salesforce orgs this may be a problem.

Let’s see how we can solve the problem of required fields and lookup during sObject creation:

Contact c = (Contact)SmartFactory.createSObject('Contact', true);

The second argument (true) toggles the cascade option, which populates any lookup fields on the object with their own newly created objects. It’s a powerful way to create hierarchies of test data quickly and easily so you can focus on writing test and implementation logic.

Now we can create an Opportunity without specifying an Account or Contact:

Opportunity o = (Opportunity)SmartFactory.createSObject('Opportunity', true);

This will create, a Account, Contact and Opportunity for you in one line.

SmartFactory uses the describe metadata to populate all fields with data of the right type. It currently handles string and lookup fields. For lookup fields, it creates an appropriate object and then uses that object’s ID.

Force.com IDE MavensMate for the serious Apex developer

You are frustrated with coding Apex in Eclipse because it is slow or you are coding in the browser :( not the best thing when you close the window and loose all your code…to the rescue MavensMate. MavensMate is an Force.com IDE is an add-on project running on Sublime Text 2. I have been using MavensMate for a while now and have seen a great benefit in time, auto completion (less time typing) and just easier all in one experience from coding, testing to deployment.

The following functionality is awesome:
1. Code completion
2. Quick code search
3. When code is saved, validation takes a few seconds (compared to Eclipse it is faster)
4. Testing code inside MavensMate is a breeze and you can play games while waiting for it to complete.
5. Direct integration to your Git repository.
6. Create new files (Apex or Visualforce) from inside a Project
7. Easy to run Anonymous Apex code.
8. Easy to deploy code to different SFDC environments.
9. Manage your metadata between environments.
10. Great documentation to explain how to use MavensMate.

I recommend MavensMate to all serious Apex developers. Follow these steps below to get started:

https://github.com/joeferraro/MavensMate-SublimeText

Query SObject without burning SOQL queries

When you are writing Apex code you want to minimize the amount of SOQL queries in your code. One of the ways todo this is to create an SObject with some information of that object.

Let us say you have the following SOQL query:

Contact getAccountId = [Select AccountId from Contact limit 1];

Now you would like more information about the Account for that contact, you where thinking of taking the AccountId and running another SOQL query like this:

Account showAccountInfo = [Select Id from Account where Id=:getAccountId.AccountId];

You are burning unnecessary SOQL queries. You can do this following:

 Account showAccountInfo = new Account(Id=:getAccountId.AccountId);

You are not burning a SOQL query as you now have created an Object of that account. So you can do the following to query or update fields:

Get Information:

showAccountInfo.Name

Assign New Value:

showAccountInfo.Name='New Account Name'
update showAccountInfo;

Salesforce Update Product Schedule from Opportunity Product using Apex Trigger

Update the Product ScheduleDate when the Opportunity Product ServiceDate changes. First we create an OpportunityProduct Trigger to see when ServiceDate changes and parse the amount of days difference between the old and new date.

trigger ProductDateUpdate on OpportunityLineItem (after update) {
   
    for (OpportunityLineItem oli: Trigger.new) {
        OpportunityLineItem oldCloseDate = Trigger.oldMap.get(oli.ID);
        if (oli.ServiceDate != oldCloseDate.ServiceDate) {
            Integer dateval = oldCloseDate.ServiceDate.daysBetween(oli.ServiceDate);
            ProductScheduleUpdate.ScheduleProductUpdate(oli.id, dateval);  
        }
    }
}

The Trigger class then updates all the Product Schedule Dates by the amount of days.

public class ProductScheduleUpdate
{
    public static void ScheduleProductUpdate(String oid, Integer dateval)
    {
           List datelist;
           datelist = [SELECT ScheduleDate FROM OpportunityLineItemSchedule WHERE OpportunityLineItemId =:oid];
           for (Integer k = 0; k < datelist.size(); k++)
               {
               System.debug('DateList = ' + datelist[k]);
               Date mydate=Date.newInstance(datelist[k].ScheduleDate.Year(),datelist[k].ScheduleDate.Month(),datelist[k].ScheduleDate.Day());
               mydate=mydate.addDays(dateval);
               datelist[k].ScheduleDate = mydate;
               update datelist;
               //date datediff = date.parse(datelist[k]);
               }
        
    }
}

Salesforce Update Product Schedule from Opportunity using Apex Trigger

The tutorial below shows how you can use Salesforce Triggers to update the CloseDate of an opportunity and the associated Product Schedule.

First thing is we must create a product Trigger, below is the code for the product trigger:

trigger OpportunityScheduleUpdate on Opportunity (after update) {
  
  for (Opportunity o: Trigger.new) {
        Opportunity oldCloseDate = Trigger.oldMap.get(o.ID);
        if (o.CloseDate != oldCloseDate.CloseDate) {
            Integer dateval = oldCloseDate.CloseDate.daysBetween(o.CloseDate);
            OppScheduleUpdate.ScheduleDateUpdate(o.id, dateval);  
        }
    }
}

The trigger class updates all the Product ScheduleDate by the amount of the days the Opportunity Close Date has changed:

public class OppScheduleUpdate
{
    public static void ScheduleDateUpdate(String oppid, Integer dateval) 
    {
       List idval = [SELECT id FROM OpportunityLineItem WHERE OpportunityId=:oppid];
       List datelist;
       for (Integer i = 0; i < idval.size(); i++)
       {
           datelist = [SELECT ScheduleDate FROM OpportunityLineItemSchedule WHERE OpportunityLineItemId =:idval[i].id];
           for (Integer k = 0; k < datelist.size(); k++)
               {
               System.debug('DateList = ' + datelist[k]);
               Date mydate=Date.newInstance(datelist[k].ScheduleDate.Year(),datelist[k].ScheduleDate.Month(),datelist[k].ScheduleDate.Day());
               mydate=mydate.addDays(dateval);
               datelist[k].ScheduleDate = mydate;
               update datelist;
               //date datediff = date.parse(datelist[k]);
               }
       }      
    }
}

Salesforce Apex check field updated trigger

The code below show how you can use oldMap.get to get the previous value of a field after an update has been made in Salesforce. In this case I calculated how many days a specific date has been updated with:

trigger CaseOnParticularFieldUpdate on Case (before update) { 
for (Case c: Trigger.new) { 
Case oldCase = Trigger.oldMap.get(c.ID); 
if (c.Field != oldCase.Field) { 
// field was updated, do some magic here } } }

Salesforce Debug Logs, Audit Trail and Field History Comparison

Below is a table showing the Salesforce Audit Tools compared between Debug Logs, Audit Trail and Field History.

Debug Logs Audit Trail Field History
Tracks Tracks automated actions – activity performed and results generated by end user or code Tracks configuration changes by administrator or developers Track changes of data value for fields.
Examples Execution of Apex trigger Change to a workflow rule Update to Status or Pay Grade field

Visualforce Standard vs Custom Apex Chart

The Visualforce chart below is key to understand how customization and development work in Salesforce.

Standard Behavior Custom Behavior
Standard Look and Feel Application Framework and Default UI (Page Layout)Example: Standard Opportunity page with a default page layout. Page Layouts and Custom Apex ClassesExample: Standard Opportunity page that validates the opportunity stage.
Custom Look and Feel Visualforce pages with standard controllersExample: Visualforce page containing opportunity information Visualforce pages with custom Apex ControllersExample:Opportunity search portal to search for specific opportunities in Salesforce.