+ Start a Discussion
Brian KoblenzBrian Koblenz 

copy object from Id in custom button

I am creating a custom button associated with a related list.  The purpose of the button is to take a set of items in the related list associated with parent object P, copy them and associate the copies with parent object P1.  I would like the copies to have all the data of the original object, but have a new parent.  Furthermore, I would like this button to continue to work without modification if new fields are added to the object.

I (naively) assumed that the SObject constructor would take an Id for an object and create a copy.  I also understand through a /id/e?clone=1 I could close a list of objects, but I assume they would be tied to object P which is undesirable.  In terms of created dates etc I dont care if the field is copied or set anew.

I have the rough button code below.   The area surrounded by **clone start** and **clone end** is where I am struggling.

On a more minor note, is there a way that my boolean check Propagated__c can be directly tested rather than comparing against the string "false" which must be coming from the query?

thanks
-brian

var idArray = {!GETRECORDIDS($ObjectType.HOMEtracker__Improvements_Adjustments__c)};
if (idArray.length == 0) { 
   alert('Please select at least one record'); 
}
else if (false) {
   alert('Improvement->Service File->Buyer field must be filled in prior to propagation.');
}
else { 
   var updated_records = [];
   var new_records = [];
   var num_updated = 0;
   var improvs = sforce.connection.retrieve(
            'Id,Propagated__c,Closure_Date__c,Current_Depreciation__c',
            'HOMEtracker__Improvements_Adjustments__c', idArray);
   for(var i=0; i<idArray.length; i++) {
      if (improvs[i].Propagated__c == 'false' &&
               improvs[i].Closure_Date__c == null) {
         num_updated++;
**clone start** 
         var i_copy = clone the object with id==idArray[i]
         i_copy.Parent__c = P1;  (this would be the reparenting step.)
         new_records.push(i_copy);
**clone end**

         var improv = new sforce.SObject(
                                "HOMEtracker__Improvements_Adjustments__c"); 
         improv.id = idArray[i]; 
         improv.Propagated__c = true; 
         updated_records.push(improv);
      } 
   } 
   result = sforce.connection.update(updated_records);
   result = sforce.connection.create(new_records);
   alert('Number of copied records: '+num_updated);
   window.location.reload(); 
}
Best Answer chosen by Brian Koblenz
Zuinglio Lopes Ribeiro JúniorZuinglio Lopes Ribeiro Júnior
Hello, 

Not sure if I got it right but if you are looking for a clone button that clones the parent and its children you can try the approach bellow.

First of all, you have to create a class with a webservice method that will be invoked by a custom button in the parent's object layout that will clone the parent and its children:
 
/**
* @description Helper class to clone parent and its children sObjects
*/
global without sharing class EnhancedCloneHelper {
       
    /**
    * @description Clone parent and its children
    * @param id of the parent sObject
    * @return String Serialized result
    */
    webservice static String clone (String sObjectId) {
        
        List<ParentSObject__c> parentSObjects;
        ParentSObject__c parent;
        CloneModel cloneModelResult = new CloneModel();
        
		// Parent query
        String query = String.format(
            'SELECT {0} FROM {1} WHERE Id = \'\'{2}\'\'',
            new String[] {
                String.join(
                    new List<String>(
                        ParentSObject__c.SObjectType.getDescribe().fields.getMap().keySet()
                    ),
                    ','
                ),
                String.valueOf(ParentSObject__c.SObjectType),
                sObjectId
           }
        );

        try {
            
            // Query and gets results
            parentSObjects = Database.query(query);         
            
			// Clone the original object. Here you can change anything without affecting the original sObject
            parent = parentSObjects[0].clone(false, true, false, false);
            parent.Name = parent.Name + ' CLONED';

            Database.insert(parent);
       
        } catch (DmlException error) {
            cloneModelResult.message = 'An error occurred while cloning the object.' + error.getMessage();
            return JSON.serialize(cloneModelResult);        
        }
        
        // Children query  
        query = String.format(
            'SELECT {0} FROM {1} WHERE ParentSObjectId__c = \'\'{2}\'\'',
            new String[] {
                String.join(
                    new List<String>(
                        ChildSObject__c.SObjectType.getDescribe().fields.getMap().keySet()
                    ),
                    ','
                ),
                String.valueOf(ChildSObject__c.SObjectType),
                sObjectId
           }
        );
        
        List<ChildSObject__c> children = new List<ChildSObject__c>();
        
        try {
            
            // Query and clone the children. Here you can change anything without affecting the original sObject
            for (ChildSObject__c child:(List<ChildSObject__c>)Database.query(query)) {
                children.add(child.clone(false,true,false,false));
            }
            
            // If there isn't any children ends the process and return success
            if (children.isEmpty()) {
                cloneModelResult.isSuccess = true;
                cloneModelResult.message = 'Object successfully cloned!';                
                cloneModelResult.url = getUrlRedirect(parent.Id);
                return JSON.serialize(cloneModelResult);
            }
            
			// Set the parent's Id
            for (ChildSObject__c child : children) {
                child.ParentSObjectId__c = parent.Id;
            }
        
            Database.insert(children);
    
            
        }  catch(DMLException error) {
            cloneModelResult.message = 'An error occurred while cloning the object.' + error.getMessage();;
            return JSON.serialize(cloneModelResult); 
        }
        
        // Return success at the end of the process
        cloneModelResult.isSuccess = true;
        cloneModelResult.message = 'Object and its children successfully cloned!';
        cloneModelResult.url = getUrlRedirect(parent.Id);
        
        return JSON.serialize(cloneModelResult);

    }

    private static String getUrlRedirect(String sObjectId){
        PageReference page = new PageReference('/'+ sObjectId);
        return page.getUrl();
    }
    
    global class CloneModel {
        Boolean isSuccess;
        String message;
        String url;
    }
}

