Dynamic Record Cloning
I’m building a custom cloning functionality that needs to meet a few objectives, most important of which is being dynamic. I don’t want to build these out for each object in our instance.
This is fine. What I’ve done is build a class that reads the meta data of the sObject I want to clone. The way to do this is to create a map of field names and field tokens and build up a SOQL query select statement with those fields.
Here’s a utility class with a method getCreatableFieldsSOQL() to accomplish just that:
public with sharing class Utils{
// Returns a dynamic SOQL statement for the whole object, includes only creatable fields since we will be inserting a cloned result of this query
public static string getCreatableFieldsSOQL(String objectName, String whereClause){
String selects = '';
if (whereClause == null || whereClause == ''){ return null; }
// Get a map of field name and field token
Map<String, Schema.SObjectField> fMap = Schema.getGlobalDescribe().get(objectName.toLowerCase()).getDescribe().Fields.getMap();
list<string> selectFields = new list<string>();
if (fMap != null){
for (Schema.SObjectField ft : fMap.values()){ // loop through all field tokens (ft)
Schema.DescribeFieldResult fd = ft.getDescribe(); // describe each field (fd)
if (fd.isCreateable()){ // field is creatable
selectFields.add(fd.getName());
}
}
}
if (!selectFields.isEmpty()){
for (string s:selectFields){
selects += s + ',';
}
if (selects.endsWith(',')){selects = selects.substring(0,selects.lastIndexOf(','));}
}
return 'SELECT ' + selects + ' FROM ' + objectName + ' WHERE ' + whereClause;
}
}
Great! Now I’m ready to create a placeholder visual force page that I can call via url from a custom button. The url is simple enough (note I’m using relative links here):
/apex/CloneSpecialWastePWS?sid={!Special_Waste_Profile__c.Id}
Creating that Visual Force page is easy enough – an otherwise blank canvass with a simple call to a doit() method:
<apex:page Controller="CloneSpecialWasteProfile" action="{!doit}" showHeader="false" sidebar="false">
</apex:page>
And finally the controller for the Visual Force page. Note the constructor that calls the above utility method, and the doit() method that returns a page reference. Also, pay attention to how I grab the sid parameter from the URL:
public class CloneSpecialWasteProfile{
private final Special_Waste_Profile__c p;
public CloneSpecialWasteProfile() {
String soql = Utils.getCreatableFieldsSOQL('Special_Waste_Profile__c','id=\'' + ApexPages.currentPage().getParameters().get('sid') + '\'');
p = (Special_Waste_Profile__c)Database.query(soql);
}
public PageReference doit() {
Special_Waste_Profile__c p2 = p.clone(false, true);
insert p2;
p2.Approval_Date__c = null;
//Reset all of the fields I don't want to copy values for
update p2;
PageReference pageRef = new PageReference('/' + p2.Id);// + '/e?retURL=' + p2.Id);
pageRef.setredirect(true);
return pageRef;
}
}
Then I add the button to the layout, and when it’s clicked I’m redirected to the view layout of a cloned record. Implementing this for another object is as simple as creating a new Visual Force page and controller, and linking to that page from a new custom button on the layout for the sObject.