6

Fails

I have a lightning component (TestParent). The component has <lightning:overlayLibrary> which shows another component called TestChild. When I fire an event from TestChild it doesn't catched by TestParent and alert is not shown.

TestParent.cmp

<aura:component description="TestParent" implements="force:appHostable">
    <aura:handler name="fromModal" event="c:GeneralEvt"
                  action="{!c.fromModal}" />

    <lightning:button label="ShowModal"
                      onclick="{!c.showModal}" />

    <lightning:overlayLibrary aura:id="overlayLib"/>
</aura:component>

TestParentController.js

({
    showModal: function (component, event, helper) {
        $A.createComponent("c:TestChild", {},
            function(content, status, error) {
                if (status === "SUCCESS") {
                    component.find('overlayLib').showCustomModal({
                        header: "Child",
                        body: content,
                        showCloseButton: true
                    });
                } else {
                    throw new Error(error);
                }
            });
    },

    fromModal: function (component, event, helper) {
        alert('from modal');
    }
});

TestChild.cmp

<aura:component description="TestChild">
    <aura:registerEvent name="fromModal" type="c:GeneralEvt"/>

    <lightning:button label="Call Parent"
                      onclick="{!c.callParent}" />
</aura:component>

TestChildController.js

({
    callParent: function (component, event, helper) {
        var evt = component.getEvent('fromModal');
        evt.fire();
    }
});

Works

If I instead of usage of <lightning:overlayLibrary> add the child component to a {!v.body} of TestParent then event catched properly and I can see the alert.

TestParent.cmp

<aura:component description="TestParent" implements="force:appHostable">
    <aura:handler name="fromModal" event="c:GeneralEvt"
                  action="{!c.fromModal}" />

    <lightning:button label="ShowModal"
                      onclick="{!c.showModal}" />

    {!v.body}
</aura:component>

TestParentController.js

({
    showModal: function (component, event, helper) {
        $A.createComponent("c:TestChild", {},
            function(content, status, error) {
                if (status === "SUCCESS") {
                    component.set('v.body', content);
                } else {
                    throw new Error(error);
                }
            });
    },

    fromModal: function (component, event, helper) {
        alert('from modal');
    }
});

GeneralEvt.evt

<aura:event type="COMPONENT" description="GeneralEvt">
</aura:event>

How to make <lightning:overlayLibrary> send events properly?

  • 1
    @glls, I've added GeneralEvt.evt to the bottom of my question. – Mikhail Ivanov Mar 30 '18 at 15:44
  • 1
    The calling component is not a parent. They are not in the same DOM tree. You would need to fire an application event to communicate from the modalBody to the originating component. – tsalb Sep 20 '18 at 0:00
3

Its a special case where in the parent won't listen to child's event, for doing so you would need to pass the reference of parent controller's method while creating it.

So add attributes here:

