简体   繁体   English

webcomponents-隐藏window.onclick上的下拉菜单

[英]webcomponents - hide dropdown menu on window.onclick

dropdown menu is created in shadowDOM 在shadowDOM中创建下拉菜单

almost perfect, but the problem is how to hide the dropdown menu when click on any where else in window 几乎是完美的,但是问题是当单击窗口中的其他任何位置时如何隐藏下拉菜单

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)

please refer this demo for complete code 请参考此演示以获得完整代码

The best solution, as @guitarino suggested is to define a dropdown menu custom element. @guitarino建议的最佳解决方案是定义一个下拉菜单自定义元素。

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 . 单击菜单后,调用将显示/隐藏菜单的(第一个)事件处理程序,并在window上添加/删除(第二个) dropdown事件处理程序。

At its turn, the (second) dropdown event handler will call the first event handler only if the action is outside the custom element itself. 仅当动作在自定义元素本身之外时,(第二个) 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 )   
}

It works with or without Shadow DOM: 无论有无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> 

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'. 一个不相关的建议:您应该将.dropdown分离到其自己的<app-nav-dropdown>组件中,并在其“构造函数”或“ connectedCallback”中分配“ click”事件监听器。

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. 注意 :我将addEventListener与true配合使用,以便事件在捕获时发生,因此不会在.dropdown单击处理程序之后立即发生。

And your handleDropdownUnfocus will look like 而且您的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);
}

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. 但是,此解决方案存在一个问题,如果您在打开菜单项后再次单击它,它将同时调用.dropdownwindow处理器,最终结果是下拉菜单将保持打开状态。 To fix that, you would normally add a check in handleDropdownUnfocus : 要解决此问题, 通常可以在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. 即使您单击.dropdown ,由于Shadow DOM,您的e.target也将是app-nav元素。 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. 我不知道您如何解决此问题,也许您可​​以提出一些幻想,或者停止使用Shadow DOM。

Also, your code has some red flags ... For example, you use let keyword in your for-loop, which is fine. 另外,您的代码有一些危险信号 。例如,在for循环中使用let关键字,这很好。 The support for let is very limited still, so you're more likely going to transpile. let的支持仍然非常有限,因此您更有可能进行移植。 The transpiler will just change every let to var . 转译器只会将每个let更改为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). 但是,如果在循环中使用var ,则在这样的循环中分配处理程序将不再起作用 ,因为每个处理程序的index将引用最后一个下拉列表(因为索引现在在函数的上下文中是全局的,而不是在每个函数的局部循环实例)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM