Question

Here is my code.

<details>
  <summary>1</summary>
  Demo 1
</details>

<details>
  <summary>2</summary>
  Demo 2
</details>

<details>
  <summary>3</summary>
  Demo 3
</details>

What I want to do is -- if the details of any single <details> tag is open and I open/view another <details> tag, then the earlier one should close/hide/minimize.

How can this be achieved?

I'm aware the <details> tag is not supported in IE or Edge.

Was it helpful?

Solution

Another approach, slightly shorter, slightly more efficient, without dependencies, and without onclick attributes in the HTML.

// Fetch all the details element.
const details = document.querySelectorAll("details");

// Add the onclick listeners.
details.forEach((targetDetail) => {
  targetDetail.addEventListener("click", () => {
    // Close all the details that are not targetDetail.
    details.forEach((detail) => {
      if (detail !== targetDetail) {
        detail.removeAttribute("open");
      }
    });
  });
});
<details>
  <summary>1</summary>Demo 1
</details>

<details>
  <summary>2</summary>Demo 2
</details>

<details>
  <summary>3</summary>Demo 3
</details>

OTHER TIPS

Whao ! before my posting...

  1. No one has ndicated that <details> elements work with the toggle event?
    -- instead of click
    -- and the toggle event also works with keyboard interaction.

  2. No one has indicated that the open attribute is a boolean,
    make it to true or false, don't do a .removeAttr("open") ;)

the doc : https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details

const All_Details = document.querySelectorAll('details');

All_Details.forEach(deet=>{
  deet.addEventListener('toggle', toggleOpenOneOnly)
})

function toggleOpenOneOnly(e) {
  if (this.open) {
    All_Details.forEach(deet=>{
      if (deet!=this && deet.open) deet.open = false
    });
  }
}
<details>
  <summary>1</summary>
  Demo 1
</details>

<details>
  <summary>2</summary>
  Demo 2
</details>

<details>
  <summary>3</summary>
  Demo 3
</details>

The same thing in a shorter way with ES10? => .ontoggle direct event method

document.querySelectorAll('details').forEach((D,_,A)=>{
  D.ontoggle =_=>{ if(D.open) A.forEach(d =>{ if(d!=D) d.open=false })}
})

See it in action : ( Plus some CSS improvements ;)

document.querySelectorAll('details').forEach((D,_,A)=>{
  D.ontoggle =_=>{ if(D.open) A.forEach(d =>{ if(d!=D) d.open=false })}
})
details {
  border        : 1px solid lightgrey;
  width         : 24em;
  padding       : 0 .6em;
  border-radius : .3em;
  margin        : .3em;
  }
details > summary  {
  font-weight   : bold;
  margin        : 0em -.6em;
  list-style    : none;
  display       : block;
  padding       : .5em;
  }
details[open] {
  padding-bottom : .6em;
  }
details[open] summary {
  border-bottom : 1px solid #aaa;
  margin-bottom : .6em  ;
  }
summary::marker {
  display : none;
  }
summary::-webkit-details-marker {
  display : none;
  }
summary::after {
  display    : block;
  float      : right;
  content    : '\1405';
  cursor     : pointer;
  transition : 180ms;
  transform  : rotate(90deg);
  }
details[open] > summary:after {
  transform  : rotate(-90deg);
  }
summary:hover {
  outline          : none;
  background-color : whitesmoke;
  }
<details>
  <summary>Lorem ipsum one</summary>
  Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, 
  dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas 
  ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, 
  enim est eleifend mi, non fermentum diam nisl sit amet erat.
</details>
<details>
  <summary>Lorem ipsum two</summary>
  Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, 
  dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas 
  ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, 
  enim est eleifend mi, non fermentum diam nisl sit amet erat.
</details>
<details>
  <summary>Lorem ipsum three</summary>
  Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, 
  dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas 
  ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, 
  enim est eleifend mi, non fermentum diam nisl sit amet erat.
</details>

Same concept, just a bit shorter.

$('details').click(function (event) {
    $('details').not(this).removeAttr("open");  
});

Yet another answer for those who don't want to use obsolete jQuery and those who loves functional javascript

[...document.getElementsByTagName("details")].forEach( (D,_,A) =>
  D.addEventListener("toggle", E =>
    D.open && A.forEach(d =>
      d!=E.target && (d.open=false)
    )
  )
)
<details>
  <summary>1</summary>Demo 1
</details>

<details>
  <summary>2</summary>Demo 2
</details>

<details>
  <summary>3</summary>Demo 3
</details>

Better late than never... I would like to point out this quote from https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details

Note: You have to remove this attribute entirely to make the details hidden. open="false" makes the details visible because this attribute is Boolean.

(Read the note appended right at the end of the attributes paragraph.)

Apparently, in this case boolean means existing or not and not settable to true or false...

Browser magic going further!

I have come up with a solution. Please correct me if this is a wrong approach.

I added an onclick event to all of the details tag and made a function thisindex(this) which returns the index of the clicked tag and the obtained index number is then passed to the another function closeAll() which minimizes/closes all the other open tags except for one whose index matches with what we obtained earlier.

Here is the code.

function thisindex(elm){
  var nodes = elm.parentNode.childNodes, node;
  var i = 0, count = i;
  while( (node=nodes.item(i++)) && node!=elm )
    if( node.nodeType==1 ) count++;
  return count;
}

function closeAll(index){
  var len = document.getElementsByTagName("details").length;

  for(var i=0; i<len; i++){
    if(i != index){
      document.getElementsByTagName("details")[i].removeAttribute("open");
    }
  }
}
<details onclick="closeAll(thisindex(this));">
  <summary>1</summary>Demo 1
</details>

<details onclick="closeAll(thisindex(this));">
  <summary>2</summary>Demo 2
</details>

<details onclick="closeAll(thisindex(this));">
  <summary>2</summary>Demo 3
</details>

Same with the help of jQuery

$(document).ready(function(){
  $('details').click(function (event) {
    var index = $('details').index(this);
    var len = $("details").length;
    for(var i=0; i<len; i++){
      if(i != index){
        $("details")[i].removeAttribute("open");
      }
    }
  });
});

Kindly suggest me a better approach if this not up to the mark.

Modification for use with polyfill jquery-details.js [Edge]

  var isIE = /*@cc_on!@*/false || !!document.documentMode;
  var isEdge = !isIE && !!window.StyleMedia;
  const details = Array.from(document.querySelectorAll("details"));
  details.forEach((targetDetail) => {
    targetDetail.addEventListener("click", () => {
      details.forEach((detail) => {
        if (detail !== targetDetail) {
          if(isEdge) {
            detail.className = detail.className.replace(/\bopen\b/g,"");
            detail.open =  false;
            detail.querySelector("summary").setAttribute("aria-expanded","false");
            var chil = detail.querySelectorAll("details > *");
            for(var j = 0; j < chil.length; j++) {
              if(chil[j].tagName != "SUMMARY") {
                chil[j].style.display = "none";
              }
            }
          } else {
            detail.removeAttribute("open");
          }
        }
      });
    });
  });**strong text**
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top