Warren Breedlove
Warren Breedlove

Reputation: 105

Make one panel close when another is opened

I would like to make it for if one of these panels is open, it'll close when you've clicked on another panel. I looked at some other questions similar to mine but it seems they all uses a different method for the panels.

Heres a Fiddle of it: https://jsfiddle.net/3q87y2u8/

Heres the JS:

    var acc = document.getElementsByClassName("accordion");
    var i;

        for (i = 0; i < acc.length; i++) {
            acc[i].onclick = function() {
            this.classList.toggle("active");
            var panel = this.nextElementSibling;
            if (panel.style.maxHeight){
            panel.style.maxHeight = null;
    } else {
    panel.style.maxHeight = panel.scrollHeight + "px";
    } 
   }
}

I'd also like to get the '+' to change color when hovered over. Kinda like the text currently does. I cant seem to call it out right in the CSS though.

Thank You!

Upvotes: 2

Views: 2336

Answers (5)

hola
hola

Reputation: 3510

A more readable and more efficient way would be to simply hold a reference to the previously toggled accordion item.

When onclick is called, you will first want to check if cur is the clicked on panel. That way, toggling cur will just close it. Next, set cur to null, so it won't open up the previous panel when you click a different panel. Then make sure to return out of the function. Note that you have to do this before the nonnull check for cur.

Otherwise, check the now active item to current to make sure it is not null, then toggle both cur and this. Wrap it up by setting cur to this, so next time it can do it all over again!

Also, here's a fiddle: https://jsfiddle.net/d7smh9ru/4/

var cur = null;
var acc = document.getElementsByClassName("accordion");
var i;

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

    // Only close already open panel
    if (cur == this) {
      toggleItem(cur);
      cur = null;
      return;
    }

    // Close current panel, and open this panel
    if (cur) {
      toggleItem(cur);
    }

    toggleItem(this);
    cur = this;
  }
}

function toggleItem(item) {
  item.classList.toggle("active");
  var panel = item.nextElementSibling;
  if (panel.style.maxHeight) {
    panel.style.maxHeight = null;
  } else {
    panel.style.maxHeight = panel.scrollHeight + "px";
  }
}

Upvotes: 1

Pango
Pango

Reputation: 673

You can get the element that is currently active and then remove the active class like so:

// Is an array of elements matching the selector
var active = document.getElementsByClassName('active');

// If there are any matching elements and it is not the same one that has just been clicked on
if (active.length && active[0] !== this) {
    active[0].classList.remove('active');
}

One side comment (you are of course free to ignore!): for the max-height you don't need to figure out the height (unless of course it interferes with how you have your page structure already set up). You can just use some large height and since it's the max-height you are setting and not the height it will automatically go to its natural height. You can also achieve this using just CSS, including the animation, and updating the hover text on the plus sign is included below as well.

// Set regular state height to 0 and add a transition effect
.panel {
    max-height: 0px;
    transition: max-height .25s ease;
}

// For whatever button is active find the adjacent panel and set its max height and a transition effect
.active + .panel {
  max-height: 250px;
  transition: max-height .25s ease;
}

// Here is where you change the color of the active and hovered plus/minus sign
button.accordion.active:after,
button.accordion:hover:after {
  color: red;
}

https://jsfiddle.net/xz2mpzg2/1/ https://jsfiddle.net/xz2mpzg2/2/

Upvotes: 1

Fissure King
Fissure King

Reputation: 1270

I believe you're looking for something like this: JSFiddle.

I'll go through each change I made, and each statement is also commented, but as a general principal, I changed your code to use a wrapper div around each group of buttons/text, and I put the text inside an inner div so it would respect max-height. Instead of the event handler modifying its next sibling, which would fail, e.g., if your paragraphs were individually enclosed in p tags and not line-broken using br tags inside your ps. This also made it trivial to implement the hover-changes-plus-color effect, using the parent's :hover pseudoclass.

So, if you change your button/text groups from:

<button class="accordion">Home</button>
<div class="panel">
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis             nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>

to:

<div class="panel">
  <button class="accordion">Home</button>
  <div class="content">
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis           nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
  </div>
</div>

and update your CSS, changing your panel class to content (previously beginning on line 50 of your JSFiddle):

.content {
  overflow: hidden;
  transition: max-height 0.2s ease-out;
  padding-left: 50px;
  background-color: #f6f6f6;
  width: 220px;
  font-size: 10px;
  max-height: 100px;
}

and use CSS to set the max-height to 0 on non-opened panels

div.panel:not(.opened) > .content {
  max-height: 0;
}

You can then set your event listeners on the buttons, and simply toggle the parent's opened class:

document.querySelectorAll('.accordion')
  .forEach(element => {
    element.onclick = function() {

      // first, iterate over other open panels and close them...
      // note: using Array.from(...) so we can filter the result below.
      Array.from(document.querySelectorAll('.panel.opened'))

        // ...but skip the clicked panel, which will be dealt with below
        // [edit] we filter here so we can close an already-open panel
        .filter(panel => panel !== this.parentNode)

        .forEach(panel => {

          // toggle the 'opened' class, forcing `false`
          panel.classList.toggle('opened', false)

          // and remove styling that was set on the element itself
          panel.querySelector('.content').style.maxHeight = null;
        });

      // now toggle the clicked panel's 'opened' class and capture its new value
      const toggled = this.parentNode.classList.toggle('opened');

      // reference the new div.content, inside the button's parent
      const content = this.parentNode.querySelector('.content');

      // and either fix its max height or unset it, depending on new state
      content.style.maxHeight = toggled? `${content.scrollHeight}px` : null;
    }
  });

Finally, if you want to change the '+' color on highlight, now you can do so using the :hover pseudoclass on .panel:

div.panel:hover > button.accordion:after {
  color: red;
}

Upvotes: 0

Lieutenant Dan
Lieutenant Dan

Reputation: 8294

Simply with jQuery you could easily use:

A.) .toggle();

B.) .change();

C.) .addClass(); / removeClass();

Upvotes: 0

CaptainHere
CaptainHere

Reputation: 688

shrink all the divs right after the button click. The jsfiddle here https://jsfiddle.net/3q87y2u8/3/

var acc = document.getElementsByClassName("accordion");
            var i;

                for (i = 0; i < acc.length; i++) {
                    acc[i].onclick = function() {
                    this.classList.toggle("active");
    var panels=document.getElementsByClassName("panel");
    for (j = 0; j < panels.length; j++) {
    panels[j].style.maxHeight = null;
    }
                    var panel = this.nextElementSibling;
                    if (panel.style.maxHeight){
                    panel.style.maxHeight = null;
            } else {
            panel.style.maxHeight = panel.scrollHeight + "px";
            } 
           }
        }

Upvotes: 0

Related Questions