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 of a typical frontend problem with what we learned earlier. Imagine a regular menu, and whenever the user clicks on something, an active element changes.

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

The principle of how it works here is as follows. The link that the user clicks on is given the active class, which makes a new menu item stand out. With the previous selected item, exactly the opposite happens, in that case, 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. This 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? The problem is that we don't know which element was selected. There are several solutions to this problem.

Memorize the selected element and remove the desired class from it when the new element is clicked. This method requires a state to be introduced; some kind of variable that stores the currently selected element. Adding additional complexity is the first load, 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. In general, this method is quite demanding to implement.

There's a much easier method. Instead of trying to update the elements point-by-point, we'll just deselect all the elements at once. And it doesn't matter whether the element has already selected or not – the class deletion operation is idempotent, i.e., it doesn't lead to an error if the element didn't have a class to be deleted.

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 does do a little more work. That would be it, but there's one issue. Menu initialization doesn't work properly. Why?

Not every menu is controlled by js, but the current code doesn't take this into account. It'll try to work with every menu on the page, which isn't what we want. The proper way to do it is based on using a special pointer, which lets us know whether the menu should be controlled using js or not. It's considered good practice to use data-* attributes for this.

I.e., adding js 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. Now it assumes that each page has only one page. But the nav component can be added to a page any number of times. For example, there are places on Hexlet with three menus on one page.

If you run the current code with several menus, you may notice the following bug. Clicking on any of the items on one menu removes activity from the other menu. It happens because of this line:

// links refers 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 and which ones don't? To do this, you need to perform two actions:

  1. First, find the root element of the current menu, the one whose element was clicked.
  2. Find the active link inside the current menu and remove the active class from it.
links.forEach((link) => {
  link.addEventListener('click', () => {
    // 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');
  });
});

Hexlet Experts

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

From a novice to a developer. Get a job or your money back!

Frontend Developer icon
Profession
beginner
Development of front-end components for web applications
start anytime 10 months

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.