Frameworkless Events

In fifty years when self-driving cars rule the road, what happens when someone needs to manually drive? The autopilot malfunctions and my great-grandson, Weston, must get home to watch the Cavs play the Warriors in game seven of the NBA Finals because modern medicine has figured out a way to keep LeBron and Curry playing at top performance well into their 80s.

But Weston has only manually driven a car once or twice in his life, the first time on a dare from a friend that resulted in the partial mangling of his parents’ garage bay.

Despite the incredible capabilities of modern JS frameworks, like React, Angular, and Vue, we probably all feel a little bit like Weston when it comes to writing plain old JS without the crutch of a framework.

I’m working on a chat application in React/Socket.io/Express where users can click on a message, respond, and the response will appear directly under the selected message. In an effort to manually drive this car, I wanted to recreate the click events in plain old JS.

For this example, I will use an unordered list of the top five NBA 3-pt leaders (you can use whatever list you like, whether books or a list of movies). I want to be able to click on and select a list item with only one item selected at a time. If I click on the same item, it will toggle the class between active and inactive. You can follow along here on Codepen.

<div class="container"> 
  <ul id="list">
    <p class="title">Choose a 3-Pt Leader</p>
    <li class="inactive" data-id="0">Ray Allen | <span>2973</span></li>
    <li class="inactive" data-id="1">Reggie Miller | <span>2560</span></li>
    <li class="inactive" data-id="2">Jason Terry | <span>2242</span></li>
    <li class="inactive" data-id="3">Paul Pierce | <span>2143</span></li>
    <li class="inactive" data-id="4">Kyle Korver | <span>2097</span></li>
  </ul>
</div>

I have an unordered list of five items with a class of inactive. In addition, each item has a data-id which we’ll use later. In our CSS we have two classes, active and inactive. The rest of the CSS is purely decorative:

.active {
  background: #cc0000;
  color: #fff;
  transition: .15s;
}

.inactive {
  background: #efefef;
}

For the JS, we first want to grab the unordered list:

const listOfThings = document.getElementById('list')

Next we want to add an event listener on the ul:

const listOfThings = document.getElementById('list')
listOfThings.addEventListener("click", function(event) {})

Inside of the event listener, we want to loop through all of the list items:

const listOfThings = document.getElementById('list')
listOfThings.addEventListener("click", function(event) {
  let list = document.querySelectorAll('li')
  for (let i = 0; i < list.length; i++) {
  // loop through all the list items…
  }
})

First in the loop, for the selected item, we want to toggle the class name between active and inactive. We do this by comparing the data-id on the target to the data-id on the current iteration.

const listOfThings = document.getElementById('list')

listOfThings.addEventListener("click", function(event) {
  let list = document.querySelectorAll('li')
  for (let i = 0; i < list.length; i++) {
    if (event.target.dataset.id === list[i].dataset.id) {
      if (event.target.className === 'inactive') {
        event.target.className = 'active'
      } else {
        event.target.className = 'inactive'
      }
    } 
  }
})

If we leave this as is, we would be able to select more than one item at a time. In order to prevent this, we add an else condition (if it’s not the target of the click) we set it to inactive. This ensures any item not selected doesn’t have a red background.

const listOfThings = document.getElementById('list')

listOfThings.addEventListener("click", function(event) {
  let list = document.querySelectorAll('li')
  for (let i = 0; i < list.length; i++) {
    if (event.target.dataset.id === list[i].dataset.id) {
      if (event.target.className === 'inactive') {
        event.target.className = 'active'
      } else {
        event.target.className = 'inactive'
      }
    } else {
      list[i].className = 'inactive'
    }
  }
})

The logic gets pretty confusing and there are a number of ways to write this but the above gets the job done. Originally, I had the opposite logic with two ternary operators smashed together but this felt like too much:

event.target.dataset.id !== list[i].dataset.id ? 
list[i].className = 'inactive' : 
(event.target.className === 'inactive' ? event.target.className = 'active' : event.target.className = 'inactive')

In conclusion, a good exercise is to rewrite high level framework code into simple vanilla JS. Not only is it good practice but it gives us an appreciation for the frameworks we use everyday.

View the final product here

<-- Back to blog list