8 min read

Create your own History Related List in Visualforce

The story starts with getting a requirement to customize the detail view of an existing page. Nothing really extreme just need to be able to display a few sections dynamically based on information on the record etc (and record types was not an option).

It's not the most ideal scenario since overriding a detail view with a custom Visualforce page costs you the ability to control the layout and security with page layouts and record types. Anyway, I made my case and despite my the drawbacks the go-ahead is given to custom build the page as it would result in a better experience for the user.

At first things are going good and the work is progressing as expected. Then my attention turns to adding the related lists to the page and I found that customizing the detail view just cost me a lot more than just customizing the page layout.

Over the last few years Salesforce has sprinkled a lot of little nice usability features to detail pages such as pop-up hover links, chatter, and inline-editing. All of those were challenges that I was prepared to address either by excluding them completely or replicating the functionality.

However, the one thing I didn't expect to have to replicate was the History Related list for an object. You would think you would be able to add an <apex:relatedlist> tag and set the list attribute to the name of the history list. I tried every name under the sun and then searched the developer forums to find that I wasn't the only one trying to do this and the overwhelming answer is that it isn't supported...currently.

For some reason I wasn't willing to lose this battle. It was one more thing I had to leave out my interface - one more thing I had to explain to the business was not possible "given platform limitations". I just couldn't give up that easy. So I decided to replicate the functionality and build my own History Related list. It wasn't as simple as I thought, so in this article I will describe the features that need to be replicated and share the code that I ultimately ended up with.

Features of the Native History Related List

  • Table of the changes made to fields configured for history tracking in descending order (most recent change to oldest change)
  • Displays three (3) columns: Date, User, Action
  • The action column displays the changes in a sentence indicating the field that changed and the old and new values
  • If more than one tracked field is changed at the same time then the date and user is only shown once indicating that the changes are part of a single transaction.
  • The list shows a default of the five (5) most recent items.
  • User has the option to show more items in increments of five (5)
  • User has the option to view all history records by directing the user to a view that shows the entire history log for the record.
  • Show More and Go To List options are only available when more records exist than are being displayed.

So let's start by breaking up the features list above in to what I would need to code. The first thing I need to create a related list table and supply it with a list of history records to display in descending order, meaning the most recent change to the oldest change. It's up to you how you want to implement this, inline or in a custom component, but I chose to use a custom component as it was cleaner and allowed me to easily implement features such as the "show more" option.

<apex:component id="SomeObjectHistory"
	controller="SomeObjectHistoryController">

	<apex:attribute type="id"
		name="subject"
		assignTo="{!SomeObjectId}"
		description="The SomeObject id to display SomeObject history information" />

	<apex:outputPanel id="RelatedEntityHistoryList"
		layout="block"
		styleclass="bRelatedList">

		<apex:pageBlock id="blockRelatedList"
			title="SomeObject History">
	
			<apex:form id="formRelatedList">
				<apex:outputPanel layout="block"
					rendered="{!SomeObjectHistories.size == 0}"
					style="border: 1px solid #D4DADC; padding: 5px 2px 4px 5px;">
	
					<span>No records to display</span>
				</apex:outputPanel>
	
				<apex:pageBlockTable id="tableRelatedList"
					value="{!SomeObjectHistories}"
					var="item"
					rendered="{!SomeObjectHistories.size != 0}">
		
					<apex:column headerValue="Date">
						<apex:outputField id="fieldCreatedDate"
							value="{!item.History.CreatedDate}"
							rendered="{!item.showDateAndUser}" />
					</apex:column>
				
					<apex:column headerValue="User">
						<apex:outputLink id="linkCreatedBy"
							value="/{!item.History.CreatedById}"
							rendered="{!item.showDateAndUser}">{!HTMLENCODE(item.History.CreatedBy.Name)}</apex:outputLink>
					</apex:column>
				
					<apex:column headerValue="Action">
						<apex:outputText id="textActionFormat"
							value="{!item.ActionFormat}"
							escape="false">
				
							<apex:param value="{!item.History.Field}" />
							<apex:param value="{!item.FieldLabel}" />
							<apex:param value="{!item.History.OldValue}" />
							<apex:param value="{!item.History.NewValue}" />
						</apex:outputText>
					</apex:column>
				</apex:pageBlockTable>
	
				<apex:outputPanel id="panelShowMore"
					layout="block"
					styleClass="pShowMore"
					rendered="{!AllowShowMore}">
				
					<apex:commandLink id="linkShowMore"
						action="{!showMore}"
						rerender="formRelatedList">Show more »</apex:commandLink>
		
						<span> | </span>
		
					<apex:outputLink id="linkGotoList"
						value="/_ui/common/history/ui/EntityHistoryFilterPage?id={!subject}">Go to list »</apex:outputLink>
				</apex:outputPanel>
			</apex:form>
	
			<!-- The following script removes unwanted style class names so that the page block resembles a native related list block -->
			<script type="text/javascript">
				document.getElementById('{!$Component.blockRelatedList}').className='bPageBlock secondaryPalette';}
			</script>	
		</apex:pageBlock>
	</apex:outputPanel>
</apex:component>

You will notice a couple of things about the markup. The first item you should notice is that I take the object id as an attribute parameter rather than a list. I do this so that the component can own the list of history records rather than the parent page. It allows me to easily re-query the list using dynamic limit sizes to support the "show more" option without having to involve the parent page. This makes the component much more self-contained.

The second item you should note is the url that is used for the "linkGotoList" output link component. This is a url to a native Salesforce page the displays the full history for an object. Fortunately, the only thing I had to do to re-use this native page was pass the objects id as a parameter in the url.

The third item you should note is my use of javascript to set the style of the page block tag. This is because the default rendering for a visualforce page block includes some extra style classes. To solve for this I just decided to overwrite the style tags with some inline javascript. Their really is no good alternative to this other than recreating the markup from scratch and I decided against it.

The final item I would like to call attention to is that I made the component very specific to the object that the list is for. I had thought about making it a generic component that I could re-use but honestly time wasn't on my side and there are some big advantages to knowing which object the history list is for ahead of time.

For instance, the action text is composed of the field that was changed along with the values. However, the field value in the data set is the API name so  I needed to be able to translate that into the field label. It was just easier to do a describe call ahead of time rather than try to figure out which object it was by the Id or passing in the object type as a parameter to the component. Also, I don't plan on using this anywhere else except for this one scenario so I didn't want to eat up any time trying to make this a one-size-fits-all component. If it does turn out that I need to use this in other places then I can use that time to expand it into a more generic component but till then I wanted to keep it simple.

So now that I have the custom component markup complete I went to work on the controller class. The following is the component controller code:

