Srki
Srki

Reputation: 51

Is there a way to add class to all elements

I browsed thru various similar questions, but I found nowhere real help on my problem. There are some Jquery examples, but none of them weren't applicable, since I want to do it by vanilla JavaScript only.

The Problem

I have rating widget which I want to build like every time I click on "p" element, eventListener adds class "good" on all p elements before and on the clicked one, and remove class good on all that are coming after the clicked one.

My Thoughts

I tried to select all the "p" elements and iterate over them by using a for loop to add class before and on clicked element, and to leave it unchanged or remove after, but somewhere I am doing it wrong, and it adds class only on clicked element not on all previous ones.

My Code

function rating () {
var paragraph = document.getElementsByTagName('p');
for (var i = 0; i < paragraph.length; i++) {
    paragraph[i].addEventListener('click', function(e) {
        e.target.className='good';
        })
      }
    } 

rating();

Result

Expected result is that every p element before the clicked one, including clicked one should be having class good after click and all the ones that are after the clicked one, should have no classes.

Actual Result: Only clicked element is having class good.

Upvotes: 2

Views: 2333

Answers (3)

JJWesterkamp
JJWesterkamp

Reputation: 7916

The key ingredient is to use Node.compareDocumentPosition() to find out if an element precedes or follows another element:

var paragraphs;

function handleParagraphClick(e) {

  this.classList.add('good');

  paragraphs.forEach((paragraph) => {

    if (paragraph === this) {
      return;
    }

    const bitmask = this.compareDocumentPosition(paragraph);

    if (bitmask & Node.DOCUMENT_POSITION_FOLLOWING) {
      paragraph.classList.remove('good');
    }

    if (bitmask & Node.DOCUMENT_POSITION_PRECEDING) {
      paragraph.classList.add('good');
    }

  });
}

function setup() {

  paragraphs = [...document.getElementsByTagName('p')];

  paragraphs.forEach((paragraph) => {
    paragraph.addEventListener('click', handleParagraphClick);
  });
}

setup();
#rating { display: flex; }
p { font-size: 32px; cursor: default; }
p:hover { background-color: #f0f0f0; }
.good { color: orange; }
<div id='rating'>
  <p>*</p>
  <p>*</p>
  <p>*</p>
  <p>*</p>
  <p>*</p>
</div>`

Upvotes: 1

kockburn
kockburn

Reputation: 17616

Using Array#forEach , Element#nextElementSibling and Element#previousElementSibling

General logic behind this is to loop through all the previous and next sibling elements. For each element, add or remove the class .good until there are no more siblings to handle.

const ps = document.querySelectorAll('p');

ps.forEach(p => {
  p.addEventListener("click", function() {
    let next = this.nextElementSibling;
    let prev = this;
    while(prev !== null){
      prev.classList.add("good");
      prev = prev.previousElementSibling;
    }
    while(next !== null){
      next.classList.remove("good");
      next = next.nextElementSibling;
    }
  })

})
p.good {
  background-color: red;
}

p.good::after {
  content: ".good"
}

p {
  background-color: lightgrey;
}
<div id='rating'>
  <p>*</p>
  <p>*</p>
  <p>*</p>
  <p>*</p>
  <p>*</p>
</div>

Alternative:

Using Array#slice to get the previous and next group of p's.

const ps = document.querySelectorAll('p');

ps.forEach((p,i,list) => {
  p.addEventListener("click", function() {
    
    const arr = [...list];
    const previous = arr.slice(0,i+1);
    const next = arr.slice(i+1);
    
    previous.forEach(pre=>{
       pre.classList.add("good");
    })
    
    next.forEach(nex=>{
        nex.classList.remove("good");
    });
  })

})
p.good {
  background-color: red;
}

p.good::after {
  content: ".good"
}

p {
  background-color: lightgrey;
}
<div id='rating'>
  <p>*</p>
  <p>*</p>
  <p>*</p>
  <p>*</p>
  <p>*</p>
</div>

Upvotes: 1

westdabestdb
westdabestdb

Reputation: 4648

function setup() {
  var paragraph = document.querySelectorAll("p");
  for (p of paragraph) {
    p.onclick = (event) => {
      let index = Array.from(paragraph).indexOf(event.target);
      [].forEach.call(paragraph, function(el) {
        el.classList.remove("good");
      });
      for (let i = 0; i <= index; i++) {
        paragraph[i].classList.add("good");
      }

    }
  }
}

//Example case

document.body.innerHTML = `
<div id='rating'>
<p>*</p>
<p>*</p>
<p>*</p>
<p>*</p>
<p>*</p>
</div>`;

setup();

Upvotes: 1

Related Questions