skip to Main Content

Consider

<javascript>
function onClick() {
  console.log("onclick");
}
function onMouseDown() {
  const child = document.querySelector("#child");
  child.replaceWith(child.cloneNode(true));
  console.log("onmousedown");
}
</javascript>
<body>
  <div id="child" onclick="onClick()" onmousedown="onMouseDown()">click me</div>
</body>

The output onclick does not appear in the browser console unless I comment out the statement child.replaceWith(child.cloneNode(true)). I think that this is because the event target is replaced in the onmousedown event handler, which is invoked before onclick.

Is there a way to make this work? Basically I need to modify div contents in my onmousedown and onmouseup handlers and receive a click event as well.

3

Answers


  1. As @boreddad420 pointed out, "when you clone the node and replace it,
    you remove all of the event listeners for that node".

    If you try to add the click handler back in (on the new node),
    the onClick() function will still not be called, since the mouse activity
    was destined for the node that you have replaced.

    "Basically I need to modify div contents", so do just that, modify
    the existing div, don’t replace it. What is it about the div that
    you want to change? Perhaps we can help you out with that.

    Login or Signup to reply.
  2. Use setTimeout to simulate onClick.

    let mouseBTN=0;
    
    function onMouseDown() {
      mouseBTN=1;
      setTimeout(function(){mouseBTN=0;},200); // onClick timeout
      const child = document.querySelector("#child");
      child.replaceWith(child.cloneNode(true));
      document.getElementById("showResult").innerHTML="down";
    }
    
    function onMouseUp() {
      const child = document.querySelector("#child");
      child.replaceWith(child.cloneNode(true));
      document.getElementById("showResult").innerHTML+="+up";
      if(mouseBTN){onclick();}
      mouseBTN=0;
    }
    
    function onclick(){
      document.getElementById("showResult").innerHTML+="+clicked";
    }
    <body>
      <div id="child" onmousedown="onMouseDown()" onmouseup="onMouseUp()" onclick="onclk()">click me</div>
    
      <div id="showResult"></div>
    </body>
    Login or Signup to reply.
  3. The click event will fire on the node that received both the mousedown and the mouseup events of a same gesture. At the time the mousedown event is fired, your clone didn’t exist it thus can’t have received it.

    What you can do, is to replicate this behavior yourself, by adding a mouseup event on the document and checking if the next such event fired on your clone (or one of its descendants). In this case, you can fire a new synthetic click event based on the mouseup event so that all its properties are set.

    function onClick() {
      console.log("onclick");
    }
    
    const child = document.getElementById("child");
    child.addEventListener("mousedown", (evt) => {
      console.log("down on original");
      // Keep a reference to the clone, we'll need it later
      const clone = child.cloneNode(true);
      child.replaceWith(clone);
      // Add a single mouseup event handler
      document.addEventListener("mouseup", (evt) => {
        // If we clicked inside the clone (including its descendants)
        if (clone.contains(evt.target)) {
          // Build a click event based on the current mouseup
          const clickEvent = new MouseEvent("click", evt);
          // Let the mouseup event bubble entirely
          setTimeout(() => {
            // Nothing stopped us
            if (!evt.defaultPrevented) {
              evt.target.dispatchEvent(clickEvent);
            }
          }, 0);
        }
      }, { once: true });
    });
    <div id="child" onclick="onClick()">click me</div>

    One thing to note, for non-synthetic events (the "normal" ones), the click event would occur right after the mouseup event has finished bubbling. We don’t have a way to access this time precisely. So we have a few possibilities:

    • Keep it simple by firing the click event in the mouseup handler directly, and accept that the click event can’t be prevented by later higher mouseup handlers.
    • Or, we could add a capturing click event on the root document element, and stop its propagation if the next mouseup was on the clone. But in this case another capturing handler added before ours would still catch this event.
    • Or, as shown in above demo, delay the click event by a full task, which means that other higher priority tasks could slip in-between both events (e.g. a full paint could occur).
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search