So I made a posting system with PHP and it's finally working, though, I have a problem. I coded a javascript that opens the post menu, but it won't open at all. If I were to use the getElementById
function, only the first post will be selected so the other posts' menu won't open, so I tried using the getElementsByClassName
, but now the menu won't open in any posts.
Here is my code:
javascript
let postMenu = document.getElementsByClassName("post-Menu");
let morebtn = document.getElementsByClassName("morebtn");
morebtn.onclick = function() {
postMenu.classList.toggle("postmenShow");
}
html
<div >
<div >
<svg viewBox="0 0 24 24" aria-hidden="true" >
<g>
<circle cx="5" cy="12" r="2"></circle>
<circle cx="12" cy="12" r="2"></circle>
<circle cx="19" cy="12" r="2"></circle>
</g>
</svg>
</div>
<div >
<a href="">
<div ><i ></i> Delete</div>
</a>
<a href="">
<div ><i ></i> Edit</div>
</a>
</div>
</div>
CodePudding user response:
It is hard to guess the exact structure of your HTML, but it could be something like this. I added a #wrapper
element and have the event listener on that. To get the right .norebtn
I use Element.closest() – so the .morebtn
that is closest to the child element that was clicked get selected. In this example I just change the class name on the .morebtn
adding .on
and then use the CSS selector
to style the following .post-Menu
from the .morebtn
.
let wrapper = document.getElementById('wrapper');
wrapper.addEventListener('click', e => {
let morebtn = e.target.closest('.morebtn');
if(morebtn){
morebtn.classList.toggle("on");
}
});
.post-Menu {
display: none;
}
.morebtn {
width: 50px;
}
.morebtn.on {
fill: red;
}
.morebtn.on .post-Menu {
display: block;
}
<div id="wrapper">
<div >
<div >
<svg viewBox="0 0 24 24" aria-hidden="true" >
<g>
<circle cx="5" cy="12" r="2"></circle>
<circle cx="12" cy="12" r="2"></circle>
<circle cx="19" cy="12" r="2"></circle>
</g>
</svg>
</div>
<div >
<a href="">
<div ><i ></i> Delete</div>
</a>
<a href="">
<div ><i ></i> Edit</div>
</a>
</div>
</div>
</div>
CodePudding user response:
As suggested previously it is likely that you had duplicate ID attributes within your HTML but as you were not using document.getElementById
to address these elements Javascript would not have had cause for concern but it is still technically invalid markup. Using document.getElementsByClassName
returns a live nodelist see here for details so each element in this collection needs to be treated individually if you are to assign an event listener to each one. ( An alternative approach is to assign a delegated event listener to a higher level parent element and inspect all click events )
The code below uses document.querySelectorAll
which behaves in a similar manner to getElementsByClassName
and returns a nodelist through which you can iterate and assign the event handler. The clickhandler
inspects the event.target
to find which element invoked the click
- from there it s possible to find the sibling element with class .post-Menu
and modify the visibility therein.
const clickhandler = (e) => {
let el = e.target == e.currentTarget ? e.target : e.target.closest('div');
el.parentNode.querySelector('.post-Menu').classList.toggle("postmenShow");
};
document.querySelectorAll('div.morebtn').forEach(bttn => bttn.addEventListener('click', clickhandler));
svg {
width: 2rem;
height: 2rem;
fill: red;
margin: 0.5rem 0;
}
.post-Menu {
display: none;
}
.postmenShow {
display: block
}
<div >
<div >
<svg viewBox="0 0 24 24" aria-hidden="true" >
<g>
<circle cx="5" cy="12" r="2"></circle>
<circle cx="12" cy="12" r="2"></circle>
<circle cx="19" cy="12" r="2"></circle>
</g>
</svg>
</div>
<div >
<a href="#">
<div ><i ></i> Delete</div>
</a>
<a href="#">
<div ><i ></i> Edit</div>
</a>
</div>
</div>
<div >
<div >
<svg viewBox="0 0 24 24" aria-hidden="true" >
<g>
<circle cx="5" cy="12" r="2"></circle>
<circle cx="12" cy="12" r="2"></circle>
<circle cx="19" cy="12" r="2"></circle>
</g>
</svg>
</div>
<div >
<a href="#">
<div ><i ></i> Delete</div>
</a>
<a href="#">
<div ><i ></i> Edit</div>
</a>
</div>
</div>
An alternative approach using a delegated event listener rather than assigning the clickhandler to each element.
// utility to try to find parent element in the dom
const getparent=(e,expr)=>{
let n=e.target;
do{
if( Element.prototype.matches.call( n, expr ) ) return n;
n=n.previousElementSibling;
}while( n!==null && n.nodeType===1 );
n=e.target;
do{
if ( Element.prototype.matches.call( n, expr ) ) return n;
n=n.parentNode;
}while( n!==null && n.nodeType===1 );
return n;
};
const delegatedclickhandler=(e) => {
if (e.target != e.currrentTarget) {
let el=getparent(e,'div.postmenu');
el.querySelector('.post-Menu').classList.toggle("postmenShow");
}
};
document.body.addEventListener('click', delegatedclickhandler);
svg {
width: 2rem;
height: 2rem;
fill: red;
margin: 0.5rem 0;
}
.post-Menu {
display: none;
}
.postmenShow {
display: block
}
<div >
<div >
<svg viewBox="0 0 24 24" aria-hidden="true" >
<g>
<circle cx="5" cy="12" r="2"></circle>
<circle cx="12" cy="12" r="2"></circle>
<circle cx="19" cy="12" r="2"></circle>
</g>
</svg>
</div>
<div >
<a href="#">
<div ><i ></i> Delete</div>
</a>
<a href="#">
<div ><i ></i> Edit</div>
</a>
</div>
</div>
<div >
<div >
<svg viewBox="0 0 24 24" aria-hidden="true" >
<g>
<circle cx="5" cy="12" r="2"></circle>
<circle cx="12" cy="12" r="2"></circle>
<circle cx="19" cy="12" r="2"></circle>
</g>
</svg>
</div>
<div >
<a href="#">
<div ><i ></i> Delete</div>
</a>
<a href="#">
<div ><i ></i> Edit</div>
</a>
</div>
</div>