Source: main.js

// main.js

import DBUtil from './JobAppDB.js'

const database = new DBUtil()

// Run the init() function when the page has loaded
window.addEventListener('DOMContentLoaded', init)

// Starts the program, all function calls trace back here
async function init () {
  // Get the jobs from database
  await database.setupDB()
  const jobs = await database.getAllJobs()
  // Add each job to the job-details-list element
  await addJobsToDocument(jobs)
  // Add the event listeners to the form elements
  initFormHandler()
  // Have the new app button show the form when clicked
  document.querySelector('#new-app-button').addEventListener('click', showForm)
  // Add <sort-bar> elements for each sortable field
  addSortBars(['Company', 'Position', 'Location', 'Status', 'Deadline'])
  // Have the delete-cancel button hide the delete-popup when clicked
  document.querySelector('#delete-cancel').addEventListener('click', hideDeletePopup)
  // Have the profile button show confetti when clicked
  const jsConfetti = new JSConfetti() /* eslint-disable-line */
  document.querySelector('#profile-button').addEventListener('click', () => {
    jsConfetti.addConfetti()
  })
}

/**
 * Takes in an array of jobs and for each job creates a
 * new `job-details` element, adds the job data to that element
 * using `element.data = {...}`, and then appends that new job
 * to `job-details-list`.
 * @param {Array<Object>} jobs An array of jobs
 */
async function addJobsToDocument (jobs) {
  // Loop through each of the jobs in the passed in array,
  // and create a <job-details> element for each one
  for (const job of jobs) {
    await addJobToDocument(job)
  }
}

/**
 * Adds the necesarry event handlers to `form`.
 */
function initFormHandler () {
  // Get a reference to the <form> element
  const form = document.querySelector('form')
  // Add an event listener for the 'submit' event,
  // which fires when the submit button is clicked
  form.addEventListener('submit', async (event) => {
    event.preventDefault()
    // Create a new FormData object from the <form> element reference above
    const formData = new FormData(form)
    // Create an empty object
    const jobObject = {}
    // Extract the keys and corresponding values from the
    // FormData object and insert them into jobObject
    for (const [key, value] of formData) {
      jobObject[key] = value
    }
    // Create or edit a <job-details> element with the data in jobObject
    await addJobToDocument(jobObject)
    // Reset the <job-details> element to edit
    jobDetailsToEdit = null
    // Clear the <form> fields
    clearForm()
    // Sort jobs
    await sort()
    // Hide the form
    hideForm()
  })
  // Add an event listener for when the cancel button is clicked
  const cancelButton = form.querySelector('#cancel')
  cancelButton.addEventListener('click', () => {
    // Hide the form
    hideForm()
    // Reset the <job-details> element to edit
    jobDetailsToEdit = null
    // Clear the <form> fields
    clearForm()
  })
  // Add an eventListener for when a key is released in the search bar
  const searchBar = document.getElementById('search')
  filterSearch = () => {
    // Get the current search query
    const query = searchBar.value.toLocaleLowerCase().replaceAll(' ', '')
    // For every job-details element on the page
    for (const jobDetails of document.querySelectorAll('job-details')) {
      const shadowRoot = jobDetails.shadowRoot
      // Get all fields that can be searched and preprocess them
      const company = shadowRoot.querySelector('#company').textContent.toLocaleLowerCase().replaceAll(' ', '')
      const position = shadowRoot.querySelector('#title').textContent.toLocaleLowerCase().replaceAll(' ', '')
      const status = shadowRoot.querySelector('#status').textContent.toLocaleLowerCase().replaceAll(' ', '').slice(7)
      const location = shadowRoot.querySelector('#location').textContent.toLocaleLowerCase().replaceAll(' ', '')
      // If any of the fields match the query
      if (company.includes(query) || position.includes(query) ||
           status.includes(query) || location.includes(query)) {
        // Show the job-details element
        jobDetails.removeAttribute('style')
      } else {
        // Hide the job-details element
        jobDetails.setAttribute('style', 'display: none')
      }
    }
  }
  searchBar.addEventListener('keyup', filterSearch)
}

/**
 * Clear the `form` fields
 */
function clearForm () {
  // Get a reference to the <form> element
  const form = document.querySelector('form')
  // Create a new FormData object from the <form> element reference above
  const formData = new FormData(form)
  // For each key in the FormData object
  for (const key of formData.keys()) {
    // Clear the corresponding field in the form
    form.querySelector(`#${key}`).value = null
  }
}

/**
 * Create or edit a `job-details` element.
 *
 * @param {Object} job The job data to pass to the `job-details` element
 */
async function addJobToDocument (job) {
  // Get a reference to the job-details-list element
  const list = document.querySelector('#job-details-list')
  // Get the <job-details> element to edit, otherwise create a new <job-details> element
  const jobDetails = jobDetailsToEdit || document.createElement('job-details')
  // If there is no <job-details> element to edit
  if (!jobDetailsToEdit) {
    // Generate a new id if the job is new
    if (!job.id) job.id = Date.now()
  } else {
    // Get the existing id
    job.id = Number(jobDetails.id)
    // Delete the existing job from the database
    database.deleteJob(job.id)
  }
  // Add the job data to <job-details>
  jobDetails.data = job
  // Add the onClickDelete function to <job-details>
  jobDetails.onClickDelete = () => {
    // Get confirmation from user
    showDeletePopup()
    document.querySelector('#delete-submit').onclick = async () => {
      // Remove the job-details element from job-details-list
      list.removeChild(jobDetails)
      // Remove the job from the database
      await database.deleteJob(job.id)
      hideDeletePopup()
    }
  }
  // Get a reference to the <form> element
  const form = document.querySelector('form')
  // Add the onClickEdit function to <job-details>
  jobDetails.onClickEdit = () => {
    // Populate the fields in the <form> with the job data
    for (const property in job) {
      const field = form.querySelector(`#${property}`)
      if (field) field.value = job[property]
    }
    // Set the <job-details> element to edit
    jobDetailsToEdit = jobDetails
    // Show the form
    showForm()
  }
  // If there is no <job-details> element to edit
  if (!jobDetailsToEdit) {
    // Append this new <job-details> to job-details-list
    list.appendChild(jobDetails)
  }
  // Save this job to the database
  await database.addJob(job)
}

/**
 * The `job-details` element to edit on form submission
 *
 * @global
 * @type {JobDetails}
 */
let jobDetailsToEdit = null

/**
 * Hide the `form` element
 */
function hideForm () {
  document.querySelector('#job-details-form').setAttribute('style', 'display: none')
}

/**
 * Show the `form` element
 */
function showForm () {
  document.querySelector('#job-details-form').removeAttribute('style')
}

/**
 * Hide the `delete-popup` element
 */
function hideDeletePopup () {
  document.querySelector('#delete-popup-container').setAttribute('style', 'display: none')
}

/**
 * Show the `delete-popup` element
 */
function showDeletePopup () {
  document.querySelector('#delete-popup-container').removeAttribute('style')
}

/**
 * Takes in an array of field names and for each name creates a
 * new `sort-bar` element, adds the name to that element
 * using `element.data = {...}`, and then appends that new element
 * to `sort-bar-list`.
 * @param {Array<Object>} fieldNames An array of field names
 */
function addSortBars (fieldNames) {
  const sortBarList = document.querySelector('#sort-bar-list')
  // For each field name
  for (const fieldName of fieldNames) {
    // Create a new sort bar
    const sortBar = document.createElement('sort-bar')
    // Set text of sort bar
    sortBar.fieldName = fieldName
    // Set behavior on click
    sortBar.onClick = async () => {
      // Disable other sort bars
      for (const otherSortBar of sortBarList.children) {
        if (otherSortBar !== sortBar) otherSortBar.disable()
      }
      // Sort jobs
      [sortField, sortOrder] = [fieldName.toLowerCase(), sortBar.orderReversed]
      await sort()
    }
    // Add new sort-bar element to sort-bar-list
    sortBarList.appendChild(sortBar)
    // Sort by deadline by default
    if (fieldName === 'Deadline') sortBar.click()
  }
}

/**
 * The field name to be sorting by
 *
 * @global
 * @type {String}
 */
let sortField = ''

/**
 * The order to be sorting by
 *
 * @global
 * @type {String}
 */
let sortOrder = false

/**
 * Sort the job-details elements by the current sortField and sortOrder
 */
async function sort () {
  const jobs = (await database.getAllJobs()).sort((a, b) => {
    [a, b] = [a[sortField], b[sortField]]
    return (sortOrder ? a < b : a > b) ? 1 : -1
  })
  // Remove all jobs from document
  document.querySelector('#job-details-list').innerHTML = '<!-- Add job-details elements here -->'
  // Add jobs back to document in sorted order
  await addJobsToDocument(jobs)
  // Filter by search query
  filterSearch()
}

/**
 * The function which filters the jobs by search query
 *
 * @global
 * @type {Function}
 */
let filterSearch = () => {}