Source: scripts/contacts.js

/**
 * Initializes the page by loading the necessary components and rendering
 * the contact list.
 *
 * @returns {Promise<void>} A promise that resolves when the page has been
 * initialized.
 */
async function init() {
  loadComponents();
  await getContactsFromData("guest");
  await getTodosFromData("guest");
  renderContactsPage();
}

/**
 * Renders the contacts page by fetching data from the given URL and
 * rendering the contact list.
 *
 * @returns {Promise<void>}
 */

async function renderContactsPage() {
  await getContactsFromData("guest");
  renderContactList();
}

/**
 * Loads all necessary components into the page.
 *
 * Currently, this function only loads the header and navbar components.
 * @returns {void}
 */
function loadComponents() {
  loadHeader();
  loadNavbar();
}

/**
 * Loads the header component into the element with the id "header".
 * If no element with that id exists, this function does nothing.
 * @returns {void}
 */
function loadHeader() {
  const header = document.getElementById("header");
  if (!header) return;

  header.innerHTML = getHeaderTemplate();
}

/**
 * Loads the navbar component into the element with the id "navbar".
 * If no element with that id exists, this function does nothing.
 *
 * @returns {void}
 */
function loadNavbar() {
  const navbar = document.getElementById("navbar");
  if (!navbar) return;
  navbar.innerHTML = getNavbarTemplate("contacts");
}

/**
 * Renders the contact list in the element with the id "contactList".
 *
 * If the element does not exist, does nothing.
 *
 * If the globalContacts array is empty, renders a message prompting the user
 * to add a contact.
 *
 * Otherwise, groups all contacts by the first letter of their name, renders
 * the HTML for the contact list given the grouped contacts, and sets the
 * innerHTML of the element to the rendered HTML.
 *
 * @returns {void}
 */
function renderContactList() {
  const contactListElement = document.getElementById("contactList");

  if (!contactListElement) return;
  if (globalContacts.length === 0) renderNoContactsMessage();

  const contactsByLetter = groupContactsByLetter();
  const contactListHtml = renderContactListHtml(contactsByLetter);
  contactListElement.innerHTML = contactListHtml;
}

/**
 * Given an object where the keys are letters and the values are arrays of
 * contacts whose name starts with the key, returns an HTML string representing
 * the contact list.
 *
 * The contact list is rendered with a masonry layout, which can be
 * customized by modifying the CSS classes used in the returned HTML.
 *
 * @param {Object<string, Array<Object>>} contactsByLetter - An object where the
 *   keys are letters and the values are arrays of contacts whose name starts
 *   with the key.
 * @returns {string} An HTML string representing the contact list.
 */
function renderContactListHtml(contactsByLetter) {
  let contactIndex = -1;
  const sortedLetters = Object.keys(contactsByLetter).sort((a, b) => a.localeCompare(b));
  return sortedLetters
    .map((letter) => {
      const contactsForLetter = contactsByLetter[letter];
      const contactElements = contactsForLetter.map((contact) => {
        const initials = getInitialsFromContact(contact);
        contactIndex++;
        return getContactTemplate(contactIndex, initials, contact.color, contact.name, contact.email);
      });
      return getContactSectionTemplate(letter, contactElements);
    })
    .join("");
}

/**
 * Returns an object where the keys are the first letters of the contacts' names
 * and the values are arrays of contacts whose name starts with the key.
 *
 * @returns {Object<string, Array<Object>>}
 */
function groupContactsByLetter() {
  return globalContacts.reduce((contactsByLetter, contact) => {
    const firstLetter = contact.name.split(" ")[0].charAt(0).toUpperCase();
    if (!contactsByLetter[firstLetter]) {
      contactsByLetter[firstLetter] = [];
    }
    contactsByLetter[firstLetter].push(contact);
    return contactsByLetter;
  }, {});
}

/**
 * If there are no contacts in the globalContacts array, renders a message
 * in the contact list element that prompts the user to add a contact.
 *
 * @returns {void}
 */
function renderNoContactsMessage() {
  const contactListElement = document.getElementById("contactList");

  if (!contactListElement) return;
  contactListElement.innerHTML = /*html*/ `
      <li class="no-contacts">
        <p>Add a contact to start growing your network!</p>
      </li>
    `;
  return;
}

/**
 * Removes the contact view by setting the innerHTML of the element with the id
 * "contact-view" to an empty string.
 *
 * This function is useful when the contact view needs to be cleared without
 * toggling the contact view. It is equivalent to calling `toggleContactView(-1)`.
 *
 * @returns {void}
 */

function removeContactView() {
  const contactViewElement = document.getElementById("contact-view");
  if (!contactViewElement) return;
  contactViewElement.innerHTML = "";
}

/**
 * Listens for the window resize event and re-renders the contact list when
 * fired.
 *
 * This is necessary because the contact list is rendered with a masonry
 * layout, which needs to be re-laid out when the window is resized.
 *
 * @listens window#resize
 * @returns {void}
 */
window.addEventListener("resize", renderContactList);
window.addEventListener("orientationchange", renderContactList);