public with sharing class SomeObjectHistoryController {
    //Protected Members
    private static final DescribeSObjectResult oSomeObjectSchema = Schema.SObjectType.SomeObject__c;
    private static final Map<string, Schema.SObjectField> mapFields = oSomeObjectSchema.fields.getMap();
    
    //Properties
    public Id SomeObjectId {get;set;}
    public integer PageSize {get;set;}
    public boolean AllowShowMore {get;set;}
    
    public List<SomeObjectHistory> SomeObjectHistories {
        get { return getSomeObjectHistory(SomeObjectId); }
    }
    
    //Constructors
    
    /**
     * Default Constructor
     */
    public SomeObjectHistoryController() {
        PageSize = 5;   
        AllowShowMore = true;
    }
    
    //Public Methods
    public void showMore() {
        PageSize += 5;
    }
    
    //Private Methods
    
    /**
     * Returns SomeObject History records associated to the current SomeObject
     *
     * @param   SomeObjectId     the SomeObject__c record id to retrieve
     * @return  a list of SomeObjectHistory objects
     */
    private List<SomeObjectHistory> getSomeObjectHistory(Id SomeObjectId) {
        List<SomeObjectHistory> listSomeObjectHistory = new List<SomeObjectHistory>();
        
        if (SomeObjectId != null) {
            DateTime dLastCreatedDate = null;
            
            integer limitPlusOne = PageSize + 1;
            
            List<SomeObject__History> listEntityHistory = [SELECT Id, Field, NewValue, OldValue, CreatedDate, CreatedById, CreatedBy.Name FROM SomeObject__History WHERE ParentId = :SomeObjectId ORDER BY CreatedDate DESC, Id DESC LIMIT :limitPlusOne];
            AllowShowMore = (listEntityHistory.size() == limitPlusOne);
             
            for (SomeObject__History oHistory : listEntityHistory) {
                SomeObjectHistory oSomeObjectHistory = new SomeObjectHistory(oHistory);
                
                if (mapFields.containsKey(oHistory.Field)) {
                    oSomeObjectHistory.FieldLabel = mapFields.get(oHistory.Field).getDescribe().Label;
                }
                                    
                if (dLastCreatedDate == oHistory.CreatedDate) {
                    oSomeObjectHistory.ShowDateAndUser = false;
                }
                else {
                    oSomeObjectHistory.ShowDateAndUser = true;
                }
                
                listSomeObjectHistory.add(oSomeObjectHistory);
                dLastCreatedDate = oHistory.CreatedDate;
                
                if (listSomeObjectHistory.size() == PageSize) break;
            }
        }
        
        return listSomeObjectHistory;
    }
    
    //Internal Classes

    /**
     * Data structure representing a SomeObject History record for display
     */
    public class SomeObjectHistory {
        //Properties
        public boolean ShowDateAndUser {get;set;}
        public string FieldLabel {get;set;}
        public SomeObject__History History {get; private set;}
        
        public string ActionFormat {
            get { return getActionFormat(); }
        }
        
        public SomeObjectHistory(SomeObject__History oHistory) {
            History = oHistory;
        }
        
        //Constructors
        public SomeObjectHistory() {
            showDateAndUser = true;
        }
        
        //Private Methods
        private string getActionFormat() {
            string sActionFormat = '';
            
            if (History != null) {
                sActionFormat = 'Record {0}.';
                
                if (History.newValue != null && History.oldValue == null) {
                    sActionFormat = 'Changed <strong>{1}</strong> to <strong>{3}</strong>.';    
                }
                else if (History.newValue != null && History.oldValue != null) {
                    sActionFormat = 'Changed <strong>{1}</strong> from {2} to <strong>{3}</strong>.';   
                }
                else if (History.Field != null && History.Field.equalsIgnoreCase('created')) {
                    sActionFormat = 'Created.';
                }
            }
            
            return sActionFormat;
        }
    }
}

The controller class is very simple and consists of a constructor that loads the history data, a private method that loads the history data, and a public method to facilitate the show more option; which basically just increments the limit counter by five.

The controller class also contains an internal class that holds a reference to the history record and contains some properties for displaying the history data on the screen in the proper format.

One thing to note is that I chose to keep the elements making up the action text as properties and creating a format string rather than concatenating the information in code. The main reason I chose this route, other than I really don't like hardcoding text , is I am hoping that I can go back in and remove that text and make it a custom label so that the text can be dynamically configured or translated should the need for multi-lingual support arise. Also, the text that I use for the action text is based on my observations of the native history object so it might not be the most complete but I think it solves for the most common.

Another thing to note about the controller is the way I sort the list of history items. I found out that I couldn't just sort by Created Date because there could be changes that were made as part of the same transaction and the even though they happen at the same time the order is key to understanding certain things such as record locking. So I thought I would just sort by Id. Not a good idea either because the Id for a history record isn't unique to an object and the order wasn't matching the native related list. So what I ended up finding is that a combination of the Created Date and Id worked to match the native related list sort order.

It would seem that all history objects share the same Id schema which leads me to believe that they are actually stored as a single entity but broken up into views, possibly to enhance performance of querying for the history of a particular object

Oh, and one more thing to note is in the way I build the list. You will notice that when I select the limit of records I am using the limit value plus one. This is my way of peeking ahead in the recordset to see if there are more records than I am about to display. If so, then I can set my "AllowShowMore" property accordingly. It also means however that I need to skip the last record in the list which isn't a problem since I have to loop through the recordset any way to build the list using the internal class.


That is pretty much it. Such a seemingly simple thing turned into hours of work, mostly trail and error, to replicate the list as close to native as I could make it.