Lightning Component Clone with Related Records

In this blog, I am going to explain a simple quick action lightning component that will clone the records and its related data also. In this example, I am controlling which all objects are allowed to as part of the cloning is through the custom metadata data types.

These are the records for metadata 

Apex Class

public class SuperClone {
    private static SuperCloneService service = new SuperCloneService();
    @AuraEnabled
    public static Id doClone(Id parentId) {
        Id clonedId = service.doClone(parentId);
        return clonedId;
    }
}
public class SuperCloneService {
    public Id doClone(String parentId) {
        Set<String> querySobject = new Set<String>();
        for(Super_Clone_Objects__mdt m : [select Id, DeveloperName, Label, API_Name__c 
                                          from Super_Clone_Objects__mdt  ]){ 
                                              querySobject.add(m.API_Name__c) ;  
                                          }
        Map <String, Schema.SObjectType> schemaMap = Schema.getGlobalDescribe();
        String objectAPIName = '';
        String keyPrefix = parentId.substring(0,3);
        for( Schema.SObjectType obj : schemaMap.Values() ){
            String prefix = obj.getDescribe().getKeyPrefix();
            if(prefix == keyPrefix){
                objectAPIName = obj.getDescribe().getName();
                break;
            }
        }
        Set <String> fieldMap = schemaMap.get(objectAPIName).getDescribe().fields.getMap().keySet();
        List<String> finalFields = new List<String>() ;
        finalFields.addAll(fieldMap);
        
        SObjectType objToken = Schema.getGlobalDescribe().get(objectAPIName); 
        DescribeSObjectResult objDef = objToken.getDescribe();
        Map<String,String> so = new Map<String,String>();
        Map<String,String> so1 = new Map<String,String>();
        
        for (Schema.ChildRelationship cr: objDef.getChildRelationships()) 
        {
            if(cr.getField().getDescribe().isAccessible()&& cr.getField().getDescribe().isCreateable()&&cr.getField().getDescribe().isAccessible() && cr.getRelationshipName()!=null){
                if(querySobject.contains(''+cr.getChildSObject())){
                    so.put(''+cr.getChildSObject()  , ''+cr.getRelationshipName());
                    so1.put(''+cr.getRelationshipName()  , ''+cr.getField());
                }
            }
        } 
        
        List<String> subqueries = prepareSubqueries(so, schemaMap);
        String query =
            'SELECT ' + String.join(finalFields, ',')+
            ','+String.join(subqueries, ',') +
            ' FROM ' +objectAPIName +
            ' WHERE Id = \''+parentId+'\'';
        
        List<Sobject> parentObj = Database.query(query);
        Sobject parentRecordId = parentObj[0];
        
        Sobject clonedRecord = parentRecordId.clone();
        insert clonedRecord;
        List<sObject> childObjects =cloneChildren(parentRecordId, clonedRecord, so  ,so1);
        insert childObjects;
        System.debug('clonedRecord'+clonedRecord.Id);
        return clonedRecord.Id ;
        
    }
    
    private List<sObject> cloneChildren(
        Sobject parent,
        Sobject child,
        Map<String , String> childRelatedListObjects,
        Map<String , String> childRelatedListObjects1
    ){
        
        List<sObject> childObjects = new List<SObject>();
        for (String childObjectDefinition : childRelatedListObjects.values()) {
            List<sObject> parentRecords = parent.getSObjects(childObjectDefinition);
            System.debug('parentRecords'+parentRecords);
            if (parentRecords != null) {
                List<sObject> records = parentRecords.deepClone();
                System.debug('records'+records);
                for (sObject record : records) {
                    record.put(childRelatedListObjects1.get(childObjectDefinition), child.Id);
                }
                childObjects.addAll(records);
            }
        }
        return childObjects;
    }
    
    private List<String> prepareSubqueries(
        Map<String , String> childrelatedListObjects,
        Map <String, Schema.SObjectType> schemaMap
    ){
        List<String> subqueries = new List<String>();
        for(String childObject : childrelatedListObjects.keySet()){
            List<String> childFields = new List<String>();
            Map <String, Schema.SObjectField> fieldMap = schemaMap.get(childObject).getDescribe().fields.getMap();
            System.debug('fieldMap'+fieldMap);
            for(Schema.SObjectField sof : fieldMap.values()){
                DescribeFieldResult dfr = sof.getDescribe();
                if(dfr.isCreateable()){
                    childFields.add(dfr.getName());
                }
            }
            if(!childFields.isEmpty()){
                String query = '(SELECT ' + String.join(childFields, ',') + ' FROM ' + childrelatedListObjects.get(childObject) + ')';
                subqueries.add(query);
            }
            
        }
        return subqueries;
    }
    
}

Lightning Component 

 

<aura:component implements="force:lightningQuickAction,force:hasRecordId"
                controller="SuperClone">
    <aura:attribute name="errorMsg" type="String" />
    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />
    <ui:message aura:id="errorMsg" severity="error" class="slds-hide">
        {!v.errorMsg}
    </ui:message>
</aura:component>

controller.js

({
    doInit: function(cmp, event, helper) {
        var action = cmp.get("c.doClone");
        action.setParams(
            {
                parentId : cmp.get("v.recordId")
            }
        );
        action.setCallback(this, function(response) {
            var state = response.getState();
            console.log('state sucess'+state);
            
            if (cmp.isValid()) {
                if (state === "SUCCESS") {
                    console.log('state sucess'+state);
                    
                    var clonedId = response.getReturnValue();
                    var navEvt = $A.get("e.force:navigateToSObject");
                    navEvt.setParams({
                        "recordId": clonedId
                    });
                    navEvt.fire();
                } else if(state == "ERROR"){
                    console.log('state'+state);
                    var errors = response.getError();
                    if(errors) {
                        console.log(errors[0].message);
                        var hideQuickAcion = $A.get("e.force:closeQuickAction");
                        hideQuickAcion.fire();
                    }
                }
            }
            helper.hideElement(cmp, 'modalSpinner');
        });
        
        $A.enqueueAction(action);
    },
    
})

helper.js

({
    hideElement: function (cmp, elementId) {
        var elm = cmp.find(elementId);
        $A.util.addClass(elm, 'slds-hide');
    },
    showElement: function (cmp, elementId) {
                component.set("v.Spinner", true); 

        var elm = cmp.find(elementId);
        $A.util.removeClass(elm, 'slds-hide');
    },
    
    showToast : function(type, title, message) {
        var toastEvent = $A.get("e.force:showToast");
        if(toastEvent) {
            toastEvent.setParams({
                "title": title,
                "message": message,
                "type": type
            });
            toastEvent.fire();
        }
    },
    
})

 

.THIS .spinnerWrapper{
    width: 24px;
    height: 24px;
}

.THIS .spinnerDiv{
    margin-right: 12px;
    margin-left: 12px;
}

.THIS .cardBody{
    padding-left: 1%;
    padding-right: 1%;
}

Creating Quick Action 

You will be able to create quick action with the above components shown below.Go to the object manager and create a quick action as shown below.

After saving the action, add this action to the page layout. this component we can reuse it on any object.

Once the user clicks on the clone button its will clone the parent and all child records data along with relationship as shown below.