12

We are building a lightning component to be used as a ‘New’ Action Override.

This question is NOT related to our inability to access the Parent Id of the new record. An example of that issue is “I click on a ‘New’ Opportunity on a related List on the Account Detail page, and there is NO official way of accessing the Account ID from within the Lightning component”. This deficiency has been discussed ad nauseum in other threads, and there is STILL no solution for it.

This question is NOT related to the fact that the behavior of the New override is inconsistent with the native ‘New’ functionality (where a modal dialog will popup OVER the existing page). This has also been discussed forever and we still cannot do this…..

This question IS about how to reliably execute initialization code for such a component.

We use the ‘init’ event handler right?

Has anyone ever done this?

Our observations so far suggest that the event WILL be fired when the component is actually created (just like the documentation says), but that the component sometimes just ‘hangs around’ after the user has completed the action or navigated away from the page or whatever, and that the existing instance of this component will be used for the subsequent invocation of the ‘New’ action.

We believe that the component still exists, as handlers for the ‘render’ event, or ‘locationChanged’ or anything else you can think of, fire when the ‘New’ action is invoked again (but the init event does NOT fire).

How do we reliably execute code ONE TIME at the start of the ‘New’ Action override then?

  1. Use another event, like ‘render’ which appears to always fire? No way we are doing that. If our one time code needs to update an attribute on the component we end up in an infinite loop mess.

  2. We somehow destroy the component ourselves (perhaps when we navigate away from the page by handling locationChange? No way we’re doing that either. Of course we’ve tried :) and it simply confuses everything. In the example given above, returning to the Account detail page and pressing ‘New’ does not create a new instance of the component, and so does not fire ‘init’ and we land on an empty page in outer space. So, that is just a big hack anyway…….

