Search code examples
javascriptonclickmouseeventui-automationpopupwindow

Emulating middle click on element with an "onclick" event


The Problem

For site testing, I am attempting to emulate a middle click (or scroll wheel click) on an element like the one below, using JS that is based on some other answers on here.

HTML:

<a 
  id="example-link" href="https://www.google.com/" 
  target="_blank" 
  onclick="window.open('https://www.google.com/', '', 
    'width=500, height=500, resizable=1, scrollbars=1');
     return false;">
  <span>Middle Click Me</span>
</a>

JS (looking to change something here):

var middleClick = new MouseEvent( "click", { "button": 1, "which": 2 });
jQuery("#example-link")[0].dispatchEvent( middleClick );

The issue is shown in this jsfiddle.

  1. Note that a regular click should normally dispatch a popup. jsfiddle is catching the popup and opening it in a new tab.
  2. jsFiddle is used instead of a stackoverflow fiddle to avoid confusion, as the stack overflow fiddle doesn't properly handle window.open().

Compare 1) middle-clicking manually to 2) running the code to emulate a click.

The problem is, when I trigger the middle click programmatically, it triggers both the onclick method of opening a new window, as well as opening the link in a new tab (creating both a popup and a tab instead of just a tab). If this link is middle clicked manually, the popup will not trigger.

The Question

Is there a better way of emulating middle clicks (preferably cross-browser compatible)? The way it is written, I can't really get the MouseEvent script to accurately emulate manual middle clicks.

Edit: unfortunately, I can't change the HTML object or modify the DOM for the purpose of testing integrity, so I would prefer that the onclick tag stays on the element.


Solution

  • To the best of my understanding, as long as that inline click handler is there, it will fire on every click event on that element, but here are two workarounds I've come up with.

    1. If you really can't modify the DOM even for an instant, then the first option won't serve. It cheats by changing the DOM and then restoring it, hopefully before anyone notices.

    2. The second option avoids the need to click the link at all by reading its attributes and using them to open a new tab, without firing a new click event. (This relies on the script having a basic understanding of link element's markup. Specifically, the code I've written here simply assumes that the "link" element has a valid url in its href attribute -- but if necessary, you could make other, more complex assumptions, and act conditionally based on the properties that the "link" element turns out to have at runtime.)

    Note: A third alternative would be create a custom event that has all the same core behavior as the built-in click event but without actually being a click event, the idea being to fool the onclick handler into sleeping through the event. I'm not sure how much work it would take to do a sufficiently good job of rebuilding this particular wheel. Maybe it wouldn't be too bad?


    In this script, the functions that open the new tab are triggered by button clicks, but of course this is just for demo purposes, and you'd presumably trigger them on page load or as callbacks to some other action. And the anchor element in the snippet has been changed slightly, too, for space-saving reasons

    .

    (Because the direct call to window.open doesn't appear to work properly in the Stack Overflow snippet, I followed your lead and pasted the code into a fiddle so it can be seen running in all its nominal glory.)

    const
      // Identifies some DOM elements
      cheatButton = document.getElementById("cheat-button"),
      mimicButton = document.getElementById("mimic-button"),
      exampleLink = document.getElementById("example-link"),
    
      // Defines an event to be dispatched programmatically
      middleClick = new MouseEvent("click", {button: 1, which: 2});
    
    // Demonstrates calling the functions
    cheatButton.addEventListener("click", emulateMiddleClick); // Option 1
    mimicButton.addEventListener("click", followLink); // Option 2
    
    // Defines the functions
    function emulateMiddleClick(){
      const onclickHandler = exampleLink.onclick; // Remembers onclick handler
      exampleLink.onclick = ""; // Supresses onclick handler
      exampleLink.dispatchEvent(middleClick); // Dispatches middle click
      exampleLink.onclick = onclickHandler; // Restores onclick handler
    }
    
    function followLink(){
      if(!exampleLink.href){ return; } // In case href attribute is not set
      const {href, target} = exampleLink; // Gets props by using "destructuring"
    
      // This doesn't seem to work in the Stack Overflow snippet...
      window.open(href, (target || "_blank")); // In case target attribute is not set
    
      // ...But we can at least log our intention to open a new tab
      console.log(`opening: "${href}" (in "${target}")`);
    }
    div{ margin: 1em; }
    button{ padding: 0.3em; border-radius: 0.5em; }
    span{ margin: 1.3em; }
    <div>
      <button id="cheat-button">Fake Click, and Suppress Handler</button>
    </div>
    <div>
      <button id="mimic-button">Follow Link without Clicking</button>
    </div>
      <a id="example-link"
         href="https://www.google.com/"
         target="_blank"
         onclick="window.open('https://www.google.com/', '', 'width=50, height=50');">
        <span>Middle Click Me</span>
      </a>