function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
tengeltengel 

Clone and re-parent multiple records via Related List

Hello! I am trying to give my users the ability select one or many records from a related list (using checkboxes) and then click a custom clone button that will allow them to clone and re-parent the selected record(s) to a different master record.

 

The requirement is that all selected items get re-parented to the same master record, so I'm sort of envisioning one simple intermediary screen after the custom clone button is selected where the user will use a Lookup field to select the new parent for all of the records they selected.

 

I'm not an Apex guru by any means, but I can usually get moving with a little input from the dev community and some sample code. Any ideas? Thanks!

Best Answer chosen by Admin (Salesforce Developers) 
sfdcfoxsfdcfox

New, revised code:

 

<apex:page standardController="Contact" recordSetVar="record" extensions="cloneAndReparentPage">
    <apex:form >
        <apex:pageBlock >
            <apex:pageBlockSection >
                <apex:outputPanel >
                    Select an account, and then {!selectedCount} contact{!if(selectedCount==1,'','s')} will be reparented and cloned.
                </apex:outputPanel>
                <apex:inputField value="{!Contact.AccountId}"/>
            </apex:pageBlockSection>
            <apex:pageBlockButtons >
                <apex:commandButton action="{!cloneAndReparent}" value="Clone & Reparent"/>
            </apex:pageBlockButtons>
        </apex:pageBlock>
    </apex:form>
</apex:page>

 

public class cloneAndReparentPage{
    ApexPages.StandardSetController controller;
    
    public cloneAndReparentPage(ApexPages.StandardSetController controller) {
        this.controller = controller;
    }
    
    public Integer getSelectedCount() {
        return controller.getSelected().size();
    }
    public PageReference cloneAndReparent() {
		Contact[] newRecords = new Contact[0], selRecords = (Contact[])controller.getSelected();	
		String query = String.format(
			'SELECT {0} FROM {1} WHERE ID IN (\'\'{2}\'\')',
			new String[] {
				String.join(
					new List<String>(
						Contact.SObjectType.getDescribe().fields.getMap().keySet()
					),
					','
				),
				String.valueOf(Contact.SObjectType),
				String.join(
					new List<Id >(
						new Map<Id,Contact>(selRecords).keySet()
					),'\',\''
				)
			}
		);
		for(Contact record:(Contact[])Database.query(query)) {
			newRecords.add(record.clone(false,false,false,false));
		}
		for(Contact record:newRecords) {
			record.AccountId = (Id)controller.getRecord().get(Contact.AccountId);
		}
		insert newRecords;
        return new ApexPages.StandardController(new Account(Id=(Id)controller.getRecord().get(Contact.AccountId))).view();
	}}

The change you made to '\\','\\' was incorrect; the goal was to include literal quotes in the string, not literal backslashes. I tested this in my org, it works.

All Answers

sfdcfoxsfdcfox

That code would look something like this:

 

<apex:page standardController="Contact" recordSetVar="record" extensions="cloneAndReparentPage">
  <apex:form>
  <apex:pageBlock>
    <apex:pageBlockSection>
        <apex:outputPanel>
            Select an account, and then {!selectedCount} contact{!if(selectedCount==1,'','s')} will be reparented and cloned.
        </apex:outputPanel>
      <apex:inputField value="{!Contact.AccountId}"/>
    </apex:pageBlockSection>
    <apex:pageBlockButtons>
      <apex:commandButton action="{!cloneAndReparent}" value="Clone & Reparent"/>
    </apex:pageBlockButtons>
  </apex:pageBlock>
  </apex:form>
</apex:page>

 

public class cloneAndReparentPage{
	ApexPages.StandardSetController controller;
    
    public cloneAndReparentPage(ApexPages.StandardSetController controller) {
        this.controller = controller;
    }
    
    public Integer getSelectedCount() {
        return controller.getSelected().size();
    }
    
    public ApexPages.PageReference cloneAndReparent() {
        Contact[] records = controller.getSelected().deepClone(false,false,false);
        for(Contact record:records) {
            record.AccountId = (Id)controller.getRecord().get(Contact.AccountId);
        }
        insert records;
        return new ApexPages.StandardController(new Account(Id=(Id)controller.getRecord().get(Contact.AccountId))).view();
    }
}

No error checking is done here, so I expect some work will have to be done (e.g. checking if any records are available, etc).

 

Usage: Create a button on contacts (type: list, behavior: visualforce). Use the "show checkboxes" option. Once saved, add it to the contact related list on account page layouts.

 