So, assuming we have created a new Lightning component to be used as an Opportunity ‘New’ override We can reliably recreate this issue by

  1. Navigate to an Account Detail page.
  2. Click on the ‘New’ button on the Opportunity related list.
  3. Observe that the ‘New’ override ‘init’ event was actually fired. Cool..
  4. Navigate to a different account.
  5. Again, click on the ‘New’ opportunity button. You will observe that the ‘init’ event did NOT fire. The ‘state’ of the component is how it was the last time it executed. :( No ‘init’ event was fired for us to do our thing….

Hmmmm?? Could anyone please confirm / deny … whatever that you have also perhaps experienced this behavior? Perhaps we are doing something wrong here? I’m not of course going to bore you with copy pasted code. It is pure vanilla 101 lightning component code, and I trust the explanation of what we’re doing is clear enough.

Thanks for any help.

7
  • 1
    I've noticed issues like this in the past, although not to the extent you're describing. Making sure I close the component using the e.force:closeQuickAction event seemed to do the trick for me. Jun 1, 2018 at 19:22
  • Thanks Antonio, however this is not a QuickAction. There is no quick action dialog to close. This is a component overriding the 'New' action, which is totally unrelated to a quick action. I'm glad you got your issue sorted out however ..... Jun 1, 2018 at 23:14
  • I understand. I thought there may have been something similar for overrides but I was mistaken. That being said, I spun up a bare-bones component in an effort to reproduce your findings to no avail. My init handler fired every time without fail. I think it would be helpful to share a code sample in order to try and diagnose; including only the essential logic for reproduction. Jun 2, 2018 at 0:21
  • @PeterByrne I have observed the similar issue in Summer 18 sandbox. Let me explain more in detail. We have overwritten the New button with a lightning component and that object has couple of record types. we need to pick the record type id based on user selection and render certain part of the markup in cmp. We are achieving this by parsing URL parameters in init method of the cmp. This solution is being worked perfectly before Summer 18 but for some reason it stopped working which means, only first time init method is getting invoked and the rest of the clicks it used to take old value.
    – brahma
    Jun 2, 2018 at 13:22
  • (2/2). Basically 'init' method is NOT getting invoked from second click (on New) onwards. It is referring same attribute which was selected in previous click. We have raised a case to Salesforce as it caused due to Summer 18. I just checked in cs84 sandbox (Summer 18 upgrade) and it looks to be resolved now. Hope i understand the problem and this comment is relevant to your question.
    – brahma
    Jun 2, 2018 at 13:24

5 Answers 5

3

I know this question is quite old, but having run into this issue myself recently I wanted to post a workaround I was able to cobble together.

For the workaround to work, you need to add the lightning:hasPageReference interface to your component and an attribute to store the page reference. I was already using this interface for something, but it should work for any case, even if you don't need the actual interface.

<aura:attribute name="pageReference" type="Object" />
<aura:handler name="change" value="{!v.pageReference}" action="{!c.handlePageChange}" />

Then in the controller, fire whatever logic you need to fire from the handlePageChange method. In my case, I needed to reload a record using lds, which then fired more logic.

{( //controller.js
    handlePageChange : function(component, event, helper) {
        console.log("pageReference attribute change");
        component.find("userRecord").reloadRecord(true);
    }
})

By doing this I was able to successfully fire any logic that needed to fire each and every time the New button was pressed.

11
  • Part 1 Good info ... Thanks... I resurrected the old code I was playing with, to test out your theory. As it turns out, my example code is pretty well the same as yours except I implement the isUrlAddressable interface rather than the hasPageReference interface. In either case (implementing either of the two interfaces, doesn't matter), the behavior that I observe is identical, and unfortunately is not the same as the behavior you have described. Dec 5, 2018 at 23:06
  • Part 2 What I see is exactly as I described in my initial big and ugly post, and that is : EITHER the init event will fire OR the change handler will fire. Never NONE. Never BOTH. Either one OR the other. ALWAYS. As I described, we could perhaps work around that using either event to fire our code, but it feels like a total hack. When delving in to the why's and wherefore's of it all, we could sort of explain the behavior, based on how Lightning is apparently caching Lightning components in the background. Dec 5, 2018 at 23:06
  • Part 3 When Pressing the 'New' button the VERY FIRST TIME you enter the application, the init event WILL ALWAYS fire (because the component does not exist and the framework will create it). OK. Very good. The 'change' handler will NEVER fire in this case, as it can't. The component does not even exist and so cannot possibly respond to a change of a field value. When navigating away from the component and then AGAIN pressing the 'New' button, THEN and ONLY THEN does the 'change' event fire. Why? Because we see that the component still actually exists (cached somewhere !!) Dec 5, 2018 at 23:07
  • Part 4 The init event will NOT fire in this case, as the component was not created. But it DOES handle the change event, because the pageReference really did change, and there is a component with a registered change handler to handle it. It must be noted that in the second scenario, it is NOT guaranteed the the Lightning Framework still has a cached verion of the component available for resuse, and we then see the init event firing again (and NOT the change event). Dec 5, 2018 at 23:08
  • Part 5 We gave up on all this, as it was simply part one of the bigger issue about not really being able to use Lightning components as 'New' action overrides as we do NOT have access to the Parent ID (the context in which the New action was invoked). (That's a whole other story) Sure, I've seen some smart dudes out there figure out how to unravel the pageReference object and actually find the Parent Id, but that (from my understanding) is totally undocumented / unsupported at this stage, and we have enough issues trying to make 'documented' things work in Lightning :) Dec 5, 2018 at 23:08
2

The answer @Piyush provided is a satisfactory solution, although it would have been easier to read if the code was formatted. In addition, I would like to propose a simplified version of the solution @lpbearden provided. Instead of creating a separate method for handling the pageReference change, I propose that you run the doInit method (or whatever your init method is called) in response to the change:

<aura:attribute name="pageReference" type="Object" />
<aura:handler name="change" value="{!v.pageReference}" action="{!c.doInit}" />

This solution worked for me when needing to address a recordTypeId change from the New action in a subsequent call. It specifically addresses the OP's request to rerun the init code.

1
  • Can you please elaborate your solution. I'm unable to use piyush's recommendation and I'm stuck on this issue.
    – Badduboy
    Jan 14 at 20:16
1

One other thing that can be done is, on the 'change' handler - just call the $A.get('e.force:refreshView').fire(); This should call the init method again on refresh of the page. I had a similar problem - and this helped me overcome this. I was using an Aura component to override the New button, which in turn is calling a LWC component, and my data was getting cached on the second call. With this solution - not facing an issue anymore.

2
  • As per your advice I've added this to my component but it is not working. Can you let me know where exactly can we use it.({ navigateTolwc : function(component, event, helper) { var evt = $A.get("e.force:navigateToComponent"); evt.setParams({ componentDef : "c:lwccomp", componentAttributes: { recordId : component.get("v.recordId") $A.get('e.force:refreshView').fire(); } }); evt.fire(); $A.get('e.force:refreshView').fire(); } })
    – Badduboy
    Jan 14 at 19:46
  • This worked for me in that my init function was fired. I note, however, that the state.count continues to increase. I tried everything to reset this. I was overriding a new event and having to add a button to return to the calendar page. Jan 19 at 14:12
0

There is an explanation for this behavior in the official documentation, so all provided workarounds actually are solid fixes in my mind

If your component uses the lightning:hasPageReference or lightning:isUrlAddressable interfaces, always implement a change handler. When the target of a navigation action maps to the same component, the routing container might simply update the pageReference attribute value instead of recreating the component. In this scenario, a change handler ensures that your component reacts correctly.

Lightning Aura Components Developer Guide

0

Here's my code for overriding the New Event from the calendar using navigate to pageReference. Still not perfect and I've left a few commented out attempts. Can anyone provide the final fix?

NewEventOverride2.cmp

<aura:component implements="lightning:actionOverride,lightning:hasPageReference,lightning:isUrlAddressable "
            controller="NewEventController" access="global">

<aura:handler name="init" value="{!this}" action="{!c.doInit}"/>

<aura:handler name="change" value="{!v.pageReference}" action="{!c.pageRefChange}" />


<lightning:button label="Return to Calendar Page" onclick="{!c.gotoCalendar}"/>


<lightning:navigation aura:id="navService"/>
<!-- pageReferenceUtil component -->
<lightning:pageReferenceUtils aura:id="pageRefUtils"/>

<aura:attribute name="eventRecordTypes" type="object" />
<aura:attribute name="eventRecordTypeId" type="String" default=""/>
<aura:attribute name="eventRecordType" type="String" default=""/>

</aura:component>

NewEventOveride2Controller

({


doInit: function(component, event, helper) {
    
   
    var pageReference = component.get("v.pageReference");
    
    console.log(JSON.parse(JSON.stringify(pageReference)));
    var getAddParms = pageReference.state.additionalParams;
    if(typeof getAddParms !== 'undefined') {
        console.log('Add Parms  =  '+getAddParms);
        
    }//endif
    var getCount = pageReference.state.count;
    if(typeof getCount !== 'undefined') {
        console.log('Count =  '+getCount);
    }//endif
    
    
    //Fetching Record Type Id  
    var eventRecordTypeId = pageReference.state.recordTypeId;
    console.log('eventRecordId in new event doInit: '+eventRecordTypeId);
    
    
    helper.hlpGetRecordType(component, eventRecordTypeId)
    .then($A.getCallback(function(records){
        
        
        var eRecType = component.get("v.eventRecordType");
        console.log('eRecType = ', eRecType);
        //set defaults according to eRecType
        var dFV;
        var defaultValues;
        
        //retURL: "~~~.com", ???
        if (eRecType == 'Training'){
            dFV = {
                Subject: "Training"
                
            };
            defaultValues = component.find("pageRefUtils").encodeDefaultFieldValues(dFV);
            
        }//endif
        
        
        
        
        var pageReference = {
            "type": "standard__objectPage",
            "attributes": {
                objectApiName: "Event",
                actionName: "new",
                additionalParams: "",
                count: "1"
            },
            "state": {
                
                count: "1",
                nooverride: '1',
                defaultFieldValues: defaultValues,
                recordTypeId: eventRecordTypeId
            }
        };
        
        
        
        console.log('launch helper with pagereference and navigate');
        
console.log(JSON.parse(JSON.stringify(pageReference)));
        //event.preventDefault();
        var navService = component.find("navService");
        navService.navigate(pageReference,true);
        
      
        
    }))//end then
    .catch(function(errors) {
        console.error('ERROR: ' + errors);
        
    });
    
        
        
    },//doInit

pageRefChange : function(component, event, helper) {
    console.log('page Reference has changed');
    var pageReference = component.get("v.pageReference");
    console.log('pr = ');
    console.log(JSON.parse(JSON.stringify(pageReference)));
    console.log(pageReference.state);
    console.log('count = '+pageReference.state.count);
    console.log(pageReference.type);
    console.log(pageReference.attributes);
    //pageReference.state.count = 1;
    //component.set("v.pageReference",pageReference);
    console.log('changed pageReference.state.count');
    
    var newState = "{additionalParams: '', count: '4'}"
    //helper.updatePageRef( pageReference, newState);
    //console.log(JSON.parse(JSON.stringify(updated)));
    //console.log(updated.state);
    
    var defaultUrl = "apex/MyVisualForcePage";
    var navService = component.find("navService");
    navService.generateUrl(pageReference)
    .then($A.getCallback(function(url) {
        
        console.log('url' + url);
        console.log('defaultUrl' + defaultUrl);
        
        //component.set("v.url", url ? url : defaultUrl);
        //now create a new one
        var newPageReference = {
            "type": "standard__objectPage",
            "attributes": {
                objectApiName: "Event",
                actionName: "home",
                additionalParams: "",
                navigationLocation: "LIST_VIEW",
                backgroundContext: "%2lightning%2o%2Event%2home"
            },
            "state": {
                count: "1",
                nooverride: '1'
            }
        };
        
        
//component.set("v.pageReference",newPageReference);    //causes infinite loop        
        console.log('now navigate to calendar with pr = ');
        console.log(JSON.parse(JSON.stringify(newPageReference)));      
        
        console.log('state = '+newPageReference.state);
        //console.log('count = '+pageReference.state.count);
        console.log(newPageReference.type);
        console.log(newPageReference.attributes);
        //navService.navigate(newPageReference,true);

//this works!!!...Thanks Piyush
        $A.get('e.force:refreshView').fire();
        
    }), $A.getCallback(function(error) {
        //component.set("v.url", defaultUrl);
    }));
    
    
    
    
    
    
},//pageRefChange


gotoCalendar : function(component, event, helper) {
    
    var navService = component.find("navService");
    var pageReference = {
        "type": "standard__objectPage",
        "attributes": {
            objectApiName: "Event",
            actionName: "home",
            additionalParams: "",
            navigationLocation: "LIST_VIEW",
            backgroundContext: "%2lightning%2o%2Event%2home"
        },
            "state": {
                count: "1",
                nooverride: '1'
            }
    };
    
    
    
    console.log('go to calendar with pr = ');
    console.log(JSON.parse(JSON.stringify(pageReference)));      
    
    console.log('state = '+pageReference.state);
    //console.log('count = '+pageReference.state.count);
    console.log(pageReference.type);
    console.log(pageReference.attributes);
    navService.navigate(pageReference,true);
    
    
    
},//gotoCalendar



})

NewEventOverride2Helper.js

({
hlpGetRecordType : function(component, recordTypeId) {
    
    return new Promise($A.getCallback(function(resolve, reject) {
        
        var action = component.get("c.getEventRecordTypes");
        action.setParams({
            objectType: 'Event',
            selectedField: 'Type',
            extended: true
        });
        action.setCallback(this, function(response) {
            
            var state = response.getState();
            if (state === 'SUCCESS') {
                
                
                var lstValues = response.getReturnValue();
                /*console.log('success ');
                console.log('return from get types');
                console.log(lstValues);*/
                
                
                var returning = [];
                var recordTypes = response.getReturnValue();
                for(var key in recordTypes){
                    //console.log("Record Type Id: "+key+" Value: "+recordTypes[key][0]);
                    if (key === recordTypeId){
                        returning.push({key:key,value:''+recordTypes[key][0]});
                        component.set("v.eventRecordType",recordTypes[key][0]);
                        //console.log(recordTypes[key][0]);
                    }//endif
                }//next
                component.set("v.eventRecordTypes",returning);
                resolve(returning);
                
            } else if(state === 'ERROR') {
                console.log('error = '+action.getError());
                reject(action.getError())
            }//endif success           
            
        });//end callback
        $A.enqueueAction(action);
    }));//end promise
    
},//hlpGetRecordType


updatePageRef: function( pageRef, newState) {
    var pageRef2 = pageRef;
    console.log(JSON.parse(JSON.stringify(pageRef2)));
    //pageRef2.state = Object.assign(pageRef2.state, newState || {});
    //console.log(JSON.parse(JSON.stringify(pageRef2)));
    //return pageRef2;
},

})

NewEventController.apxc

public class NewEventController {


@AuraEnabled
public static Map<String,List<String>> getEventRecordTypes()
{        
    
Map<String,List<String>> recordTypeMap = new Map<String,List<String>>();
    List<RecordType> listRec = new List<RecordType>();
    Schema.DescribeSObjectResult eventObj = Event.SObjectType.getDescribe();  
    List<Schema.RecordTypeInfo> rtInfo = eventObj.getRecordTypeInfos();
    for(RecordType rec : [SELECT Id, Name, Description FROM RecordType WHERE SobjectType = 'Event']) { //and here
        listRec.add(rec);
        system.debug(rec);
    }
    
    for( Schema.RecordTypeInfo recordType : rtInfo )
    {
        List<String> nameAndDesc = new List<String>();
        system.debug('record type '+recordType);
        if(recordType.isAvailable())
        {
            //system.debug('record type name '+recordType.Name);
            if(recordType.Name!='Master') {
                for(RecordType rList: listRec){
                    if(rList.Id==recordType.getRecordTypeId()){
                        nameAndDesc.add(rList.Name);
                        //ignore description
                        nameAndDesc.add(rList.Description);
                    }
                }
                recordTypeMap.put(recordType.getRecordTypeId(),nameAndDesc);
                    
            }
        }
    }
    return recordTypeMap;
}//getEventRecordTypes



}
1
  • Got it at last: backgroundContext works... in pageReference "state": { navigationLocation: "LIST_VIEW", backgroundContext: "/lightning/o/Event/home", count: "1", nooverride: '1', defaultFieldValues: encodedValues, recordTypeId: eventRecordTypeId } Apr 21 at 8:29

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.