6

I'm familiar with the stuff that SLDS is CSS framework, and know that we need to incorporate our own JS to implement additional logic, which I can easily do in VF pages with jQuery etc, but I'm puzzled on how to achieve same behavior of a Dropdown Menu of SLDS in a Lightning Component.

I'm trying to use https://www.lightningdesignsystem.com/components/menus/ and I can make dropdown Show/Hide by clicking the Drop Arrow button, what I need now is, to be able to Close this Dropdown when clicked Outside of the Dropmenu.

Can anyone suggest some way for me to do that in a Lightning Component?

8
  • are you working on section kind of functionality, even i am looking for solution for same issue , where i need to show card based section based on select +/-.
    – Raj_SFDC
    Sep 29, 2016 at 10:37
  • Not exactly mine is straightforward dropdown that shows on a link click and needs to be made hidden when clicked on document body outside dropmenu.
    – VarunC
    Sep 29, 2016 at 10:44
  • @VarunC have you tried ui:menu .
    – Praveen
    Sep 29, 2016 at 11:19
  • Yes, I've been looking into it. But I could not make up the interface like this using <ui:menu> - screencast.com/t/uhIKvfNJq ...
    – VarunC
    Sep 29, 2016 at 11:33
  • @VarunC if you are developing your own dropdown, onblur event as mentioned by SantanuHalder will work if only if tabindex is added to the div because its a non-input element.check out this so answer
    – Praveen
    Sep 30, 2016 at 4:18

6 Answers 6

6

You have to toggle the slds-is-open class from the top most div of the dropdown.

Let's say this is your dropdown:

<div class="slds">
        <div aura:id="ddId" class="slds-dropdown-trigger slds-dropdown-trigger--click slds-is-open" onclick="{!c.toggleVisibility}">
            ..... 
        </div>
    </div>

Now in the controller:

toggleVisibility : function(component, event, helper)
{
var ddDiv = component.find('ddId');
$A.util.toggleClass(ddDiv,'slds-is-open');
}

This should do. You can call this toggleVisibility function from blur of the button as well to close the dropdown.

Have this on the button :

onblur="{!c.toggleVisibility}"

UPDATE:

After VarunC's comment that the dropdown hides as soon as you click on it. To avoid that, update the toggleVisibility function which will hide the menu options after small delay.

    toggleVisibility : function(component, event, helper)
        {
            setTimeout(function(){
              var ddDiv = component.find('ddId');
              $A.util.toggleClass(ddDiv,'slds-is-open');
            }, 300);
        }
6
  • 1
    As i mentioned earlier, showing and hiding the menu from Drop arrow button is working for me. I could add and remove class there click event but what i am looking for is, to hide the menu when clicked outside the menu i.e. The document element but binding a click event to document is what i cannot figure out in Lightning Component...
    – VarunC
    Sep 29, 2016 at 10:42
  • Updated the answer. onblur event of the button should do the trick. Sep 29, 2016 at 11:23
  • 1
    @SantanuHalder you need add tabindex for onblur event to work on non-input elements : stackoverflow.com/questions/18504139/div-onblur-function
    – Praveen
    Sep 30, 2016 at 4:19
  • Oh I see but I added onblur on button and button is a kind of input element I guess. It worked when I tried in my org though. Sep 30, 2016 at 7:11
  • 1
    It worked to hide the menu onblur, but it is closing the Menu (opened by the button) as soon I click anywhere inside it. I actually want to close the menu as soon as we click outside the menu. Almost the behavior like I would need to add onblur to Menu instead of button.
    – VarunC
    Sep 30, 2016 at 13:19
0

Ok so obviously from looking at lightning:uiinputrichtext and its 'limitations' in regard to its font menu. SFDC is having problems with its own rules on how to handle this ie. I have a menu that opens. I simply want to to close it. when I click anywhere else but the menu. ie. bootstrap datepicker or any other normal web component. I don't want to use onblur as that just gets too confusing to our users and doesn't act like what they are used to.. is there any 'secure' way / locker service blessed way to say. slap on onclick on an outer tag and determine any id. don't care if it's local. global.. etc.etc. in all circumstances of what was click.. then I can simply close the menu if it's not the id of the menu.

0

