You need to sign in to do that
Don't have an account?

History related List using Visualforce
When developing a Visualforce page for overiding view page for any object, one problem that creeps up is to display the History details of a record. The standard related list Component doesn't works for History.
With the help of some code from Community ( I now can't find the link to it :( ), I wrote my own code then to display the history of an object. It mimics the standard list as far as possible.
Heres the code. It is for the Case object but it can be used for any other object.
1.Component Code
<apex:component controller="CaseHistoriesComponentController">
<!-- Attribute Definition -->
<apex:attribute name="CaseId" description="Salesforce Id of the Case whose Case History needs to be rendered" type="Id" required="true" assignTo="{!caseId}" />
<!-- Case History Related List -->
<apex:pageBlock title="Case History">
<apex:pageBlockTable value="{!histories}" 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:component>
2. Apex Code
public class CaseHistoriesComponentController {
public Id caseId {get; set;}
public cHistories[] histories;
// Variables
public Static final Map<String, Schema.SObjectField> CaseFieldmap = Schema.SObjectType.Case.fields.getMap();
public Static final List<Schema.PicklistEntry> fieldPicklistValues = CaseHistory.Field.getDescribe().getPicklistValues();
public List<cHistories> getHistories()
{
list<cHistories> histories = new list<cHistories>();
String prevDate = '';
for(CaseHistory cHistory : [Select CreatedDate, CreatedBy.Name, CreatedBy.Id, Field, NewValue, OldValue from CaseHistory where CaseId = :caseId order by CreatedDate desc])
{
if((cHistory.newValue == null && cHistory.oldValue == null)
|| (cHistory.newValue != null && !(string.valueOf(cHistory.newValue).startsWith('005') || string.valueOf(cHistory.newValue).startsWith('00G')))
|| (cHistory.oldValue != null && !(string.valueOf(cHistory.oldValue).startsWith('005') || string.valueOf(cHistory.oldValue).startsWith('00G'))))
{
cHistories tempHistory = new cHistories();
// Set the Date and who performed the action
if(String.valueOf(cHistory.CreatedDate) != prevDate)
{
tempHistory.theDate = String.valueOf(cHistory.CreatedDate);
tempHistory.who = cHistory.CreatedBy.Name;
tempHistory.userId = cHistory.CreatedBy.Id;
}
else
{
tempHistory.theDate = '';
tempHistory.who = '';
tempHistory.userId = cHistory.CreatedBy.Id;
}
prevDate = String.valueOf(cHistory.CreatedDate);
// Get the field label
String fieldLabel = CaseHistoriesComponentController.returnFieldLabel(String.valueOf(cHistory.Field));
// Set the Action value
if (String.valueOf(cHistory.Field) == 'created') { // on Creation
tempHistory.action = 'Created.';
}
else if(cHistory.OldValue != null && cHistory.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(cHistory.OldValue).format() + ' in <b>' + fieldLabel + '</b>.';
} catch (Exception e){
tempHistory.action = 'Deleted ' + String.valueOf(cHistory.OldValue) + ' in <b>' + fieldLabel + '</b>.';
}
}
else{ // all other scenarios
String fromText = '';
if (cHistory.OldValue != null) {
try {
fromText = ' from ' + Date.valueOf(cHistory.OldValue).format();
} catch (Exception e) {
fromText = ' from ' + String.valueOf(cHistory.OldValue);
}
}
String toText = '';
if (cHistory.OldValue != null) {
try {
toText = Date.valueOf(cHistory.NewValue).format();
} catch (Exception e) {
toText = String.valueOf(cHistory.NewValue);
}
}
if(toText != '')
tempHistory.action = 'Changed <b>' + fieldLabel + '</b>' + fromText + ' to <b>' + toText + '</b>.';
else
tempHistory.action = 'Changed <b>' + fieldLabel;
}
// Add to the list
histories.add(tempHistory);
}
}
return histories;
}
// Function to return Field Label of a Case field given a Field API name
public Static String returnFieldLabel(String fieldName)
{
if(CaseHistoriesComponentController.CaseFieldmap.containsKey(fieldName))
return CaseHistoriesComponentController.CaseFieldmap.get(fieldName).getDescribe().getLabel();
else
{
for(Schema.PicklistEntry pickList : fieldPicklistValues)
{
if(pickList.getValue() == fieldName)
{
if(pickList.getLabel() != null)
return pickList.getLabel();
else
return pickList.getValue();
}
}
}
return '';
}
// Inner Class to store the detail of the case histories
public class cHistories {
public String theDate {get; set;}
public String who {get; set;}
public Id userId {get; set;}
public String action {get; set;}
}
}
Let me know your views on the code or if you have any questions
Thanks for that, I have used your original code as a basis for a generic history component, since you shared your original code I thought I should return the favor.
It is still a little rough around the edges, but you can use the component in a VF page using the following method:
<c:GenericHistoryComponent recordLimit="50" myObject="{!Software_License__c}"/>
The component code is as follows:
<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:component>
And the controller class is as follows:
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(){
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 thew history table
String objectHistoryTableName = objectDescription.getName();
//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);
}
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 ParentId =\'' + 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 = String.valueOf(historyLine.get('CreatedDate'));
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('oldValue') != 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;}
}
}
All Answers
I've done something similar to this for an existing project (although I'm using a popup window rather than related list). As you've gone this far (and kindly shared) you might wish to consider making it work across any object, rather than being tied to a single object.
There's not a lot of extra code required :
-> pass in the name of the history class and the underlying class (CaseHistory and Case, for this example),
-> alter your select statement to something like the following:
String soql='select ParentId, OldValue, NewValue, IsDeleted, Id, Field, CreatedDate,' + 'CreatedById, CreatedBy.Name From ' + objectType + ' where ParentId = :objectId order by CreatedDate desc'; return Database.query(soql);
where objectType is the history class.
->alter the field name lookup to figure out (and cache) the non-history object type field map and pull the fields out from that.
It certainly cut down on our duplicated code going this route :)
hmmm ... thts a good idea ... i will try implement the same when I get time.
Thanks for that, I have used your original code as a basis for a generic history component, since you shared your original code I thought I should return the favor.
It is still a little rough around the edges, but you can use the component in a VF page using the following method:
<c:GenericHistoryComponent recordLimit="50" myObject="{!Software_License__c}"/>
The component code is as follows:
<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:component>
And the controller class is as follows:
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(){
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 thew history table
String objectHistoryTableName = objectDescription.getName();
//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);
}
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 ParentId =\'' + 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 = String.valueOf(historyLine.get('CreatedDate'));
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('oldValue') != 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;}
}
}
Was able to leverage code for a Custom Object, however, when trying to create a new record for the Custom Object, receiving an error from the Controller Class at:
list<sObject> historyList = Database.query( 'SELECT CreatedDate,'+
'CreatedById,'+
'Field,'+
'NewValue,'+
'OldValue ' +
'FROM ' + objectHistoryTableName + ' ' +
'WHERE ParentId =\'' + myObjectId + '\' ' +
'ORDER BY CreatedDate DESC '+
'LIMIT ' + String.valueOf(recordLimit));
stipulating that the Id is NULL. Appears that everything is fine is the Custom Object record already exists.
Additionally, is there a way to hide the Related History in edit mode? Currently it is defaulted to 'true'.
There is a bug in the code for the following case:
If the old value is null and the new value is not null. Instead of showing "Changed to <value>" it would just show "Changed". The error is on line 97:
if (historyLine.get('oldValue') != null) {
try {
toText = Date.valueOf(historyLine.get('newValue')).format();
} catch (Exception e) {
toText = String.valueOf(historyLine.get('newValue'));
}
}
The first line above should read if(historyLine.get('newValue') != null) {
Justin
thanks for the code RyanGossink ..
the above code needs minor changes for standard objects.. parentid column is only in custom object history table. for standard objects the column name is objectname+id (eg. caseId)..
rest of the code remains the same..
Great code, how can this be applied to an Account's Related Activity History?
-Thanks
@Rajesh; Thanks man - ur code really helped me in doing my task today
Account related activity history related list can be directly created. Eg code is given below:
One minor issue I noticed when implementing this is that the history Date is displayed as GMT, not in the user's local time zone. To fix this, in the controller (~line 60) where you set tempHistory.theDate, don't use String.valueOf(). Instead, cast to a DateTime object and use the format() method like this:
tempHistory.theDate = ((Datetime)historyLine.get('CreatedDate')).format('yyyy-MM-dd HH:mm:ss');
This should show the local time. The format string here matches that currently displayed by the history component but you can always change it to day-month-year, change to am/pm time or whatever else works better for you. The format string is the same as that used by Java so just Google that for the details.
Has anyone notice this failing since the spring release?
It appears that the 'Field' now have the namespace on them, and don't lookup in the objectfieldmap like they used to.
I just used the code for custom objects and it seems to be working for me.
Hi I am trying to implement this component but getting error as
Error: Compile Error: Method does not exist or incorrect signature: [Schema.SObjectType].getDescribe() at line 30 column 99Error: Compile Error: Method does not exist or incorrect signature: [Schema.SObjectType].getDescribe() at line 30 column 99
Plz help !
Hi everyone,
Can anyone provide test case for GenericHistoryComponentController class. Please I need urgently.
Thanks for your help in advance.
Rajiv
I just had one issue using this component in my VF page and I think it was because I was using an action="myActionMethod" attribute in my apex:page tag.
My action method is sometimes returning a PageReference that navigates away from the VF page, but when it does it seems to be passing a null instead of myObject into the component and this was giving me a 'de-reference a null object' error and stopping the page redirect. It worked fine when there was no redirect.
I just made the beginning of the method look like this and all was well. (new code in red)
Excellent work .
I am getting one issue when custom object field is of type picklist it is returning the id as new entry in list.
Is it something to do with the below code
string.valueOf(historyLine.get('newValue')).startsWith('005') || string.valueOf(historyLine.get('newValue')).startsWith('00G')).
How can i get rid of that records.
hi JustinMcc
have you solved that bug using the (newValue != null)?
but i am still facing the same error. can u please help me....?
any kind of help will be appriciated......
thanks in advance.
=========================================
=========================================
Here is the latest up to date working Component,
Controller, and my Unit Test (tricky) for
the GenericHistoryComponent
=========================================
=========================================
The bug fixes here are: (see previous posts for specifics.)
1. ParentID vs ObjectNameID (fixed so this works for standard objects.)
2. OldValue was supposed to read NewValue.
3. If no object passed in, return empty list.
4. Opportinuty history object has the suffix FieldHistory.
5. v24 of the Salesforce API test classes cannot see existing org data by default. Added (SeeAllData=true) to the test class.
6. Fixed the display of the history record created date to show as the users local time instead of GMT.
Component:
Controller:
Unit Test:
Mike Katulka
www.itedgecrm.com
Mike, you still need to test for myObject being equal to null (I described a few posts earlier) for it to work everywhere.
cheers
Colin.
Hey Colin, I just updated the controller, and the unit test in my last post. Thanks!
Mike Katulka
www.itedgecrm.com
Opportunity field history is stored in the OpportunityFieldHistory object, so I had to change the code this way:
Thanks a lot for sharing.
How can I use this on a VF email?
I already:
- Changed the controller access to global
- Changed the apex:pageBlock to a apex:outputPanel
- Changed the apex:pageBlockTable to a apex:dataTable
But I can't reference the SObject directly in the VF Email template and If a reference the related list name, it doesn't take it
With this line:
<c:GenericHistoryComponent recordLimit="50" myObject="{!relatedTo.Related_List__r}"/>
I get this error:
Error: Wrong type for attribute <c:generichistorycomponent myObject="{!relatedTo.Related_List__r}">. Expected SObject, found Custom_Object__c[]
Any ideas?
You would have to explain what kind of object "relatedTo" was in your email. Also you would have to expand on what the lookup / master-detail field Related_List__c was.
I was able to figure it out.
My recipientType is a User and my relatedToType is a custom object (CO1), which is related to another custom_object (CO2) with a master-detail relationship. I want to show the history table for each of the records of type CO2 that are related to my current CO1 record.
This is what I did, and its working:
<apex:repeat var="cx" value="{!relatedTo.Related_List__r}">
<c:GenericHistoryComponent recordLimit="50" myObject="{!cx}"/>
</apex:repeat>
This is very impressive.
Thanks
I can't get the test class to run. I am getting the following error on this line.. I tried executing the same line in the developer console and eclipse and it returns me 1 row. I have had to remark all the system.assert statements also.
Please help.
string accid = [select Accountid from accounthistory where isdeleted=false limit 1].accountid;
Class.GenericHistoryControllerTester.myTestGenericHistoryComponentController: line 28, column 1
System.QueryException: List has no rows for assignment to SObject
Have you created this in version 24 of the API - that hides the data in the system from you. If there is a record in there, you can use the @IsTest(SeeAllData=true) annotation on your test class/method.
Thanks a million... your recommendation worked.
I updated the test class and revision notes in my previous post. See the latest component, class, and unit test on page 2, at the bottom.
Thanks Bob for the input!
Hello
I want to track the history of assets with fields Asset name,Asset status and contact name. I am not getting how to do it can someone please hepl me with it i am new to apex.
Thanks!
Assets don't look like they have History functionality. See here http://success.salesforce.com/questionDetail?qId=a1X30000000JNiUEAW
Also, this post is for a more specific need, to be able to display history on a "Custom Visualforce Page".
I just found a bug related to displaying the correct time to the user in the "Date" column of the component. It was displaying the GMT time pulled from the history table. Now it will display the proper time for the user logged in to view the component.
I updated the component controller and revision notes in my previous post. See the latest component, class, and unit test on page 2, at the bottom.
I have implemented this code. When I view in salesforce standard view, username is displaying properly in history. When I view in customer portal, it is not displaying the username instead it displays the userID. Do you have any thoughts to use it for both ways?
Thanks Rajesh Shah,
I expanded on your code to ignore object Ids in the history list and added the "Show more » | Go to list » links.
You can get the code here:
https://github.com/pcjones10/Genteric-History-Component
pcjones - for some reason your code is not filtering out the line items with Ids. Cannot figure out why...I am using it for Case History and I am still seeing Ids in the history related list. I have tried to change this.
Also, all - the code filters out any lines where the "value" of the field starts with "005" or "00G", this works fine unless you are referencing an auto numbered item like Case Number, once your Cases start with "005" then they won't show up... Anyone know how to filter out Ids without using "StartsWith"?
I'm amazed that Salesforce haven't provided this as standard!
But equally amazed by the contributions of others to solve the problem - saved me days of work.
Thanks guys :)
Hi
I am getting the following error:
Error: <apex:attribute assignTo> cannot be same as the <apex:attribute name> ( myObject )
any ideas?
Hi All,
This helped a lot for me, just having one issue for the same. if someone can help me ?
I am using GenericHistoryComponent for showing Approval history rather then using Standerd approval history, hence i am using approval process on that object, so in history section, it is displaying "Changed Record Locked"
So any one can help me to get rid of this, i don't want to display Record locked/Unlocked history. can i get rid of from histroy section ?
Thanks in advance. Appriciating your help for the same.
Thanks for the help!
I wonder if it is also possible to filter the results by created date, for example only display fields that where changed after 01/01/2012?
Thanks again!
Mike -- very helpful. Thanks. I made a small modification to your test method to:
Code is here (V27.0) - 85% test coverage
This is fantastic!
I am still getting history rows with IDs in them when a lookup is changed from null to a value. If it's changed from one value to another the ID row is not appearing. Any ideas on how to modify the code to eliminate these ID rows?
Thanks!
Like this:
Account a = new Account();
a.Status__c = 'New';
insert a;
AccountHistory ah = new AccountHistory();
ah.Field = 'Status__c'; // API name of the field you want to track
ah.parentId = a.Id
insert ah;
Then call the Controller methods
I don't have the possibility to write Apex apparently with my version (professionnal) so please make it accessible for all!
Thank you :)
My visualforce page was 38.0. I changed it to 39.0 and now the above code doesn't cause the error.
See screenshot below: