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
  • 179
  • 1
  • 5
  • 16
  • 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

4 Answers4

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
  • 263,463
  • 30
  • 215
  • 251
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
  • 32,412
  • 7
  • 33
  • 72
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
  • 824
  • 8
  • 7
  • 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
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
  • 735
  • 4
  • 14