With that in mind, I have created a little helper tool I have called "Clone Plus" that does just that. It consists of an Apex controller, a single Visualforce page and custom button.
The clone plus button can be created on custom and standard object page layouts. When clicked, the button redirects the user to a Visualforce page that displays the children that can be cloned and associated with the new object. The user selects the objects they want to clone using checkboxes, and then clicks on a clone button to finish the operation. After the saving process is completed, the user is redirected to the new object.
Create the clone button by following the following steps:
The clone plus button can be created on custom and standard object page layouts. When clicked, the button redirects the user to a Visualforce page that displays the children that can be cloned and associated with the new object. The user selects the objects they want to clone using checkboxes, and then clicks on a clone button to finish the operation. After the saving process is completed, the user is redirected to the new object.
The clone button on the page
The child clone selection screen
The new cloned object
Create the clone button by following the following steps:
1) Create a new Apex class (Navigate to Setup -> Develop -> Apex Classes and click new). Copy in the following code into the class body:
public class ClonePlusController { public List<relatedObjects> objectChildren { get; set; } public String objectTypeName { get; set; } public String objectName { get; set; } private SObject headSObject, headClone; // Initialisation method called when the clone plus page is loaded. // Use the id page parameter to find out what kind of // object we are trying to clone. // Then load the object from the database. // Finally call the populateObjectChildren method to public void initialiseObjectsForCloning() { // Here we generate a keyprefixmap using the global describe // Then compare that to our object to determine type. Map<String, Schema.SObjectType> gd = Schema.getGlobalDescribe(); Map<String,String> keyPrefixMap = new Map<String,String>{}; for(String sObj : gd.keySet()){ Schema.DescribeSObjectResult r = gd.get(sObj).getDescribe(); keyPrefixMap.put(r.getKeyPrefix(), r.getName()); } String objectID = ApexPages.currentPage().getParameters().get('id'); String objectTypeKey = objectId.subString(0,3); objectTypeName = keyPrefixMap.get(objectTypeKey); String primaryObjectQueryString = 'SELECT Id, Name FROM ' + objectTypeName + ' WHERE Id = \'' + objectId + '\''; headSObject = Database.query(primaryObjectQueryString); objectName = '' + headSObject.get('Name'); populateObjectChildren(); } // Get all of the children of the current object that have a // object type contained in the child object types page parameter. // Not restricting the child objects to particular types // results in unclonable system objects being added to the options, // which we need to avoid (You will not want to clone these!) // Making these object type choices also allows us // focus our efforts on the specific kinds of objects // we want to allow users to clone. public void populateObjectChildren() { objectChildren = new List<relatedObjects>{}; Set<String> childObjectTypes = new Set<String>{}; // read the object types from the page parameter. childObjectTypes.addAll( ApexPages.currentPage().getParameters() .get('childobjecttypes') .split(',') ); // Use the sobjecttype describe method to retrieve all // child relationships for the object to be cloned. Schema.DescribeSObjectResult headDescribe = headsObject.getSObjectType().getDescribe(); List<Schema.ChildRelationship> childRelationships = headDescribe.getChildRelationships(); // Iterate through each relationship, // and retrieve the related objects. for (Schema.ChildRelationship childRelationship : childRelationships) { Schema.SObjectType childObjectType = childRelationship.getChildSObject(); // Only retrieve the objects if their type is // included in the page argument. if (childObjectTypes.contains( childObjectType.getDescribe().getName())) { List<relatedObjectRow> relatedObjects = new List<relatedObjectRow>{}; Schema.SObjectField childObjectField = childRelationship.getField(); String relatedChildSObjectsquery = 'SELECT ID, Name FROM ' + childObjectType.getDescribe().getName() + ' WHERE ' + childObjectField.getDescribe().getName() + ' = \'' + headsObject.Id + '\''; for (SObject childObject : Database.query(relatedChildSObjectsquery)) { relatedObjects.add(new relatedObjectRow(childObject)); } if (!relatedObjects.isEmpty()) { objectChildren.add(new relatedObjects(relatedObjects, childObjectType.getDescribe().getLabelPlural(), childObjectField.getDescribe().getName())); } } } } // Perform the cloning process. // First clone the parent, then all of the child objects. // Then redirect the user to the new object page. public PageReference doClone() { headClone = cloneObjects(new List<sObject>{headSObject}).get(0); insert headClone; cloneSelectedObjects(); return new PageReference('/' + headClone.Id); } // Clone the selected child objects. // Associate the cloned objects with the new cloned parent object. public void cloneSelectedObjects() { List<sObject> clonedObjects = new List<sObject>{}; List<sObject> selectedRelatedObjects; for (relatedObjects relatedObject : objectChildren) { selectedRelatedObjects = new List<sObject>{}; clonedObjects = new List<sObject>{}; for (relatedObjectRow row : relatedObject.objectRows) { if (row.selected) { selectedRelatedObjects.add(row.obj); } } if (!selectedRelatedObjects.isEmpty()) { clonedObjects = cloneObjects(selectedRelatedObjects); for (sObject clone : clonedObjects) { clone.put(relatedObject.relatedFieldName, headClone.Id); } insert clonedObjects; } } } // Clone a list of objects to a particular object type // Parameters // - List<sObject> sObjects - the list of objects to be cloned // The sObjects you pass in must include the ID field, // and the object must exist already in the database, // otherwise the method will not work. public static List<sObject> cloneObjects(List<sObject> sObjects){ Schema.SObjectType objectType = sObjects.get(0).getSObjectType(); // A list of IDs representing the objects to clone List<Id> sObjectIds = new List<Id>{}; // A list of fields for the sObject being cloned List<String> sObjectFields = new List<String>{}; // A list of new cloned sObjects List<sObject> clonedSObjects = new List<sObject>{}; // Get all the fields from the selected object type using // the get describe method on the object type. if(objectType != null) { for (Schema.SObjectField objField : objectType.getDescribe().fields.getMap().values()) { Schema.DescribeFieldResult fieldDesc = objField.getDescribe(); // If the field type is location, then do not include it, // otherwise it will cause a soql exception. // Note that excluding the field does not stop the location from // being copied to the new cloned object. if(fieldDesc.getType() != DisplayType.LOCATION) { sObjectFields.add(fieldDesc.Name); } } } // If there are no objects sent into the method, // then return an empty list if (sObjects != null || sObjects.isEmpty() || sObjectFields.isEmpty()){ // Strip down the objects to just a list of Ids. for (sObject objectInstance: sObjects){ sObjectIds.add(objectInstance.Id); } /* Using the list of sObject IDs and the object type, we can construct a string based SOQL query to retrieve the field values of all the objects.*/ String allSObjectFieldsQuery = 'SELECT ' + sObjectFields.get(0); for (Integer i=1 ; i < sObjectFields.size() ; i++){ allSObjectFieldsQuery += ', ' + sObjectFields.get(i); } allSObjectFieldsQuery += ' FROM ' + objectType.getDescribe().getName() + ' WHERE ID IN (\'' + sObjectIds.get(0) + '\''; for (Integer i=1 ; i < sObjectIds.size() ; i++){ allSObjectFieldsQuery += ', \'' + sObjectIds.get(i) + '\''; } allSObjectFieldsQuery += ')'; system.debug('allSObjectFieldsQuery: ' + allSObjectFieldsQuery); try{ // Execute the query. For every result returned, // use the clone method on the generic sObject // and add to the collection of cloned objects for (SObject sObjectFromDatabase: Database.query(allSObjectFieldsQuery)){ clonedSObjects.add(sObjectFromDatabase.clone(false,true)); } } catch (exception e){ } } return clonedSObjects; } // Related objects data construct - // used to store a collection of child objects connected to // the head object through the same relationship field. public class relatedObjects { public List<relatedObjectRow> objectRows { get; set; } public String pluralLabel { get; set; } public String relatedFieldName { get; set; } public relatedObjects(List<relatedObjectRow> objectRows, String pluralLabel, String relatedFieldName) { this.objectRows = objectRows; this.pluralLabel = pluralLabel; this.relatedFieldName = relatedFieldName; } } // An indidual child object row. // Each instance simply contains the object definition, // and a checkbox to select the row for cloning // on the clone plus page. public class relatedObjectRow { public sObject obj { get; set; } public Boolean selected { get; set; } public relatedObjectRow(Sobject obj) { this.obj = obj; // All object rows are selected by default. this.selected = true; } public String getName(){ try{ return '' + obj.get('Name'); } catch (Exception e){ return ''; } } } }
2) Create a new Visualforce Page (Navigate to Setup -> Develop -> Pages and click new).
Enter ClonePlus in both the label and name fields.
3) Copy the following into the Visualforce Markup section:
Enter ClonePlus in both the label and name fields.
3) Copy the following into the Visualforce Markup section:
<apex:page controller="ClonePlusController" action="{!initialiseObjectsForCloning}"> <apex:sectionHeader title="Clone Plus: {!objectName}"/> <apex:form id="theform" > Please select the child objects you would like to clone.<br/><br/> <apex:repeat value="{!objectChildren}" var="child"> <apex:PageBlock title="{!child.pluralLabel}"> <apex:pageBlockTable value="{!child.objectRows}" var="objectRow"> <apex:column headerValue="Clone" width="10%"> <apex:inputCheckbox value="{!objectRow.selected}"/> </apex:column> <apex:column headerValue="Name" value="{!objectRow.name}" width="90%"/> </apex:pageBlockTable> </apex:PageBlock> </apex:repeat> <apex:PageBlock > <apex:commandButton action="{!doClone}" value="Clone"/> </apex:PageBlock> </apex:form> </apex:page>
4) Navigate to the custom button and links menu for the object you want to add the clone with children functionality for.
If this is standard object, select Setup -> Customize -> *Object Name* -> Buttons and Links. See the following example for contact.
If instead you want to clone a custom object, go to the custom objects definition page (Setup -> Create Object) and select the object from the list. Once the custom object definition page is open, scroll down to see the button and links menu. The following screenshots show how to do this for an example object called Invoice Statement
6) In the button definition body, write a URL in the following form:
/apex/ClonePlus?id={!*Parent Object Type*.Id}&childobjecttypes=*Child Object Types*
Where *Parent Object Type* is the API name of the object you are creating the button for, and *Child Object Types* is a comma separated list of the types of child object you want to be able to clone alongside your main parent object.
Here are some examples to help:
If you want to clone the standard Account object from the accont page, but also want to clone related standard Contact objects at the same time:
/apex/ClonePlus?id={!Account.Id}&childobjecttypes=Contact
If you want to clone the standard Campaign object, and also want the option to clone related standard Opportunity objects, but also related child instances of a custom object called Campaign Advert (Api name: Campaign_Advert__c):
/apex/ClonePlus?id={!Campaign.Id}&childobjecttypes=Opportunity,Campaign_Advert__c
If you want to clone a custom object of type Custom Parent (Api name: Custom_Parent__c) and two related child custom object types Custom Child (Api name: Custom_Child__c) and Another Child (Api name: Another_Child__c):
/apex/ClonePlus?id={!Custom_Parent__c.Id}&childobjecttypes=Custom_Child__c,Another_Child__c
/apex/ClonePlus?id={!*Parent Object Type*.Id}&childobjecttypes=*Child Object Types*
Where *Parent Object Type* is the API name of the object you are creating the button for, and *Child Object Types* is a comma separated list of the types of child object you want to be able to clone alongside your main parent object.
Here are some examples to help:
If you want to clone the standard Account object from the accont page, but also want to clone related standard Contact objects at the same time:
/apex/ClonePlus?id={!Account.Id}&childobjecttypes=Contact
If you want to clone the standard Campaign object, and also want the option to clone related standard Opportunity objects, but also related child instances of a custom object called Campaign Advert (Api name: Campaign_Advert__c):
/apex/ClonePlus?id={!Campaign.Id}&childobjecttypes=Opportunity,Campaign_Advert__c
If you want to clone a custom object of type Custom Parent (Api name: Custom_Parent__c) and two related child custom object types Custom Child (Api name: Custom_Child__c) and Another Child (Api name: Another_Child__c):
/apex/ClonePlus?id={!Custom_Parent__c.Id}&childobjecttypes=Custom_Child__c,Another_Child__c
Your form should look something like this:
Click the save to confirm the changes.
NOTE: If you have any problems defining your URL, write a comment below with the object names and relationships and I would be happy to reply with what the value should be.
7) Finally, add the new "Clone Plus" custom button to the page layouts you want it to appear on, using the handy WYSIWYG editior.
Page Layouts for standard objects can be found through the menu ( Setup -> Customize -> *Object Name* -> Page Layouts)
For custom objects, the page layouts appear on the same object summary page as the custom button and links menu.
So there you have it, Happy Cloning!! This is a first iteration of Clone Plus. I hope to develop this concept as time moves forward, all feedback and suggestions is greatly appreciated!
A few notes:
- Due to the generic nature of the method, you can add this functionality to as many objects as you like. You do not need to create any extra controllers or pages, they can all use the two already defined.
- At the moment clone plus only clones direct children, not parents, grandchildren or further relationships. Of course this is possible, all it takes is some more complex method calls. For this example I wanted to keep things simple.
- In this example I make the user define the types of object they want to clone. You may be asking, why not just clone everything? This was actually the approach I had at the beginning, but after trying it out, there are a lot of setup objects that are related to each object type behind the scenes. Most of these related objects would never be cloned, so including them does nothing but confuse the user.
Nice Post!!
ReplyDeleteCan you provide some tips or an example for cloning of 3 level objects (Parent --> Child --> Grand Child)
Thanks
Hi,
ReplyDeleteCloning Grandchild objects is entirely possible, my tips for doing this would be to turn the "populateObjectChildren" method into a static method that accepts an sobject as an argument and returns a list of "related object" classes. The "initialiseObjectsForCloning" method would have to be adapted to make numerous calls to this function, one for the parent object as before, and then once for each child object.
You would then have to adapt the class structures and Visualforce page markup to accomadate the three levels of object relations. In terms of the page arguments, a new "grandchildobjects" variable should be introduced to specify which children you want to add.
As you have probably gathered by now, this would not be a five minute adaptation :D . This is really where I saw clone plus progressing next, so watch this space, I may have an update in the none to distant future!
Hey, Nice Post!
ReplyDeleteBeeing a rookie with test methods for page controllers I'm struggling with this one.
Do you have some help to offer? Specifically I have problems with how to test the controller with the data normally coming from the page. How to do that?
/Mats
Never mind, I figured it out. Your code works like a charm :)
DeleteIt's going to be really useful in our environment where objects that have 20-100 child objects regularly need to be copied.
I'll make some changes though, I will ask for a new name of the copied object and demand some other property changes before initiating the copy process. Should not be too difficult.
Cheers!
/Mats
Thanks Mats! Great work on figuring out the testing and adapting it to your own needs.
DeleteHope all goes well with the adaptation of the name field, if you get stuck, post on here and I'll help where I can :)
I could use some help with test methods please.... Kinda lost on what to do. I am getting an error saying I only have 63% coverage. Thank you.
DeleteHere is a test method. Thank you to BritishBoyinDC at the npsf google group!
Delete@istest
public class ClonePlus_Test {
public static testmethod void testcloneplus () {
//Create test data
Account ta = new Account (Name = 'Test A Original');
insert ta;
Contact tc = new Contact (LastName = 'Test C Original', AccountId = ta.Id);
insert tc;
//Go to Page
PageReference tpageRef = Page.ClonePlus;
Test.setCurrentPage(tpageRef);
//Set Parameters that would be passed in
ApexPages.currentPage().getParameters().put('Id', ta.Id);
ApexPages.currentPage().getParameters().put('childobjecttypes', 'Contact');
// Instantiate a new controller with all parameters in place
ClonePlusController pcp = new ClonePlusController();
//Simulate intial action call on page
pcp.initialiseObjectsForCloning();
//Simulate Click Button
pcp.doClone();
//Check there are now two accounts
Account [] testresults = [Select Id, Name from Account WHERE NAME = 'Test A Original' ORDER BY CREATEDDATE ASC];
system.assertequals(2,testresults.size());
//Check there are now two contacts
Contact [] testcresults = [Select Id, LastName, AccountId from CONTACT WHERE AccountId IN :testresults ORDER BY CREATEDDATE ASC];
system.assertequals(2,testcresults.size());
//Confirm Contacts attached to different accounts
system.assertequals(testresults[0].Id, testcresults[0].AccountId);
system.assertequals(testresults[1].Id, testcresults[1].AccountId);
}
}
Good example test Ben!
DeleteHi Chris,
ReplyDeleteThis code is awesome!
Could it be possible to use this code in a list? We are already using some specific force page in the view mode, so adding new buttons is not possible (out of my hands). So wondering if adapting this piece of code to be placed in a list would be possible.
I guess it is, but I'm not an expert here.
Lot of thanks!
Hi David,
DeleteIn the above post I used a custom button on an object page layout purely because it is how most systems would want to access the clone plus functionality. When you look at the button definition, it is simply a URL link with arguments added to it.
So to access this functionality, all that is required is to place a link to this URL on the page. Could be a link/button/or clickable section, it doesn't matter as long as the arguments (object id and child object names) are populated correctly.
Also, I'm not exactly sure what you mean by in a list, is this a list on a custom Visualforce page someone has built for you, or a console view list, or something else?
Hi Chris,
DeleteI'm sorry but I wanted to say/write View. You know, from the standard salesforce tab we can create views, then from those view it would be great to be able to select one record and click on that button.
I already tested by creating a button setting the list view option and showing it in the Targets List List View, but I received this error message,
Ending position out of bounds: 3
Error is in expression '{!initialiseObjectsForCloning}' in component in page cloneplus
An unexpected error has occurred. Your development organization has been notified.
Maybe that's because I'm not viewing the child object in that view, not sure.
Thanks for your quick response.
Hi David,
DeleteThe reason why you are getting the error is because the record id attribute in the link is not being populated by the selected object in your list.
However, I have found a way to allow list custom buttons to access clone plus. I have even enhanced the process so the user can only pick a single object to clone at any time.
In your custom button definition simply change the behaviour value to "Execute Javascript" and the Content Source to "Onclick Javascript".
Finally, change the button definition to the following (you have to adapt is slightly to reference your own object and child objects, let me know if you need help with this!) :
{!REQUIRESCRIPT("/soap/ajax/15.0/connection.js")}
var records = {!GETRECORDIDS( $ObjectType.Invoice_Statement__c)};
if (records[0] == null) {
alert("Please select a record for cloning");
}
else if (records[1] != null) {
alert("Records can only be cloned one at a time");
}
else{
window.location.href="/apex/ClonePlus?id=" + records[0] + "&childobjecttypes=Line_Item__c,Delivery__c";
}
so there you have it, clone plus now works with lists! :D
Hi again,
ReplyDeleteI did indeed amend the code to allow for changing name and some other properties on the master object in the copying process.
However I'm not happy with the solution.
Ideally I would like to change the properties on the headClode object before it is inserted but I get an error then: "Save error: Field expression not allowed for generic SObject" (I tried to use headClone.Name='some name' etc)
I got around it instead by immediately reading the rcord back after insert and then change the properties. It works but since I log all changes to the master object in a separate table I now get two entries everytime for every copying process where I want only one.
Is there any weay I can change the properties on the headClone object before "insert headClone;" is exceuted?
/Mats
Hello again Mats,
DeleteThe reason why you are getting the exception is because the sObject has no method that gets or set the object name. I believe this is because there are some system sObjects out there that do not have name fields, just ids.
But fear not, if you want to change the name (or any property) of the object before it is inserted, there are two ways to do this:
1) Use the sobject method "put (fieldName, Value)" to change the field, like this:
headClone.put('Name', 'My new cloned object');
you could also use the get sOjbect method in conjunction with the put method if you want the name to contain part of the original object name.
More info here -- http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_methods_system_sobject.htm
2) Cast the object into its intended type, and then populate the name field as you normally would, like this:
MyObject__c myObjectClone = (MyObject__c) headClone;
myObjectClone.name = 'my new cloned object';
insert myObjectClone;
here is some more information on casting using dynamic DML - http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_dynamic_dml.htm
hope that helps, if you need more information, please let me know!
Now you are sure a fountain of wisdom!
ReplyDeleteNot only you solved my immediate problem but also the one of casting, something I have been looking for but that somehow have been eluding me. The problem with the net is as always separating the few gold nuggets from a mountain of chaff...
Cheers
/Mats
Cheers Mats! Glad I could help.
DeleteThanks for your post!! One question, I'm attempting to edit a property in my child objects (similar to what you posted above for the headClone object). Any suggestions?
ReplyDeleteIf it helps, my clone object only has one child so I've tried casting it to that type and then editing the field. The child object has an associated 'order' property that labels the children from 1 to n. However, now when I perform clone plus this field is being set to 1 on all children.
Hello Melissa,
DeleteIn terms of manipulating the cloning process to act differently for a particular type of object, you can create if logic statements and compare the object type of the cloned objects to a schema sObjectType. You could, for example, edit the cloneObjects method to look like this to act differently for accounts...
public static List cloneObjects(
List sObjects){
Schema.SObjectType objectType = sObjects.get(0).getSObjectType();
// A list of IDs representing the objects to clone
List sObjectIds = new List{};
// A list of fields for the sObject being cloned
List sObjectFields = new List{};
// A list of new cloned sObjects
List clonedSObjects = new List{};
...
// This is the comparison, you could use the instanceOf keyword against the sObject collection
if(objectType == Schema.SObjectType.Account)
{
// do some object specific stuff here, using casting
for (Integer i = 0; i < clonedObjects.size(); i++){
clonedObjects[i].put('Name', 'Cloned Account' + i);
}
}
}
I hope that helps! If you have any questions or need further info let me know.
Does this action work differently if the object is a custom object?
DeleteFor example, I want the Order__c field to be the value of i, so this section here would be
for (Integer i = 0; i < clonedObjects.size(); i++){
clonedObjects[i].put('Order__c', i);
}
How would the rest of the code adjust for custom objects?
To add to that question, how would the comparison for custom objects work? The object name is Rate__c, so would the if statement be:
Deleteif(objectType == Schema.SObjectType.Rate__c)
{
for (Integer i = 0; i < clonedObjects.size(); i++){
clonedObjects[i].put('Order__c', i);
}
}
Even though Rate__c isn't a standard object?
Hello again Melissa,
DeleteYes what you describe above will work, you can reference custom objects in exactly the same way as standard objects.
Let me know how you get on!
Error: Compile Error: Comparison arguments must be compatible types: Schema.SObjectType, Schema.DescribeSObjectResult at line 243 column 10
DeleteThat error is occurring at the if statement. Any suggestions?
Ahh, I have actually found an easier way to do the if statement comparison that is a lot more straightforward. Simply use the "instanceOf" keyword to check if the list of cloned objects is a collection of a particular type, like in the following examples.
Deleteif (clonedOjbects instanceOf List <Contact>)
{
system.debug('This is a list of Contacts');
}
else if (clonedOjbects instanceOf List<Order__c>)
{
system.debug('This is a list of Orders');
}
Hope that helps, I didn't know that you could use this keyword with collections before, learn something new every day :)
Thanks!
DeleteOne more question, in the clone plus visualforce page is it possible to show other fields of the child object besides name? Say I wanted to show the Custom objects Factor__c and Value__c fields in the table on select.. how would this be done?
No problem!
DeleteAdding custom fields to the display table rows is possible, but is tricky to manage for many objects. The page definition above is intended to be used generically, and "name" is a common useful field that all objects you want to clone will probably have.
Being of a generic nature means the page works for all objects, the trade-off, as your finding here, is that it cannot show data relevant to specific object types.
If you want to display these fields, my recommendation would be to develop a custom extension page and controller that is based on the clone plus example above, but is designed to specifically work with a particular object and its children.
However I appreciate this is probably not the answer you are looking for. A simpler, if less pure approach could be to add the fields to all of the child object rows, ignoring what type they are. You can achieve this using the same code that I use in the above controller and page to show the name field, except this time your column code would be something like:
<apex:column headerValue="Factor" value="{!objectRow.factor}" width="90%"/>
and your controller method would be:
public String getFactor(){
try{
return '' + obj.get('Factor__c');
} catch (Exception e){
return '';
}
}
I hope that helps! Its not the prettiest as it would apply the column to all objects, but null values would be catered for.
As a workaround while waiting for your half promised 3 level cloning program (cough...) I wonder if its possible to reassign a child object to a different master object?
ReplyDeleteMeaning that you could do a single level clone on the master object and then use ClonePlus on the child objects to clone them and their children.
Lastly you reassign the child object(s) to the new master object.
If one did the first two steps manually, we would only need some nifty button code since Salesforece doesn't support reassignment of master objects in the interface.
Can one do that from button Javascript (show a list of Master objects to choose from and then implement the change as you press ok)?
Cheers /Mats
Hi Mats,
DeleteFunnily enough, that scenario you have just described is what inspired me to explore cloning methods in the first place.
I had to write a method that reallocated detail child sobjects to a new master. I created a method (with two sobjects as arguments) that cloned selected child sobjects (with all fields), set the relationship field as a new value, saved them before deleting their previoius equivalents.
So yes its possible, it should be a secondary button, "redirect child" sobjects. Let me dig out my old code and I'll post it up this weekend with a bit of luck :)
Hello again Mats,
DeleteI have found all the code used to redirect cases from one parent object to another. Before I post the example, to make it as useful as possible, I just want to make sure of how you would like the button to appear.
Do you want users to click on a child object page and redirect it to another parent, or would you prefer to redirect selected children from the perspective of the master object (like clone plus above)??
Mats,
DeleteCheck this out, Christopher get credit for getting me 90% of the way there!
http://nwilliamsscu.blogspot.com/2013/09/deeper-clone-clone-salesforce-objects.html
Sweet!
ReplyDeleteFor our purposes, I think that a button on the child object where you change master, would be optimal.
However, Ideally you should stay on the Master object and then select n number of child structures and their children. But I think that the real challenge here is visualization (imho) since the number of levels, children and childen's children quickly makes the whole thing very messy to visualize.
As it works now it's both crystal clear and fast, it only takes a second or two to copy a hundred child objects!
Cheers,
/Mats
Can I ask if what your trying to re-parent, as in is it custom objects or standard objects? If it is a field on a custom object, there is a new option in the Summer 12 release called "Allow Reparenting" on master detail fields.
DeleteSelecting this option means that you can change the parent of a master detail relationship at any time, without the need for writing code.
Yeah the visualization of multi levels is the hard part. You could debate how many levels do you go down before it starts to become too confusing to keep track of what your actually closing.
At the moment I am leaning towards collapsible sections for each child, and the option to either clone or change parent on-mass, sound about right?
Eh? Where can I find that option? Not on the object properties as far as I can see.
DeleteYour suggestion sounds absolutely right.
/Mats
The "Allow Reparenting" property can be found on the edit screen of the master detail relationship field on the child object. This option only appears in custom object relationships, are you using custom objects?
DeleteOh, I looked everywhere else. Thank you for the help.
DeleteYes, I use custom objects, lots of them. For my purposes, it's enough with this. But I'd still like to see your code for achieving the same thing :)
Once again, thanks for your help /Mats
This comment has been removed by the author.
ReplyDeleteBTW, are you going to Dreamforce this year Christopher?
ReplyDeleteHello Mats,
DeleteI am indeed, really excited going to be amazing! I am even giving a presentation on automating business processes in the community common, see here for more details: http://goo.gl/YYJOf
Are you going this year?
Yes I'm going -and guess what, I'm signed up for your presentation! :)
ReplyDeleteDF12 is going to be extremely interesting with tons of stuff to see and learn. I was early and could choose all of the sessions I was interested in. Now the sessions are filling up fast.
Excellent, good to hear, thanks for signing up! If you want to talk cloning or anything else after the talk then by all means come and find me.
DeleteI have filled my agenda in too. I missed out on the jQuery talk I wanted to go to, but other than that I'm really looking forward to what I have booked!
Hi Christopher,
DeleteI just subscribed to your blog. Lots of good info. I have a question regarding pushing info from fields in a custom object to pre populate a CONTRACT when created from this custom object. Can you help out with this or point me in the right direction?
Thanks
Hello Kenny,
DeleteThanks for the subscribe! Before I answer the question, can you clarify what you want to accomplish. How are you creating the contract object from the custom object currently? Also, what is the role of your custom object? is it simply to record contract details so they can then be pushed into a qualified contract object? If so, why not just enter the details directly into a contract object straight away.
Thanks,
Chris
Hi Christopher,
ReplyDeleteI am trying this Clone Plus: Clone Salesforce objects with children.
But on adding to Create the clone button by following the following steps:
1) Create a new Apex class (Navigate to Setup -> Develop -> Apex Classes and click new). Copy in the following code into the class body:
I get an error.... Error: Compile Error: expecting right curly bracket, found 'and' at line 81 column 7
Line 81 and retrieve the related objects.
for (Schema.ChildRelationship childRelationship :
childRelationships)
?
Matthew
Hello Matthew,
DeleteThank you for bringing this to my attention. This error occurred because that particular line was a part of a comment from the previous line, and had not been marked as a comment line (using the // characters).
Sorry about that, it is somtimes quite difficult to display code correctly in blogger as the text area is quite narrow!
I have updated the code sample above, so copy it out, and it should now work for you.
Hope that helps,
CAL
I am trying to use this to clone an opportunity with products and contact roles. However, I can get it to show either child relationships. Is the name different for the button than it is for a trigger?
ReplyDeleteThanks,
Amanda
Hello Amanda,
DeleteI had a go at creating a clone plus for this relationship, and I noticed two interesting things regarding the child object relationships.
Firstly, the two relationship objects are called "OpportunityContactRole" and "OpportunityLineItem", so your button code should look like this:
/apex/ClonePlus?id={!Opportunity.Id}&childobjecttypes=OpportunityContactRole,OpportunityLineItem
Secondly though, using this URL will cause an error, as neither object has a Name field.
I am going to find a way around this. Post back soon!
Christopher,
DeleteI had the same issue wanting to add the Opportunity Line Items in the clone process.
Due to the limitations you described with the generic clone process you created, I created a solution that while not overly elegant, does the trick.
Since a lot of times the Name field of an object in our instance is an Autonumber that is really meaningless to determine what the actual record is, I created a formula field on all of the records I want to clone named "Clone__c". I then create the formula to concatenate meaningful fields from the record to display in the VF page.
I then simply changed your child query to pull back the Clone__c field rather than the Name field, and then udpated the getName function to obj.get('Clone__c'). Then I ensure that I have a single field name I can use that as a developer or admin, would simply need to be created on each child object I want clone before I put the button on the parent object.
Again, while I don't think it's very elegant and requires additional work, it does solve the issue.
Ernie
Ahhhh, of course, nice work Ernie! The use of a formula field is nicely done here.
DeleteSince this post has come up I have been considering how to move this clone plus functionality forward. It has a good core concept, but the execution is a little cumbersome. I am in the process of creating a more component based solution that will allow for the definition of which fields to include, and multi-level cloning (child-parent-grandparent etc.).
In the mean time though, whilst I agree that the solution is not the most elegant, it still is a suitable answer in the interim.
Thanks again!
Christopher Alun Lewis
Just realized that I can't do that for Opportunity Contact Roles. There isn't a way to create a custom field. I could use a standard field from the contact roles but then it won't match fields from the Opportunity Products. Any ideas?
ReplyDeleteThanks,
Amanda
Would it be possible to have the code just use the ID field since they always have the API name of ID?
ReplyDeleteYes, I see that the contact roles object is not customisable unfortunately (vote the idea https://sites.secure.force.com/appexchange/ideaView?id=08730000000BrEEAA0 up to try and make this the case in a future release).
ReplyDeleteYou could simply substitute the the Id field for the name field, all objects will have an ID field, although it doesn't really make the individual items easy to identify in the interface.
Still working on finding a good solution though!
I can't get this to work with the ID instead of the name. There are so many sections that say name that it's hard to tell exactly which ones I should switch to say ID. Any chance you would be willing to repost using the ID fields?
ReplyDeleteThanks,
Amanda
Hi again Amanda
DeleteNo problemo, I have created a new post (http://christopheralunlewis.blogspot.co.uk/2012/12/clone-plus-quick-update.html) that contains controller and page code for just using the id field to clone objects.
Let me know if you have any probs.
Thank you so much!
ReplyDeleteHi Christopher
ReplyDeleteI've created a copy of your generic controller and need to modify it for my specific custom master/detail objects as I need to set field values in the cloned parent and child records. I've made the appropriate changes to the "put" statements for the parent fields easy enough, but I can't get my head around the cloneSelectedObjects method. Would it be possible to get an example of how to modify the "put" statement:
clone.put(relatedObject.relatedFieldName, headClone.Id);
for a specific custom object and field?
I think it starts with modifying the database query string:
String relatedChildSObjectsquery = ....
but I'm not sure.
Thanks, Kevin
Hi Kevin,
DeleteThe "put" method is a generic sObject method used to populate an individual field value in any Salesforce object (see http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_methods_system_sobject.htm) for more info).
In the case of clone plus, it is used to populate the relationship field of the cloned children to point to the newly cloned parent record.
If you want to populate field values for certain object types, you could add some looped put method calls inside if statements for specific data types. For example, if you wanted to set the account number of every cloned account object to be 0, you could use the following modified cloneSelectedObjects method:
public void cloneSelectedObjects()
{
List clonedObjects = new List{};
List selectedRelatedObjects;
for (relatedObjects relatedObject : objectChildren)
{
selectedRelatedObjects = new List{};
clonedObjects = new List{};
for (relatedObjectRow row : relatedObject.objectRows)
{
if (row.selected)
{
selectedRelatedObjects.add(row.obj);
}
}
if (!selectedRelatedObjects.isEmpty())
{
clonedObjects =cloneObjects(selectedRelatedObjects);
String cloneObjectType = clonedObjects.get(0).getSObjectType().getDescribe().getName();
for (sObject clone : clonedObjects)
{
clone.put(relatedObject.relatedFieldName, headClone.Id);
}
if (cloneObjectType = 'Account')
{
for (sObject clone : clonedObjects)
{
clone.put(AccountNumber, 0);
}
}
insert clonedObjects;
}
}
}
Apologies for the formatting, difficult in a comment :)
I hope that helps, let me know how you get on!
Cheers,
Chris
Thank you Chris - your response was extremely helpful and I now have it working perfectly.
DeleteFor the sake of future readers (and copy/paste specialists like myself), I made two minor edits as follows:
1.) Original: if (cloneObjectType = 'Account')
Changed To: if (cloneObjectType == 'Account')
2.) Original: clone.put(AccountNumber, 0);
Changed To: clone.put('AccountNumber', 0);
Thank you again.
Best, Kevin
No problem Kevin,
DeleteThanks for the corrections, much appreciated! I have made the changes in the source and pasted it below, so it will only take one copy/paste for all the specialists (including myself).
public void cloneSelectedObjects()
{
List clonedObjects = new List{};
List selectedRelatedObjects;
for (relatedObjects relatedObject : objectChildren)
{
selectedRelatedObjects = new List{};
clonedObjects = new List{};
for (relatedObjectRow row : relatedObject.objectRows)
{
if (row.selected)
{
selectedRelatedObjects.add(row.obj);
}
}
if (!selectedRelatedObjects.isEmpty())
{
clonedObjects =cloneObjects(selectedRelatedObjects);
String cloneObjectType = clonedObjects.get(0).getSObjectType().getDescribe().getName();
for (sObject clone : clonedObjects)
{
clone.put(relatedObject.relatedFieldName, headClone.Id);
}
if (cloneObjectType == 'Account')
{
for (sObject clone : clonedObjects)
{
clone.put('AccountNumber', 0);
}
}
insert clonedObjects;
}
}
}
cheers,
Chris.
Hi Kevin,
DeletePlease can you assist me how did you set field values on the parent record ? My parent record is Opportunity and I want few field values to be hardcoded.
Thanks
Rohit
Hi Kevin,
DeletePlease can you assist me how did you set field values on the parent record ? My parent record is Opportunity and I want few field values to be hardcoded.
Thanks
Rohit
Chris,
ReplyDeleteWe got it working perfect for cloning a custom object and its children, and was able to limit the fields in the children object that were desired in the cloning process.
Is it possible with this code to copy an object (parent/child) and insert it as a totally different object (parent/child)? For example in our case we are trying to copy a purchase order to a sales order (two totally different object with different children).
Great work! Thank you for your time and efforts!
Chris
Hi Chris, glad to help.
ReplyDeleteIt is possible to copy one type of object to another, but it is not as straighforward. Cloning an object (including all its fields) is simple because you can guarantee that the two objects( the original and the clone ) will have all the same fields. This allows you to be very generic when it comes to defining the cloning method.
When it comes to cloning one complete object to another, it becomes increasingly difficult because the two sets of fields (and child relationships) have to stay exactly same. If you have a specific object to object mapping you want to accomplish (such as purchase order to sales order), I would actually recommend creating a non-generic method specifically for this purpose. The outline of the method would be something like this:
public class PurchaseOrderToSalesOrderConverter{
public static SalesOrder ConvertPurchaseOrder(PurchaseOrder po)
{
PurchaseOrder poAllFields = populateAllFields(po);
SalesOrder salesOrder = mapToSalesOrder(poAllFields);
insert salesOrder;
return SalesOrder
}
/*
This method should be used to pull all field values for the
purchase order from the database using SOQL.
*/
static PurchaseOrder populateAllFields(PurchaseOrder po){}
/*
converts a purchase order to a sales order, using direct field mapping
*/
static SalesOrder mapToSalesOrder(PurchaseOrder po) {
SalesOrder so = new SalesOrder();
so.name = po.name // etc.......
return so;
}
}
This only clones the head object to another type. To clone the children, if they are of the same type on both objects, then you can use something very similar to the populateObjectChildren method in clone plus above. If they are different types (PurchaseOrderLineItem to SalesOrderLineItem) then another set of methods needs to be created to manage this specific translation.I hope this helps, please let me know if you require any clarification.
Cheers,
Chris
Chris,
DeleteI have found this very useful thread while searching for a solution to convert (copy) an opportunity won including "staffing" child objects (as an n:n relationship between opps and contacts) into a custom object "project" (including copied "staffing" child objects). Parent object to opportunity object (account) will be the same in project object.
Have you detailed out your method outline from 2013 (above) to a workable solution yet? This seems to be the solution I need. What would happen if the target object has a smaller field set than the original object?
Your way of explaining things is exactly on my level of knowledge/skills...
Thank you!
-Ralf
Hi Christopher,
ReplyDeleteReally helpful and nice post.
With the help of your post I have implemented clone functionality in my project for custom object.It is redirecting me to detail page of the record.I want to show edit page after clicking clone button. Is it possible? Your comments are highly appreciable.
Regards,
Gayatri
Hi Gayatri,
DeleteThis is definitely possible. Simply replace the line
"return new PageReference('/' + headClone.Id);"
with this:
"return new PageReference('/' + headClone.Id + '/e');"
This should return you to the edit page instead of the view.
Happy Cloning,
Chris.
Thanks a lot Chris!! I have made change and now I can see edit page.
ReplyDeleteI want to clone related list after clicking on clone button. I am new to Sales force so require your help. Can you please suggest me something to clone related list?
Thanks,
Gayatri
Hi Gayatri, apologies for not getting back to you sooner. Hmm I am a bit confused as to what you mean by cloning a related list. If you mean clone all the related objects that appear in related lists on an object view page, then the post above will help you to do that.
DeleteHowever, I feel like I am missing something, so if you could reply with some further explanation (including an example if possible), I may be better positioned to help you out.
Thanks,
Chris.
Chris,
ReplyDeleteYour code works great. Thank you. What I really need, though, is to copy the original children and grandchildren and not create new records for them. Just need the Parent to be new. I am not an APEX developer and wondered if you had (or knew of) some code out there for this. Thank you!
Hi Edirajus,
DeleteThanks for your query, I may be able to help. But first, do you want some code to simply re-parent a large amount of child records to a different object, or create copies of child and grandchild objects and then assign them all to an existing different object?
Edirajus,
DeleteCheck this out, modified his work to work with more than just a parent and a set of children (grand-children, great-grand-children, great-great-... you get the idea!):
http://nwilliamsscu.blogspot.com/2013/09/deeper-clone-clone-salesforce-objects.html
Hello Christopher,
ReplyDeleteI am trying to help out an NGO with their Salesforce Non Profit edition, but I do not have an IT background and I am now reaching my limit with the following request : they follow classes (parent custom object) given by volunteers and classes attendance (child custom object) to record the students. When a trimester is over, they would like to clone each class without having to reenter the list of all students.
Would that be a good application of your Clone Plus button or would you suggest another solution ?
I have tried to follow your instructions but I get the following message :
unexpected token: 'null'
Error is in expression '{!initialiseObjectsForCloning}' in component in page cloneplus
The URL sent is : https://cs1.salesforce.com/apex/ClonePlus?id=C2013-002&childobjecttypes=Class2_Attendance__c
Maybe I didn't create the parent and child objects correctly ? The Class2 Attendance appears as a Related List in the Class2 tab
Thanks in advance for any help you could provide.
Olivier
Hi Oliver,
DeleteYes, this sounds like a text book case for using clone plus. I think the problem with your URL is that the id is should be a long 15 or 18 digit code, rather than "C2013-002" That looks more like the name field, rather than the id.
I hope that helps, let me know if you need any further info/assistance. Glad to help in any way I can for a non-profit.
Oh and 1 more thing, when you say they "follow" classes, is that via chatter?
Thanks,
CAL
Hi Christopher,
ReplyDeleteThanks a lot for responding so quickly and for offering your help as I imagine you are a very busy man.
Of course it works now with the Id instead of the Name !
I just have a suggestion for possible further improvement : it would be great if the Clone Plus page could show another field in case the name is not enough to select
which records should be duplicated (in my case, I would ideally need the student name which is a lookup from the contacts whereas the actual class attendance name is just an Auto-number that is not useful. Or can I use the student name as the name field ?)
So I don't know if it's possible to modify the Apex class and allow to pass the field name in the URL as such :/apex/ClonePlus?Id=!Custom_Parent__c.Id}&childobjecttypes=Custom_Child__c(Field_Name1),Another_Child__c(Field_Name2)
Anyway, I have one last problem before enabling the Clone Plus : I need to switch the Class name to be an Auto-number, and everything else works fine except for a VF page that is showing a student attendance report for which I get the following error message :
Field is not writeable: Class__c.Name
Error is in expression '{!StudentClass}' in component in page studentattendancesheet
Error occurred loading controller 'StudentAttendance' for page studentattendancesheet
If I put the Class Name back to Text then it's ok. Do you have any idea why ? The Class Name doesn't even appear in the report page.
Since this has nothing to do with your Clone Plus development, you can answer me at olivier.cardinne@gmail.com if you'd prefer.
Finally, to answer your question, no they don't use chatter. They currently use Salesforce to manage Organizations, Contacts, Donations, Classes, Job leads and Interview results for the students they are helping. In my opinion, they don't really use the true CRM functionalities, but i don't think that it's their priority right now.
Ultimately, they would like to integrate PayPal with Salesforce to manage recurring donations. Does it seem possible to you ?
Best regards,
Olivier
I got the error like this from the code....
ReplyDeleteSystem.NullPointerException: Attempt to de-reference a null object
Error is in expression '{!initialiseObjectsForCloning}' in component in page clone
Hi Subra mani,
DeleteAs an educated guess I would say you are getting this error because a value from your page URL is not being passed to your controller properly. You might want to check your custom button definition again to ensure that the object Id and child object types you want to clone are being populated properly.
If you still get the error, can you provide any more information regarding your error? Does the error description contain a line number explaining where the code went wrong?
Regards,
CAL
Greetings Christopher - thanks so much for this excellent solution to a common pain point!
ReplyDeleteI have a wrinkle that I have been trying to code around using your class but I am a neophyte coder, and beyond my depth. I am looking to implement a clone button on a Custom Object (Service_Plan__c) where I clone it, along with it's related Custom Object child records (Assigned_Activity__c). The challenge I have is that I would like to block certain fields from being Cloned (specifically Service_Plan__c.Account__C and Service_Plan__c.Opportunity__c) so the new cloned record can be associated with a different Opportunity__c.
I see from the comments above that I can create a way to see what the associated Object is and define some discrete custom code for that Object. I've been trying for a little more than a day but have not been successful. If you could point me towards a solution I would be extremely grateful!
Regards,
PW
This comment has been removed by the author.
DeleteHi Paul,
DeleteYes by all means if you want to make it specific to a particular object this is possible.
You need to follow these steps:
1) Add a new variable to the controller, and populate it on initialisation by adding the following code at the top of the class:
public Service_Plan__c inputServicePlan {get; set}
public ClonePlusController()
{
inputServicePlan = new Service_Plan__c();
}
2) Add two new input fields to the top of the Visualforce Page to capture the account and opportunity you want to assign to your new cloned object, like so:
<apex:inputField value="{!inputServicePlan.Account__c}"/>
<apex:inputField value="{!inputServicePlan.Opportunity__c}"/>
3)Replace the do clone method with the following code:
public PageReference doClone()
{
headClone = cloneObjects(
new List{headSObject}).get(0);
headClone.put('Account__c',
inputServicePlan.Account__c);
headClone.put('Opportunity__c',
inputServicePlan.Opportunity__c);
insert headClone
cloneSelectedObjects();
return new PageReference('/' + headClone.Id);
}
Let me know how you get on, Any further questions / clarifications needed send me a reply! :D
Regards,
CAL
Hi,
ReplyDeleteThanks for this code, it is exactly what I need!
However I am receiving the following error:
unexpected token: 'null'
Error is in expression '{!initialiseObjectsForCloning}' in component in page cloneplus
An unexpected error has occurred. Your development organization has been notified.
I am currently testing this code in a developer org. I have copied your class exactly and the visual force page exactly. My Custom button definition is:
/apex/ClonePlus?id={Local_Authority__c}&childobjecttypes=Contact
Local Authority is a custom object and it is the object which has the button on. Contact is a related list on the Local Authority Page.
Can you possibly help?
Thanks,
Leesa
Hello Leesa,
DeleteLooking at your custom button definition, it seems you are missing the Id field from the end of your custom object.
Try this instead:
/apex/ClonePlus?id={Local_Authority__c.Id}&childobjecttypes=Contact
Hope that helps, let me know if it does not!
Thanks,
CAL
Christopher, Excellent tool as tested in my Sandbox..
ReplyDeleteTwo questions:
1) When cloning my "Account", it clones the "Contacts" fine. - But how to make it also clone "Contact Roles"?
2) When I try and deploy to Production using change sets, I get "Deploy Error" - "Average test coverage across all Apex Classes and Triggers is 70%, at least 75% test coverage is required."
- I turned off all Validation Rules, and only get the error above.
Note: In the Sandbox, I did change the version from v28 to v27 if that makes any difference? (Our Org is v27)
Christopher, disregard question two.. I got it deployed!
DeleteLast question:
Would be possible to have the New "Cloned" record..
- open up in edit mode
- allow Rep to make minor changes/updates
- then allow them to Save or Cancel?
Hi Jerry, apologies for the late reply,
DeleteIn terms of test coverage, you can see the following recent post that provides a sample test class
http://christopheralunlewis.blogspot.co.uk/2013/09/clone-plus-test-method-example.html
You can also make the cloned record open in an edit mode by simply replacing the line in the code:
return new PageReference('/' + headClone.Id);
With:
return new PageReference('/' + headClone.Id + '/e');
Regards,
CAL
Hello Christopher,
ReplyDeleteThanks for providing such a wonderful solution to the problem of cloning records.
I found that, even with very limited programming skills, I was able to adapt the code to clone the Case object and its related records.
For the benefit of others:
My application is for cloning Cases, and I found that the reference to the field "Name" in the Select string had to be replaced with another field - e.g. Id or Case Number. However, for the core code to work with any related object, only Id or, perhaps, CreatedDate can be used if the objects don't have a Name field - as is the case for Email Message, Case Comment etc.
Rick
Hello Christopher,
ReplyDeleteThank you for providing a great solution to cloning parent child (Master-Detail) related objects. I understand that this cloning only works with Parent -> Child
I do have an issue....
I have three custom objects :
Master Detail > Survey -> Survey Questions
Master Detail > Survey Questions -> Survey Answers Choices
Is there a way to modify the controller to handle the cloning of a Survey with all its questions and its Answer Choices scenario?
Being a new-be in APEX I am feeling quite challenged on this.
Can you please point me to the right direction on how to handle this.
Thank you,
Navaid
Hi Navaid,
DeleteCheck this out. I tweaked Christopher's application to work with more than two levels:
http://nwilliamsscu.blogspot.com/2013/09/deeper-clone-clone-salesforce-objects.html
Excellent code. Pasted it into a dev org and it works great. I see that Jerry Alexander posted a question about Test Coverage. That is my next step. To create a test for the class I am guessing that I will need to create a parent and some children records for my custom object and then test against them. Correct?
ReplyDeleteNot sure how to create records when have parent child relationships. Here is a snippet.
public void createMPMs( Integer mpmToCreate, Set fieldsToCheck ){
List auxList = new List();
for( Integer i = 1; i <= mpmToCreate; i++ ){
MPM__c accAux = new MPM__c();
accAux.Name = this.createRandomWord();
// accAux.RecordTypeID = 'a03E0000005KfDz' ;
accAux.MPM_Region__c = 'EMEA';
accAux.MPM_Business_Unit_Division__c = 'Enterprise';
accAux.MPM_Proj_Description__c = 'This is a test record';
accAux.MPM_Plan_Status__c = 'In Plan';
accAux.MPM_Est_Complete_Date__c = date.today();
accAux.Project_Status__c = 'Research';
accAux.MPM_Budget_Year__c = '2013';
accAux.MPM_Budget__c = 'Events';
// accAux.Parent__C = null;
auxList.add( accAux );
}
if ( this.checkObjectCrud('MPM__c', fieldsToCheck, 'create') ){
try{
insert auxList;
}
catch( Exception ex ){
System.assert( false ,'Pre deploy test failed, This may be because of custom validation rules in your Org. You can check ignore apex errors or temporarily deactivate your validation rules for MPM recs and try again.');
}
this.testMPMList = new List();
this.testMPMList.addAll( auxList );
}
else{
System.Assert(false , 'You need right over MPM Object');
}
}
Note: custom object (is child of another custom object) and has 2 record types.
Do you have a sample of test for your vanilla code that I can use as a starting point?
Again, good job.
Thanks Livio,
DeleteI have written a post recently that includes a sample test class for the code plus controller, that should serve as a good starting point.
You can find it at: http://christopheralunlewis.blogspot.co.uk/2013/09/clone-plus-test-method-example.html
Cheers,
CAL
You, Sir, are amazing!
ReplyDeleteThank you!
Hello Christoper,
ReplyDeleteThis is truly an amazing piece and I look forward to seeing it making its debut in the AppExchange.
I was wonder if there is any way of preventing a certain field from the parent object from being sent across to the cloned equivalent?
Long story but we have a foreign key from another system recorded in the opportunity object that we'd prefer is populated as a NULL on the cloned record
Hi Daniel,
DeleteI took care of this with a Custom Setting (FieldExclusion__c) while tweaking the overall framework to work with a long list of children (not just one level):
http://nwilliamsscu.blogspot.com/2013/09/deeper-clone-clone-salesforce-objects.html
Hey Christopher,
ReplyDeleteMy org had a requirement to clone all levels of a structure, so I took your idea and ran with it. I also added in some of my own magic, such as auto-posting a chatter message to the original sObject stating where it came from! Check it out:
Deeper Clone: Clone Salesforce objects with all levels of children
Take any record and clone not only it, but all its children, grand-children, great-grand-children, etc...
http://nwilliamsscu.blogspot.com/2013/09/deeper-clone-clone-salesforce-objects.html
Happy coding!
Thanks Nathan,
DeleteI have checked out your solution and was impressed! Works well in further level situations.
I would recommend anybody interested in three or higher level cloning should have a look at the solution.
Cheers,
CAL
Hi Nice post!! very useful.. In fact we have the same requirement to get cloned child objects records.
ReplyDeleteI tried it n my Dev account but I am getting error.. I am sure It would be something silly which I am not able to figure out.. please help!
Error:
System.UnexpectedException: No such column 'geolocation_of_home__c' on entity 'Account'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names.
Error is in expression '{!doClone}' in component in page cloneplus
Class.ClonePlusController.cloneObjects: line 236, column 1
Class.ClonePlusController.doClone: line 129, column 1
Hi Sachin,
DeleteThanks for your question. The reason why this error message is appearing is because you are attempting to clone an object that includes a geolocation field. If you attempt to make a SOQL query that includes a geolocation field, then you will see the exception above. The way to accommodate for this while cloning is to exclude the field (see the updated code in the post above).
Note that this does not stop the location data from being copied from the old record to the new clone, as the two sub location fields, "geolocation_of_home__longitude__s" and "geolocation_of_home__lattidute__s" are still included in the query.
For more information on geolocation fields see : http://www.salesforce.com/us/developer/docs/dbcom_soql_sosl/Content/sforce_api_calls_soql_geolocate.htm
Hope that helps,
CAL
HI,
ReplyDeleteI could clone a record along with a related list records, but I could noe clone an object.
Regards,
Adelchi
Hello Adelchi,
DeleteIf you are just trying to clone an object by itself, and not any of the related objects, you can simply add the "Clone" button to the page layout of the object you are trying to copy, clone plus is not required. However, you can obviously deselect all of the children on the object selection screen in clone plus to only copy the parent object, and not any of the children.
Regards,
CAL.
Your code worked for me great, but I would like to know how I can duplicate attachments found in the child object.
ReplyDeleteThank you.
Hi there borreg0sistem,
DeleteYou can copy the attachments belonging to an object in the same way as any other child object using clone plus, just include the "Attachment" string object name in the clone plus button for the object. So, from our example, if you wanted to clone all the attachments belonging to a contact, your button would look like this:
/apex/ClonePlus?id={!Contact.Id}&childobjecttypes=Attachment
Hope that helps, let me know if you have any problems,
cheers,
CAL.
Hi there Christopher
DeleteThank you very much for your reply.
We already have proven and served me otherwise.
What I try to do is duplicate the attachment that is in my child object.
The structure is as follows.
cotiz__c (here is the button). Father Object
line__c (this is the attachment). Son Object.
Attachment (grandson Object).
I appreciate your response is very helpful.
Hello again, borreg0sistem,
DeleteIn terms of cloning multiple levels, clone plus does not currently accommodate this, but as you can see from earlier comments in this post, Nathan Williams has created a solution that can allow for three levels of cloning.
Find it at http://nwilliamsscu.blogspot.com/2013/09/deeper-clone-clone-salesforce-objects.html
Hope that helps,
CAL
Hello.
DeleteThank you very much for taking my question again.
I had already tried that solution but can not make it work.
I thought it would be easier modifying the solution you pose.
If you know any other way I'd appreciate it very much.
Thank you.
Hi borreg0sistem,
DeleteHmm not off the top of my head, have you tried leaving a question or comment on Nathan's solution, he seems pretty good at getting back to people.
In the meantime, if I do come up with more of a multi-tiered adaptation, I'll let you know by posting a reply here.
Wish you luck,
CAL
Hello,
DeleteI have adapted your code to work with opportunities and quotes. I have added some fields to the select statement and visual force page to make it easier for the user to select the records they want. The changes work well in the sandbox. My issue is in trying to get the test to work at 75% so that I can deploy it to production. Are there any known constraints between these two objects that I am overlooking?
Hi Joesizer,
DeleteI don't know of any direct constraints in testing you would encounter specifically from cloning opportunities and quotes of hand. You mention that you are trying to get the test coverage level up to 75%, have you actually written any test methods to cover the controller code of your implementation of clone plus?
If you have not done any testing so far, check out this example test class for the clone plus controller:
http://christopheralunlewis.blogspot.co.uk/2013/09/clone-plus-test-method-example.html
Hope that helps,
CAL
Hi Christopher,
ReplyDeletethank you for sharing this useful tool with the community, I was wondering if it is possible to clone only certain fields from the Parent and Child. I my case I have the Parent "Purchase Order" which has 80 fields and the Child "Purchase Order Details" which has 120 fields, but I wan to clone half of them.
Thank you in advance!
Hi Carlos,
DeleteGlad you found it useful. It is entirely possible to only clone certain fields from the parent or child. My implementation of clone plus above has been set up to be as generic as possible, and assumes that you want to clone all fields.
If you want to only clone specific fields for certain objects, then the easiest solution is to create a relationship specific version / branch of the clone plus methods. Instead of grabbing all of the fields for the objects using the describe methods, and making a SOQL query string from the result, you can define your own specific SOQL query including all the fields you want to retrieve. So for example in your constructor if you recognize the parent object type is a Purchase order, you could have a clonePurchaseOrderDetails method that you call to clone your purchase order detail objects, and keep clone child objects to clone all other child objects.
However, if you want to maintain the solution flexibility and adaptability, I would recommend you find a way to store the fields you want to clone in each object and or relationship in maintainable configurable manner. This could be directly in mapped variables in the class itself, but in order to control configurations for individual objects / parent & child relationships outside code, two good places to start would be custom settings and field sets for the objects.
I hope that helps, let me know how you get on!
CAL
Hi Christopher -
ReplyDeleteGreat article, code and followup!
I have one question. I am trying to clone a custom object, and with it, the notes and attachments. I successfully cloned the attachments, but am unable to use 'Note' or 'Notes' appended to the custom button URL. Please advise.
Thank you!
David
Hi David,
DeleteWhat does your URL for the custom button look like? I found that I could clone both notes and attachments by separating the two child object types with a comma, like so:
/apex/ClonePlus?id={!*Parent Object Type*.Id}&childobjecttypes=Attachment,Note
I hope that helps,
CAL
Hi Christopher,
ReplyDeleteFirst of all, thank you!.Everything worked great, except when I click on the "Clone Plus" button there are no child objects to select. The page has the "clone" button only.
The main reason I am needing this feature is for contact roles. We clone new business opportunities into renewal opportunities and we need the contact roles to come over as well.
This is the code I used for the button:
/apex/ClonePlus?id={!Opportunity.Id}&childobjecttypes=contact roles
Thanks again.
Hi Kelsey,
DeleteYour button code is slightly incorrect, try this on for size:
/apex/ClonePlus?id={!Opportunity.Id}&childobjecttypes=OpportunityContactRole
Regards,
CAL
This comment has been removed by the author.
ReplyDeleteHi Christopher,
ReplyDeleteVery nice article. I wanted to know how can we implement the cloning in a trigger of a custom object.
This comment has been removed by the author.
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteHi Christopher,
ReplyDeleteAwesome code....works at first try itself with no errors...great post...thanks.
But i want to be redirected on edit page of parent record. As per your suggestion from previous posts i replaced
return new PageReference('/' + headClone.Id);
With:
return new PageReference('/' + headClone.Id + '/e');
But now after clicking on save it should go to detail page, rather its going to home page showing my dashboard. Please guide. Thanks in advance.
Hi Ridhi,
DeleteEven i am getting the same problem. Did u got any solution.
If some one knows the solution for my above query ,please reply. It will be very helpful. Thanks
ReplyDeleteIs there any way tot avoid the screen where they choose what to clone? Instead just auto clone, opportunity, opportunity product lines, contract, contract attachments, contact roles
ReplyDeleteHi CAL,
ReplyDeleteFirst of all i would like thank you for this post . Now my issue is that only parent custom object is being cloned and i don't see the standard object being clone .
custom object "GroupLineItems' and standard object is QuoteLineItem..please advise.
thank you
I am having a problem with cloning a Product that has custom look-up fields. Apparently, the custom look-up fields do not copy, even when I use the standard Clone button. I am running as system administrator with modify all data permissions on all objects.
ReplyDeleteStrange.
Chris, this is great! I am working with a team who has extended it a bit and updated the VisualForce page. Do you have the code hosted on Github where we can fork it and possibly submit a pull request? Thanks!
ReplyDeleteNot at the moment Mark, but thats a great idea! It's about time I got the blog content on GitHub, watch this space!
DeleteThis comment has been removed by the author.
ReplyDeleteHi Chris,
ReplyDeleteAmazing work !! you have found the pain killer. Really appreciate your effort.
I am wondering if you could assist me to use ClonePlus as I am a new developer and have some tweaks in the requirement of cloning.
I want to clone 'opportunity' record and two of the related lists "Set__C" and "Invoicing_Lite_Item__C". The tweak is :
1) when cloning the opportunity I want its 'record type' and 'StageName' to be changed to a hardcoded value.
2) Want to clone only few fields of Opportunity as there are over 150 fields total.
3) The related list object "Set__c" contains a field "Product__c". I want only records to be cloned where "Product__c" = "xyz" and not other.
4) Finally when the opp gets cloned the record on the related list "Set__c" where Product is "xyz" should be removed from the original record.
Please can you assist me here what manipulation should i do the the controller and VF page?
Thanks,
Rishi
Chris ?
ReplyDelete"Nice and good article.. it is very useful for me to learn and understand easily.. thanks for sharing your valuable information and time.. please keep updating.php jobs in hyderabad.
ReplyDelete"
Christopher...nice post...It seems Note object does not have a Name field...so it doesn't display and doesn't copy..any thoughts on that?
ReplyDeleteThis works great thanks for the code, we use if for Service Contracts and Entitlements. However sometimes we need to clone 100's of items, it would be great if we could have a 'Select All' checkbox on the Visual Source page that will auto select all the children.
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteNo research activity is reported on the Coach Outlet Store integration of Coach Outlet SOFC, or any Ray Ban Outlet fuel cell, with liquid desiccant air conditioning in a tri generation system configuration. The novel tri generation system is suited to applications that require simultaneous electrical power, heating and dehumidification/cooling. There are several New Jordan Shoes 2020 specific benefits to the integration of SOFC and liquid desiccant air conditioning technology, including; very high operational electrical efficiencies even at low system capacities and the Ray Ban Glasses ability to utilise low grade thermal energy in a (useful) cooling process.
ReplyDeleteBest Sunset Cruise in Key Largo!Reviewed 1 July 2019 We decided to take a sunset cruise with my family on Yeezy Boost 350 a Sunday afternoon. We drove out to the Keys from Miami and stumbled upon the Carolina moon catamaran sunset cruise tour. Yeezy Discount Coach Handbags Clearance We had no idea where we would be catching the boat, which happened to be located at a beautiful and charming resort off of mile marker 95.
Best tution classes in Gurgaon
ReplyDeleteazure solution architect certification
ReplyDeleteaws solution architect training
azure data engineer certification
openshift certification
oracle cloud integration training
An awesome blog for the freshers. Thanks for posting this information.
ReplyDeleteSalesforce CPQ Training
Learn Salesforce CPQ
Great Post. Very informative. Keep Sharing!!
ReplyDeleteApply Now for Salesforce Training Classes in Noida
For more details about the course fee, duration, classes, certification, and placement call our expert at 70-70-90-50-90