【问题标题】:webcomponents - hide dropdown menu on window.onclickwebcomponents - 在 window.onclick 上隐藏下拉菜单
【发布时间】:2017-06-03 04:05:55
【问题描述】:

在 shadowDOM 中创建下拉菜单

几乎完美, 但问题是如何在单击窗口中的任何其他位置时隐藏下拉菜单

class NavComponent extends HTMLElement {
    constructor() {
        super();

        let template = document.currentScript.ownerDocument.querySelector('template');
        let clone = document.importNode(template.content, true);
        let root = this.attachShadow({ mode: 'open' });
        root.appendChild(clone);
    }


    connectedCallback() {
        let ddc = this.shadowRoot.querySelectorAll('.dropdowncontainer')
        let dd = this.shadowRoot.querySelectorAll('.dropdown');
        let ddc_length = ddc.length

        for (let index = 0; index < ddc_length; index++) {
            ddc[index].addEventListener('click', e => {
                dd[index].classList.toggle('show');
            });
        }

        /**   have to update the code ............ */
        window.onclick = function (event) {

        }  /**  END - have to update the code ............ */

    }
}

customElements.define('app-nav', NavComponent)

完整代码请参考demo

【问题讨论】:

    标签: javascript drop-down-menu web-component shadow-dom custom-element


    【解决方案1】:

    正如@guitarino 建议的那样,最好的解决方案是定义一个下拉菜单自定义元素。

    单击菜单时,调用将显示/隐藏菜单的(第一个)事件处理程序,并在window 上添加/删除(第二个)dropdown 事件处理程序。

    只有当操作在自定义元素本身之外时,(第二个)dropdown 事件处理程序才会调用第一个事件处理程序。

    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 )   
    }
    

    不管有没有 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>

    【讨论】:

      【解决方案2】:

      一个不相关的建议:您可能应该将.dropdown 分离到它自己的&lt;app-nav-dropdown&gt; 组件中,并在其'constructor' 或'connectedCallback' 中分配'click' 事件侦听器。

      解决您的问题的最佳方法是

      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);
          }
      });
      

      注意:我将 addEventListener 与 true 一起使用,这样事件就会在捕获时发生,因此它不会在 .dropdown 点击处理程序之后立即发生。

      你的handleDropdownUnfocus 看起来像

      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);
      }
      

      这个解决方案有一个问题,如果您在打开菜单项后再次单击它,它将同时调用.dropdownwindow 处理程序,最终结果将是下拉菜单将保持打开状态。要解决这个问题,您通常会在handleDropdownUnfocus 中添加一个检查:

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

      但是,它不起作用。即使您单击.dropdown,由于Shadow DOM,您的e.target 也将是app-nav 元素。这使得切换变得更加困难。我不知道你会如何解决这个问题,也许你可以想出一些花哨的东西,或者停止使用 Shadow DOM。

      另外,您的代码有一些危险信号...例如,您在 for 循环中使用 let 关键字,这很好。对let 的支持仍然非常有限,因此您更有可能转译。转译器只会将每个let 更改为var。但是,如果您在循环中使用var,则在循环中分配处理程序将不再起作用,因为每个处理程序的index 将引用最后一个下拉列表(因为现在索引将在函数的上下文中是全局的,而不是每个循环实例的局部)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-08-18
        • 2014-03-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-01
        • 1970-01-01
        相关资源
        最近更新 更多