For a comment list I use the event delegation pattern after a recommendation from Stackoverflow colleagues (mplungjan, Michel). It works well and I am very enthusiastic about this pattern. But as I already suspected, there will be problems if the bound element (button) contains two child elements (span, span
).
Since I want to get the CommentID from the target in the parent element of the child element, it only works in the cases when you click exactly between the spans inside the button. Actually a case for currentTarget but that doesn't work in this case because the tapped element is the whole comment list.
Question: What do I have to do to fix it?
const commentList = document.querySelector('.comment-list');
commentList.addEventListener('click', (ev) => {
console.log('1. clicked');
const getObjectId = () => {
return ev.target.parentNode.parentNode.getAttribute('data-comment-id');
}
if (! getObjectId()) return false;
if (ev.target.classList.contains('delete')) {
console.log('2. Delete action');
console.log('3. for relatedID', getObjectId());
}
if (ev.target.classList.contains('edit')) {
console.log('2. Edit action');
console.log('3. for relatedID', getObjectId());
}
if (ev.target.classList.contains('flag')) {
console.log('2. Flag action');
console.log('3. for relatedID', getObjectId());
}
});
.controller {
display: flex;
gap:20px;
}
.comment {
margin-bottom: 20px;
background: gray;
}
.controller button > span {
background: orange;
}
.controller button span:first-child {
margin-right: 10px;
}
<div >
<div >
<div >lorem 1. Dont work! Nested button.</div>
<div data-comment-id="1">
<div >
<button ><span>delete</span><span>ICON</span></button>
</div>
<div >
<button ><span>edit</span><span>ICON</span></button>
</div>
<div >
<button ><span>flag</span><span>ICON</span></button>
</div>
</div>
</div>
<div >
<div >lorem 2. Work! </div>
<div data-comment-id="2">
<div ><button >delete</button></div>
<div ><button >edit</button></div>
<div ><button >flag</button></div>
</div>
</div>
<div >
<div >lorem 3. Work! </div>
<div data-comment-id="3">
<div ><button >delete</button></div>
<div ><button >edit</button></div>
<div ><button >flag</button></div>
</div>
</div>
</div>
CodePudding user response:
The problem is that you're using .parentNode.parentNode
to get to the element with data-comment-id
, but the number of parents changes when the target is nested inside additional <span>
elements.
Don't hard-code the nesting levels, use .closest()
to find the containing controller node.
const getObjectId = () => {
return ev.target.closest('.controller').getAttribute('data-comment-id');
}
CodePudding user response:
Building on my last comment in the other question
const tgtButtonWhenSpansInsideButton = e.target.closest("button")
- Cache the objects
- the closest method will get the button itself even if no children
- Make sure you get the class from the containing element of what you want to call a button
const commentList = document.querySelector('.comment-list');
const getObjectId = (tgt) => tgt.closest('.controller').dataset.commentId;
commentList.addEventListener('click', (ev) => {
const tgt = ev.target.closest("button")
const objectId = getObjectId(tgt);
if (!objectId) return;
console.log(objectId,"clicked")
if (tgt.classList.contains('delete')) {
console.log('2. Delete action');
console.log('3. for relatedID', objectId);
}
if (tgt.classList.contains('edit')) {
console.log('2. Edit action');
console.log('3. for relatedID', objectId);
}
if (tgt.classList.contains('flag')) {
console.log('2. Flag action');
console.log('3. for relatedID', objectId);
}
});
.controller {
display: flex;
gap: 20px;
}
.comment {
margin-bottom: 20px;
background: gray;
}
.controller button>span {
background: orange;
}
.controller button span:first-child {
margin-right: 10px;
}
<div >
<div >
<div >lorem 1. Dont work! Nested button.</div>
<div data-comment-id="1">
<div >
<button ><span>delete</span><span>ICON</span></button>
</div>
<div >
<button ><span>edit</span><span>ICON</span></button>
</div>
<div >
<button ><span>flag</span><span>ICON</span></button>
</div>
</div>
</div>
<div >
<div >lorem 2. Work! </div>
<div data-comment-id="2">
<div ><button >delete</button></div>
<div ><button >edit</button></div>
<div ><button >flag</button></div>
</div>
</div>
<div >
<div >lorem 3. Work! </div>
<div data-comment-id="3">
<div ><button >delete</button></div>
<div ><button >edit</button></div>
<div ><button >flag</button></div>
</div>
</div>
</div>
CodePudding user response:
In this case I would "traverse" the DOM up if it wasn't a button that was clicked, something like this
const commentList = document.querySelector('.comment-list');
commentList.addEventListener('click', (ev) => {
console.log('1. clicked', ev.target.tagName);
let target = ev.target
if (target.tagName === "SPAN") {
target = target.parentElement
}
const commentId = target.parentElement.parentElement.getAttribute('data-comment-id');
if (!commentId) return false;
if (target.classList.contains('delete')) {
console.log('2. Delete action');
console.log('3. for relatedID', commentId);
}
if (target.classList.contains('edit')) {
console.log('2. Edit action');
console.log('3. for relatedID', commentId);
}
if (target.classList.contains('flag')) {
console.log('2. Flag action');
console.log('3. for relatedID', commentId);
}
});
.controller {
display: flex;
gap:20px;
}
.comment {
margin-bottom: 20px;
background: gray;
}
.controller button > span {
background: orange;
}
.controller button span:first-child {
margin-right: 10px;
}
<div >
<div >
<div >lorem 1. Dont work! Nested button.</div>
<div data-comment-id="1">
<div >
<button ><span>delete</span><span>ICON</span></button>
</div>
<div >
<button ><span>edit</span><span>ICON</span></button>
</div>
<div >
<button ><span>flag</span><span>ICON</span></button>
</div>
</div>
</div>
<div >
<div >lorem 2. Work! </div>
<div data-comment-id="2">
<div ><button >delete</button></div>
<div ><button >edit</button></div>
<div ><button >flag</button></div>
</div>
</div>
<div >
<div >lorem 3. Work! </div>
<div data-comment-id="3">
<div ><button >delete</button></div>
<div ><button >edit</button></div>
<div ><button >flag</button></div>
</div>
</div>
</div>