NLWSetup.js

/**
 * @classdesc This class will be used in NLW #11 Setup and will help students
 * to create the event's project by simplifying several JS concepts.
 */
var NLWSetup = class NLWSetup {
  /** 
   * Object that contains the data composed by
   * 
   * **key**: The habit name as the `data-name` of the `.habit` container
   * **value**: `Array` of days to be checked as done
   * **day**: Each day must be like the format `MM-DD` _MM: month, DD: day_
   * 
   * 
   * ```js
   *  { run: ['01-01', '01-02', '01-06'] }
   * ```
   * @private
   */
  data = {}

  /**
   * Array of habits
   * camelCase: `run, eatFood`
   * @private
   */
  habits = []

  /**
   * `new Set()` of days to build the display of habits by days
   * @private
   */
  days = new Set()

  /**
   * You must build the `HTML` structure like this
   * 
   * ```html
   * <form>
   *   <div class="habits">
   *     <div class="habit" data-name="run">🏃🏽‍♂️</div>
   *     <div class="habit" data-name="water">💧</div>
   *     <div class="habit" data-name="food">🍎</div>
   *   </div>
   * 
   *   <div class="days"></div>
   * </form>
   * ```
   * 
   * Example of usage
   * 
   * ```js
   * const form = document.querySelector('form')
   * const nlwSetup = new NLWSetup(form)
   * ```
   * 
   * @param {HTMLFormElement} form
   * 
   */
  constructor(form) {
    this.form = form
    this.daysContainer = this.form.querySelector(".days")
    this.form.addEventListener("change", () => this.#update())
    this.createHabits()
    this.load()
  }

  /**
   * It will load the internal [data](#data) and [render the layout](#renderLayout)
   * 
   */
  load() {
    const hasData = Object.keys(this.data).length > 0
    if (!hasData) return

    this.#registerDays()
    this.renderLayout()
  }

  #registerDays() {
    Object.keys(this.data).forEach((key) => {
      this.data[key].forEach((date) => {
        this.days.add(date)
      })
    })
  }

  /**
   * Will render the layout by checking the registered days of the habits data
   * Each day that was included in the habits data, will be checked in the input layout
   * 
   * The `<div class="days"></div>` container will be filled with html, 
   * repeating the .day container for every day registered
   * 
   * example 
   ```html
    <div class="day">
        <div>01/01</div>
        <input type="checkbox" name="run" value="01/01"/>
        <input type="checkbox" name="water" value="01/01"/>
        <input type="checkbox" name="food" value="01/01"/>
    </div>
   ```
   *
   */
  renderLayout() {
    this.daysContainer.innerHTML = ""
    for (let date of this.#getSortedDays()) {
      const [month, day] = date.split("-")
      this.#createDayElement(day + "/" + month)
    }
  }

  createHabits() {
    this.form
      .querySelectorAll(".habit")
      .forEach((habit) => this.#addHabit(habit.dataset.name))
  }

  /**
   * @param {string} habit
   * camelCase: `run, eatFood`
   * @return {NLWHabits} this
   */
  #addHabit(habit) {
    this.habits = [...this.habits, habit]
    return this
  }

  /**
   * @param {string} date `DD/MM`
   * - `DD` day with 2 digits (01-31)
   * - `MM` month with 2 digits (01-12)
   
   * @return {string} `MM-DD`
   */
  #getFormattedDate(date) {
    const [day, month] = date.split("/")
    return month + "-" + day
  }

  /**
   * Get form input values by input name,
   * and format an object:
   * - ` { inputName: ["value1", "valueN", ...]} `
   *
   * Add the object to this.data
   */
  #update() {
    const formData = new FormData(this.form)
    const prepareData = {}
    for (let habit of this.habits) {
      prepareData[habit] = formData.getAll(habit)
    }

    this.setData(prepareData)
  }

  /**
   * The data to build the habits table
   *
   * Example of usage
   * 
   * ```js
   * const data = { 
   *   run: ['01-01', '01-02', '01-06'], 
   *   water: ['01-04', '01-05'],
   *   food: ['01-01', '01-03'],
   * }
   * 
   * nlwSetup.setData(data)
   * ```
   * 
   * @param {object} data Object that contains the data composed by
   * 
   * - key: The habit name as the `data-name` of the `.habit` container
   * - value: `Array` of days to be checked as done
   * 
   * _Each day must be like the format `MM-DD` 
   * where `MM`: is the month and `DD`: is the day_
   * 
   * ```js
   *  { run: ['01-01', '01-02', '01-06'] }
   * ```
   */
  setData(data) {
    if (!data) {
      throw "Object data is needed { habitName: [...days: string]"
    }
    this.data = data
  }

  #getSortedDays() {
    return [...this.days].sort()
  }

  /**
   * Check if day already exists in the set of days
   * 
   * example of usage
   * ```js
   * nlwSetup.dayExists('31/12') // true or false
   * ```
   * 
   * @param {string} date `DD/MM`
   * - `DD` day with 2 digits (01-31)
   * - `MM` month with 2 digits (01-12)
   *
   * @return {boolean}
   */
  dayExists(date) {
    const formattedDate = this.#getFormattedDate(date)
    return [...this.days].includes(formattedDate)
  }
  /**
   * Add a day to registered days and render the layout after that
   * 
   * example of usage
   * ```
   * nlwSetup.addDay('31/12')
   * ```
   * @param {string} date `DD/MM`
   * - `DD` day with 2 digits (01-31)
   * - `MM` month with 2 digits (01-12)
   */
  addDay(date) {
    if (!date || !date?.includes("/")) return
    if (this.dayExists(date)) return
    this.days.add(this.#getFormattedDate(date))
    this.renderLayout()
  }

  /**
   * @param {string} date `DD/MM`
   * - `DD` day with 2 digits (01-31)
   * - `MM` month with 2 digits (01-12)
   */
  #createDayElement(date) {
    const divDay = document.createElement("div")
    divDay.setAttribute("class", "day")
    divDay.innerHTML = `<div>${date}</div>` + this.createCheckboxes(date)
    this.daysContainer.append(divDay)
  }

  createCheckboxes(date) {
    const formattedDate = this.#getFormattedDate(date)
    let checkboxes = ""
    for (let habit of this.habits) {
      checkboxes += `<input 
        type="checkbox" name="${habit}" value="${formattedDate}" 
        ${this.data[habit]?.includes(formattedDate) && "checked"}/>`
    }

    return checkboxes
  }
}