'How can I add an onClick event listener to a table?
I am currently trying to add a feature where when I click the name of a user in the table it will display all of the posts made by that User however I am completely stumped. The url for the posts is https://jsonplaceholder.typicode.com/posts
I currently have the table set up and will attach images of the table and code below.
Here is the problem I am trying to solve for further context
Using jQuery or vanilla JS you will display each USER in a table. When the user selects a USER in the table, it will display all of the 'POSTS' that were created by that USER.




Solution 1:[1]
Make use of event delegation together with Element.closest and the data-* attribute respectively its HTMLElement.dataset counterpart.
document
.querySelector('tbody')
.addEventListener('click', ({ target }) => {
const currentRow = target.closest('tr');
const userId = currentRow.dataset.id;
console.clear();
console.log('userId: ', userId);
});
body {
margin: 0 0 0 2px;
}
table {
border-collapse: collapse;
}
thead th {
color: #fff;
font-weight: bolder;
background-color: #009577;
}
th, td {
padding: 4px 10px 6px 10px;
color: #0a0a0a;
}
tbody tr:nth-child(even) {
background-color: #eee;
}
tbody tr:hover {
outline: 2px dotted orange;
}
tbody tr {
cursor: pointer;
}
tbody tr:nth-child(even) { background-color: #eee; }
tbody tr:hover { outline: 2px dotted orange; }
<table>
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Username</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<tr data-id='1'>
<td>1</td>
<td>Leanne Graham</td>
<td>Bret</td>
<td>[email protected]</td>
</tr>
<tr data-id='2'>
<td>2</td>
<td>Ervin Howell</td>
<td>Antonette</td>
<td>[email protected]</td>
</tr>
<tr data-id='3'>
<td>3</td>
<td>Clementine Bauch</td>
<td>Samantha</td>
<td>[email protected]</td>
</tr>
<tr data-id='4'>
<td>4</td>
<td>Patricia Lebsack</td>
<td>Karianne</td>
<td>[email protected]</td>
</tr>
<tr data-id='5'>
<td>5</td>
<td>Chelsey Dietrich</td>
<td>Kamren</td>
<td>[email protected]</td>
</tr>
</tbody>
</table>
A vanilla based implementation of a component-like approach which is partially configurable via data-* attributes then possibly might look like the next provided example code ...
function emptyElementNode(node) {
[...node.childNodes].forEach(child => child.remove());
}
function clearTableContent(root) {
[
...root.querySelectorAll('thead'),
...root.querySelectorAll('tbody'),
].forEach(child => child.remove());
}
function createTableHead(headerContentList) {
const elmThead = document.createElement('thead');
const elmTr = headerContentList
.reduce((root, content) => {
const elmTh = document.createElement('th');
elmTh.textContent = content;
root.appendChild(elmTh);
return root;
}, document.createElement('tr'));
elmThead.appendChild(elmTr);
return elmThead;
}
function createTableBody(rowContentKeyList, userList) {
return userList
.reduce((elmTbody, userItem) => {
const elmTr = rowContentKeyList
.reduce((root, key) => {
const elmTd = document.createElement('td');
elmTd.textContent = userItem[key];
root.appendChild(elmTd);
return root;
}, document.createElement('tr'));
elmTr.dataset.id = userItem.id;
elmTbody.appendChild(elmTr);
return elmTbody;
}, document.createElement('tbody'));
}
function createAndRenderPostItem(root, { title, body }) {
const elmDt = document.createElement('dt');
const elmDd = document.createElement('dd');
elmDt.textContent = title;
elmDd.textContent = body;
root.appendChild(elmDt);
root.appendChild(elmDd);
return root;
}
function updateSelectedStates(selectedRow) {
[...selectedRow.parentNode.children]
.forEach(rowNode =>
rowNode.classList.remove('selected')
);
selectedRow.classList.add('selected');
}
function handleUserPostsRequestFromBoundData({ target }) {
const { postsRoot, requestUrl, placeholder } = this;
const currentRow = target.closest('tr');
const userId = currentRow?.dataset?.id;
if (userId) {
createListOfUserPosts({
postsRoot,
url: requestUrl.replace(placeholder, userId)
});
updateSelectedStates(currentRow);
}
}
async function createListOfUserPosts({ postsRoot, url }) {
emptyElementNode(postsRoot);
if (postsRoot && url) {
const response = await fetch(url);
const postList = await response.json();
postList.reduce(createAndRenderPostItem, postsRoot);
}
}
async function createListOfUsers({ usersRoot, postsRoot }) {
const usersRequestUrl = usersRoot.dataset.request;
const userPostsRequestUrl = postsRoot.dataset.request;
const userPostsPlaceholder = postsRoot.dataset.placeholder;
const response = await fetch(usersRequestUrl);
const userList = await response.json();
if (userList.length >= 1) {
const displayConfig = JSON.parse(
usersRoot.dataset.display ?? '{}'
);
const headerContentList = Object.values(displayConfig);
const rowContentKeyList = Object.keys(displayConfig);
emptyElementNode(postsRoot);
clearTableContent(usersRoot);
usersRoot.appendChild(
createTableHead(headerContentList)
);
usersRoot.appendChild(
createTableBody(rowContentKeyList, userList)
);
usersRoot.addEventListener(
'click',
handleUserPostsRequestFromBoundData
.bind({
postsRoot,
requestUrl: userPostsRequestUrl,
placeholder: userPostsPlaceholder,
})
);
}
}
function initializeUserPostsComponent(root) {
const usersRoot = root.querySelector('[data-users]');
const postsRoot = root.querySelector('[data-posts]');
createListOfUsers({ usersRoot, postsRoot });
}
document
.querySelectorAll('[data-user-posts]')
.forEach(initializeUserPostsComponent);
body { margin: 0 0 0 2px; font-size: .8em; }
table { border-collapse: collapse; }
table caption { text-align: left; padding: 3px; }
thead th { color: #fff; font-weight: bolder; background-color: #009577; }
th, td { padding: 3px 5px; color: #0a0a0a; }
tbody tr:nth-child(even) { background-color: #eee; }
tbody tr:hover { outline: 2px dotted orange; }
tbody tr.selected { background-color: rgb(255 204 0 / 18%); }
tbody tr { cursor: pointer; }
[data-user-posts]::after { clear: both; display: inline-block; content: ''; }
.user-overview { float: left; width: 63%; }
.user-posts { float: right; width: 36%; }
.user-posts h3 { margin: 0; padding: 4px 8px; font-size: inherit; font-weight: normal; }
<article data-user-posts>
<table
data-users
data-request="https://jsonplaceholder.typicode.com/users"
data-display='{"id":"Id","name":"Name","username":"Username","email":"Email"}'
class="user-overview"
>
<caption>Select a user to view posts</caption>
</table>
<div class="user-posts">
<h3>Posts:</h3>
<dl
data-posts
data-request="https://jsonplaceholder.typicode.com/users/:userId/posts"
data-placeholder=":userId"
>
</dl>
</div>
<article>
Solution 2:[2]
To add a click event handler to a table row with JavaScript, we can loop through each row and set the onclick property of each row to an event handler.
For instance, we add a table by writing:
<table>
<tbody>
<tr>
<td>foo</td>
<td>bar</td>
</tr>
<tr>
<td>baz</td>
<td>qux</td>
</tr>
</tbody>
</table>
Then we write:
const createClickHandler = (row) => {
return () => {
const [cell] = row.getElementsByTagName("td");
const id = cell.innerHTML;
console.log(id);
};
};
const table = document.querySelector("table");
for (const currentRow of table.rows) {
currentRow.onclick = createClickHandler(currentRow);
}
to add event handlers to each row.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Bhavya Khurana |
