DEV Community

Daniel Warren
Daniel Warren

Posted on • Edited on

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.

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. Here is what we will be building:

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).

<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>
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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

const listOfThings = document.getElementById('list')
Enter fullscreen mode Exit fullscreen mode

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

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

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…
  }
})
Enter fullscreen mode Exit fullscreen mode

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'
      }
    } 
  }
})
Enter fullscreen mode Exit fullscreen mode

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'
    }
  }
})
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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

View the final product here
http://danielwarren.io/2017/11/26/frameworkless-events

Top comments (4)

Collapse
 
marlysson profile image
Marlysson Silva

Good approach.. But there are some framework to handle event listeners besides the vanillajs native api?

Collapse
 
warrend profile image
Daniel Warren

To clarify, I meant vanilla JS as in plain old JS, not to be confused with the VanillaJS framework.

Collapse
 
ukazap profile image
Ukaza Perdana

LOL

Collapse
 
warrend profile image
Daniel Warren • Edited

JS frameworks like React and Angular handle event listeners much better than the approach I took here.