Search code examples
javascriptcsstabspanel

Collapsible panel with variable content height


A couple of tabs have content of different heights (e.g. one has more text than the other), but the parent collapsible panel will not resize when switching between child tabs. How do I change the parent collapsible panel content to a max height that matches the child tabs heights? Or make the max height depending on the tab heights?

EDIT 2: Snippet now contains CSS active class

EDIT: Semi-solution found!
In JavaScript, instead of using this code:

content.style.maxHeight = content.scrollHeight + "px";

I changed it to the following:

content.style.maxHeight = "initial";

And this works but it comes with a new problem. Because now the CSS transition does no longer work like it does when using scrollHeight:

.calendar-unfold {
    max-height: 0;
    transition: max-height 0.4s ease-out;
}

SNIPPET:

var coll = document.getElementsByClassName("calendar-fold");
  var i;
  for (i = 0; i < coll.length; i++) {
    coll[i].addEventListener("click", function () {
      this.classList.toggle("active");
      var content = this.nextElementSibling;
      if (content.style.maxHeight) {
        content.style.maxHeight = null;
      } else {
        content.style.maxHeight = content.scrollHeight + "px";
      }
    });
  }
  /* CALENDAR TABS 3*/
  function calendarTabCalendarBtn3(){
    document.getElementById('calendarTabCalendar3').style.display ='block';
    document.getElementById('calendarTabPruning3').style.display ='none';
    document.getElementById('calendarTabPregrow3').style.display ='none';
    document.getElementById('calendarTabAftergrow3').style.display ='none';
    document.getElementById('calendarTabNeighbour3').style.display ='none';
  }
  function calendarTabPruningBtn3(){
    document.getElementById('calendarTabCalendar3').style.display ='none';
    document.getElementById('calendarTabPruning3').style.display ='block';
    document.getElementById('calendarTabPregrow3').style.display ='none';
    document.getElementById('calendarTabAftergrow3').style.display ='none';
    document.getElementById('calendarTabNeighbour3').style.display ='none';
  }
  function calendarTabPregrowBtn3(){
    document.getElementById('calendarTabCalendar3').style.display ='none';
    document.getElementById('calendarTabPruning3').style.display ='none';
    document.getElementById('calendarTabPregrow3').style.display ='block';
    document.getElementById('calendarTabAftergrow3').style.display ='none';
    document.getElementById('calendarTabNeighbour3').style.display ='none';
  }
  function calendarTabAftergrowBtn3(){
    document.getElementById('calendarTabCalendar3').style.display ='none';
    document.getElementById('calendarTabPruning3').style.display ='none';
    document.getElementById('calendarTabPregrow3').style.display ='none';
    document.getElementById('calendarTabAftergrow3').style.display ='block';
    document.getElementById('calendarTabNeighbour3').style.display ='none';
  }
  function calendarTabNeighbourBtn3(){
    document.getElementById('calendarTabCalendar3').style.display ='none';
    document.getElementById('calendarTabPruning3').style.display ='none';
    document.getElementById('calendarTabPregrow3').style.display ='none';
    document.getElementById('calendarTabAftergrow3').style.display ='none';
    document.getElementById('calendarTabNeighbour3').style.display ='block';
  }
.calendar-fold {
    margin: 4px;
    padding: 10px;
    font-size: 1.25em!important;
    background-color: rgba(255,255,255,1.00);
    border: 1px solid rgba(55,175,75,1.00);
    color: rgba(55,175,75,1.00);
    border-radius: 5px;
    cursor: pointer;
    transition: 0.4s;
}
.calendar-fold:hover {
    border: 1px solid rgba(0,145,255,1.00);
    color: rgba(0,145,255,1.00);
}
.calendar-fold.active {
    background-color: rgba(55,175,75,1.00);
    border: 1px solid rgba(55,175,75,1.00);
    color: rgba(255,255,255,1.00);
}
.calendar-fold.active:hover {
    background-color: rgba(55,175,75,0.80);
    border: 1px solid rgba(55,175,75,0.00);
    color: rgba(255,255,255,1.00);
}
.calendar-unfold {
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.4s ease-out;
    background-color: rgba(200,200,200,1.00);
}
.calendar-tabbuttongroup {
    width: calc(100% - 8px);
    margin: 0 4px;
}
.calendar-tabbutton {
    background-color: rgba(55,175,75,1.00);
    color: rgba(255,255,255,1.00);
    padding: 8px;
    font-size: 1.00em;
    float: left;
    width: 10%;
    cursor: pointer;
}
.calendar-tabbutton:hover {
    background-color: rgba(55,175,75,0.80);
}
.calendar-tabgroup {
    float: left;
    width: 100%;
}
.calendar-tabcontent {
    width: 100%;
    padding: 0 0 8px 0;
}
#calendarTabCalendar3 { display: block; }
#calendarTabPruning3 { display: none; }
#calendarTabPregrow3 { display: none; }
#calendarTabAftergrow3 { display: none; }
#calendarTabNeighbour3 { display: none; }
<div class="calendar-fold">Collapsible</div>
<div class="calendar-unfold">
  <div class="calendar-tabbuttongroup">
    <div class="calendar-tabbutton calendarTabbtnCalendar" onclick="calendarTabCalendarBtn3();">Btn 1</div>
    <div class="calendar-tabbutton calendarTabbtnPruning" onclick="calendarTabPruningBtn3();">Btn 2</div>
    <div class="calendar-tabbutton calendarTabbtnPregrow" onclick="calendarTabPregrowBtn3();">Btn 3</div>
    <div class="calendar-tabbutton calendarTabbtnAftergrow" onclick="calendarTabAftergrowBtn3();">Btn 4</div>
    <div class="calendar-tabbutton calendarTabbtnNeighbour" onclick="calendarTabNeighbourBtn3();">Btn 5</div>
  </div>
  <div class="calendar-tabgroup">
    <div class="calendar-tabcontent" id="calendarTabCalendar3">
      <div class="calendar-moreinfo">
        <p class="calendar-pc">
          Some default content<br>
          Some default content<br>
          Some default content
          <hr>
        </p>
      </div>
    </div>
    <div class="calendar-tabcontent" id="calendarTabPruning3">
      <div class="calendar-moreinfo">
        <p class="calendar-pc">
          Some small content<br>
          Some small content
          <hr>
        </p>
      </div>
    </div>
    <div class="calendar-tabcontent" id="calendarTabPregrow3">
      <div class="calendar-moreinfo">
        <p class="calendar-pc">
          Some large content<br>
          Some large content<br>
          Some large content<br>
          Some large content<br>
          Some large content<br>
          Some large content
          <hr>
        </p>
      </div>
    </div>
    <div class="calendar-tabcontent" id="calendarTabAftergrow3">
      <div class="calendar-moreinfo">
        <p class="calendar-pc">
          Some medium content<br>
          Some medium content<br>
          Some medium content<br>
          Some medium content
          <hr>
        </p>
      </div>
    </div>
    <div class="calendar-tabcontent" id="calendarTabNeighbour3">
      <div class="calendar-moreinfo">
        <p class="calendar-pc">
          Some small content<br>
          Some small content
          <hr>
        </p>
      </div>
    </div>
  </div>
