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>