1
\$\begingroup\$

Good night. It is me again (last post about this code).

Summary: This project is meant to be a mobile calendar made with HTML, CSS and JS. I'm using a <table> to show all the days of the month. And each one of the days are off course a <td>. Currently they all receive an eventListener, that open a pop_up when clicked. The problem I believe is that I'm using they wrongly. More details below.

Main issue: After the last post here, regarding this code, many improvements were made. Now, I'm facing a hard time dealing with event listeners. I have:

A 7x7 <table>. The first <tr> contains the <th>, in conclusion I have a 6x7 <table> containing my days of the month. Each one of these days are a <td>. I added an eventListener to each one of this days. This event triggers a pop-up div that contains a form to add a new schedule event. Once its form is filled, and the 'Confirm' button is clicked, again, by another eventListener, I create a div within the display_data div. So, each time an event is added, this div receives a new item. The problem in here is the amount of times the parameter 'day' is being changed/runned, as you can see in the script, I ended up having to get the value in other way, not directly from the function.

In Conclusion: I'm not finished with all the verifications yet, I plan to only show the scheduled events on the month that they belong to, etc... However, I've been burning my midnight oil trying to solve this issue with no success at all. If you have any suggestion on how to improve the code in general, fell free to do so!

I'll be posting the code below, also the git link in the correct branch if you rather it.

Github project link

HTML:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Calendar</title>
    <script src="../script/script.js"></script>
    <link rel="stylesheet" href="../css/style.css" />
  </head>
  <body>
    <div id="add_schedule" class="hide_pop_up">
      <div class="close_button">
        <span id="schedule_day"></span>
        <span id="close_pop_up">X</span>
      </div>
      <form id="pop_up">
        <div class="schedule_time_div">
          <div class="schedule_time_div_2">
            <label for="schedule_initial_time">Starting at:</label>
            <input id="schedule_initial_time" type="time" value="00:00" />
          </div>
          <div class="schedule_time_div_2">
            <label for="schedule_final_time">Ending at:</label>
            <input id="schedule_final_time" type="time" value="23:59" />
          </div>
        </div>
        <div class="schedule_title_div">
          <label for="schedule_title">Title</label>
          <input
            id="schedule_title"
            placeholder="My title..."
            type="text"
            required
          />
        </div>
        <div class="schedule_description_div">
          <label for="schedule_description">Description</label>
          <input
            id="schedule_description"
            placeholder="My description..."
            type="text"
          />
        </div>
        <div class="schedule_button_div">
          <button id="save_schedule" form="pop_up" type="button">
            Confirm
          </button>
        </div>
      </form>
    </div>
    <div class="main">
      <div class="title">
        <span class="year_title" id="year_title"></span>
        <span class="month_title" id="month_title"></span>
      </div>
      <div class="calendar">
        <div id="month_days" class="month_days">
          <table id="days">
            <tr>
              <th>Sun</th>
              <th class="even">Mon</th>
              <th>Tue</th>
              <th class="even">Wed</th>
              <th>Thu</th>
              <th class="even">Fri</th>
              <th>Sat</th>
            </tr>
          </table>
        </div>
        <div id="data_display" class="data_display"></div>
      </div>
      <div class="buttons">
        <button id="back_button">
          <img class="arrow_img" alt="back" />
        </button>
        <button id="t_button">T</button>
        <button id="next_button">
          <img class="arrow_img" alt="next" />
        </button>
      </div>
    </div>
  </body>
</html>

CSS:

* {
  padding: 0px;
  margin: 0px;
  font-family: Verdana, Geneva, Tahoma, sans-serif;
  font-weight: lighter;
}

body {
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  background-image: url(../../media/grass.jpg);
  /* Blurring the background. Applies behind the element... */
  backdrop-filter: blur(9px);
  background-size: cover;
}

