Actually there are no easy ways to show the Account, Opportunity, Case histories through the <apex:relatedList/>
.
Though from the API it is found relationship name is 'Histories'
but those are not accessible through the related List.
We need to have custom coding to display the Opportunity History table.
I have taken the code as provided in this link History related list in Visualforce and it is perfectly working.
Hopefully it will help to all Salesforce developers like us.
Visualforce Component (GenericHistoryComponent)
<apex:component controller="GenericHistoryComponentController">
<!-- Attribute Definition -->
<apex:attribute name="myObject" description="Object we wish to view the history of" type="SObject" required="true" assignTo="{!myObject}" />
<apex:attribute name="recordLimit" description="Number of lines of history to display" type="Integer" required="false" assignTo="{!recordLimit}" />
<!-- Object History Related List -->
<apex:pageBlock title="{!objectLabel} History">
<apex:pageBlockTable value="{!ObjectHistory}" var="History" >
<apex:column headerValue="Date" value="{!History.thedate}"/>
<apex:column headerValue="User">
<apex:outputLink value="/{!History.userId}"> {!History.who} </apex:outputLink>
</apex:column>
<apex:column headerValue="Action"><apex:outputText escape="false" value="{!History.action}"/></apex:column>
</apex:pageBlockTable>
</apex:pageBlock>
Apex Controller (GenericHistoryComponentController)
public class GenericHistoryComponentController {
// External variables
public SObject myObject {get; set;}
public Integer recordLimit {get; set;}
public static String objectLabel {get;}
// Internal Variables
public objectHistoryLine[] objectHistory;
public static final Map<String, Schema.SObjectType> mySObjectTypeMap = Schema.getGlobalDescribe();
public static Map<String, Schema.SObjectField> myObjectFieldMap;
public static List<Schema.PicklistEntry> historyFieldPicklistValues;
public List<objectHistoryLine> getObjectHistory(){
// if no object passed in, return empty list
if (myObject == null) {
return new List<objectHistoryLine>();
}
Id myObjectId = String.valueOf(myObject.get('Id'));
Schema.DescribeSObjectResult objectDescription = myObject.getSObjectType().getDescribe();
myObjectFieldMap = objectDescription.fields.getMap();
objectLabel = String.valueOf(objectDescription.getLabel());
//Get the name of the history table
String objectHistoryTableName = objectDescription.getName();
//ID field name
string ObjectIdName;
//if we have a custom object we need to drop the 'c' off the end before adding 'History' to get the history tables name
if (objectDescription.isCustom()){
objectHistoryTableName = objectHistoryTableName.substring(0, objectHistoryTableName.length()-1);
ObjectIdName = 'ParentId';
}
else{
ObjectIdName = objectHistoryTableName+ 'Id';
}
if(objectHistoryTableName == 'Opportunity') {objectHistoryTableName = objectHistoryTableName + 'FieldHistory';}
else {objectHistoryTableName = objectHistoryTableName + 'History';}
Schema.DescribeFieldResult objectHistoryFieldField = mySObjectTypeMap.get(objectHistoryTableName).getDescribe().fields.getMap().get('Field').getDescribe();
historyFieldPicklistValues = objectHistoryFieldField.getPickListValues();
list<objectHistoryLine> objectHistory = new list<objectHistoryLine>();
String prevDate = '';
if (recordLimit== null){
recordLimit = 100;
}
list<sObject> historyList = Database.query( 'SELECT CreatedDate,'+
'CreatedById,'+
'Field,'+
'NewValue,'+
'OldValue ' +
'FROM ' + objectHistoryTableName + ' ' +
'WHERE ' + ObjectIdName + ' =\'' + myObjectId + '\' ' +
'ORDER BY CreatedDate DESC '+
'LIMIT ' + String.valueOf(recordLimit));
for(Integer i = 0; i < historyList.size(); i++){
sObject historyLine = historyList.get(i);
if ((historyLine.get('newValue') == null && historyLine.get('oldValue') == null)
|| (historyLine.get('newValue') != null && !(string.valueOf(historyLine.get('newValue')).startsWith('005') || string.valueOf(historyLine.get('newValue')).startsWith('00G')))
|| (historyLine.get('oldValue') != null && !(string.valueOf(historyLine.get('oldValue')).startsWith('005') || string.valueOf(historyLine.get('oldValue')).startsWith('00G')))){
objectHistoryLine tempHistory = new objectHistoryLine();
// Set the Date and who performed the action
if (String.valueOf(historyLine.get('CreatedDate')) != prevDate){
tempHistory.theDate = datetime.valueof(historyLine.get('CreatedDate')).format();
tempHistory.userId = String.valueOf(historyLine.get('CreatedById'));
tempHistory.who = String.valueOf(historyLine.get('CreatedById'));
}
else{
tempHistory.theDate = '';
tempHistory.who = '';
tempHistory.userId = String.valueOf(historyLine.get('CreatedById'));
}
prevDate = String.valueOf(historyLine.get('CreatedDate'));
// Get the field label
String fieldLabel = GenericHistoryComponentController.returnFieldLabel(String.valueOf(historyLine.get('Field')));
// Set the Action value
if (String.valueOf(historyLine.get('Field')) == 'created') { // on Creation
tempHistory.action = 'Created.';
}
else if (historyLine.get('oldValue') != null && historyLine.get('newValue') == null){ // when deleting a value from a field
// Format the Date and if there's an error, catch it and re
try {
tempHistory.action = 'Deleted ' + Date.valueOf(historyLine.get('oldValue')).format() + ' in <b>' + fieldLabel + '</b>.';
} catch (Exception e){
tempHistory.action = 'Deleted ' + String.valueOf(historyLine.get('oldValue')) + ' in <b>' + fieldLabel + '</b>.';
}
}
else{ // all other scenarios
String fromText = '';
if (historyLine.get('oldValue') != null) {
try {
fromText = ' from ' + Date.valueOf(historyLine.get('oldValue')).format();
} catch (Exception e) {
fromText = ' from ' + String.valueOf(historyLine.get('oldValue'));
}
}
String toText = '';
if (historyLine.get('newValue') != null) {
try {
toText = Date.valueOf(historyLine.get('newValue')).format();
} catch (Exception e) {
toText = String.valueOf(historyLine.get('newValue'));
}
}
if (toText != ''){
tempHistory.action = 'Changed <b>' + fieldLabel + '</b>' + fromText + ' to <b>' + toText + '</b>.';
}
else {
tempHistory.action = 'Changed <b>' + fieldLabel;
}
}
// Add to the list
objectHistory.add(tempHistory);
}
}
List<Id> userIdList = new List<Id>();
for (objectHistoryLine myHistory : objectHistory){
userIdList.add(myHistory.userId);
}
Map<Id, User> userIdMap = new Map<ID, User>([SELECT Name FROM User WHERE Id IN : userIdList]);
for (objectHistoryLine myHistory : objectHistory){
if (userIdMap.containsKey(myHistory.userId) & (myHistory.who != '') ){
myHistory.who = userIdMap.get(myHistory.who).Name;
}
}
return objectHistory;
}
// Function to return Field Label of a object field given a Field API name
public Static String returnFieldLabel(String fieldName){
if (GenericHistoryComponentController.myObjectFieldMap.containsKey(fieldName)){
return GenericHistoryComponentController.myObjectFieldMap.get(fieldName).getDescribe().getLabel();
}
else {
for(Schema.PicklistEntry pickList : historyFieldPicklistValues){
if (pickList.getValue() == fieldName){
if (pickList.getLabel() != null){
return pickList.getLabel();
}
else {
return pickList.getValue();
}
}
}
}
return '';
}
// Inner Class to store the detail of the object history lines
public class objectHistoryLine {
public String theDate {get; set;}
public String who {get; set;}
public Id userId {get; set;}
public String action {get; set;}
}
}
Usage of component in Visualforce page (OpportunityRelatedList)
<apex:page standardController="Opportunity">
<apex:pageBlock >
You're looking at some related lists for {!opportunity.name}:
</apex:pageBlock>
<!--it will display Field History -->
<c:GenericHistoryComponent recordLimitAttribute="50" myObjectAttribute="{!Opportunity}"/>
<!--it will display Stage History -->
<apex:relatedList list="OpportunityHistories"/>
<!--it will display Opportunity Line Items -->
<apex:relatedList list="OpportunityLineItems"/></apex:page>
Access visualforce page as follows:
/apex/OpportunityRelatedList?id=<OpportunityId>
Expected Results