Home > Software design >  How to close a detail tag automatically as one of their siblings is opened?
How to close a detail tag automatically as one of their siblings is opened?

Time:09-03

Hi,

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') : '');
 }
});

Thats 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 dont have it down right quite yet (clicking on the child will close its parent which I dont 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?

Thank you.

CodePudding user response:

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>

CodePudding user response:

Here comes document version.

note: you need to find the correct target in this version.

function close_slblings() {
  for (let sib of this.parentElement.querySelectorAll(':scope > details'))
    if (sib != this)
      sib.removeAttribute('open')
      // optionally recursively close their children details
}

document.addEventListener('click',e=>{
  let target_detail=e.target
  while(target_detail && !(target_detail instanceof HTMLDetailsElement)){
    target_detail = target_detail.parentElement
  }
  if(target_detail)
    close_slblings.call(target_detail)
})
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>

something to click on

CodePudding user response:

You can query the details element in the same level instead of querying every node.

document.addEventListener("click", function (e) {
  let details = e.target.closest("details");
  let childNodes = Array.from(details.parentNode.childNodes);
  for (let i = 0; i < childNodes.length; i  ) {
      let node = childNodes[i];
      if(node.tagName === 'DETAILS')
        node.removeAttribute("open");
  }
  details.setAttribute("close");
});
<details>
 <summary>Parent 1</summary>
 <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>

  • Related