12

I'm trying to do in vanilla js what the siblings() does in jQuery. I've seen a lot of helpful explanations here but I'm having trouble implementing them on my code.

I have a div that has 4 buttons inside it. Every time i click on a button a class (.btn-anim) is added to that button (this works ok so far). What I want to do is when I click on one of the buttons (that doesn't have the class already) to remove the class of any other button and add it to the clicked one.

My Html markup:

<div id="js-colors-div">
  <button class="yellow"></button>
  <button class="green"></button>
  <button class="blue"></button>
  <button class="pink"></button>
</div>

And Js:

var colorsDiv = document.getElementById('js-colors-div'); 
var colors = colorsDiv.getElementsByTagName("button");

for (var i = 0; i < colors.length; i++) {                   
    colors[i].onclick = function(e) {                                

    var highlight = e.target;

    //trying to achieve this         
    $(this).addClass('btn-anim').siblings().removeClass('btn-anim');          

    }
};

And this is my code for adding the class

highlight.classList.add('btn-anim'); // 
grenos
  • 307
  • 1
  • 6
  • 18
  • Loop over the elements in `colors`. If it is the clicked button add `btn-anim`, else remove it. – Andreas Feb 24 '18 at 12:13

5 Answers5

16

In vanilla JS you could loop over the parent's children and just skip the element itself. You can use classList methods for adding/removing the class:

    this.classList.add('btn-anim');
    for (let sibling of this.parentNode.children) {
        if (sibling !== this) sibling.classList.remove('btn-anim');
    }

Note however that you can (also in jQuery) simplify a bit: just remove the class from all buttons, and then add it to the current one:

    for (let sibling of this.parentNode.children) {
        sibling.classList.remove('btn-anim');
    }
    this.classList.add('btn-anim');
trincot
  • 317,000
  • 35
  • 244
  • 286
1

You can use previousElementSibling and nextElementSibling elements.

var colorsDiv = document.getElementById('js-colors-div');
var colors = colorsDiv.getElementsByTagName("button");

for (var i = 0; i < colors.length; i++) {
  colors[i].addEventListener('click', function(e) {
    var highlight = e.target;

    //trying to achieve this         
    this.classList.add('btn-anim');
    addClassSiblings.bind(this, 'btn-anim')();
  });
}

function addClassSiblings(classNames) {
  var cs = this.nextElementSibling;
  while(cs) {
    cs.classList.remove(classNames);
    cs = cs.nextElementSibling;
  }
  
  cs = this.previousElementSibling;
  while(cs) {
    cs.classList.remove(classNames);
    cs = cs.previousElementSibling;
  }
}
.yellow {
  color: yellow
}

.pink {
  color: pink
}

.green {
  color: green
}

.blue {
  color: blue
}

.btn-anim {
  color: black
}
<div id="js-colors-div">
  <button class="yellow">yellow</button>
  <button class="green">green</button>
  <button class="blue">blue</button>
  <button class="pink">pink</button>
</div>
Ele
  • 33,468
  • 7
  • 37
  • 75
1

.siblings() in jQuery also allows you to use a selector argument to target specific siblings and I don't think that's been addressed.

const getSiblings = (el, sel) => {
    const siblings = [];
    let targets;
    if (sel)
        targets = el.parentNode.querySelectorAll(':scope > ' + sel)
    else
        targets = el.parentNode.children;

    for (const target of targets) {
        if (target !== el)
            siblings.push(target);
    }
    return siblings;
};

Then this could be used like so for the OP's use case...

const removeAnimClass = (ev) => {
    const siblings = getSiblings(ev.currentTarget, '.btn-anim');
    for (const sibling of siblings) {
        sibling.classList.remove('btn-anim');
};

const colorsDiv = document.getElementById('js-colors-div'); 
const colorBtns = colorsDiv.getElementsByTagName("button");

for (const colorBtn of colorBtns) {                   
    colorBtn.onclick = removeAnimClass;
}
Dylan Maxey
  • 976
  • 10
  • 9
  • Great stuff! Note for others: Browser compatibility for :scope is non IE and from Safari 7. https://developer.mozilla.org/en-US/docs/Web/CSS/:scope#browser_compatibility – Jonas Äppelgran Feb 23 '22 at 10:36
  • `getSiblings` has one big drawback - it will work correctly only if the passed `sel` matches only the siblings (and `el` itself) and not their children because if the children of the siblings are returned then the filtering in the `for` loop will not work. In the end we might get the descendants of the `el` itself. – adanski Sep 01 '22 at 22:23
  • Not true. With the use of `:scope` and the direct child selector, you will only get the siblings, none of the children. See this demo https://codepen.io/dylan9o4/pen/KKRKQpm – Dylan Maxey Sep 02 '22 at 18:04
1

const colorsDiv = document.getElementById('js-colors-div');
const children = [...colorsDiv.children];

children.forEach((el) => {
  el.addEventListener('click', (e) => {
    children.forEach((btn) => {
      btn.classList.remove('active');
    })
    e.target.classList.add('active');
  });
});
button {
  color: white
}

.yellow {
  background: #ffaf00
}

.pink {
  background: pink
}

.green {
  background: green
}

.blue {
  background: blue
}

.active {
  background: black
}
<div id="js-colors-div">
  <button class="yellow">yellow</button>
  <button class="green">green</button>
  <button class="blue">blue</button>
  <button class="pink">pink</button>
</div>
Piterden
  • 771
  • 5
  • 17
0

Perhaps try this, give all buttons the same class (i.e .checked).

Next, create a loop and iterate over each li DOM Element. Once inside the loop, create another loop that will, in turn, remove the class .checked from all elements, and then it will add only to the one you clicked.

const rates = document.querySelectorAll(".rate");

for (const rate of rates) 
{
  rate.addEventListener("click", function () 
  {
    for (const i of rates) 
    {
      i.classList.remove("checked");
    }

    rate.classList.add("checked");
  });
}
<ul class="rating-box">
 <li><button class="rate rate-1">1</button></li>
 <li><button class="rate rate-2">2</button></li>
 <li><button class="rate rate-3">3</button></li>
 <li><button class="rate rate-4">4</button></li>
 <li><button class="rate rate-5">5</button></li>
</ul>
Geeky Quentin
  • 2,469
  • 2
  • 7
  • 28