Skip to content
Advertisement

How to test if contextmenu is open or displayed?

I have a page with a few pre tags in it containing computer code. I have a mouseover event listener, which highlights all the code in the pre tag. I also have it remove the highlighting on a mouseout event. Works real well if you use the keyboard to copy (ctrl-C).

But if you want to right-click and copy from the context menu, there is a problem. The moment the mouse enters the context menu, it triggers the mouseout event of the pre tag.

I need a way to test if the context menu is currently open or displayed. Then I can cancel removing the highlighting. Is there a way to test if the context menu is open or displayed?

I don’t want anything jquery, please.

My final alternative to this problem might be the oncontextmenu, but I don’t know how I would find out if it closes. Unless I try an event listener for the mouseout event of the context menu, if posible.

Here’s my code so far:

window.onload = function(){

    function selectText(element) {
        var range, selection;

        if(window.getSelection) {
            selection = window.getSelection();
            range = document.createRange();
            range.selectNodeContents(element);
            selection.removeAllRanges();
            selection.addRange(range);
        }
    }

    function unSelectText() {
        window.getSelection().removeAllRanges();
    }

    preTags = document.getElementsByTagName('PRE');

    for(var i = 0; i < preTags.length; i++) {
        preTags[i].onmouseover = function() {selectText(this)};
        preTags[i].onmouseout = function() {unSelectText(this)};
    }

    codeTags = document.getElementsByTagName('CODE');

    for(var i = 0; i < codeTags.length; i++) {
        codeTags[i].onmouseover = function() {selectText(this)};
        codeTags[i].onmouseout = function() {unSelectText(this)};
    }
};

Advertisement

Answer

As there is no event in JS to trigger the closing action of the context menu box and no reliable workarround: so far as I know from different researches the answer to your question is no.

But there is a way to solve your problem with an custom context menu if you may think about a similar method.

Short explanation

  1. Add a custom context menu to your code and pre elements. In your case there is only one item copy needed (in the example quick! and very! simplified! demonstrated as simple box).
  2. When open the menu deactivate right click to avoid stopping the selection of your code (was your question/issue as I understand)
  3. When open the menu deactivate right click on other elements so you don’t have other context menus open
  4. When click on menu item start copy action close and reset
  5. When click outside menu close and reset

SIMPLIFIED example

Please notice: the example is a QUICK, DIRTY AND VERY SIMPLIFIED example to demonstrate the technique. I tsure needs to be adapted to your special project.

window.onload = function(){

    // general vars
    let isOpenContextMenu = false;
    const $contextMenu = document.getElementById('contextMenu');
    // all code/pre elements in one object to use in one loop
    const $codeElements = document.querySelectorAll('pre, code');
    let $actualCodeElement = {};



    // methods
    function selectText(element) {
        var range, selection;

        if(window.getSelection) {
            selection = window.getSelection();
            range = document.createRange();
            range.selectNodeContents(element);
            selection.removeAllRanges();
            selection.addRange(range);
        }
    }

    function unSelectText() {
        window.getSelection().removeAllRanges();
    }



    // listeners


    // block right clicke when context menu on code/pre element is open
    function listenerContextMenuBlocked(evt){
        evt.preventDefault();
    }


    // clicks when context menu on code/pre elements is open
    function listenerMenuClick(ev){ 

        let $clickedElement = ev.target;
        do{

            if($clickedElement == $contextMenu){

                // clicked on context menu

                // --> copy action
                let codeToCopy = $actualCodeElement.innerText;
                let temporaryInput = document.createElement('input');
                temporaryInput.type = 'text';
                temporaryInput.value = codeToCopy;
                document.body.appendChild(temporaryInput);
                temporaryInput.select();
                document.execCommand('Copy');
                document.body.removeChild(temporaryInput);

                // --> close menu and reset
                $contextMenu.classList.remove('contextMenu--active');
                isOpenContextMenu = false;
                window.removeEventListener('contextmenu', listenerContextMenuBlocked);
                return;
            }
            $clickedElement = $clickedElement.parentNode;

        } while($clickedElement)


        // clicked outside context menu
        // --> close and reset
        $contextMenu.classList.remove('contextMenu--active');
        isOpenContextMenu = false;
        window.removeEventListener('contextmenu', listenerContextMenuBlocked);

    }


    // open custom context menu when right click on code/pre elements
    function listenerOpenContextMenuCodeBlock(e) {

        e.preventDefault();
    
        // used to copy conten in listenerMenuClick() 
        $actualCodeElement = e.target;
            
        if(false === isOpenContextMenu){
            
            // open context menu
            $contextMenu.style.top = e.clientY + 2 + 'px';
            $contextMenu.style.left = e.clientX + + 2 + 'px';
            $contextMenu.classList.add('contextMenu--active');
            
            isOpenContextMenu = true;
            window.addEventListener('click', listenerMenuClick);
            window.addEventListener('contextmenu', listenerContextMenuBlocked);

        }

    }



    for(var i = 0; i < $codeElements.length; i++) {

        //$actualElement = $codeElements[i];  // 
        $codeElements[i].addEventListener('contextmenu', listenerOpenContextMenuCodeBlock);
        $codeElements[i].onmouseover = function() {
            if(false === isOpenContextMenu){
                selectText(this)
            }
        };
        $codeElements[i].onmouseout = function() {
            if(false === isOpenContextMenu){
                unSelectText(this)
            }
        };
    }

};
/* styles needed for custom context menu */
html {
    position: relative;

}
#contextMenu {
    display: none;
    position: absolute;
    font-family: sans-serif;
    font-size: 11px;
    line-height: 12px;
    padding: 2px 5px;
    background-color: #eeeeee;
    border: 1px solid #a5a5a5;
    box-shadow: 2px 3px 1px -1px rgba(0,0,0,0.4);;
    cursor: context-menu; 
    z-index: 10;
}

#contextMenu:hover {
    background-color: #aad7f3;
}
#contextMenu.contextMenu--active {
    display: block;
}
<!-- SIMPLIFIED custom context menu for code/pre elements = hidden on page -->
<nav id="contextMenu" style="top: 50px; left: 30px" >
    <div>Copy Codeblock</div>
</nav>


<!-- example elements for code presentation / testing -->
<pre>
    Lorem, ipsum dolor sit amet consectetur adipisicing elit. 
    Provident magni blanditiis, ea necessitatibus esse nihil, 
    quae iste explicabo beatae perspiciatis quibusdam tempora minima, 
    eos molestias illum voluptatum voluptate ipsum perferendis!
</pre>

<code>
    Li Europan lingues es membres del sam familie. Lor separat existentie 
    es un myth. Por scientie, musica, sport etc, litot Europa usa li sam vocabular. 
    Li lingues differe solmen in li grammatica, li pronunciation e li plu commun vocabules. 
    Omnicos directe al desirabilite de un nov lingua franca: On refusa continuar payar 
    custosi traductores. 
</code>
User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement