How to make sure clickable objects don't propagate to the wrong element?

Viewed 80

Languages involved: HTML, CSS, JS

Context: I'm relatively new to web development. I have two elements overlapping each other. One is a slider, one is a div. The slider is on top of the div.

Code snippets:

<div id="myDiv">
  <input id="mySlider" type="range" min=1 max=100 step=1>
</div>

and

initListeners() {
  document.getElementById("myDiv").addEventListener("click", divFunction);
  document.getElementById("mySlider").addEventListener("input", sliderFunction);
}

I need to make it that when you click the slider, it doesn't click the div. How would I go about doing that? I've tried z-index, but that doesn't seem to change anything.

Thanks in advance!

3 Answers

As I'm sure you've figured out by now, events in JavaScript by default bubble up from a child to a parent. You need to stop that from happening at the child level, also known as preventing propagation.

Using the stopPropagation function, you can handle this as follows:

function sliderFunction(e) {
     e.stopPropagation();
}

Simple. That event will no longer reach the parent.

EDIT

While stop propagation is the correct method to use, event listeners must also match in type. Therefore, both the slider and the parent DIV must have click event listeners (instead of input and click). stopPropagation stops propagation of a specific type of event.

function divFunction() {
 console.log('DIV clicked!');
}

function sliderFunction(event) {
  event.stopPropagation();
 console.log('Slider clicked!');
}

function initListeners() { 
 document.getElementById('myDiv').addEventListener('click', divFunction);
  document.getElementById('mySlider').addEventListener('click', sliderFunction); 
} 

initListeners();
/* unnecessary visual aides */

body *:not(label) {
  padding: 2rem;
  outline: 1px solid red;
  position: relative;
}

label {
  display: inline-block;
  position: absolute;
  background: #222;
  color: #fff;
  top: 0; left: 0;
}
<div id="myDiv">
  <label>#myDiv</label>
  <div id="tools">
    <label>#tools</label>
    <input type="range" id="mySlider">
  </div>
</div>

You can also check the target once you fire that click event. I've used this approach before:

JSFiddle: http://jsfiddle.net/L4ck7ygo/1/

function divFunction(e) {
  if (e.target !== this) {
    return;
  } else {
    console.log('hit');
  }
}

When the fiddle first loads, click the slider and you'll see the console log out some text. To see it work, remove the line that is being pointed to and rerun the fiddle. Now when you click the slider, you won't see anything logged in the console, but if you click on the div and not the slider, it will log to the console.

function initListeners() {
  document.getElementById("myDiv").addEventListener("click", divFunction);
  document.getElementById("mySlider").addEventListener("input", sliderFunction);
}

initListeners();

function divFunction(e) {
  console.log('Firing...') // <-- This will log on any click
  if (e.target !== this) {
    return;
  } else {
    console.log('hit'); // <-- This will NOT log except for div click
  }
}

function sliderFunction() {
  console.log('Doing stuffs...');
}
<div id="myDiv">
  <input id="mySlider" type="range" min=1 max=100 step=1>
</div>

UPDATE: Stupidity on my part. I had the ordering wrong for the elements which caused propagation to not act as intended.

Related