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
- Add a custom context menu to your
code
andpre
elements. In your case there is only one itemcopy
needed (in the example quick! and very! simplified! demonstrated as simple box). - When open the menu deactivate right click to avoid stopping the selection of your code (was your question/issue as I understand)
- When open the menu deactivate right click on other elements so you don’t have other context menus open
- When click on menu item start copy action close and reset
- 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>