2

This is my first subject since I'm very new to Salesforce. My problem is that I'm trying to create simple Lightning component which could clone record with custom logic. This component suppose to be available through Lightning Quick Action on the record page. I'm trying to use force:recordData to retrieve original record from database, then change some of the fields values according to my needs, and then fill the form inside my component, so the user either can just clone it as it is or change some details and save the record as new one. But while calling my Apex controller from the component, I'm getting very strange error with this message:

Unfortunately, there was a problem. Please try again. If the problem continues, get in touch with your administrator with the error ID shown here and any other related details. Action failed: forceChatter:lightningComponent$controller$doInit [Error while creating content for lightning component quick action] quickActionHandlerHelper.js failed to create component - forceChatter:lightningComponent

Please take a look at my code, maybe I do not see something what is really simple...

PS. I'm aware of not completed code f.e. lack of save and cancel methods and maybe something more. But if the component cannot even start itself, I believe we can skip the rest of the code for now :)

Lightning Component:

    <aura:component description="TargetRealizationReplicator" implements="force:hasRecordId,force:lightningQuickAction " access="global" controller="TargetRealizationCloneController" >
        <aura:attribute name="Record" type="Target_Realization__c" />
        <aura:attribute name="SimpleRecord" type="Target_Realization__c" />
        <aura:attribute name="recordError" type="String"/>
        <force:recordData aura:id="RecordReplicator"
                          layoutType="FULL"
                          targetFields="{!v.SimpleRecord}"
                          targetRecord="{!v.Record}"
                          targetError="{!v.recordError}"
                          fields="Start_Date__c, End_Date__c, Target__c, Value__c, Value_Distribution_Type__c"
                          recordUpdated="{!c.recordUpdated}"
                          mode="EDIT"/>
        <aura:handler name="init" value="{!this}" action="{!c.doInit}" />



        <div aura:id="editDialog" role="dialog" tabindex="-1" aria-labelledby="header43" class="slds-modal slds-fade-in-open">
            <div class="slds-modal__container">
                <div class="slds-modal__header">
                    <h2 class="slds-text-heading--medium">Clone Target Realization Record</h2>
                </div>
                <div class="slds-modal__content slds-p-around--medium slds-grid slds-wrap ">

                    <ui:inputDate aura:id="StartDate" label="Start Date" value="{!v.SimpleRecord.Start_Date__c}" displayDatePicker="true"/>

                    <ui:inputDate aura:id="EndDate" label="End Date" value="{!v.SimpleRecord.End_Date__c}" displayDatePicker="true"/>


                    <lightning:select aura:id="Value_Distribution_Type__c" name="Value_Distribution_Type__c" value="{!v.SimpleRecord.Value_Distribution_Type__c}" required="true" label="Target Type" class="slds-size--1-of-2 slds-p-horizontal_x-small">
                        <option>Select Distribution Type</option>
                        <option value="Individual" >Individual</option>
                        <option value="Group">Group</option>
                    </lightning:select>

                </div>
                <div class="slds-modal__footer">
                    <lightning:button variant="neutral" label="Cancel" onclick="{!c.cancelDialog}"/>
                    <lightning:button variant="brand" label="Submit" onclick="{!c.saveRecord}"/>
                </div>
            </div>
        </div>        
</aura:component>

Component Controller:

({
    doInit : function(component, event, helper){

   var action = component.get("c.getCurrentRecordValues");
   action.setParam({"recordId": component.get("v.recordId")});
   action.setCallback(this, function(response) {
       var result = response.getReturnValue();
       component.set("v.SimpleRecord", result);
       console.log(result);
   })
   $A.enqueueAction(action);
},
})

Apex Controller:

public with sharing class TargetRealizationCloneController {

    @AuraEnabled
    public static Target_Realization__c getCurrentRecordValues(String recordId){
        Target_Realization__c record = [SELECT Start_Date__c, End_Date__c, Value_Distribution_Type__c, Value__c, Target__c FROM Target_Realization__c WHERE Id = :recordId LIMIT 1];
        return record;
    }



EDIT:

After adding this to my Init method, I have finally got the object loaded properly with all the fields I need:

 doInit : function(component, event, helper){
        // Prepare a new record from template
        component.find("RecordReplicatorLoad").getNewRecord(
          "Target_Realization__c", // sObject type (entityAPIName)
          null,      // recordTypeId
          false,     // skip cache?
          $A.getCallback(function() {
              var rec = component.get("v.RecordLoad");
              var error = component.get("v.recordError");
              if(error || (rec === null)) {
                  console.log("Error initializing record template: " + error);
              }
              else {
                  console.log("Record template initialized: " + rec.sobjectType);
              }
          })
        );
    },
  • I wonder if you have a QueryException from your LIMIT 1 hiding behind the error from the Chatter component. Can you validate that that query returns an object for the running user? – David Reed May 21 '18 at 15:18
  • Sorry, didn't mentioned - yes, the query works perfectly. Also recordData is returning record ID but not assigning entire object to my SimpleRecord attribute. – Bartosz Beli PL May 21 '18 at 15:20
  • 1
    You don't need init event and Apex class... force:recordData automatically queries and puts that data in simpleRecord. – Pranay Jaiswal May 21 '18 at 15:27
  • Theoretically you are right. It did work in my previous components I've made, but in this very example - it doesn't work which I really don't understand why. I even tried to console.log() SimpleRecord and Record attributes after the doInit is done, but both of them are null. – Bartosz Beli PL May 21 '18 at 15:36
  • 1
    try commenting force:recordData – Pranay Jaiswal May 21 '18 at 15:45
0

If you are implementing this functionality for a client, please refer to my below comments.

When we are developing in Salesforce, there are couple of things we need to keep in mind. Salesforce platform provides point and click configurations which can be customised for our needs. Usually we try to avoid any coding if there is a possibility for a config only solution. There are few reasons behind this. 1) We don't need to maintain any config functionality. 2) Any updates/improvements provided by Salesforce get applied automatically. By looking at your requirement, this functionality can be easily implemented by adding the "Clone" action to the layout.

If you are trying to learn how to implement a lightning component with record data refer to below comments.

Whole point of implementing lightning record data is to avoid the server-side call back. So you do not need to implement your apex controller at all. Use force:recordData to load and create your new record. Also note that you don't have to specify both layoutType and fields. If you want to define layoutType make sure any fields you want to display is added to the layout as well.

So refactored component code should look like this.

<aura:attribute name="RecordSave" type="Target_Realization__c" />
<aura:attribute name="SimpleRecordSave" type="Target_Realization__c" />

<aura:attribute name="recordError" type="String"/>

<force:recordData aura:id="RecordReplicatorLoad"
                    recordId="{!v.recordId}"
                    targetFields="{!v.SimpleRecordLoad}"
                    targetRecord="{!v.RecordLoad}"
                    targetError="{!v.recordError}"
                    fields="Start_Date__c, End_Date__c, Target__c, Value__c, Value_Distribution_Type__c"
                    mode="EDIT"/>

<force:recordData aura:id="RecordReplicatorSave"
                targetFields="{!v.SimpleRecordSave}"
                targetRecord="{!v.RecordSave}"
                targetError="{!v.recordError}"
                fields="Start_Date__c, End_Date__c, Target__c, Value__c, Value_Distribution_Type__c"
                mode="EDIT"/>

<div aura:id="editDialog" role="dialog" tabindex="-1" aria-labelledby="header43" class="slds-modal slds-fade-in-open">
    <div class="slds-modal__container">
        <div class="slds-modal__header">
            <h2 class="slds-text-heading_medium">Clone Target Realization Record</h2>
        </div>
        <div class="slds-modal__content slds-p-around--medium slds-grid slds-wrap ">

            <ui:inputDate aura:id="StartDate" label="Start Date" value="{!v.SimpleRecordLoad.Start_Date__c}" displayDatePicker="true"/>

            <ui:inputDate aura:id="EndDate" label="End Date" value="{!v.SimpleRecordLoad.End_Date__c}" displayDatePicker="true"/>


            <lightning:select aura:id="Value_Distribution_Type__c" name="Value_Distribution_Type__c" value="{!v.SimpleRecordLoad.Value_Distribution_Type__c}" required="true" label="Target Type" class="slds-size--1-of-2 slds-p-horizontal_x-small">
                <option>Select Distribution Type</option>
                <option value="Individual" >Individual</option>
                <option value="Group">Group</option>
            </lightning:select>

        </div>
        <div class="slds-modal__footer">
            <lightning:button variant="neutral" label="Cancel" onclick="{!c.cancelDialog}"/>
            <lightning:button variant="brand" label="Submit" onclick="{!c.saveAndCreateRecord}"/>
        </div>
    </div>
</div>        

Then the controller:

  ({

    saveAndCreateRecord : function(component, event, helper) {
        // Prepare a new record from template
        component.find("RecordReplicatorSave").getNewRecord(
            "Target_Realization__c", // sObject type (objectApiName)
            null,      // recordTypeId
            false,     // skip cache?
            $A.getCallback(function() {
                var rec = component.get("v.RecordSave");
                var error = component.get("v. recordError");
                if(error || (rec === null)) {
                    console.log("Error initializing record template: " + error);
                    return;
                }
                console.log("Record template initialized: " + rec);
                //map all clone fields
                component.set("v.SimpleRecordSave.Start_Date__c", component.get("v.SimpleRecordLoad.Start_Date__c"));
                component.set("v.SimpleRecordSave.End_Date__c", component.get("v.SimpleRecordLoad.End_Date__c"));
                component.set("v.SimpleRecordSave.Value_Distribution_Type__c", component.get("v.SimpleRecordLoad.Value_Distribution_Type__c"));

                component.find("RecordReplicatorSave").saveRecord(function(saveResult) {
                    if (saveResult.state === "SUCCESS" || saveResult.state === "DRAFT") {
                        // record is saved successfully
                        var resultsToast = $A.get("e.force:showToast");
                        resultsToast.setParams({
                            "title": "Saved",
                            "message": "The record was saved."
                        });
                        resultsToast.fire();
                    } else {
                        console.log(JSON.stringify(saveResult.error));
                    }
                });
            })
        );
     },

})

Note that I haven't complied or tested this code. So please be aware that there may be syntax issues. But by following this approach you should be able to get this working.

  • I couldn't agree more with your comments. But there is must be always some "but" :) 1) I decided to make custom Lightning component because standard clone action is available only in Visualforce and doesn't let me implement any custom logic (in my case it is prepopulating Date fields so cloned record would have start date same as original end date, and cloned end date would be older for same amount of time as it was in original record). 2) The code you provided still doesn’t load any data from original record (except record ID). – Bartosz Beli PL May 22 '18 at 7:04
  • Standard clone button is available for you to use in custom objects. But as you say if you want to implement custom functionality, it make sense. I just realised in the code I've provided, I haven't set the record Id on the record loading. I've updated the code and have a look again – Ranga May 22 '18 at 12:33
  • Did you leave two force:recordData tags on purpose? – Bartosz Beli PL May 22 '18 at 13:01
  • Yes I think you would need two. One will load the existing record and the other can be used to save the newly created record. If you use the same record data, I think you will loose the loaded values at point of creating the new record. – Ranga May 22 '18 at 13:13
  • Now I see... With your code and using recordUpdated event I got the custom function working properly. Last thing I cannot solve is why in your saveAndCreateRecord code doesn't go inside component.find("RecordReplicatorSave").saveRecord() ? Like this block of code doesn't exist. – Bartosz Beli PL May 22 '18 at 14:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.