1

I have a modal that allows a user to select some options and then upload a file. I want to close the modal as soon as the upload completes (the user clicks the Done button). So here's my code:

Parent markup

<lightning:overlayLibrary aura:id="overlayLib"/>
...
<lightning:buttonIcon iconName="utility:upload" 
                      variant="border" 
                      alternativeText="Upload Files" 
                      title="Upload Files"
                      onclick="{!c.handleShowModal }" disabled="{!v.disabled}"/>

Parent controller

handleShowModal: function(component, evt, helper) {
    var modalBody;
    var modalHeader;
    var recordId = component.get("v.myObjId");
    $A.createComponent("c:fileUploadModal", {relatedRecordId: recordId},
        function(content, status) {
            if (status === "SUCCESS") {
                modalBody = content;
                modalHeader = $A.get("$Label.c.Upload_Modal_Header");
                component.find('overlayLib').showCustomModal({
                   header: modalHeader,
                   body: modalBody, 
                   showCloseButton: true,
                   cssClass: "mymodal",
                   closeCallback: function() {
                       // Refresh our list of files
                       helper.retrieveDocs(component, event, helper);
                   }
               })
           }                               
        });
}

Modal markup

<lightning:fileUpload  name="fileUploader"
                       multiple="false"
                       accept="{!v.fileExtensions}"
                       recordId="{!v.relatedRecordId}"
                       onuploadfinished="{!c.handleUploadFinished }"/>  

Modal controller:

handleUploadFinished: function (component, event) {
    var uploadedFiles = event.getParam("files");
    console.log(uploadedFiles[0].documentId); // See? it works.
    component.find("overlayLib").notifyClose();
}

Obviously the .notifyClose() isn't working because component.find("overlayLib") returns undefined since that component does not have access to the parent component.
I tried document.getElementsByClassName('uiButton--modal-closeBtn')[0].click() but that doesn't work because of Aura.
So how do I programmatically close the modal after the user acknowledges that their file has been uploaded successfully?

2 Answers 2

2

Final Edit:

Solution based on passing overlayLib as an attribute to dynamically created modal component.

Last idea that I had (and tested) is to pass overlayLib as an attribute when dynamically creating modal:

<aura:component implements="flexipage:availableForAllPageTypes">
    <aura:attribute name="lib" type="Object"/>
    <lightning:fileUpload  name="fileUploader"
                           multiple="false"
                           accept="{!v.fileExtensions}"
                           recordId="{!v.relatedRecordId}"
                           onuploadfinished="{!c.handleUploadFinished }"/>  
</aura:component>


handleUploadFinished: function (component, event) {
    var uploadedFiles = event.getParam("files");
    console.log(uploadedFiles[0].documentId); // See? it works.
    component.get("v.lib").notifyClose();
}

Creating modal

handleShowModalFooter : function (component, event, helper) {
    var modalBody;
    var modalFooter;
    $A.createComponents([
        ["c:modalContent",{'lib':  component.find('overlayLib')}],
        ["c:modalFooter",{}]
    ],
    function(components, status){
        if (status === "SUCCESS") {
            modalBody = components[0];
            modalFooter = components[1];
            component.find('overlayLib').showCustomModal({
               header: "Application Confirmation",
               body: modalBody, 
               footer: modalFooter,
               showCloseButton: true,
               cssClass: "my-modal,my-custom-class,my-other-class",
               closeCallback: function() {
                   alert('You closed the alert!');
               }
           })
        }
    }
   );
}

Edit: Ok I didnt fully understand question, so I'm adding new answer with old one staying just for reference.

You can of course create footer for your modal with buttons that will handle modal close.

Example mainly from the docs:

Modal footer component:

<aura:component>
    <lightning:overlayLibrary aura:id="overlayLib"/>
    <lightning:button name="cancel" label="Cancel" onclick="{!c.handleCancel}"/>
    <lightning:button name="Close" label="Close" variant="brand" onclick="{!c.handleCancel}" disabled="true"/>
</aura:component>

Footer controller:

({
    handleCancel : function(component, event, helper) {
        //closes the modal or popover from the component
        component.find("overlayLib").notifyClose();
    }
})

Creating modal controller:

({
handleShowModalFooter : function (component, event, helper) {
    var modalBody;
    var modalFooter;
    $A.createComponents([
        ["c:modalContent",{}],
        ["c:modalFooter",{}]
    ],
    function(components, status){
        if (status === "SUCCESS") {
            modalBody = components[0];
            modalFooter = components[1];
            component.find('overlayLib').showCustomModal({
               header: "Application Confirmation",
               body: modalBody, 
               footer: modalFooter,
               showCloseButton: true,
               cssClass: "my-modal,my-custom-class,my-other-class",
               closeCallback: function() {
                   alert('You closed the alert!');
               }
           })
        }
    }
   );
}
})