@keyframes display_data {
  0% {
    transform: scale3d(0, 1, 1);
  }
  100% {
  transform: scale3d(1, 1, 1);

  }
}
@keyframes opacity {
  from {
    opacity: 0%;
  }
  to {
    opacity: 100%;
  }
}
@keyframes display_button_back {
  0% {
    right: 25px;
    transform: scale3d(0.75, 0.75, 1);
  }
  100% {
    right: 0px;
    transform: scale3d(1, 1, 1);
  }
}
@keyframes display_button_next {
  0% {
    left: 25px;
    transform: scale3d(0.75, 0.75, 1);
  }
  100% {
    left: 0px;
    transform: scale3d(1, 1, 1);
  }
}
@keyframes display_opacity_zoom {
  from {
    opacity: 0%;
    transform: scale3d(0.5, 0.5, 1);
  }
  to {
    opacity: 100%;
    transform: scale3d(1, 1, 1);
  }
}
@keyframes display_schedule {
  from{    
    opacity: 0%;
    transform: scale3d(.25,1,1);
  }
  to{
    opacity: 100%;
    transform: scale3d(1,1,1);
  }
}
@keyframes close_schedule {
  from{    
    opacity: 100%;
    transform: scale3d(1,1,1);
  }
  to{
    opacity: 0%;
    transform: scale3d(.25,1,1);
  }
}

.main {
  width: 100vw;
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-start;
  color: white;
  background-color: rgba(0, 0, 0, 0.65);
}

.title {
  margin-top: 7%;
  height: 80px;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-evenly;
  /* animation: display_opacity_zoom 1s ease-out; */
}

.year_title {
  margin-left: 5px;
  font-size: 40px;
  letter-spacing: 5px;
  color: lightsalmon;
  text-align: center;
}

.month_title {
  margin-left: 15px;
  font-size: 25px;
  letter-spacing: 15px;
  text-align: center;
}

.calendar {
  height: 75%;
  width: 100vw;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
}

.month_days {
  margin-top: 10px;
  width: 100%;
  height: 50%;
  /* animation: opacity 1s ease-in-out; */
}

table {
  margin-top: 20px;
  width: 100%;
  font-size: 22px;
}

tr,
th,
td {
  background-color: none;
}

th {
  width: 14%;
  text-align: center;
  color: white;
}

th:first-child,
th:last-child {
  color: lightsalmon;
}

td {
  width: 2.38em;
  height: 2.38em;
  color: white;
  text-align: center;
  border-radius: 50%;
}

td:hover {
  background-color: rgba(112, 203, 255, 0.349);
}

.data_display {
  width: 95%;
  height: 30%;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
  background-color: white;
  border: none;
  border-radius: 5px;
  overflow-y: scroll;
  /* animation: display_data 2s ease; */
}

.data_display_item{
  width: 100%;
  
}

.data_display_div_title{
  display: flex;
  width: 100%;
  justify-content: space-between;
  align-items: center;
  color: black;
  margin-top: 5px;
  margin-bottom: 5px;
  font-size: 20px;
}

.data_display_div_title :first-child{
  margin-left: 5px;
  }


.data_display_div_title :last-child{
    margin-right: 10px;
    background-color: lightsalmon;
    border-radius: 5px;
  }

.data_display_div_description {
  display: flex;
  width: 100%;
  flex-wrap: wrap;
  font-size: 17px;
  color: black;
}

.data_display_div_description span{
  margin-left: 10px;
}

.schedule_day{
  background-color: rgba(112, 203, 255, 0.349);
} 

.other_month {
  background: none;
  color: rgba(175, 175, 175, 0.45);
}

.buttons {
  width: 100vw;
  height: 70px;
  display: flex;
  justify-content: space-around;
  align-items: flex-start;
}

button {
  width: 60px;
  height: 60px;
  display: flex;
  justify-content: center;
  align-items: center;
  background: none;
  border: none;
  font-size: 35px;
  font-weight: 400;
  color: white;
}

button:hover{
  cursor: pointer;
}

button:first-child{
  /* animation: display_button_back 1s ease;  */
  position: relative;
}

button:first-child img{
  content: url(../../media/left-arrow-line-symbol.svg);
}

/* 
button:not(:first-child):not(:last-child){
  animation: display_opacity_zoom 1s ease-out;
} */

button:last-child{
  /* animation: display_button_next 1s ease;  */
  position: relative;
}

button:last-child img{
  content: url(../../media/right-arrow-angle.svg);
}

.arrow_img{
  width: 35px; 
  height: 35px"
}

.hide_pop_up{
  display: none;

}

.schedule_display{
  display: flex;
  width: 97vw;
  height: 80vh;
  position: absolute;
  display: flex;
  flex-direction: column;
  border-radius: 5px;
  background: white;
  justify-content: space-between;
  align-items: flex-start;
  /* animation: display_schedule .3s ease; */
}

/* .schedule_close{
   animation: close_schedule .3s ease;
  animation-fill-mode: forwards;
 #87FFA7 <= Color for schedules 
} */

.close_button{
  width: 100%;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.close_button span{
  font-size: 25px;
  margin-right: 10px;
}

.close_button span:hover{
  cursor: pointer;
}

form{    
  width: 100%;
  height: 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  font-size: 25px;
}

.schedule_button_div, .schedule_time_div, .schedule_title_div, .schedule_description_div {
  width: 100%;
  height: 50px;
  padding: 20px;
  display: flex;
  flex-direction: column;
  justify-content: space-evenly;
  align-items: flex-start;
}

input{
  width: 100%;
  font-size: 22px;
  border: 2px black solid;
  border-top: none;
  border-right: none;
  border-left: none;
}
.schedule_time_div{  
  height: 15%;
  flex-direction: row;
}

.schedule_time_div input{  
  width: 150px;
  height: 50px;
}

.schedule_time_div_2{
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-start;
}

.schedule_button_div{
  justify-content: center;
  align-items: center;
}

.schedule_button_div button{
  font-size: 20px;
  color: black;
  border: 2px black solid;
  width: 30%;

}


@media only screen and (min-width: 1279px){
  .title{
    margin-top: 2%;
  }
  
  .data_display{
    margin-top: 35px;    
    height: 70vh;
  }

  .calendar{
    width: 97vw;
    flex-direction: row;
    align-items: flex-start; 
  }

  .month_days{
    height: fit-content;
  }

  td{
    border-radius: 0%;
  }
  .buttons{
    width: 50vw;
  }

}

JS:

// Returns the amount of days in a month.
const amount_of_days = (year, month) => new Date(year, month + 1, 0).getDate();

// Returns the day of the week in which the month starts.
const first_day_week_for_month = (year, month) =>
  new Date(year, month, 1).getDay();

// When given the name, it returns the month number (0-11).
function month_name_in_number(month_name) {
  const month_names = [
    "January",
    "February",
    "March",
    "April",
    "May",
    "June",
    "July",
    "August",
    "September",
    "October",
    "November",
    "December",
  ];
  for (let i = 0; i < 12; i++) {
    return month_names.indexOf(month_name);
  }
}

// Returns a date object, with more properties.
const date_object = (date_year, date_month) => {
  const month_names = [
    "January",
    "February",
    "March",
    "April",
    "May",
    "June",
    "July",
    "August",
    "September",
    "October",
    "November",
    "December",
  ];

  const date_object = new Date(date_year, date_month);
  const date = {
    year: date_object.getFullYear(),
    month: date_object.getMonth(),
    month_name: month_names[date_object.getMonth()],
    amount_of_days: amount_of_days(
      date_object.getFullYear(),
      date_object.getMonth()
    ),
    get_first_Day: first_day_week_for_month(
      date_object.getFullYear(),
      date_object.getMonth()
    ),
  };
  return date;
};

// Returns a date object based on the table data.
function get_table_date() {
  const table_year = parseInt(document.getElementById("year_title").innerText);
  const table_month = month_name_in_number(
    document.getElementById("month_title").innerText
  );
  return date_object(table_year, table_month);
}

// Prints year + month on the html.
function print_year_and_month(date_year, date_month) {
  const date = date_object(date_year, date_month);
  document.getElementById("year_title").innerText = date.year;
  document.getElementById("month_title").innerText = date.month_name;
}

// Creates the table.
function create_table() {
  const table = document.getElementById("days");
  // Creates 6 rows.
  for (let i = 0; i < 6; i++) {
    let current_row = table.insertRow(1 + i);
    // Creates 7 cells.
    for (let x = 0; x < 7; x++) {
      current_row.insertCell(x);
    }
  }
}

// Resets the 'td' data style properties.
function reset_table_data_style() {
  const table = document.getElementById("days");
  for (let i = 1; i < 7; i++) {
    for (let x = 0; x < 7; x++) {
      table.rows[i].cells[x].style.color = "";
      table.rows[i].cells[x].style.background = "";
      table.rows[i].cells[x].classList.remove("td");
      table.rows[i].cells[x].classList.remove("other_month");
    }
  }
}

// Changes the background color of the current month cell if it is a weekend.
const change_background_color_if_weekend = (row_number) => {
  const table = document.getElementById("days");
  if (table.rows[row_number].cells[6].classList == "td") {
    table.rows[row_number].cells[6].style.color = "lightsalmon";
  }
  if (table.rows[row_number].cells[0].classList == "td") {
    table.rows[row_number].cells[0].style.color = "lightsalmon";
  }
};

// Changes the background color of the current month cell if it is today's day.
const change_background_color_if_today = (row_number) => {
  const table = document.getElementById("days");
  const table_date_object = get_table_date();
  if (
    table_date_object.year === new Date().getFullYear() &&
    table_date_object.month === new Date().getMonth()
  ) {
    for (let i = 0; i < 7; i++) {
      if (
        table.rows[row_number].cells[i].innerText == new Date().getDate() &&
        table.rows[row_number].cells[i].className === "td"
      ) {
        table.rows[row_number].cells[i].style.background = "black";
      }
    }
  } else {
    return;
  }
};

// Applies the background + today style. + loads schedules
function load_table_style() {
  for (let x = 1; x < 7; x++) {
    change_background_color_if_weekend(x);
    change_background_color_if_today(x);
  }
}

// Populates a row.
function populate_row(
  execution_number,
  row_number,
  first_cell,
  first_value,
  cell_class
) {
  if (execution_number <= 7) {
    var table = document.getElementById("days");
    for (let i = 0; i < execution_number; i++) {
      table.rows[row_number].cells[first_cell + i].innerText = first_value + i;
      table.rows[row_number].cells[first_cell + i].classList.add(cell_class);
    }
  } else {
    console.log("Alert on populate_row function.");
  }
}

// Populates the table.
function populate_table(date_year, date_month) {
  // AD = Amount of Days. AC = Amount of cells. CM = Current Month.
  const date = date_object(date_year, date_month);
  const AC_CM_1_row = 7 - date.get_first_Day;
  const AC_last_month = 7 - AC_CM_1_row;
  const AD_last_month = amount_of_days(date.year, date.month - 1);
  let AD_next_month = 42 - date.amount_of_days - AC_last_month;
  let day_counter = AC_CM_1_row;
  let lasting_days = date.amount_of_days - day_counter;

  // Populates the first row.
  if (AC_CM_1_row < 7) {
    populate_row(
      7 - AC_CM_1_row,
      1,
      0,
      AD_last_month - (7 - AC_CM_1_row) + 1,
      "other_month"
    );
  }
  populate_row(AC_CM_1_row, 1, date.get_first_Day, 1, "td");

  // Populates the other rows.
  let i = 2;
  while (day_counter < date.amount_of_days) {
    populate_row(7, i, 0, day_counter + 1, "td");
    day_counter += 7;
    lasting_days = date.amount_of_days - day_counter;
    i++;
    // If lasting days won't fill a whole row, fill the rest of the table.
    if (lasting_days <= 7 && lasting_days !== 0) {
      populate_row(lasting_days, i, 0, day_counter + 1, "td");
      while (AD_next_month !== 0) {
        populate_row(7 - lasting_days, i, lasting_days, 1, "other_month");
        AD_next_month -= 7 - lasting_days;
        if (AD_next_month > 0) {
          populate_row(7, i + 1, 0, 1 + (7 - lasting_days), "other_month");
          AD_next_month -= 7;
        }
      }
      day_counter = date.amount_of_days;
    }
  }
  load_table_style();
}

function open_pop_up() {
  const pop_up = document.getElementById("add_schedule");
  pop_up.classList.remove("schedule_close");
  pop_up.classList.add("schedule_display");
}

function close_pop_up() {
  const pop_up = document.getElementById("add_schedule");
  pop_up.classList.add("schedule_close");
  pop_up.classList.remove("schedule_display");
}

function add_schedule_event_to_cells() {
  const table = document.getElementById("days");
  for (let i = 1; i < 7; i++) {
    for (let x = 0; x < 7; x++) {
      table.rows[i].cells[x].addEventListener("click", () => {
        add_new_schedule_event(table.rows[i].cells[x].innerText);
      });
    }
  }
}

function add_new_schedule_event(day) {
  open_pop_up();
  const confirm_button = document.getElementById("save_schedule");
  const exit_button = document.getElementById("close_pop_up");
  const date = document.getElementById("schedule_day");
  date.innerText = day;
  // ADD a list system that starts in the smallest day, and shows the irformation of the
  // current month.
  confirm_button.addEventListener("click", () => {
    console.log(this.schedule_day.innerText);
    const input_title = document.getElementById("schedule_title");

    if (input_title.value !== "") {
      // Create
      const data_display = document.getElementById("data_display");
      const input_init_time = document.getElementById("schedule_initial_time");
      const input_final_time = document.getElementById("schedule_final_time");
      const input_description = document.getElementById("schedule_description");
      const data_item = document.createElement("div");
      const title_div = document.createElement("div");
      const span_title = document.createElement("span");
      const span_time = document.createElement("span");
      const description_div = document.createElement("div");
      const span_description = document.createElement("span");
      // Add class
      data_item.classList.add("data_display_item");
      title_div.classList.add("data_display_div_title");
      description_div.classList.add("data_display_div_description");
      // Append child
      data_display.appendChild(data_item);
      data_item.appendChild(title_div);
      data_item.appendChild(description_div);
      title_div.appendChild(span_title);
      title_div.appendChild(span_time);
      description_div.appendChild(span_description);
      // Values
      span_title.innerText = "⬤ " + this.schedule_day.innerText + ": " + input_title.value;
      span_time.innerText =
        input_init_time.value + " - " + input_final_time.value;
      span_description.innerText = input_description.value;
      // Clean fields
      input_title.value = "";
      input_init_time.value = "00:00";
      input_final_time.value = "23:59";
      input_description.value = "";

      close_pop_up();
      return;
    }
    input_title.style.borderBottom = "2px red solid";
  });
  exit_button.addEventListener("click", () => {
    close_pop_up();
    return;
  });
}

// Loads today's data.
function main() {
  print_year_and_month(new Date().getFullYear(), new Date().getMonth());
  create_table();
  populate_table(new Date().getFullYear(), new Date().getMonth());
  add_schedule_event_to_cells();
}

// Loads buttons.
function load_buttons() {
  const back_button = document.getElementById("back_button");
  const t_button = document.getElementById("t_button");
  const next_button = document.getElementById("next_button");
  let table_date = get_table_date();

  back_button.addEventListener("click", () => {
    reset_table_data_style();
    table_date.month -= 1;
    print_year_and_month(table_date.year, table_date.month);
    populate_table(table_date.year, table_date.month);
  });
  t_button.addEventListener("click", () => {
    reset_table_data_style();
    table_date = date_object(new Date().getFullYear(), new Date().getMonth());
    print_year_and_month(new Date().getFullYear(), new Date().getMonth());
    populate_table(new Date().getFullYear(), new Date().getMonth());
  });
  next_button.addEventListener("click", () => {
    reset_table_data_style();
    table_date.month += 1;
    print_year_and_month(table_date.year, table_date.month);
    populate_table(table_date.year, table_date.month);
  });
}

// Loads main function as soon as the raw html loads.
function trigger_script() {
  document.addEventListener("DOMContentLoaded", () => {
    main();
    load_buttons();
  });
}

// Triggers the code.
trigger_script();

Thanks in advance, all help is welcome!

\$\endgroup\$

1 Answer 1

0
\$\begingroup\$

Found a solution.

It may have been obvious all along, but, at least now I see it. Every time that I triggered the function add_new_schedule_event(), I was adding a new eventListener, hence I would have multiple values.

My solution: Divided the main function into sub-functions:

open_pop_up() // Display pop up
close_pop_up() // Hide pop up
load_button_close_pop_up()
load_button_confirm_pop_up()

The last two functions go on the load_buttons() function. By doing so, the events now are only generated once.

Edit: I was also adding eventListeners by passing function executions instead of just the functions names, and sometimes, I'd be even putting them on anonymous functions for some sort of reason. Now, a better fix would be:

Once the form is opened: Add the listeners.
Once the form is closed: Remove the listeners.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.