This code compiles and should run without a problem, but user input (e.g. user doesn't select any records) might cause it to fail.

tengeltengel

@sfdcfox, thanks! This definitely got me going in the right direction.

 

I read into the deepClone list methods here (http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_methods_system_list.htm) and can't quite figure out why the "cloned" records don't appear to be cloning any of the field-level data from the originating records. The method you provided does create new records at the new parent record, but all fields are blank and the record name is defaulting to the new child record's Salesforce ID. I must be missing something?

sfdcfoxsfdcfox

That was actually my fault; the list doesn't return all fields by default, so we just need to make a small adjustment:

 

    public PageReference cloneAndReparent() {
        Contact[] newRecords = new Contact[0], selRecords = (Contact[])controller.getSelected();
        for(Contact record:[SELECT Id,FirstName,LastName... FROM Contact WHERE Id IN :selRecords]) {
			newRecords.add(record.clone(false,false,false,false));
		}
		for(Contact record:newRecords) {
			record.AccountId = (Id)controller.getRecord().get(Contact.AccountId);
		}
		insert newRecords;
        return new ApexPages.StandardController(new Account(Id=(Id)controller.getRecord().get(Contact.AccountId))).view();
	}
	

 

tengeltengel

I see. So I have to explicitly call out each each field that I want cloned? That could be problematic due to the number of fields in the originating record. Also, field additions in the future would require altering the class, correct? Is there any statement that automatically just makes an exact copy of the record's field data for all fields except those that are defined in the clone method?

sfdcfoxsfdcfox

You could use a dynamic query, like this:

 

    public PageReference cloneAndReparent() {
		Contact[] newRecords = new Contact[0], selRecords = (Contact[])controller.getSelected();	
		String query = String.format(
			'SELECT {0} FROM {1} WHERE ID IN (\'\'{2}\'\')',
			new String[] {
				String.join(
					new List<String>(
						Contact.SObjectType.getDescribe().fields.getMap().keySet()
					),
					','
				),
				String.valueOf(Contact.SObjectType),
				String.join(
					new List<String>(
						new Map<Id,Contact>(selRecords).keySet()
					),'\',\'
				)
			}
		);
		for(Contact record:(Contact[])Database.query(query)) {
			newRecords.add(record.clone(false,false,false,false));
		}
		for(Contact record:newRecords) {
			record.AccountId = (Id)controller.getRecord().get(Contact.AccountId);
		}
		insert newRecords;
        return new ApexPages.StandardController(new Account(Id=(Id)controller.getRecord().get(Contact.AccountId))).view();
	}

This might get you into trouble if you have too many fields (e.g. I think there's a limit of 5 or 10 "long text area fields"), but this would work for most normal cases. You could also build a blacklist of fields to exclude if you wanted to.

tengeltengel

Ok, feel that I'm getting closer but runnning into a compile issue. I was getting the error: "line breaks not allowed in string literals at line 30 column -1" so I modified your line:

),'\',\'

 to this:

),'\\','\\'

 But now I am getting this error:

Compile Error: Invalid initial value type SET<Id> for LIST<String> at line 28 column 21  

 

sfdcfoxsfdcfox
Try changing it from List<string> to List<Id>.
sfdcfoxsfdcfox

New, revised code:

 

<apex:page standardController="Contact" recordSetVar="record" extensions="cloneAndReparentPage">
    <apex:form >
        <apex:pageBlock >
            <apex:pageBlockSection >
                <apex:outputPanel >
                    Select an account, and then {!selectedCount} contact{!if(selectedCount==1,'','s')} will be reparented and cloned.
                </apex:outputPanel>
                <apex:inputField value="{!Contact.AccountId}"/>
            </apex:pageBlockSection>
            <apex:pageBlockButtons >
                <apex:commandButton action="{!cloneAndReparent}" value="Clone & Reparent"/>
            </apex:pageBlockButtons>
        </apex:pageBlock>
    </apex:form>
</apex:page>

 

public class cloneAndReparentPage{
    ApexPages.StandardSetController controller;
    
    public cloneAndReparentPage(ApexPages.StandardSetController controller) {
        this.controller = controller;
    }
    
    public Integer getSelectedCount() {
        return controller.getSelected().size();
    }
    public PageReference cloneAndReparent() {
		Contact[] newRecords = new Contact[0], selRecords = (Contact[])controller.getSelected();	
		String query = String.format(
			'SELECT {0} FROM {1} WHERE ID IN (\'\'{2}\'\')',
			new String[] {
				String.join(
					new List<String>(
						Contact.SObjectType.getDescribe().fields.getMap().keySet()
					),
					','
				),
				String.valueOf(Contact.SObjectType),
				String.join(
					new List<Id >(
						new Map<Id,Contact>(selRecords).keySet()
					),'\',\''
				)
			}
		);
		for(Contact record:(Contact[])Database.query(query)) {
			newRecords.add(record.clone(false,false,false,false));
		}
		for(Contact record:newRecords) {
			record.AccountId = (Id)controller.getRecord().get(Contact.AccountId);
		}
		insert newRecords;
        return new ApexPages.StandardController(new Account(Id=(Id)controller.getRecord().get(Contact.AccountId))).view();
	}}

The change you made to '\\','\\' was incorrect; the goal was to include literal quotes in the string, not literal backslashes. I tested this in my org, it works.

This was selected as the best answer
tengeltengel

You are a rockstar, thank you (again) @sfdcfox!