Issue
In the following snippet, the menu is successfully being opened when you click en "open" button and successfully closed when you click outside the menu. However, I am not able to fully close the menu if I click on another rows "open" button while the menu is still opened for the previous row.
The issue I run into is that the e.stopPropagation()
method is preventing the click from bubbling up to trigger the documents onclick
event which is responsible for closing the menu. However, I also cannot remove e.stopPropagation()
due to how it would cause this line document.documentElement.addEventListener("click", handle_click_outside_menu);
to trigger instantly, which leads to the menu not opening at all.
Additionally, I just noticed that the menu is not closing when clicking the "close" button either :(
const menu = document.getElementById("menu");
const table = document.getElementById("table");
const option_buttons = Array.from(table.querySelectorAll("button"));
let target_row = null;
table.addEventListener("click", (e) => {
if (option_buttons.includes(e.target)) {
e.stopPropagation();
menu.style.top = e.target.getBoundingClientRect().top + "px";
menu.style.display = "flex";
menu.is_open = true;
target_row = e.target.closest(".row");
target_row.classList.add("selected");
document.documentElement.addEventListener("click", handle_click_outside_menu);
}
});
function handle_click_outside_menu(e) {
if (e.target !== menu && !menu.contains(e.target)) {
close_menu();
document.documentElement.removeEventListener("click", handle_click_outside_menu);
}
}
function close_menu() {
menu.style.display = "none";
menu.is_open = false;
target_row.classList.remove("selected");
target_row = null;
}
.table {
overflow: hidden;
}
.row {
display: flex;
gap: 2rem;
padding: 0.5rem 0;
border-bottom: 0.0625rem solid #ccc;
}
.row .btn_option > span {
pointer-events: none;
display: block;
}
.row:not(.selected) .btn_option > span:last-child {
display: none;
}
.row:is(.selected) .btn_option > span:first-child {
display: none;
}
.menu {
display: none;
position: absolute;
left: 10rem;
padding: 0.5rem;
background: #ccc;
flex-direction: column;
gap: 0.5rem;
}
.menu::before {
content: '';
position: absolute;
top: 0;
left: -0.75rem;
width: 0;
height: 0;
border-top: 0.75rem solid transparent;
border-bottom: 0.75rem solid transparent;
border-right: 0.75rem solid #ccc;
}
<div class="table" id="table">
<div class="row">
<span>Item 1</span>
<button class="btn_option" type="button"><span>Open</span><span>Close</span></button>
</div>
<div class="row">
<span>Item 2</span>
<button class="btn_option" type="button"><span>Open</span><span>Close</span></button>
</div>
<div class="row">
<span>Item 3</span>
<button class="btn_option" type="button"><span>Open</span><span>Close</span></button>
</div>
<div class="row">
<span>Item 4</span>
<button class="btn_option" type="button"><span>Open</span><span>Close</span></button>
</div>
<div class="row">
<span>Item 5</span>
<button class="btn_option" type="button"><span>Open</span><span>Close</span></button>
</div>
</div>
<div class="menu" id="menu">
<button type="button">View</button>
<button type="button">Edit</button>
<button type="button">Delete</button>
</div>
Solution
There is a lot of (imho unnecessary) complexity in your script. It is for example not necessary to add/remove event handling every time. If you specify the exact triggers in you handler function, stopPropagation
is not needed.
I have tried to simplify the code: it uses one (click) handler function on the document level, with exacted specifications for handling the event. The button content is now dependent on the class of its parent row. When the parent row is not selected, the handler opens the menu. Otherwise the menu is closed and the row is deselected.
document.addEventListener("click", handle);
function handle(e) {
console.clear();
const openCloseBttn = e.target.closest(".btn_option");
if (openCloseBttn) {
const target_row = e.target.closest(".row");
if (!target_row.classList.contains(`selected`)) {
const menu = document.querySelector(".menu");
document.querySelector(`.selected`)?.classList.remove(`selected`);
menu.style.top = openCloseBttn.getBoundingClientRect().top + "px";
menu.classList.add(`visible`);
return target_row.classList.add("selected");
}
}
// click on #menu triggers nothing (menu stays visible)
// click on #menu>button triggers reporting the button text
if (e.target.closest(".menu")) {
return e.target.nodeName === `BUTTON` ?
console.log(`clicked menu ${e.target.textContent}`) : true
}
// all other cases: close menu
document.querySelector(".menu").classList.remove("visible");
return document.querySelector(".selected")?.classList.remove("selected");
}
.table {
overflow: hidden;
}
.row {
display: flex;
gap: 2rem;
padding: 0.5rem 0;
border-bottom: 0.0625rem solid #ccc;
}
.menu {
display: none;
position: absolute;
left: 10rem;
padding: 0.5rem;
background: #ccc;
flex-direction: column;
gap: 0.5rem;
}
.menu::before {
content: '';
position: absolute;
top: 0;
left: -0.75rem;
width: 0;
height: 0;
border-top: 0.75rem solid transparent;
border-bottom: 0.75rem solid transparent;
border-right: 0.75rem solid #ccc;
}
.menu.visible {
display: flex;
}
.row .btn_option::after {
content: 'Open';
}
.row.selected .btn_option::after {
content: 'Close';
}
<div class="table">
<div class="row">
<span>Item 1</span>
<button class="btn_option"></button>
</div>
<div class="row">
<span>Item 2</span>
<button class="btn_option"></button>
</div>
<div class="row">
<span>Item 3</span>
<button class="btn_option"></button>
</div>
<div class="row">
<span>Item 4</span>
<button class="btn_option"></button>
</div>
<div class="row">
<span>Item 5</span>
<button class="btn_option"></button>
</div>
</div>
<div class="menu">
<button>View</button>
<button>Edit</button>
<button>Delete</button>
</div>
Answered By - KooiInc
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.