I on purpose made one button disabled in a footer so you can enable that in your code

5
  • Yeah, I thought about that. But coming from frameworks that use Redux, firing events into the void and hoping the right element will catch them just feels wrong. Also it astonishes me that a modal cannot close itself. I can't have a Cancel button in my UI? I need to use that wonky-looking X button? Commented Jan 8, 2019 at 12:14
  • Of course you can :) I'll update my answer in a second I didnt fully understand the question before, also component events are not firing into the void. They're firing only for the component tree from the bottom to the top. We can say that platform events are firing into the void but of course they can be also handled Commented Jan 8, 2019 at 13:06
  • That's great. But I still can't close the modal from within the modal, only from the footer. I tried adding <lightning:overlayLibrary aura:id="overlayLib"/> in my modal (which is exactly what the footer does) and tried call notifyClose() on that. But that didn't work. [NoErrorObjectAvailable] Script error Commented Jan 8, 2019 at 14:01
  • So I tried the event route, and like I suspected: it doesn't work. I think it might be because the modal is technically at the top of the component tree, and therefore its parent (the component that launched the modal) can't catch an event fired from the modal. Commented Jan 9, 2019 at 9:12
  • Hey I also tested event solution and for some reason yeah it doesnt work -> component hierarchy should be ok, my assumption is that something is wrong because of dynamically creating components So I tried next solution -> passing overlayLib as an attribute, tested and it works :) Commented Jan 9, 2019 at 10:03
2

Before I get to the answer, thank you very much to user1974566 for your assistance, you eventually led me to the correct solution which is as follows:

First of all (which I discovered towards the end), things change and the way to close a modal now is via promise. See this question. The correct solution is as provided by Oles Malkov. I'll summarize it here.
On the parent component (the one that launched the modal) notifyClose() no longer does anything. I discovered this after I was eventually able to capture the event fired by the modal in the parent component (explained below) but it wasn't closing the modal.
The correct way to do that is to save the promise returned by showCustomModal() as an aura:attribute and use that to close the modal.

Second, there is a way to subvert the component hierarchy that got subverted by the modal: APPLICATION events. That fired an event into the void of the application, regardless of the component hierarchy. This allowed my parent component to capture the event from the modal (which should have been its child) and close it.
Full solution:

Parent markup

<aura:attribute name="modalPromise" type="Aura.Action"/> <!--Save the promise from opening the file upload modal-->
<aura:handler event="c:fileUploaded" action="{!c.handleFileUploaded}"/>
<lightning:overlayLibrary aura:id="overlayLib"/>
...
<lightning:buttonIcon iconName="utility:upload" 
                  variant="border" 
                  alternativeText="Upload Files" 
                  title="Upload Files"
                  onclick="{!c.handleShowModal }" disabled="{!v.disabled}"/>

Parent Controller

handleShowModal: function(component, evt, helper) {
...
$A.createComponent("c:fileUploadModal", {relatedRecordId: recordId},
    function(content, status) {
        if (status === "SUCCESS") {
            ...
            var modalPromise = component.find('overlayLib').showCustomModal({
               ...
            });
           component.set("v.modalPromise", modalPromise);
       }                               
    });
},

handleFileUploaded : function(component, event, helper){
    // component.find("overlayLib").notifyClose(); DOES NOT WORK
    component.get('v.modalPromise').then(function (modal){
        modal.close();
    });
}

Event

<aura:event type="APPLICATION">
</aura:event>  

Modal Markup

<aura:attribute name="relatedRecordId" type="string"/>
<aura:registerEvent name="fileUploaded" type="c:fileUploaded"/>

<lightning:fileUpload  name="fileUploader"
                   multiple="false"
                   accept="{!v.fileExtensions}"
                   recordId="{!v.relatedRecordId}"
                   onuploadfinished="{!c.handleUploadFinished }"/>  

Modal Component

handleUploadFinished: function (component, event) {
    var fireEvent = $A.get("e.c:fileUploaded");
    fireEvent.fire();
}

tl;dr:

  • In the parent component save the promise from launching the modal.
  • From the modal send an APPLICATION event when files are uploaded.
  • In the parent component listen for that event and close the modal using saved promise.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .