0 Comments

When creating a custom Visualforce page there are times when we would like to have a section showing system fields. In a standard visualforce page (the ones automatically generated by the platform) we see the following fields:

  • Created By: showing the name of the user who created the record and the creation date and time
  • Last Modified By: showing the name of the last user who modified the record and the date and time of this modification
  • Owner ID: The name of the user who owns the record, its picture, and a link that allows to change the owner.

Most of this information can be showed by using apex:outputField commands. For example:

<apex:pageBlock mode="mainDetail" >
  <apex:pageBlockSection title="{!$Label.SystemInformation}" columns="2">
    <apex:outputField value="{!Contact.CreatedById}" />
    <apex:outputField value="{!Contact.CreatedDate}" />
    <apex:outputField value="{!Contact.LastModifiedById}"/>
    <apex:outputField value="{!Contact.LastModifiedDate}"/>
    <apex:outputField value="{!Contact.OwnerId}"/>
  </apex:pageBlockSection>
</apex:pageBlock>

This will produce the following output:

standardSystemInformation

Notice that we get all the necessary information, but we lose some cool features:

  • There is no “Change” link to change the owner
  • There is no photo of the owner
  • The format is not like the one in the standard page.

We could get the format we desire by tweaking the visualforce a little bit (e.g. use apex:outputPanel to group fields together in the same line) but having the “change” and the photo is not trivial thing. Besides, by using apex:outputPanel and apex:outputText we lose the cool popup that shows summary information when we hover the mouse over the links.

The following example contains a Visualforce component that can be added to any custom page to display the system fields in a format that mimics the format produced by a standard page.

<apex:component controller="SystemInformationComponentController"> 
    <apex:attribute name="record" assignTo="{!recordValue}" 
        type="sObject" description="The object for which to display system information" required="true"/>
    
    <apex:pageBlockSection title="{!$Label.SystemInformation}" columns="2">
        <apex:pageBlockSectionItem >
            <apex:outputLabel value="{!$Label.CreatedBy}" />
            <apex:outputPanel >
                <apex:outputLink id="createdBy"
                    onblur="LookupHoverDetail.getHover('{!$Component.createdBy}').hide();" 
                    onfocus="LookupHoverDetail.getHover('{!$Component.createdBy}', '/{!createdById}/m?retURL={!URLENCODE($CurrentPage.Url)}&isAjaxRequest=1').show();" 
                    onmouseout="LookupHoverDetail.getHover('{!$Component.createdBy}').hide();" 
                    onmouseover="LookupSHoverDetail.getHover('{!$Component.createdBy}', '/{!createdById}/m?retURL={!URLENCODE($CurrentPage.Url)}&isAjaxRequest=1').show();"                       
                 value="{!URLFOR('/' + createdById)}">{!createdByName}</apex:outputLink>&nbsp;
                <apex:outputText value="{!convertedCreatedDate}" />                                                                         
            </apex:outputPanel>
        </apex:pageBlockSectionItem> 
        <apex:pageBlockSectionItem >
            <apex:outputLabel value="{!$Label.LastModifiedBy}" />
            <apex:outputPanel >
                <apex:outputLink id="lastModifiedBy"
                    onblur="LookupHoverDetail.getHover('{!$Component.lastModifiedBy}').hide();" 
                    onfocus="LookupHoverDetail.getHover('{!$Component.lastModifiedBy}', '/{!lastModifiedById}/m?retURL={!URLENCODE($CurrentPage.Url)}&isAjaxRequest=1').show();" 
                    onmouseout="LookupHoverDetail.getHover('{!$Component.lastModifiedBy}').hide();" 
                    onmouseover="LookupHoverDetail.getHover('{!$Component.lastModifiedBy}', '/{!lastModifiedById}/m?retURL={!URLENCODE($CurrentPage.Url)}&isAjaxRequest=1').show();"                       
                 value="{!URLFOR('/' + lastModifiedById)}">{!lastModifiedByName}</apex:outputLink>&nbsp;
                <apex:outputText value="{!convertedLastModifiedDate}" />                                                                         
            </apex:outputPanel>
        </apex:pageBlockSectionItem>
        <apex:pageBlockSectionItem >
            <apex:outputLabel for="owner" value="{!$Label.Owner}" />
            <apex:outputPanel >
                <apex:image value="{!ownerPhoto}" width="16" height="16"/>&nbsp;
                <apex:outputLink id="owner"
                    onblur="LookupHoverDetail.getHover('{!$Component.owner}').hide();" 
                    onfocus="LookupHoverDetail.getHover('{!$Component.owner}', '/{!ownerId}/m?retURL={!URLENCODE($CurrentPage.Url)}&isAjaxRequest=1').show();" 
                    onmouseout="LookupHoverDetail.getHover('{!$Component.owner}').hide();" 
                    onmouseover="LookupHoverDetail.getHover('{!$Component.owner}', '/{!ownerId}/m?retURL={!URLENCODE($CurrentPage.Url)}&isAjaxRequest=1').show();"
                 value="{!URLFOR('/' + ownerId)}">{!ownerName}</apex:outputLink>&nbsp;
                <apex:outputLink value="{!URLFOR('/' + recordId + '/a?retURL=' + URLENCODE($CurrentPage.Url))}">[Change]</apex:outputLink>
            </apex:outputPanel>
        </apex:pageBlockSectionItem>        
    </apex:pageBlockSection>
</apex:component>

This component requires the following controller:

public class SystemInformationComponentController{
    public sObject recordValue { get; set;}
    
    public Id recordId {
        get {
            return recordValue.Id;
        }
    }  
       
    public Id createdById {
        get {
            return (Id)recordValue.get('CreatedById');
        }
    }  
    
    public String createdByName {
        get {
            User createdByUser = [select name from user where id = :createdById limit 1];
            return createdByUser == null ? null : createdByUser.Name;
        }
    }
        
    public String convertedCreatedDate {
        get { 
            DateTime createdDate = (DateTime)recordValue.get('CreatedDate');
            return createdDate.format(); 
        }
    }
    
    public Id lastModifiedById {
        get {
            return (Id)recordValue.get('LastModifiedById');
        }
    }  
    
    public String lastModifiedByName {
        get {
            User lastModifiedByUser = [select name from user where id = :lastModifiedById limit 1];
            return lastModifiedByUser == null ? null : lastModifiedByUser.Name;
        }
    }
        
    public String convertedLastModifiedDate {
        get {
            DateTime lastModifiedDate = (DateTime)recordValue.get('LastModifiedDate');
            return lastModifiedDate.format(); 
        }
    } 
    
    public Id ownerId {
        get {
            return (Id)recordValue.get('OwnerId');
        }
    }  
    
    public String ownerName {
        get {
            User ownerUser = [select name from user where id = :ownerId limit 1];
            return ownerUser == null ? null : ownerUser.Name;
        }
    }
    
    public String ownerPhoto {
        get {
            Id ownerId = (Id)recordValue.get('ownerId');
            User owner = [select smallphotourl from user where id = :ownerId limit 1];
            return owner == null ? null : owner.SmallPhotoUrl;

        }
    }           
}

To use the component, simply call it from your custom page passing the record for the page:

<apex:page standardController="Contact">
  <apex:pageBlock mode="mainDetail" >
    <apex:outputField value="{!Contact.CreatedById}" rendered="false" />
    <apex:outputField value="{!Contact.CreatedDate}" rendered="false" />
    <apex:outputField value="{!Contact.LastModifiedById}" rendered="false" />
    <apex:outputField value="{!Contact.LastModifiedDate}" rendered="false" />
    <apex:outputField value="{!Contact.OwnerId}" rendered="false" />
    <c:SystemInformationComponent record="{!record}" />
  </apex:pageBlock>
</apex:page>

This will produce the following:

customizedSystemInformation

Notice how we get all the features we need and they look as a standard page output.

There are some things to consider when using this component:

  • Custom labels: for the component to be used in multiple languages, notice that the field labels use the following custom labels:
    • $Label.SystemInformation
    • $Label.CreatedBy
    • $Label.LastModifiedBy
    • $Label.Owner
  • The controller is based in the generic sObject type, allowing the component to be used in any standard or custom object
  • Datetimes are formatted using the format() method to show the converted datetime in the user’s profile timezone
  • In order to display the owner photo, the system must have Chatter enabled.
  • When using the component, the calling page must include the following fields (using an <apex:outputField value=”…” rendered=”false” />):
    • CreatedById
    • CreatedDate
    • LastModifiedById
    • LastModifiedDate
    • OwnerId

This is to make the fields available to the standard controller (and thus, the underlying record property) so it can be used on the component’s controller (see the above example to see how)

For completeness, here is the test for the controller:

@isTest
public class SystemInformationComponentTest{
 
    @isTest public static void TestComponent() {
        
        Account record = new Account(Name = 'Test');
        insert record;
        record = [select ownerId,createdById,lastModifiedById,createdDate,lastModifiedDate from account where id = :record.id];
        User owner = [select name,smallPhotoUrl from user where id = :record.ownerId];
            
        SystemInformationComponentController controller = new SystemInformationComponentController();
        controller.recordValue = record;
        
        System.assertEquals(record.Id, controller.recordId);
        System.assertEquals(record.CreatedById, controller.createdById);
        System.assertEquals(record.LastModifiedById, controller.lastModifiedById);
        System.assertEquals(record.OwnerId, controller.ownerId);
        System.assertEquals(record.CreatedDate.format(), controller.convertedCreatedDate);
        System.assertEquals(record.LastModifiedDate.format(), controller.convertedLastMOdifiedDate);
        System.assertEquals(owner.Name, controller.ownerName);
        System.assertEquals(owner.SmallPhotoUrl, controller.ownerPhoto); 
        System.assertEquals(owner.Name, controller.lastModifiedByName);
        System.assertEquals(owner.Name, controller.createdByName);
    }
}

I have written a better approach to this, please have a look at Showing System Fields in a Custom Visualforce Page (an improved approach) for more information