I believe that your idea of using a fieldset on Opportunity is the way that you want to go here.
The idea is that you'd have a controller extension to go along with your visualforce page, and in the constructor, you'd use the fieldset to determine which records in OpportunityHistory
to display.
public class MyControllerExtension{
public MyControllerExtension(ApexPages.StandardController con){
// Retrieve the fieldset
Schema.Fieldset oppFieldSet = sObjectType.Opportunity.Fieldsets.get('myFieldset');
// Extract the api names of the fields in the fieldset
List<String> fields = new List<String>();
for(Schema.FieldSetMember fsm :oppFieldSet.getFields()){
fields.add(fsm.getFieldPath());
}
// Use the field names to filter your query
List<OpportunityHistory> oppHistory = [SELECT OldValue, NewValue, Field FROM OpportunityHistory WHERE Field IN :fields];
}
}
Note that while my example code is mostly complete, it will need some tweaking to be useful with a Visualforce page.
This method won't run into any code issues if someone happens to include a field in the fieldset that isn't being tracked in history. That field will simply just never appear as a result in your Visualforce page.
+edit:
Whoops, looks like I forgot about the requirement of ordering, and I was assuming work on OpportunityFieldHistory
instead of OpportunityHistory
.
While the fields on OpportunityHistory
are indeed simply a subset of the fields on Opportunity
, the Field
field doesn't exist on this object (so we can't filter on that).
What you're trying to accomplish is to replace the standard Stage History
related list (which can't be edited, hence, your question).
This makes matters just a little more complicated. We can still use a fieldset, but we now need to ensure the fields in the fieldset are actually in OpportunityHistory
.
Sets will help us out here.
public class MyControllerExtension{
public List<String> fieldsToDisplay {get; private set;}
public List<OpportunityHistory> historyResults {get; private set;}
public MyControllerExtension(ApexPages.StandardController con){
// Retrieve the fieldset
Schema.Fieldset oppFieldSet = sObjectType.Opportunity.Fieldsets.get('myFieldset');
// Because we're working with OpportunityHistory, we need to ensure that
// our given fields are actually on the object.
// Normally, I'd suggest using the retainAll() method of the set class
// but that would destroy the ordering.
// We'll just need to use contains() as we iterate over the fieldset.
Set<String> oppHistoryFields = sObjectType.OpportunityHistory.fields.getMap().keySet();
// Extract the api names of the fields in the fieldset
for(Schema.FieldSetMember fsm :oppFieldSet.getFields()){
if(oppHistoryFields.contains(fsm.getFieldPath())){
fieldsToDisplay.add(fsm.getFieldPath());
}
}
// This SOQL query doesn't need to be dynamic, but it might save some typing.
historyResults = database.query('SELECT ' + String.join(new List<String>(sObjectType.OpportunityHistory.fields.getMap().keySet()), ', ') + FROM OpportunityHistory WHERE OpportunityId = <id from standard controller>);
}
}
In the Visualforce page, an <apex:repeat>
inside of an <apex:pageBlockTable>
should be able to take care of ordering the fields
<apex:PageBlockTable value="{!historyResults}" var="History">
<apex:repeat value="{!fieldsToDisplay}" var="field">
<apex:column hearderValue="{!field}" value="{!History[field]}" />
</apex:repeat>
</apex:PageBlockTable>
NewValue
,OldValue
,Field
, etc?OpportunityHistory
was the same thing asOpportunityFieldHistory
.