Home > Back-end >  How to toggle display style using vanilla JS on multiple elements
How to toggle display style using vanilla JS on multiple elements

Time:07-16

I wasn't sure how to write that question logically, I pretty much figured the togglw out, but I struggle with something else. When I click on one element and open it, I want to click on another one to open and the previously opened to close. It doesn't work, however. Whenever I click on another element, the opened one just closes without opening the other. Basically, I want to close the element only when I click on that specific element. Or when I click on another one (which kinda works) but in this case also want the other to open, if that makes sense. I am stuck at this point.

Thanks for any help.

Here is my codepen: https://codepen.io/danosvk/pen/JjLEGMK

Here is my code:

    const questions = document.querySelectorAll("section");
    const answers = document.querySelectorAll(".answer");
    
    toggle = false
    function open() {
      for (let i = 0; i < answers.length; i  ) {
        answers[i].style.display = "none";
      }
    toggle = !toggle
      this.lastElementChild.style.display = toggle ? "block" : "none"
    }
    
    questions.forEach((question) => question.addEventListener("click", open));
    .card {
      width: 20.4375rem;
      background-color: #ffffff;
      border-radius: 1.4375rem;
      margin: auto;
      padding: 132px 24px 48px;
      text-align: center;
      transform: translateY(-125px);
    }
    
    section {
      display: flex;
      align-items: center;
      justify-content: space-between;
      border-bottom: 1px solid #e8e8ea;
      flex-wrap: wrap;
    }
    
    p {
      color: var(--main-text-color);
      font-size: 0.75rem;
    }
    
    .answer {
      flex-basis: 100%;
      text-align: left;
      display: none;
    }
    <div >
      <h1 >faq</h1>
      <section>
        <p >How many team members can i invite?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
          You can invite up to 2 additional users on the Free plan. There is no
          limit on team members for the Premium plan.
        </p>
      </section>
      <section>
        <p >What is the maximum file upload size?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
          No more than 2GB. All files in your account must fit your allotted
          storage space.
        </p>
      </section>
      <section>
        <p >How do I reset my password?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
          Click “Forgot password” from the login page or “Change password” from
          your profile page. A reset link will be emailed to you.
        </p>
      </section>
      <section>
        <p >Can I cancel my subscription?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
          Yes! Send us a message and we’ll process your request no questions
          asked.
        </p>
      </section>
      <section>
        <p >Do you provide additional support?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
          Chat and email support is available 24/7. Phone lines are open during
          normal business hours.
        </p>
      </section>

CodePudding user response:

Using toggle won't work as it is always false when an answer is showing.

Instead, save the style of the clicked option before wiping all the answers, and then use it to determine whether or not to show the answer.

If this.lastElementChild.style.display is '' (i.e. on the first time the menu is clicked), it is set to none.

    const questions = document.querySelectorAll("section");
    const answers = document.querySelectorAll(".answer");
    
    function open() {

      currentDisplay=this.lastElementChild.style.display||'none';

      for (let i = 0; i < answers.length; i  ) {
        answers[i].style.display = "none";
      }

      if (currentDisplay=='none') this.lastElementChild.style.display = "block"

    }
    
    questions.forEach((question) => question.addEventListener("click", open));
    .card {
      width: 20.4375rem;
      background-color: #ffffff;
      border-radius: 1.4375rem;
      margin: auto;
      padding: 132px 24px 48px;
      text-align: center;
      transform: translateY(-125px);
    }
    
    section {
      display: flex;
      align-items: center;
      justify-content: space-between;
      border-bottom: 1px solid #e8e8ea;
      flex-wrap: wrap;
    }
    
    p {
      color: var(--main-text-color);
      font-size: 0.75rem;
    }
    
    .answer {
      flex-basis: 100%;
      text-align: left;
      display: none;
    }
    <div >
      <h1 >faq</h1>
      <section>
        <p >How many team members can i invite?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
          You can invite up to 2 additional users on the Free plan. There is no
          limit on team members for the Premium plan.
        </p>
      </section>
      <section>
        <p >What is the maximum file upload size?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
          No more than 2GB. All files in your account must fit your allotted
          storage space.
        </p>
      </section>
      <section>
        <p >How do I reset my password?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
          Click “Forgot password” from the login page or “Change password” from
          your profile page. A reset link will be emailed to you.
        </p>
      </section>
      <section>
        <p >Can I cancel my subscription?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
          Yes! Send us a message and we’ll process your request no questions
          asked.
        </p>
      </section>
      <section>
        <p >Do you provide additional support?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
          Chat and email support is available 24/7. Phone lines are open during
          normal business hours.
        </p>
      </section>

CodePudding user response:

I would do this by using this CSS rule to show the answers rather than changing their style inline:

.expanded .answer {
    display: block;
}

...and then using event delegation on the .card element that contains the FAQ to catch clicks anywhere within it; if the click passed through a section, do this:

  • If the section has the expanded class, remove it to un-expand the answer.
  • If the section doesn't have the expanded class, remove it from any other section that has it, then add it to the section that was clicked.

Here's the JavaScript part:

const card = document.querySelector(".card");
card.addEventListener("click", (event) => {
    const section = event.target.closest("section");
    if (section) {
        if (section.classList.contains("expanded")) {
            // This is the expanded one, un-expand it
            section.classList.remove("expanded");
        } else {
            // This isn't the expanded one, un-expand the expanded
            // one (if any) and expand this one
            document.querySelector("section.expanded")?.classList.remove("expanded");
            section.classList.add("expanded");
        }
    }
});

Notice the optional chaining on the querySelector looking for an expanded section; it's there in case there is no expanded section yet.

Live Example:

const card = document.querySelector(".card");
card.addEventListener("click", (event) => {
    const section = event.target.closest("section");
    if (section) {
        if (section.classList.contains("expanded")) {
            // This is the expanded one, un-expand it
            section.classList.remove("expanded");
        } else {
            // This isn't the expanded one, un-expand the expanded
            // one (if any) and expand this one
            document.querySelector("section.expanded")?.classList.remove("expanded");
            section.classList.add("expanded");
        }
    }
});
.card {
    width: 20.4375rem;
    background-color: #ffffff;
    border-radius: 1.4375rem;
    margin: auto;
    padding: 132px 24px 48px;
    text-align: center;
    transform: translateY(-125px);
}

section {
    display: flex;
    align-items: center;
    justify-content: space-between;
    border-bottom: 1px solid #e8e8ea;
    flex-wrap: wrap;
}

p {
    color: var(--main-text-color);
    font-size: 0.75rem;
}

.answer {
    flex-basis: 100%;
    text-align: left;
    display: none;
}

.expanded .answer {
    display: block;
}
<div >
    <h1 >faq</h1>
    <section>
        <p >How many team members can i invite?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
            You can invite up to 2 additional users on the Free plan. There is no limit on team
            members for the Premium plan.
        </p>
    </section>
    <section>
        <p >What is the maximum file upload size?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
            No more than 2GB. All files in your account must fit your allotted storage space.
        </p>
    </section>
    <section>
        <p >How do I reset my password?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
            Click “Forgot password” from the login page or “Change password” from your profile page.
            A reset link will be emailed to you.
        </p>
    </section>
    <section>
        <p >Can I cancel my subscription?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
            Yes! Send us a message and we’ll process your request no questions asked.
        </p>
    </section>
    <section>
        <p >Do you provide additional support?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
            Chat and email support is available 24/7. Phone lines are open during normal business
            hours.
        </p>
    </section>
</div>

It might be worth adding a class to the section and using that in the selectors, in case you have other parts of the page using section, like this:

const card = document.querySelector(".card");
card.addEventListener("click", (event) => {
    const section = event.target.closest(".faq");
    if (section) {
        if (section.classList.contains("expanded")) {
            // This is the expanded one, un-expand it
            section.classList.remove("expanded");
        } else {
            // This isn't the expanded one, un-expand the expanded
            // one (if any) and expand this one
            document.querySelector(".faq.expanded")?.classList.remove("expanded");
            section.classList.add("expanded");
        }
    }
});
.card {
    width: 20.4375rem;
    background-color: #ffffff;
    border-radius: 1.4375rem;
    margin: auto;
    padding: 132px 24px 48px;
    text-align: center;
    transform: translateY(-125px);
}

section {
    display: flex;
    align-items: center;
    justify-content: space-between;
    border-bottom: 1px solid #e8e8ea;
    flex-wrap: wrap;
}

p {
    color: var(--main-text-color);
    font-size: 0.75rem;
}

.answer {
    flex-basis: 100%;
    text-align: left;
    display: none;
}

.expanded .answer {
    display: block;
}
<div >
    <h1 >faq</h1>
    <section >
        <p >How many team members can i invite?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
            You can invite up to 2 additional users on the Free plan. There is no limit on team
            members for the Premium plan.
        </p>
    </section>
    <section >
        <p >What is the maximum file upload size?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
            No more than 2GB. All files in your account must fit your allotted storage space.
        </p>
    </section>
    <section >
        <p >How do I reset my password?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
            Click “Forgot password” from the login page or “Change password” from your profile page.
            A reset link will be emailed to you.
        </p>
    </section>
    <section >
        <p >Can I cancel my subscription?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
            Yes! Send us a message and we’ll process your request no questions asked.
        </p>
    </section>
    <section >
        <p >Do you provide additional support?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
            Chat and email support is available 24/7. Phone lines are open during normal business
            hours.
        </p>
    </section>
</div>

CodePudding user response:

You can also fix it by simply keeping a reference (currentlyOpen) of the currently opened section and using it in your event listener.

It would only require you to change some js and keep the same css.

Here's a little bit of code to explaining the change:

let currentlyOpen; // Keep track of the currently openend section.

function toggle() {
    if (currentlyOpen) {
    // Something currently open
    currentlyOpen.lastElementChild.style.display = "none" // Close it
  }
  
  if (currentlyOpen === this) {
    // The element the user clicked on is already opened, close it!
    currentlyOpen.lastElementChild.style.display = "none" // Close it
    currentlyOpen = undefined; // Delete it's refrence
    return // And return (stop execution)
  }
  
  // Show clicked element
  this.lastElementChild.style.display = "block"
  
  // Store it for later
  currentlyOpen = this
}

For more info see the code snipped:

const questions = document.querySelectorAll("section");
const answers = document.querySelectorAll(".answer");

let currentlyOpen;

function toggle() {
    if (currentlyOpen) {
    // Something currently open
    currentlyOpen.lastElementChild.style.display = "none" // Close it
  }
  
  if (currentlyOpen === this) {
    // Element is already open
    currentlyOpen.lastElementChild.style.display = "none" // Hide it
    currentlyOpen = undefined; // Delete it's refrence
    return // And return
  }
  
  // Show clicked element
  this.lastElementChild.style.display = "block"
  
  // Store it for later
  currentlyOpen = this
}


questions.forEach((question) => question.addEventListener("click", toggle));
.card {
  width: 20.4375rem;
  background-color: #ffffff;
  border-radius: 1.4375rem;
  margin: auto;
  padding: 132px 24px 48px;
  text-align: center;
  transform: translateY(-125px);
}

section {
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid #e8e8ea;
  flex-wrap: wrap;
}

p {
  color: var(--main-text-color);
  font-size: 0.75rem;
}

.answer {
  flex-basis: 100%;
  text-align: left;
  display: none;
}
<div >
      <h1 >faq</h1>
      <section>
        <p >How many team members can i invite?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
          You can invite up to 2 additional users on the Free plan. There is no
          limit on team members for the Premium plan.
        </p>
      </section>
      <section>
        <p >What is the maximum file upload size?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
          No more than 2GB. All files in your account must fit your allotted
          storage space.
        </p>
      </section>
      <section>
        <p >How do I reset my password?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
          Click “Forgot password” from the login page or “Change password” from
          your profile page. A reset link will be emailed to you.
        </p>
      </section>
      <section>
        <p >Can I cancel my subscription?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
          Yes! Send us a message and we’ll process your request no questions
          asked.
        </p>
      </section>
      <section>
        <p >Do you provide additional support?</p>
        <img  src="./images/icon-arrow-down.svg" alt="" />
        <p >
          Chat and email support is available 24/7. Phone lines are open during
          normal business hours.
        </p>
      </section>
    </div>

Or a working version hosted on jsfiddle: https://jsfiddle.net/fmr9tvw8/

  • Related