</div>


Solution

  • You may find some of the ideas in this revised version of your code useful.


    Primary changes to note include:

    • Instead of inline event handlers, this version employs a single event listener on the buttons' parent div (which works because DOM events typically bubble up to ancestor elements).
    • Decisions about which element to modify use the event's target property plus a data-attribute on each button (which tells the listener which tab is associated with that button).
    • The HTML includes two new classes (which are targeted by selectors in the CSS): hidden and highlight. The script adds or removes these classes as appropriate instead of manipulating styles manually.

    These changes help with separation of concerns on the page (Markup vs Styling vs Scripting) and allow for less repetitious code, both of which can serve to make your code less error-prone and easier to maintain.


    The "tabgroup" div dynamically resizes to fit its contents as you would expect, with no need to change its height via explicit styling.

    See the in-code comments for some further explanation (and note that some of the original HTML and CSS have been changed or omitted for brevity and clarity.)

    // Identifies some DOM elements
    const
      buttonGroup = document.getElementsByClassName("tabbuttongroup")[0],
      buttons = document.getElementsByClassName("tabbutton");
      tabs = document.querySelectorAll(".tabgroup .tabcontent");
    
    // Calls `updateTabsDisplay` when user clicks inside buttonGroup
    buttonGroup.addEventListener("click", updateTabsDisplay);
    
    // Defines listener (which always has access to the triggering event)
    function updateTabsDisplay(event){
      const clicked = event.target; // Event's `target` property is useful
    
      // Ignores irrelevant clicks
      if(!clicked.classList.contains("tabbutton")){ return; }
    
      // Un-highlights buttons, and highlights clicked button
      for(let button of buttons){
        button.classList.remove("highlight");
        if(button == clicked){
          clicked.classList.add("highlight");
        }
      }
    
      // Reads button's `data-tab` attribute, where associated tabClass is stored
      const tabClass = clicked.dataset.tab;
    
      // Hides tabs, and un-hides the matching tab
      for(let tab of tabs){
        tab.classList.add("hidden");
        if(tab.classList.contains(tabClass)){
          tab.classList.remove("hidden");
        }
      }
    }
    .tabbutton { background-color: rgba(55,175,75,1.00); color: white; padding: 8px; float: left; width: 12ch; text-align: center; }
    .tabbutton + .tabbutton { border-left: 1px solid white; }
    .tabbutton:hover { background-color: rgba(55,175,75,0.80); }
    .tabbutton.highlight { background-color: rgba(55,175,75,0.50); }
    
    .tabgroup { clear: both; }
    .tabcontent { padding: 0 0 8px 0; }
    
    .hidden{ display: none; } 
    <div class="tabbuttongroup">
      <!-- `data-attributes` can be used to store relevant strings -->
      <div class="tabbutton highlight" data-tab="tab-calendar">Calendar</div>
      <div class="tabbutton" data-tab="tab-pruning">Pruning</div>
      <div class="tabbutton" data-tab="tab-pregrow">Pregrow</div>
      <div class="tabbutton" data-tab="tab-aftergrow">Aftergrow</div>
      <div class="tabbutton" data-tab="tab-neighbour">Neighbour</div>
    </div>
    
    <div class="tabgroup">
      <!-- Each tab has a class matching its button's `data-tab` attribute -->
      <div class="tabcontent tab-calendar">
        <p>"Calendar Tab" content<br/>..<br/>..<br/>..<hr></p>
      </div>
      <div class="tabcontent tab-pruning hidden">
        <p>"Pruning Tab" content<hr></p>
      </div>
      <div class="tabcontent tab-pregrow hidden">
        <p>"Pregrow Tab" content<br/>..<br/>..<br/>..<br/>..<br/>..<br/>..<hr></p>
      </div>
      <div class="tabcontent tab-aftergrow hidden">
        <p>"Aftergrow Tab" content<br/>..<br/>..<br/>..<br/>..<hr></p>
      </div>
      <div class="tabcontent tab-neighbour hidden">
        <p>"Neighbour Tab" content<hr></p>
      </div>
    </div>