After that, you will have to create a custom javascript button and place it at the parent's layout. Once clicked, the button will invoke the clone method and will clone the desired sObject.

User-added image

Button's code:
 
{!REQUIRESCRIPT("/soap/ajax/30.0/connection.js")} 
{!REQUIRESCRIPT("/soap/ajax/30.0/apex.js")}

var clonedSobjectResult = JSON.parse(sforce.apex.execute("EnhancedCloneHelper","clone", { sObjectId : "{!ParentSObject__c.Id}" }));

alert(clonedSobjectResult.message);

if(clonedSobjectResult.isSuccess){
  window.location = clonedSobjectResult.url;
}

This is one way to achieve what you are looking for. Keep in mind that you can change it as much as you want.

Hope to have helped. Regards.

Don't forget to mark your thread as 'SOLVED' with the answer that best helps you.

All Answers

Zuinglio Lopes Ribeiro JúniorZuinglio Lopes Ribeiro Júnior
Hello, 

Not sure if I got it right but if you are looking for a clone button that clones the parent and its children you can try the approach bellow.

First of all, you have to create a class with a webservice method that will be invoked by a custom button in the parent's object layout that will clone the parent and its children:
 
/**
* @description Helper class to clone parent and its children sObjects
*/
global without sharing class EnhancedCloneHelper {
       
    /**
    * @description Clone parent and its children
    * @param id of the parent sObject
    * @return String Serialized result
    */
    webservice static String clone (String sObjectId) {
        
        List<ParentSObject__c> parentSObjects;
        ParentSObject__c parent;
        CloneModel cloneModelResult = new CloneModel();
        
		// Parent query
        String query = String.format(
            'SELECT {0} FROM {1} WHERE Id = \'\'{2}\'\'',
            new String[] {
                String.join(
                    new List<String>(
                        ParentSObject__c.SObjectType.getDescribe().fields.getMap().keySet()
                    ),
                    ','
                ),
                String.valueOf(ParentSObject__c.SObjectType),
                sObjectId
           }
        );

        try {
            
            // Query and gets results
            parentSObjects = Database.query(query);         
            
			// Clone the original object. Here you can change anything without affecting the original sObject
            parent = parentSObjects[0].clone(false, true, false, false);
            parent.Name = parent.Name + ' CLONED';

            Database.insert(parent);
       
        } catch (DmlException error) {
            cloneModelResult.message = 'An error occurred while cloning the object.' + error.getMessage();
            return JSON.serialize(cloneModelResult);        
        }
        
        // Children query  
        query = String.format(
            'SELECT {0} FROM {1} WHERE ParentSObjectId__c = \'\'{2}\'\'',
            new String[] {
                String.join(
                    new List<String>(
                        ChildSObject__c.SObjectType.getDescribe().fields.getMap().keySet()
                    ),
                    ','
                ),
                String.valueOf(ChildSObject__c.SObjectType),
                sObjectId
           }
        );
        
        List<ChildSObject__c> children = new List<ChildSObject__c>();
        
        try {
            
            // Query and clone the children. Here you can change anything without affecting the original sObject
            for (ChildSObject__c child:(List<ChildSObject__c>)Database.query(query)) {
                children.add(child.clone(false,true,false,false));
            }
            
            // If there isn't any children ends the process and return success
            if (children.isEmpty()) {
                cloneModelResult.isSuccess = true;
                cloneModelResult.message = 'Object successfully cloned!';                
                cloneModelResult.url = getUrlRedirect(parent.Id);
                return JSON.serialize(cloneModelResult);
            }
            
			// Set the parent's Id
            for (ChildSObject__c child : children) {
                child.ParentSObjectId__c = parent.Id;
            }
        
            Database.insert(children);
    
            
        }  catch(DMLException error) {
            cloneModelResult.message = 'An error occurred while cloning the object.' + error.getMessage();;
            return JSON.serialize(cloneModelResult); 
        }
        
        // Return success at the end of the process
        cloneModelResult.isSuccess = true;
        cloneModelResult.message = 'Object and its children successfully cloned!';
        cloneModelResult.url = getUrlRedirect(parent.Id);
        
        return JSON.serialize(cloneModelResult);

    }

    private static String getUrlRedirect(String sObjectId){
        PageReference page = new PageReference('/'+ sObjectId);
        return page.getUrl();
    }
    
    global class CloneModel {
        Boolean isSuccess;
        String message;
        String url;
    }
}

