2
  • Let's say I open a popup overlay box with a background that can be clicked to close it.

  • Then in that box I have a text input.

  • If the user drags their mouse to select all the text from the input and overshots by releasing the mouse over the background, then the click event fires and the whole popup closes.

Of course I could use other events on that background like mousedown or pointerdown but it leads to a loophole with all sorts of other issues. For example, if the background uses that then all other elements of the page should use the same type of event in order to make the stopPropagation() calls work. This creates many problems, such as the fact that a button mousedown/pointerdown will trigger before an input blur, and so on.

Is there a way to know from the click event of that background that the mousedown was not initiated on that same element? (meaning it was a drag)

Otherwise, is there another simple way to deal with this issue?

CODE EXAMPLE: https://jsfiddle.net/4aLz1ft7/

EDIT: Of course it should be a reasonably simple solution (or as simple as possible). Adding an event on every input is not realistic because this content could be dynamic, could contain anything else. Ideally the solution would happen with the wrappers themselves (#overlay and #overlay_content).

FlorianB
  • 2,188
  • 18
  • 30
  • Similar - https://stackoverflow.com/questions/58353280/prevent-click-when-leave-drag-to-scroll-in-js – James Sep 20 '22 at 15:26
  • Is this actual code or just a discussion? which elements actually have events assigned? are you assigning the click event to a div or the background img? if the event is assigned to the div, it seems to fire for anything within that div ( as expected ). Code can help to reproduce and solve this. – This Guy Sep 20 '22 at 15:33
  • @ThisGuy : Just added JSfiddle – FlorianB Sep 20 '22 at 15:51

2 Answers2

0

so this is working by removing the click event from your overlay using a mousedown event on the content, and then adding it back 1 second after mouseup. I add this back using either of two different events, mouseup on overlay and content.

1 second is just a basic time, it could work properly with less.

I'm not sure why .stopPropagation() or .stopImmediatePropagation() is not working though...

updated the fiddle:

https://jsfiddle.net/1763toyx/1/

ele_overlay.addEventListener('click', overlay_close, false);

ele_overlay_content.addEventListener('mousedown', (v) => {
  ele_overlay.removeEventListener('click', overlay_close);
}, false);

ele_overlay_content.addEventListener('mouseup', (v) => {
  ele_overlay.removeEventListener('click', overlay_close);
  setTimeout(() => {
    ele_overlay.addEventListener('click', overlay_close, false);
  }, 1000);
}, false);

ele_overlay.addEventListener('mouseup', (v) => {
  setTimeout(() => {
    ele_overlay.addEventListener('click', overlay_close, false);
  }, 1000);
}, false);

UPDATE: Cleaner, but still specific implementation https://jsfiddle.net/7jbdkovy/2/

function assignListener() {
  ele_overlay.addEventListener('click', overlay_close, false);
}
assignListener();

function removeListener() {
  ele_overlay.removeEventListener('click', overlay_close);
}

ele_overlay_content.addEventListener('mousedown', (v) => {
  removeListener();
}, false);

ele_overlay_content.addEventListener('mouseup', (v) => {
  removeListener();
  setTimeout(() => {
    assignListener();
  }, 1);
}, false);

ele_overlay.addEventListener('mouseup', (v) => {
  setTimeout(() => {
    assignListener();
  }, 1);
}, false);
This Guy
  • 495
  • 4
  • 9
  • hmm... I'm sorry but that's ultra dirty, isn't it? – FlorianB Sep 20 '22 at 17:31
  • @FlorianB -- it's working within the constraints that you have laid out. functions against the #overlay and #overlay_content. as for the repeating aspect, a refactor could be applied to remove some duplication, but it does work. So, "dirty" and working vs clean and doesn't... the choice is yours :D haha – This Guy Sep 20 '22 at 17:40
  • Ok. I'm just hoping someone might come up with a both clean and working solution that nobody is thinking about ;) But it's true that sometimes only dirty solutions work. We'll see if that's true in this case. – FlorianB Sep 20 '22 at 18:14
0

All right, if we're allowed to go (somewhat) dirty, I have another approach than the one @ThisGuy mentioned.

https://jsfiddle.net/1e0yjofc/1/

const ele_overlay = document.getElementById('overlay');
const ele_overlay_content = document.getElementById('overlay_content');

function overlay_open(){
    ele_overlay.classList.add('open');
}

function overlay_close(){
    ele_overlay.classList.remove('open', 'waitingForClick');
}

ele_overlay_content.addEventListener('pointerdown', function(v){
    v.stopPropagation();
}, false);

ele_overlay_content.addEventListener('click', function(v){
    v.stopPropagation();
    ele_overlay.classList.remove('waitingForClick');
}, false);

ele_overlay.addEventListener('pointerdown', function(v){
    ele_overlay.classList.add('waitingForClick');
}, false);

ele_overlay.addEventListener('click', function(v){
    if( ele_overlay.classList.contains('waitingForClick') )
        overlay_close();
}, false);

The idea is to set a class on the wrapper (background) of the overlay that allows the click event to trigger. And we only allow it if it is preceded by a pointerdown event, because as far as I know a click cannot happen without a pointerdown, or can it?

Of course this could also be a variable instead of a class, but then we would have to deal with declaring it somewhere and it would also make it hard to scale with highly dynamic content (like a whole library). Right now the whole code still doesn't need any global variable since the getElementById calls can be made in the functions themselves.

I find that slightly less dirty than messing around with timers, but if someone has a simpler idea I would love to hear it.

FlorianB
  • 2,188
  • 18
  • 30