Search code examples
javascriptaddeventlistener

How to close a detail tag automatically as one of their siblings is opened?


This code below closes all other detail tags open if one of them gets opened

document.addEventListener('click', function (e) {
 const details = [...document.querySelectorAll('details')];
 if (!details.some(f => f.contains(e.target))) {
     details.forEach(f => f.removeAttribute('open'));
 } else {
     details.forEach(f => !f.contains(e.target) ? f.removeAttribute('open') : '');
 }
});

That's fine but I want to modify it so it will only close detail tags next to each other (siblings)

document.addEventListener('click', function (e) {
     const details = [...document.querySelectorAll('details')];
      details.some(f => {
       if(f.previousElementSibling && f.previousElementSibling.contains(e.target) || f.nextElementSibling && f.nextElementSibling.contains(e.target)) {
        details.forEach(f => f.removeAttribute('open'));
       } 
      }); 
    });
<div id="siblings1">
<details>
<summary>Parent 1</summary>
<details><summary>Child 1</summary>opened</details>
<details><summary>Child 2</summary>opened</details>
<details><summary>Child 3</summary>opened</details>
</details>
<details>
<summary>Parent 2</summary>
<details><summary>Child 1</summary>opened</details>
<details><summary>Child 2</summary>opened</details>
<details><summary>Child 3</summary>opened</details>
</details>
<details>
<summary>Parent 3</summary>
<details><summary>Child 1</summary>opened</details>
<details><summary>Child 2</summary>opened</details>
<details><summary>Child 3</summary>opened</details>
</details>
</div>

but I don't have it down right quite yet (clicking on the child will close its parent which I don't intend).

Goal: you click parent 1 and then child 1, then you click child 2, that should close child 1. Then you click parent 2, which should close parent 1 but its child 2 should remain open.

What is missing?


Solution

  • I think it my be easier if you don't start at document.

    const details = document.querySelectorAll('details')
    
    function close_slblings(event) {
      event.stopPropagation()
      for (let sib of this.parentElement.querySelectorAll(':scope > details'))
        if (sib != this)
          sib.removeAttribute('open')
          // optionally recursively close their children details
    }
    
    for (let el of details) {
      el.addEventListener('click',close_slblings)
    }
    details{padding-left: 1em;}
    <details>
     <summary>Parent 1</summary>
     <details>
       <summary>Parent 1-1</summary>
       <details><summary>Child 1-1-1</summary></details>
       <details><summary>Child 1-1-2</summary></details>
       <details><summary>Child 1-1-3</summary></details>
     </details>
     <details><summary>Child 1</summary></details>
     <details><summary>Child 2</summary></details>
     <details><summary>Child 3</summary></details>
    </details>
    <details>
     <summary>Parent 2</summary>
     <details><summary>Child 1</summary></details>
     <details><summary>Child 2</summary></details>
     <details><summary>Child 3</summary></details>
    </details>
    <details>
     <summary>Parent 3</summary>
     <details><summary>Child 1</summary></details>
     <details><summary>Child 2</summary></details>
     <details><summary>Child 3</summary></details>
    </details>