After that, you will have to create a custom javascript button and place it at the parent's layout. Once clicked, the button will invoke the clone method and will clone the desired sObject.

User-added image

Button's code:
 
{!REQUIRESCRIPT("/soap/ajax/30.0/connection.js")} 
{!REQUIRESCRIPT("/soap/ajax/30.0/apex.js")}

var clonedSobjectResult = JSON.parse(sforce.apex.execute("EnhancedCloneHelper","clone", { sObjectId : "{!ParentSObject__c.Id}" }));

alert(clonedSobjectResult.message);

if(clonedSobjectResult.isSuccess){
  window.location = clonedSobjectResult.url;
}

This is one way to achieve what you are looking for. Keep in mind that you can change it as much as you want.

Hope to have helped. Regards.

Don't forget to mark your thread as 'SOLVED' with the answer that best helps you.
This was selected as the best answer
Brian KoblenzBrian Koblenz
Thanks so much for your help. I really do appreciate it. However, I think I must have poorly described my problem. Also, I have made a little progress in my research so can still benefit from additional input. First, I am not trying to clone a parent and all of its children. My custom button is on a related list of children. Lets call the children of the related list c1, c2,...cn and each of those children are tied to a common master object I am calling the parent P. (I am on the P's detail page and in the related list section I see c1, c2,....cn and the custom button is associated with the related list. I believe this button executes in the context of P and getobjectids gets me the selected items for my related list. Is that correct? Where your code refers to a clone() function, it also seems I can clone c1 by "executing" the url '/c1/e?clone=1'. In fact, it seems I can clone c1 and c2 by executing '/c1,c2/e?clone=1'. This would be pretty good except that c1 and c2 would still refer to P. I have tried to do this in the code below (even temporarily accepting that the clones would be linked to the wrong object (essentially doubling the size of my related list), but the clones did not get created even though the debug string looked correct. Any ideas why not? var clone_str = '/'+ ids_to_clone + '/e?clone=1'; alert('clone string: '+ clone_str); window.location.href = clone_str; When I interactively put that url in a browser, I did get a clone but until I hit "save" it did not really get created. I wonder if that is somehow related to why I am not seeing my clones. Furthermore, it turns out the object P has a lookup field L that refers to another object of the same type as P. Let's call the object referred to by P.L=P1. I read that if I knew the Id of the of field where I want to effect the reparenting I could do something like '/cloneids/e?clone=1&fieldidforreparenting=P1'. Then I would get my clone and reparenting in a single step. I think I can look through the "view source" of my clone attempt and try that id. and see what happens. I currently have two problems with this part. 1) I dont know really where to find the field id and am not sure if it is persistent. 2) I tried to refer to {!Object__c.New_Parent__c} as the id for the new parent, but since it is a lookup field I think I am having a problem getting the value. I also tried {!Object__c.New_Parent__r.Id} to no avail. var buyer = {!Object__c.New_Parent__c}; alert('buyer: ' + buyer); Does that help clarify my problem? -brian
Brian KoblenzBrian Koblenz
Well that previous comment (from email) formatted rather poorly.  Sorry.

I did figure out how to clone (manually) and get the reparenting by passing a magic id referring to the object's field id to the clone routine.  So, the following string when entered in a browser and then clicking save actually creates the appropriate behavior.  The _lkid is a hidden id name that allows the id to be set for a lookup field.

      var clone_str = '/'+ ids_to_clone + '/e?clone=1' +
         '&CF00Nd0000004Oten_lkid=a0fd000000EZODL' + // magic to set service_file__c
         '&00Nd0000004Otiq=3'; // magic to set value__c to 3
      alert('clone string: '+ clone_str);
      window.location.href = clone_str;

Unfortunately, the code when execute in the custom button does not perform the clone.  I really dont know how to debug that.  Any help?

Also, I still have the problem of getting the value to assign to CF00Nd0000004Oten_lkid.  That is a lookup field on found on P that I am having trouble referencing in my button.   {!Object__c.Service_File__c} does not work when the service_file field is a lookup.

thanks

 
Brian KoblenzBrian Koblenz
Zuinglio,

I tried to compile your apex class and get the following error:
[Error] Error: Compile Error: Invalid type: ParentSObject__c at line 13 column 14

I asked in another thread how this parentsobject__c works and now with the compile time error I am even more confused.  Can you help?

thanks