webcomponents - hide dropdown menu on [removed]

后端 未结 2 1030
长发绾君心
长发绾君心 2021-01-27 10:48

dropdown menu is created in shadowDOM

almost perfect, but the problem is how to hide the dropdown menu when click on any where else in window

class NavC         


        
相关标签:
2条回答
  • 2021-01-27 10:56

    An unrelated suggestion: you should probably separate .dropdown into its own <app-nav-dropdown> component and assign the 'click' event listeners in its 'constructor' or 'connectedCallback'.

    The best idea for your problem is to

    ddc[index].addEventListener('click', e => {
        if(dd[index].classList.contains('show')) {
            dd[index].classList.remove('show');
            window.removeEventListener('click', handleDropdownUnfocus, true);
        }
        else {
            dd[index].classList.add('show');
            window.addEventListener('click', handleDropdownUnfocus, true);
        }
    });
    

    Note: I use addEventListener with true, such that the event happens at capture, so that it's will not happen immediately after the .dropdown click handler.

    And your handleDropdownUnfocus will look like

    function handleDropdownUnfocus(e) {
        Array.from(document.querySelectorAll('app-nav')).forEach(function(appNav) {
            Array.from(appNav.shadowRoot.querySelectorAll('.dropdown')).forEach(function(dd) {
                dd.classList.remove('show');
            });
        });
        window.removeEventListener('click', handleDropdownUnfocus, true);
    }
    

    There's a problem with this solution though, that if you click on the menu item again after opening it, it will call both .dropdown and window handlers, and the net result will be that the dropdown will remain open. To fix that, you would normally add a check in handleDropdownUnfocus:

    if(e.target.closest('.dropdown')) return;
    

    However, it will not work. Even when you click on .dropdown, your e.target will be app-nav element, due to Shadow DOM. Which makes it more difficult to do the toggling. I don't know how you'd address this issue, maybe you can come up with something fancy, or stop using Shadow DOM.

    Also, your code has some red flags... For example, you use let keyword in your for-loop, which is fine. The support for let is very limited still, so you're more likely going to transpile. The transpiler will just change every let to var. However, if you use var in your loop, assigning the handlers in a loop like that will not work anymore, because index for every handler will refer to the last dropdown (because index will now be global within the function's context and not local for every loop instance).

    0 讨论(0)
  • 2021-01-27 11:03

    The best solution, as @guitarino suggested is to define a dropdown menu custom element.

    When the menu is clicked, call a (first) event handler that will show/hide the menu, and also add/remove a (second) dropdown event handler on window.

    At its turn, the (second) dropdown event handler will call the first event handler only if the action is outside the custom element itself.

    connectedCallback()
    {        
        //mousedown anywhere
        this.mouse_down = ev => !this.contains( ev.target ) && toggle_menu()
    
        //toggle menu and window listener 
        var toggle_menu = () => 
        {
            if ( this.classList.toggle( 'show' ) )
                window.addEventListener( 'mousedown', this.mouse_down )
            else
                window.removeEventListener( 'mousedown', this.mouse_down )
        }
    
        //click on menu
        this.addEventListener( 'click', toggle_menu )   
    }
    

    It works with or without Shadow DOM:

    customElements.define( 'drop-menu', class extends HTMLElement 
    {
      constructor ()
      {
        super()
        this.attachShadow( { mode: 'open'} )
            .innerHTML = '<slot></slot>'
      }
    
      connectedCallback()
      {        
        //mousedown anywhere
        this.mouse_down = ev => !this.contains( ev.target ) && toggle_menu()
            
        //toggle menu and window listener 
        var toggle_menu = () => 
        {
          if ( this.classList.toggle( 'show' ) )
            window.addEventListener( 'mousedown', this.mouse_down )
          else
            window.removeEventListener( 'mousedown', this.mouse_down )
        }
    
        //click on menu
        this.addEventListener( 'click', toggle_menu )   
      }
    
      disconnectedCallback ()
      {
        this.removeEventListener( 'mousedown', this.mouse_down )
      }
    } )
    drop-menu  {
        position: relative ;
        cursor: pointer ;
        display: inline-block ;
    }
    
    drop-menu > output {
        border: 1px solid #ccc ;
        padding: 2px 5px ;
    }
    
    drop-menu > ul {
        box-sizing: content-box ;
        position: absolute ;
        top: 2px ; left: 5px ;
        width: 200px;
        list-style: none ;
        border: 1px solid #ccc ;
        padding: 0 ;
        opacity: 0 ;
        transition: all 0.2s ease-in-out ;
        background: white ;
        visibility: hidden ;
        z-index: 2 ;
    
    }
    
    drop-menu.show  > ul {
        opacity: 1 ;
        visibility: visible ;
    }
    
    drop-menu > ul > li {
        overflow: hidden ;
        transition: font 0.2s ease-in-out ;
        padding: 2px 5px ;
        background-color: #e7e7e7;
    }
    
    drop-menu:hover {
        cursor: pointer;
        background-color: #f2f2f2;
    }
    
    drop-menu  ul li:hover {
        background-color: #e0e0e0;
    }
    
    drop-menu ul li span {
        float: right;
        color: #f9f9f9;
        background-color: #f03861;
        padding: 2px 5px;
        border-radius: 3px;
        text-align: center;
        font-size: .8rem;
    }
    
    drop-menu ul li:hover span {
        background-color: #ee204e;
    }
    <drop-menu><output>Services</output>
      <ul>
        <li>Graphic desing</li>
        <li>web design</li>
        <li>app design</li>
        <li>theme</li>
      </ul>
    </drop-menu>
    <drop-menu><output>tutorial</output>
      <ul>
        <li>css <span>12 available</span></li>
        <li>php <span>10 available</span></li>
        <li>javascript <span>40 available</span></li>
        <li>html <span>20 available</span></li>
      </ul>
    </drop-menu>

    0 讨论(0)
提交回复
热议问题