/**
* Retrieve data from an API.
* @param {String} api - The API url.
* @returns {Promise} The result from api fetching.
*/
async function fetchData(api) {
const headers = new Headers();
const init = {
method: "GET",
headers,
mode: "cors",
cache: "default",
};
const response = await fetch(api, init);
const result = await response.json();
return result;
}
/**
* Create a list item with a link.
* @param {Object} data - An object containing an id and an username.
* @returns {HTMLElement} A list item containing a link.
*/
function getListItem(data) {
const item = document.createElement("li");
const link = document.createElement("a");
link.textContent = data.username;
link.href = `?user=${data.id}`;
link.id = `user-${data.id}`;
link.classList.add("users-list__link");
item.appendChild(link);
item.classList.add("users-list__item");
return item;
}
/**
* Print the users list.
* @param {Object[]} users - An array of user object.
*/
function printUsersList(users) {
const list = document.querySelector(".users-list");
users.map((user) => list.appendChild(getListItem(user)));
}
/**
* Create a description term element.
* @param {String} body - The description term body.
* @returns {HTMLElement} The description term element.
*/
function createUserInfoDt(body) {
const dt = document.createElement("dt");
dt.classList.add("user-info__label");
dt.textContent = body;
return dt;
}
/**
* Create a description details element.
* @param {String} body - The description details body.
* @param {Boolean} [hasHTML] - True if body contains HTML; false otherwise.
* @returns {HTMLElement} The description details element.
*/
function createUserInfoDd(body, hasHTML = false) {
const dd = document.createElement("dd");
dd.classList.add("user-info__content");
hasHTML ? (dd.innerHTML = body) : (dd.textContent = body);
return dd;
}
/**
* Get the markup to display info for a given user.
* @param {Object} user - An user with fullname, email address, phone & website.
* @returns {Object} Two HTMLElement: title and body.
*/
function getUserInfoTemplate(user) {
const userAddress = `${user.address.suite}
${user.address.street}
${user.address.zipcode} ${user.address.city}`;
const fullName = document.createElement("h2");
const userDetails = document.createElement("dl");
const emailLabel = createUserInfoDt("Email");
const email = createUserInfoDd(user.email);
const addressLabel = createUserInfoDt("Address");
const address = createUserInfoDd(userAddress, true);
const phoneLabel = createUserInfoDt("Phone");
const phone = createUserInfoDd(user.phone);
const websiteLabel = createUserInfoDt("Website");
const website = createUserInfoDd(user.website);
fullName.textContent = user.name;
fullName.classList.add("user-info__title");
userDetails.classList.add("user-info__body");
userDetails.append(
emailLabel,
email,
addressLabel,
address,
phoneLabel,
phone,
websiteLabel,
website
);
return {
title: fullName,
body: userDetails,
};
}
/**
* Print the info for a given user.
* @param {Integer} userId - The user id.
*/
async function printUserInfo(userId) {
const api = `https://jsonplaceholder.typicode.com/users/${userId}`;
const user = await fetchData(api).then((data) => data);
const userInfo = document.querySelector(".user-info");
const userInfoTemplate = getUserInfoTemplate(user);
userInfo.hasChildNodes()
? userInfo.replaceChildren(userInfoTemplate.title, userInfoTemplate.body)
: userInfo.append(userInfoTemplate.title, userInfoTemplate.body);
}
/**
* Add a class "active" to a link.
* @param {Integer} linkId - The link id.
*/
function addActiveClassTo(linkId) {
const link = document.getElementById(linkId);
link.classList.add("active");
}
/**
* Print the user info on click.
* @param {MouseEvent} e - The click event.
* @param {NodeList} links - The users links.
*/
function handleClick(e, links) {
e.preventDefault();
const userId = e.target.id.split("user-")[1];
const linkId = `user-${userId}`;
history.pushState({ userId }, e.target.textContent, e.target.href);
printUserInfo(userId);
for (let i = 0; i < links.length; i++) {
links[i].classList.remove("active");
}
addActiveClassTo(linkId);
}
/**
* Listen all users links.
*/
function listenUsersLinks() {
const usersLinks = document.querySelectorAll(".users-list__link");
for (let i = 0; i < usersLinks.length; i++) {
const link = usersLinks[i];
link.addEventListener("click", (e) => handleClick(e, usersLinks));
}
}
/**
* Load user info when URL refers to an user page.
*/
function listenURL() {
const currentURL = window.location.href;
const isUserPage = currentURL.includes("user=");
if (isUserPage) {
const userId = currentURL.split("?user=")[1];
const linkId = `user-${userId}`;
printUserInfo(userId);
addActiveClassTo(linkId);
}
}
/**
* Init the app.
*/
async function init() {
const api = "https://jsonplaceholder.typicode.com/users";
const users = await fetchData(api).then((data) => data);
printUsersList(users);
listenUsersLinks();
listenURL();
}
init();
id='n49' href='#n49'>49
50
51
@use "@styles/abstracts/functions" as fun;
@use "@styles/abstracts/mixins" as mix;
@use "@styles/abstracts/placeholders";
.section {
--column-3: 0;
--grid-gap: 0;
composes: grid from "@styles/layout/_grid.scss";
align-items: first baseline;
}
.year {
grid-column: 2;
margin-bottom: 0;
@include mix.media("screen") {
@include mix.dimensions("md") {
grid-column: 1;
justify-self: end;
position: sticky;
top: var(--spacing-xs);
margin-right: var(--spacing-lg);
}
}
}
.list {
@extend %reset-ordered-list;
grid-column: 2;
margin: 0 auto var(--spacing-md);
}
li.item {
border-bottom: fun.convert-px(1) solid var(--color-border);
&:not(:last-of-type) {
margin: 0 0 var(--spacing-md) 0;
}
&:first-of-type {
margin-top: var(--spacing-sm);
@include mix.media("screen") {
@include mix.dimensions("md") {
margin-top: 0;
}
}
}
}