Here is how I accomplished this... 1. on outer div of parent component fire application event on click. 2. in child menu component. have onclick handler on input and menu that sets an attribute saying hey this child was clicked. 3. in child menu component handle parent component event (can't really get any info about it but you know it happens look at attribute value in #2. if false.. hide the menu.. if true.. don't hide.. set it back to false. 4. to handle tabbing an not mouse clicks on parent component. in the onfocus event of every input fire the same event as #1.

whew.. a lot of work to close a menu but seems to work.

0

i found a way to do this which is working for me.

<div class="slds-form-element__control" tabindex="0" onblur="{!c.closeMenu}">
            <div class="slds-combobox_container">
                <div class="slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click" aria-expanded="true" aria-haspopup="listbox" role="combobox" id="combo-parent-div">

i added tabindex=0 to the top parent of combobox and i am calling a function "closeMenu" on blur of that.

closeMenu : function(component, event, helper) {
    var comboParentDiv = document.getElementById('combo-parent-div');
    comboParentDiv.classList.remove('slds-is-open');
},

i am just removing 'slds-is-open' from combobox. its working for me.

Thanks,

0

Here is the entire code:

  <aura:component >
    <aura:attribute name="options" type="List" default="[{'label': 'New', 'value': 'new'},{'label': 'In Progress', 'value': 'inProgress'},{'label': 'Finished', 'value':'finished'},]"/>
    <aura:handler name="init" value="{! this }" action="{! c.init }"/>
    <aura:attribute name="RelatedTo" type="String" />
    <div class="slds-grid">
        <div class="slds-col slds-size_1-of-6">   
            <div class="slds-form-element">
                <label class="slds-form-element__label" for="combobox-id-12">Relate to</label>
                    <div class="slds-form-element__control">
                        <div class="slds-combobox_container">
                            <div class="slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click slds-is-open"  aura:id="ddId" aria-expanded="false" aria-haspopup="listbox" role="combobox" onclick="{!c.toggleVisibility}" >
                                <div class="slds-combobox__form-element slds-input-has-icon slds-input-has-icon_right" role="none">
                                    <input type="text" class="slds-input slds-combobox__input" id="combobox-id-12" aria-autocomplete="list" aria-controls="listbox-id-9" value="{!v.RelatedTo}" onkeyup="{!c.serverCall}" autoComplete="off" role="textbox" placeholder="Search..." onblur="{!c.blurtoggleVisibility}"/>
                                </div>
                                <div id="listbox-id-9" class="slds-dropdown slds-dropdown_length-5 slds-dropdown_fluid" role="listbox">
                                    <ul class="slds-listbox slds-listbox_vertical" role="presentation">
                                        <aura:iteration items="{!v.options}" var="item" indexVar="i">
                                            <li role="presentation" class="slds-listbox__item" onclick="{!c.itemSelected}" data-selectedIndex="{#i}">                            
                                                <span id="{#'listbox-option-unique-id-'+i+1}" class="slds-media slds-listbox__option slds-listbox__option_entity slds-listbox__option_has-meta" role="option">
                                                <span class="slds-media__body singleRow">
                                                    <span class="optionTitle slds-listbox__option-text slds-listbox__option-text_entity">{#item.label}</span>
                                                </span>
                                            </span>
                                            </li>
                                        </aura:iteration>
                                    </ul>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
</aura:component>

Controller :

({
toggleVisibility : function(component, event, helper)
{
    var RelatedTo=component.get("v.RelatedTo");
    if (RelatedTo==null || RelatedTo.length==0)
    {
        var ddDiv = component.find('ddId');
        $A.util.toggleClass(ddDiv,'slds-is-open');
        console.log("Toggle");
    }
    else
        console.log('We will not toggle as the value is selected');
},

blurtoggleVisibility : function(component, event, helper)
{
    var RelatedTo=component.get("v.RelatedTo");
    if (RelatedTo==null || RelatedTo.length==0)
    {
        var ddDiv = component.find('ddId');
        window.setTimeout(
            $A.getCallback(function() {
                $A.util.toggleClass(ddDiv,'slds-is-open');
                console.log('Toggle on Blur event');
         }), 250);
    }
},

init: function(component, event, helper)
{
    var ddDiv = component.find('ddId');
    $A.util.toggleClass(ddDiv,'slds-is-open');
},

itemSelected : function(component, event, helper)
{

    console.log('We are in Select function');
    var target = event.target;   
    var SelIndex = helper.getIndexFrmParent(target,helper,"data-selectedIndex");  
    if(SelIndex){
        var selectedOption = component.get("v.options");
        var selItem = selectedOption[SelIndex];
        if(selItem.label){
            console.log('Selected Value : ',selItem.label);
            component.set("v.RelatedTo",selItem.label);
        }
    }
},

serverCall : function(component, event, helper) {  
    var target = event.target;  
    var RelatedTo = target.value; 
    var PreviousRelatedValue=component.get("v.RelatedTo");
    if (RelatedTo.length==0 || RelatedTo==null)
    {
        var ddDiv = component.find('ddId');
        $A.util.toggleClass(ddDiv,'slds-is-open');
        console.log('Value in input box is empty');
    }
    else if ((PreviousRelatedValue==undefined && (RelatedTo.length>0)) ||
             ((PreviousRelatedValue!=undefined) && (PreviousRelatedValue.length==0) && (RelatedTo.length>0)))
    {
        var ddDiv = component.find('ddId');
        $A.util.toggleClass(ddDiv,'slds-is-open');
    }
    else
    {
        console.log('Value in the Input Box:',RelatedTo);
    }
    component.set("v.RelatedTo",RelatedTo);
    console.log('Value in Variable :',component.get("v.RelatedTo"));
}

})

Helper:

({
getIndexFrmParent : function(target,helper,attributeToFind){
    //User can click on any child element, so traverse till intended parent found
    var SelIndex = target.getAttribute(attributeToFind);
    while(!SelIndex){
        target = target.parentNode ;
        SelIndex = helper.getIndexFrmParent(target,helper,attributeToFind);           
    }
    return SelIndex;
},
})

Above code is the use case for Lightning Combobox and hides the dropdown, when clicked outside. Just copy paste the code. It would work. You may further customize it.

0

I achieve this by doing something like this, following ChiCloud response, I try to listen to click events on my component and on document, and react based on it. Does not feel like the most optimal way since I am messing with the DOM outside my component. But all methods using onblur do not help me as I have interactions in my menu (like deleting items from list) and as soon as I click on focused element it closes it.

closeMenuWhenClickOutside() {
        let componentClick = false;
        document.addEventListener("click", (event) => {
          if(!componentClick) {
              this.hideMenu();
            }; 
        });
        this.template.addEventListener("click", (event) => {
          componentClick = true;
          setTimeout(() => {
            componentClick = false;
            }, 300);
        });
      };

You must log in to answer this question.

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