Register to get access to free programming courses with interactive exercises

Events in practice JS: DOM API

In this lesson, we'll look at a solution to a typical front-end task with what we learned earlier.

Let's imagine a typical menu. An active element changes when a user clicks on something:

See the Pen js_dom_events_in_action_nav by Hexlet (@hexlet) on CodePen.

Let's observe how it works. A user clicks on the link, so the program adds the active class to the given link. It makes a new menu item stand out.

The opposite happens with the previously selected item. The link loses the active class.

If we're going to do it straightforwardly, we need to extract all the menu links from the DOM and assign a click handler to each of them.

The handler will activate the current element and deactivate the previous one:

// We need to extract all links
const links = document.querySelectorAll('a');
// Each button has an event
// To do this, we bypass all the links and assign a handler to each of them
links.forEach((link) => {
  link.addEventListener('click', () => {
    // We need to deactivate the previous selected item

    // Select the current one
    link.classList.add('active');
  });
});

How do we remove the highlighting from the previous element correctly? We don't know which element was selected, so there are several solutions to this problem.

Solution 1. To memorize the selected element and remove the desired class when users click a new element.

To use this method, we introduce a state — a variable that stores the currently selected element. The first load adds extra complexity since the highlighted element most likely already comes in finished HTML, which means there is no information in the js yet about who is active. This method is quite demanding to implement.

Solution 2. To deselect all the elements at once. There's a much easier method. Instead of trying to update the elements one by one, we'll just deselect all the elements at once. It doesn't matter whether the element has already been selected or not.

The class deletion operation is idempotent. In other words, it doesn't lead to an error if the element doesn't have a class to delete:

links.forEach((link) => {
  link.addEventListener('click', () => {
    // Removing the active class from all links
    links.forEach((link) => link.classList.remove('active'));
    link.classList.add('active');
  });
});

This solution is much simpler, although it demands a little more work. It would be it, but there's one issue. Menu initialization doesn't work correctly. Why?

We can't control every menu with JavaScript, but the current code doesn't take it into account. The code will try to work with every menu on the page, which isn't what we want.

The proper way to do it is to use a pointer. It will let us know whether the menu should be controlled with JavaScript or not. A good practice is to use data-* attributes for this.

So, adding JavaScript logic only works for those menus that have the desired attribute:

<ul class="nav">
  <li>
    <a class="active" data-toggle="tab" href="#home">Home</a>
  </li>
  <li>
    <a class="active" data-toggle="tab" href="#profile">Profile</a>
  </li>
  <li>
    <a class="active" data-toggle="tab" href="#contact">Contact</a>
  </li>
</ul>

Accordingly, our code should change to look like this:

const links = document.querySelectorAll('[data-toggle="tab"]');

But even in this case, the code doesn't work properly. It assumes that each page has only one page. But we can add the nav component to a page any number of times. For example, somewhere on Hexlet, we have three menus on one page.

If you run the current code with several menus, you may notice a bug: clicking on any of the items on one menu removes activity from the other menu.

It happens because of this line:

// The links refer to all links in all menus on the page
links.forEach((link) => link.classList.remove('active'));

To solve this problem, you need to update only those links that relate to the current menu. But how do you know which ones relate to it?

To do this, you need to perform two actions:

  1. Find the current menu's root element, which was clicked
  2. Find the active link inside the current menu and remove the active class
links.forEach((link) => {
  link.addEventListener('click', () => {
    // The `closest` finds the nearest parent with the required selector
    // Our menu has the .nav class
    const nav = link.closest('.nav');
    // Find the active element inside the menu
    const activeElement = nav.querySelector('.active');
    activeElement.classList.remove('active');
    link.classList.add('active');
  });
});

Are there any more questions? Ask them in the Discussion section.

The Hexlet support team or other students will answer you.

About Hexlet learning process

For full access to the course you need a professional subscription.

A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.

Get access
130
courses
1000
exercises
2000+
hours of theory
3200
tests

Sign up

Programming courses for beginners and experienced developers. Start training for free

  • 130 courses, 2000+ hours of theory
  • 1000 practical tasks in a browser
  • 360 000 students
By sending this form, you agree to our Personal Policy and Service Conditions

Our graduates work in companies:

<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.bookmate">Bookmate</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.healthsamurai">Healthsamurai</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.dualboot">Dualboot</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.abbyy">Abbyy</span>
Suggested learning programs
profession
Development of front-end components for web applications
10 months
from scratch
Start at any time

Use Hexlet to the fullest extent!

  • Ask questions about the lesson
  • Test your knowledge in quizzes
  • Practice in your browser
  • Track your progress

Sign up or sign in

By sending this form, you agree to our Personal Policy and Service Conditions
Toto Image

Ask questions if you want to discuss a theory or an exercise. Hexlet Support Team and experienced community members can help find answers and solve a problem.