Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript

Owa Aquino
Owa Aquino
19,277 Points

How to add class to an element and remove class to its sibling

Hi Guys!

I have been working on this side project for a week now and got stuck on some feature.

http://w.trhou.se/e7mk4ithk8

The specific feature is that, when I clicked on a movie/ticket (img) a class style will be added to that selected ticket. And if I click on a different movie/ticket that class style will be added to the newly selected and remove that same class in the previously selected ticket.

I think to make it short, it's like list item behaving as a radio button.

Thanks!

Owa

2 Answers

Rune Andreas Nielsen
Rune Andreas Nielsen
5,354 Points

Hi, Owa.

I've created a cleaner example of the code where we don't have to loop over all elements in the DOM list, to find the one who had the 'selectedTicket' class on it.

I also helped you out by moving some of the toggle logic out into a single function named 'toggleClickedElement', making it easier to maintain and more readable, using the DRY principle (don't repeat yourself).

The way the new code works is that we save the last clicked element and therefore next time a new element is being clicked, we can remove the class from the last clicked element, a simple and clean solution.

So the only code that is needed is here:

const ul = document.querySelector('ul');

// I added this variable outside of the eventListener
// because we need to hold the state of what the last clicked element was. 
// if it was declared inside of the eventListener
// it would not work since it would get garbage collected.
let lastClickedElement;

ul.addEventListener('click', (e) => {

    if (e.target.tagName === 'IMG') {
        const li = e.target.parentNode;
        const allList = ul.getElementsByTagName('li');

        // Toggle the clicked element
        toggleClickedElement(li);

        // Here we check if lastClickedElement is set, this is because first time
        // we open the browser it is not defined, when we click it is defined.
        // After that we check if the currenctly clicked element is the same as the last clicked
        // if it is we dont do anything.
        if (lastClickedElement !== undefined && e.target.parentNode !== lastClickedElement) {
            // Here we know that we need to remove the selectedTicket css classList so we only call
            // addNotSelectedTicket function.
            addNotSelectedTicket(lastClickedElement)
        }

        // We set the last clicked element to the currenctly clicked element.
        // We do this so we can use it next time an element is being clicked
        lastClickedElement = e.target.parentNode;
    }
});

// I splitted this code up into three functions.
// By doing that your code is more flexiable and easier to read
// I also did it because we need to use the 'addNotSelectedTicket' function
// Seperatly from the other code.
function toggleClickedElement(clickedItem) {
    if (clickedItem.classList.contains('notSelectedTicket')) {
        addSelectedTicket(clickedItem);
    } else {
        addNotSelectedTicket(clickedItem);
    }
}

// This function add the 'notSelectedTicket' css class
// and remove the 'selectedTicket' css class
function addNotSelectedTicket(clickedItem) {
    clickedItem.classList.add('notSelectedTicket');
    clickedItem.classList.remove('selectedTicket');
}

// This function add the 'selectedTicket' css class
// and remove the 'notSelectedTicket' css class
function addSelectedTicket(clickedItem) {
    clickedItem.classList.add('selectedTicket');
    clickedItem.classList.remove('notSelectedTicket');
}

Here is the code without comments.

const ul = document.querySelector('ul');
let lastClickedElement;

ul.addEventListener('click', (e) => {

    if (e.target.tagName === 'IMG') {
        const li = e.target.parentNode;
        const allList = ul.getElementsByTagName('li');

        toggleClickedElement(li);

        if (lastClickedElement !== undefined && e.target.parentNode !== lastClickedElement) {
            addNotSelectedTicket(lastClickedElement)
        }

        lastClickedElement = e.target.parentNode;
    }
});

function toggleClickedElement(clickedItem) {
    if (clickedItem.classList.contains('notSelectedTicket')) {
        addSelectedTicket(clickedItem);
    } else {
        addNotSelectedTicket(clickedItem);
    }
}

function addNotSelectedTicket(clickedItem) {
    clickedItem.classList.add('notSelectedTicket');
    clickedItem.classList.remove('selectedTicket');
}

function addSelectedTicket(clickedItem) {
    clickedItem.classList.add('selectedTicket');
    clickedItem.classList.remove('notSelectedTicket');
}

The problem with your solution using a loop is that you select all of the elements in the loop and therefore add the CSS class to all of the elements. To handle that problem you would have to make some more specific conditions to make sure that you only change the one element who was clicked.

Hope this helps.

  • Rune *<:)
Owa Aquino
Owa Aquino
19,277 Points

Hey Rune,

I really appreciate your help. and thank you for making time on explain as well. I think I somehow get it now I'll try to study and understand your code. I'm really new in javascript so it's really hard for me in creating logics when doing the coding. :)

Thank you very much for your help!

Owa

Rune Andreas Nielsen
Rune Andreas Nielsen
5,354 Points

Hi, Owa.

This should do it. Not the most efficient or beautiful code, I've written, but it gets the job done. The only thing I added is the code after allList. If you need an explanation of what the codes does, just poke me.

const ul = document.querySelector('ul');

ul.addEventListener('click', (e) => {

    if (e.target.tagName === 'IMG') {
        const li = e.target.parentNode;
        const allList = ul.getElementsByTagName('li');

        for (var index = 0; index < allList.length; index++) {
            if (e.target.parentNode !== allList[index] && allList[index].classList.contains('selectedTicket')) {
                allList[index].classList.remove('selectedTicket');
                allList[index].classList.add('notSelectedTicket');
            }
        }

        if (li.classList.contains('notSelectedTicket')) {
            li.classList.add('selectedTicket');
            li.classList.remove('notSelectedTicket');
        } else {
            li.classList.add('notSelectedTicket');
            li.classList.remove('selectedTicket');
        }
    }

    if (e.target.tagName === 'LI') {
        const li = e.target;

        if (li.classList.contains('notSelectedTicket')) {
            li.classList.add('selectedTicket');
            li.classList.remove('notSelectedTicket');
        } else {
            li.classList.add('notSelectedTicket');
            li.classList.remove('selectedTicket');
        }
    }
});

Happy coding! :)

Owa Aquino
Owa Aquino
19,277 Points

Hi Rune,

Thank you very much for your help. I really appreciate it.

I've been stuck on this for two days now. Now I can move forward.

But before doing so. I would like to ask how does it work? I though using for loops on list items would select all the list items? I tried this before but I got is once I clicked on any ticket it automatically update all tickets classes

Below was my solution with for loop before.

const ul = document.querySelector('ul');

  ul.addEventListener('click', (e)=>{ 

  const li = ul.getElementsByTagName('li');

   for(let i=0; i < li.length; i++){
    let ticket= li[i];
    if(ticket.classList.contains('notSelectedTicket')){
      ticket.classList.add('selectedTicket');
      ticket.classList.remove('notSelectedTicket');
    }else{
      ticket.classList.add('notSelectedTicket');
      ticket.classList.remove('selectedTicket');
    }
 }   

});