$A.createComponent("c:TestChild", {},

like:

$A.createComponent("c:Child_213329", {
    "fromModal": component.getReference("c.fromModal")
},

With parent event would be binded and called whenever parent fires that specific event.

Note: I might be missing something; looks like some kind of bug in lightning:overlayLibrary It just doesn't works the first time popup is opened but works afterwards.


Passing parameter to parent:

Add params to the component event, TestChildController.js-

({
    callParent: function (component, event, helper) {
        var evt = component.getEvent('fromModal')
            .setParams({
                "message" : "A component event fired me. " +
                "It all happened so fast. Now, I'm here!" })
            .fire();
    }
});

Get parameter in TestParentController.js-

fromModal: function (component, event, helper) {
    var message = event.getParam("message");
    console.log('message from modal', message);
}

Complete code used for testing/demo.

  • can I send some parameters (for ex. record Id) to fromModal from child component with your component.getReference("c.fromModal") approach? Could you add some more detailed example? – Mikhail Ivanov Mar 30 '18 at 15:46
  • Here is an example. From the fromModal method you can get the parameter from event using event property like event.getParam – Raul Mar 31 '18 at 9:36
  • Rahul, thank you. Your solution works with a limitation "doesn't works the first time popup is opened". But in most cases it is fine. – Mikhail Ivanov Apr 10 '18 at 16:58
  • Apparently this works fine in my application. Will investigate further – Raul Apr 10 '18 at 18:27
  • 1
    Parent not listening to child is because they are not hierarchical. You need to communicate with app events. – tsalb Sep 20 '18 at 0:01
2

For whatever reason, passing the handler as an attribute seems flaky (i.e., doesn't work on the first pop-up). But if I call addEventHandler on the component in the $A.createComponent callback instead, that seems to work every time.

More info here: https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/js_cb_dynamic_handler.htm

1

Rather Than Passing the Event to the OverlayLibrary, Why not Close the OverlayLibrary on success. This will trigger the callback function on the overlaylibrary allowing you to fire any events from that point on.

Example:

Inside your Overlay Component on success it should fire this

    handleSuccess: function (component, event, helper) {

    component.find("overlayAccountLib").notifyClose();

    // record is saved successfully
    var resultsToast = $A.get("e.force:showToast");
    resultsToast.setParams({
        "title": "Saved",
        "message": "The record was saved.",
        "type": "success"
    });
    resultsToast.fire();
},

Since you close the overlay it will fire the closeCallback Function and here you can fire your methods or events

var modalBody;
    $A.createComponent("c:YourComponent", {
      "Data1": data1,
      "data2": Data2
    },
      function (content, status) {
        if (status === "SUCCESS") {
          modalBody = content;
          component.find('overlayLib').showCustomModal({
            header: "Your Header Title",
            body: modalBody,
            showCloseButton: true,
            cssClass: "mymodal",
            closeCallback: function () {
              // Because you closed your overlay library You have access to this. 
              helper.fireYourEvent(component);
            }
          });
        }
      });
  • +1 Nice, This seems new and now is in documentation. We had to use the hacky way earlier with story overlay from then promise method. Thanks for sharing! – Raul Aug 16 '18 at 11:10
  • If you follow this pattern how do you get information from your modal back to your parent? For example, my modal has a recordForm where you add a new contact. I can get the id of the contact in the onsuccess handler and that is where I'd like to fire an event containing the new contact's id. How would you get this id back to the parent component in the method you describe above? – Homerlex Sep 20 '18 at 13:27
  • ... perhaps using an application event (as @tsalb suggests) is the way to go. I just tried using an app event and it worked. – Homerlex Sep 20 '18 at 13:44
1

From my research and playing around with this, I have a slight alteration/suggestion to Raul's answer (which was really helpful and I've up-voted by the way).

There are two solutions. 1: do what Raul suggested by make sure you create the components at least once in the component's init function (you don't need to open the modal. Just create the components). This way, the event will register even on the first open.

2: A better solution is to use an application event. This allows you to not only handle the event in the parent, but also from the modal footer to the modal body and THEN in the parent. This also negates the need for an init function at all if you don't otherwise need one.

for example:

the event:

<!-- exampleEvent.evt -->
<aura:event type="APPLICATION">
    <aura:attribute name="message" type="String"/>
    <aura:attribute name="number" type="Integer"/>
</aura:event>

in the parent mark up:

<!-- parent.cmp -->
<aura:handler event="c:exampleEvent" action="{!c.handleEventFromModalBody}" />
<lightning:overlayLibrary aura:id="overlayLib"/>
<lightning:button label="open modal" variant="success" onclick="{!c.openModal}" />

Note: DON'T give the handler a name or it won't work with an application event.see here)

The handler for an application event won’t work if you set the name attribute in . Use the name attribute only when you’re handling component events. :

In the modal footer markup (it's fine to have a name for aura:registerEvent):

<!-- ModalFooter.cmp -->
<aura:registerEvent name="addToCartEvent" type="c:exampleEvent"/>
<lightning:button label="Add to Cart" variant="success" onclick="{!c.addToCart}"/>

In the modal footer js controller: (Note: notice the different syntax for getting the aura:register event for an application event. You have to use $A.get("e.c:eventName") rather than component.getEvent("eventName") )

/* method in ModalFooterController.js */
addToCart : function(component, event, helper) {
    var addToCartEvent = $A.get("e.c:exampleEvent");
    addToCartEvent.fire();
}

In the modal body markup (You need the lightning:overlayLibrary here to close the modal):

<!-- ModalBody.cmp -->
<aura:handler event="c:exampleEvent" action="{!c.handleEventFromFooter}" />
<aura:registerEvent name="addToCartEvent" type="c:MarketingPortalProduct_addToCart"/>
<lightning:overlayLibrary aura:id="overlayLib"/>

In the modal body js controller: (This code checks that event came from the footer, then fires it's own event with some data attached to it. The data could be a list of accounts or a custom object or whatever you want, as long as you can store it in an aura:attribute)

/* method in ModalBodyController.js */
handleEventFromFooter : function(component, event, helper) {
    var source = event.getSource();

    if (source.getName() ===  'cModalBody') {
        console.log('event handled in modal body');

        var addToCartEvent = $A.get("e.c:exampleEvent");
        addToCartEvent.setParams({
            message: 'this event message was defined just before closing the modal',
            number: 5
        });

        addToCartEvent.fire();

        // close the modal
        component.find('overlayLib').notifyClose();
    }
}

Finally, in the parent component js controller: (This code checks that the event came from the body, rather than the footer, then executes whatever you want it to)

/* method in parentController.js */
handleEventFromModalBody: function (component, event, helper) {
    var source = event.getSource();

    if (source.getName() ===  'cModalBody') {
        console.log('handled in parent.');
        //your code here
    }
}

Hope this helped someone out there!

Cheers, Dunks

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.