5

I am trying the below code to add field history and product as a related list of opportunity in vf page, but its not working .

<apex:relatedList list="OpportunityFieldHistory"/>
<apex:relatedList list="Product"/>

Can anyone tell me what is the correct way to write it.

Regards


Edit:

There are two Standard objects that have Opportunity history information, OpportunityFieldHistory and OpportunityHistory. The relatedList component works fine with a value of OpportunityHistories for the list attribute, but an error is thrown when using the value of Histories, even though the SObject describe information shows "Histories" as the child relationship name.

<apex:relatedList list="OpportunityHistories"/> <!-- Works fine -->
<apex:relatedList list="Histories"/>            <!-- Error -->

enter image description here

enter image description here

3 Answers 3

2

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

enter image description here

2

For the products you can use:

<apex:relatedList list="OpportunityLineItems"/>

For the history, take a look at Santanu's answer.

6
  • Hi Fernando...<apex:relatedList list="Histories"/>.. is throwing error.. can u check plz.. Commented Jul 17, 2016 at 10:51
  • Sorry @user2542953 I just fixed the answer =D Commented Jul 17, 2016 at 12:01
  • There is a difference between OpportunityFieldHistory and OpportunityHistory.
    – martin
    Commented Jul 17, 2016 at 12:28
  • 2
    <apex:relatedList list="OpportunityHistories"/> will give only stage history not the field history.. Commented Jul 17, 2016 at 13:51
  • then how to resolve the issue.. Commented Jul 17, 2016 at 15:44
2

FYI, This has been fixed in API versions later than 38.0. My visualforce page kept on failing with the same error 'Histories' is not a valid child relationship....

My visualforce page was 38.0. I changed it to 39.0 and now the below line doesn't cause the error.

<apex:relatedList id="HistoryList" list="Histories" />

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .