Event Bubbling (with a challenge at the end)

Event Bubbling

<div id="parentEl">
  <div></div>
  <div></div>
  <div></div>
</div>
#parentEl {
  position: relative;
}
#parentEl div {
  position: relative;
  width: 200px;
  height: 100px;
  left: 0px;
  border: 2px solid red;
}

If we register just the parent element, we need only one listener attached. Any event triggered on any of the children will bubble up to the parent. We can then trace the event back to its source target by polling the event object that is instantiated by the event.

const parentEl = document.querySelector('#parentEl')

This node is now permanently cached (for session length). It is dynamic in nature meaning it can be constantly updated (elements can be added to it, changed or taken away at liberty).

The action you wish to effect with each click is defined in the event handler as you well know.

const handler = function (e) {
  const target = e.target
  const styles = window.getComputedStyle(target)
  const offset = styles.getPropertyValue('left')
  const newOffset = `${+(offset.replace('px', '')) + 10}px`
  target.textContent = newOffset
  target.style.left = newOffset
}
parentEl.onclick = handler

Given the child divs have a width less than the parent, and they have a border so that we can see them, the above will move whichever box you click to the right by 10px, and will show its currrent offset.

The above process leverages bubbling and node targeting in such a way as to allow us to delegate which handler to use, if we so wish. As it stands, there is no delegation.

Event Bubbling and Node Targeting


When you find yourself going into a rabbit hole, why not see where it goes? We’ve taken the above example to a new level by delegating within the handler depending whether the Shift key is down or not.

Event Bubbling and Delegation

style.css
body {
  background: #000;
  color: #fff;
  padding: 25px;
  font-size: 100%;
  font-family: sans-serif;
}
#parentEl {
  width: 100%;
  position: relative;
}
#parentEl div {
  box-sizing: border-box;
  position: relative;
  margin: 0 auto;
  text-align: center;
  line-height: 96px;
  width: 200px;
  height: 100px;
  left: 0px;
  border: 2px solid red;
  user-select: none;
}
h1 {
  text-align: center;
  font-size: 1em;
}
script.js
const parentEl = document.querySelector('#parentEl')
const handler = function (e) {
  const target = e.target
  if (target.parentNode == parentEl) { 
    const styles = window.getComputedStyle(target)
    const offset = styles.getPropertyValue('left').replace('px', '')
    let newOffset;
    if (e.shiftKey) {
      newOffset = `${+offset - 10}px`
    } else {
      newOffset = `${+offset + 10}px`
    }
    target.textContent = newOffset
    target.style.left = newOffset
  }
}
parentEl.addEventListener('click', handler)

1 Like

We’ve gone one step further, so the code sample above is all that is preserved. The link is to the current code. Now we can move our boxes in any general direction, vertical or horizontal. This is still within the single handler.

There are three modifiers to the click event that triggered this handler. The default behavior is what a non-infringed event would look like: Move to the right ten pixels. The modifiers, well, they modify the behavior.

Behavior, as we see it, coordinates as the program sees. It’s SOP without any cleverness involved. Just playing the role.

Bottom line, we did this not because we can, which is obvious, but because we wanted to. It still took some learning along the way.

There are numerous subtleties that would escape any learner who hasn’t buckled in. This writer always insists upon simplicity. The subtlety begins there.

1 Like

yup_thats_what_happensPNG

1 Like

Due to reliability issues with the workspace,

https://replit.com/@mtf/Event-Bubbling-and-Delegation

1 Like

Readers who have come this far deserve some explanatory narrative. They will be ready for it, one can trust.

The obvious is, well, obvious. We’ve hooked on to a document element, registered a listener on that node, and given an event handler function to invoke whence triggered.

We’re concerned about bubbling and the adverse side effects clicking outside of the designated regions can have.

Once we have cached the target node, it’s important to know that it does not exceed our requisite zone.

const handler = function (e) {
  const target = e.target
  if (target.parentNode == parentEl) { 

We only want elements that are children of parentEl.

The next line will cache the style attributes of the target element, being now approved.

    const styles = window.getComputedStyle(target)

Now with that object we can target the attributes…

    const vOffset = +(styles.getPropertyValue('top').replace('px', ''))
    const hOffset = +(styles.getPropertyValue('left').replace('px', ''))

The net from this operation is two integers. Their use is covered in the previous discussion of default and modifiers, so is moot now. For the fun of it, we’ve added border color changes with each transition.

  target.style.borderColor = `hsl(${Math.floor(Math.random() * 19) * 20}, 100%, 50%)`

HSL lets us only have to generate one random number which above is 0…18, then multiplied by 20 gives us an absolute range of 360. The hues are now multiples of 20 degrees around the color wheel. The saturation is 100% for the narrowest bandwidth in the particular color spectrum. Luminosity is set at 50% so we can limit the approach to white light. The higher we go from here, the more white we are adding. Luminosity of 100% in any hue/saturation will still be white.

In other words, the latter two arguments may well be constants and the only computed value is the hue. For this given case, HSL has proven a viable function in CSS, and one we can easily work with in script in a dynamic fashion, not just string literal.

This demo contains and illustrates some really useful stuff to add to one’s kit. Only by thinking this way can we expect it to surface naturally in our code patterns.

if (! (target.parentNode == parentEl)) return false;

would appear to the user as if nothing happened. It was a non-event.

1 Like

We still haven’t delegated functions yet, but now that we’ve covered the above conditional delegation, functions are almost moot. So here is my challenge: Without using id or class, have each of the three target elements show a different computation, +, *, and exponent. Use the values in the existing variables in your expressions. Have it display in its respective element. (I’m content with just the code.)