DEV Community

AlexMcD-git
AlexMcD-git

Posted on

"this" and the DOM

Hello world,

As someone with just under 3 weeks of experience with each of javascript, HTML, and css, I've found a couple things that seemed to work together very well. While surely not the first to do so, I couldn't find much information about it (probably because I'm not well versed enough in the lingo to Google it properly).

The problem, like for many beginners, starts with an array or object that itself is full of objects, some of which have their own arrays or objects. I would like to iterate through this array and add that content to the DOM. Easy enough with "forEach" and a separate function for "rendering" each of them to the DOM.

const arrayOfObjects = [{stuff}, ...,{the last of the stuff}]
arrayOfObjects.forEach(renderFunction)
Enter fullscreen mode Exit fullscreen mode

Since just looking at things is boring, I'd like to add some interactivity through event listeners. The basic breakdown is "I want to display something. I want to add an input of some kind. Input will change what is displayed". Now, to be less abstract I am going to move over to my specific example, but first a bare minimum background so anyone can understand.

I want to render the stats for the characters (also known as "champions") of "League of Legends". Understanding the gameplay is completely unrelated here. Each character has health, attack damage, armor, and many other stats. Every time you level up those stats increase. The starting number and increase per level is different for every character. Our input will be a slider, from 1(starting level) to 18(max level). The goal here is to update the displayed stat as the slider moves. For example: Character has 500 health at level 1 and 100 health per level up. Moving the slider to 6 means the character has leveled up 5 times, so their health stat should display 1000.

For over 150 Champions this is a lot of data to display. So we instead show a thumbnail for each character and clicking it will load the details at the top. Here on out, I will try to keep the amount of code to a minimum, only sharing the most relevant details.

const arrayOfObjects = [{stuff}, ...,{the last of the stuff}]
arrayOfObjects.forEach(renderFunction){
    //all the other stuff
    //setting the name of the character to the alt text of our image
    pic.alt = champion.id
    pic.addEventListener('click', clickChampionFunction)
}

const clickChampionFunction = (event)=>{
    //accessing the correct champion object, by the name(aka id)
    const featuredObject = championsObj[event.target.alt]
    statsListFeatured.innerHTML = statsRender(featuredObject.stats, 1)
}

function statsRender(statsObj, level){
    return `(string with a lot of interpolation that makes the data pretty)`
}
Enter fullscreen mode Exit fullscreen mode

Some rudimentary styling later, we can see our champion, base stats on the left side of the picture, and per-level stats on the right. Great!
Clicking a champion renders a bigger picture with stats

Now we just need to add an event listener that puts in the slider's value as the level.

    //still inside of clickChampionFunction()
    levelSlider = document.createElement('form')
    levelSlider.innerHTML = `<input type="range" min="1" max="18" value="1">`
    levelSlider.addEventListener('input', updateValue)
}

function updateValue(event){
  levelDisplay.textContent = `Level: ${event.target.value}`
  document.querySelector(`.baseStats`).innerHTML = statsRender(featuredObject.stats, event.target.value)
}
Enter fullscreen mode Exit fullscreen mode

All good, right? No. Even though the updateValue function, as of now, is only called within a block of code that has access to featuredObject, the updateValue function does not have access to that object. Since updateValue is not within the scope of the clickChampionFunction it cannot access the variable. This took a good bit of thinking (and console.log-ing to realize) the first time. So, how do we pass parameters through an event listener callback? This seems to be an old question that was also asked on stackoverflow over 13 years ago. I don't just want an answer, I want to know why. Luckily the comment "JavaScript is a prototype-oriented language, remember!" helped me get there.

What does this mean? We can see what type of data we are working with to determine how we can use it.
Google Chrome Console

So of course, you can do object things with objects. Is it really that simple? Just set a key to a value that I want pass through?

levelSlider.obj = featuredObject
console.log(levelSlider)
Enter fullscreen mode Exit fullscreen mode

Hidden or not?

Logging the object itself does not seem to reveal our data (only the DOM element), but calling levelSlider.obj will reveal the invisible data hitching a ride on our object. On the MDN page for this we can see:
"As a DOM event handler
When a function is used as an event handler, its this is set to the element on which the listener is placed."
So if this is our slider, and our slider has the .obj, is it really as simple as this.obj?

function updateValue(event){
  console.log(this.obj)
}
Enter fullscreen mode Exit fullscreen mode

it is that simple!
Yes, everything seems to have clicked into place. A strange bit is that this does not work for arrow functions. As MDN puts it, "Arrow functions don't provide their own this binding"

So to finish things up, lets look at our level, and update the stats to be correct for the slider-selected level.

function updateValue(event){
  levelDisplay.textContent = `Level: ${event.target.value}`
  document.querySelector(`.baseStats`).innerHTML = statsRender(this.obj.stats, event.target.value)
}
Enter fullscreen mode Exit fullscreen mode

Working Slider

Not even a week later, this seems very obvious, but at the time this was a little bit of a breakthrough moment.

The takeaway for my fellow beginners:
To access data in an event listener callback function, first add that data to an attribute of the same element that is getting the event listener. It can then be accessed any time the event listener is called with this.attribute.

Top comments (0)