Search code examples
javascriptionic-frameworkinputdatetimepicker

ion-datetime dynamic value change doesn't work for certain weeks


This is a very strange problem to describe, but I'll try to do my best.

First of all, I'm working with vanilla JavaScript, I don't want to use Angular, React or anything like that because I don't know anything about them (although I wouldn't be against using jQuery if it was necessary).


What I'm trying to achieve

I'm trying to make a week selector using an ion-datetime element. I use this because it is the best option I could find to make a week selector while showing the whole calendar. If there is a less problematic way to do it, I'm open to hear it.

The way I do this is by listening to the ionChange event, detecting the clicked day, calculating the 7 days that belong to the same week and replacing calendar.value with the new array (see code below).


The problem

The thing is that when I click on certain weeks (it always happens in the same ones), an error pops up: TypeError: Cannot read properties of undefined (reading 'year'), but the process is always the same.

I add here a video of the problem in action: https://streamable.com/8toc8g


Code

@import url('https://fonts.googleapis.com/css2?family=Lilita+One&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Dancing+Script:wght@700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Indie+Flower&display=swap');

:root {
    --bg-rosa: #f3e1e3;
    --top1-rosa: #fbb6ad;
    --top2-rosa: #b4dadf;
    --top1-rosa-aux: #facec8;
    --top2-rosa-aux: #d5e7e8;

    --bg-turquesa: #d5e7e8;
    --top1-turquesa: #6ccac8;
    --top2-turquesa: #efb049;
    --top1-turquesa-aux: #8fd9d8;
    --top2-turquesa-aux: #f0cb90;

    --bg-amarillo: #fffdeb;
    --top1-amarillo: #ffe373;
    --top2-amarillo: #ffabab;
    --top1-amarillo-aux: #fff0b3;
    --top2-amarillo-aux: #ffc7c7;

    --bg-morado: #ece4ff;
    --top1-morado: #a4a4eb;
    --top2-morado: #a1dae6;
    --top1-morado-aux: #c2c2f2;
    --top2-morado-aux: #c7ebf2;

    --bg: var(--bg-morado);
    --top1: var(--top1-morado);
    --top1-aux: var(--top1-morado-aux);
    --top2: var(--top2-morado);
    --top2-aux: var(--top2-morado-aux);
    --top-text: #444c53;
}

body {
    position: relative;
    background: var(--bg);
    padding: 0;
    margin: 0;
}

.week {
    display: grid;
    grid-template-columns: repeat(7, minmax(10em, 25em));
    width: 100%;
    height: 100%;
    padding: 1em;
}

.day {
    position: relative;
    flex-grow: 1;
    height: 100%;
}

.day::after {
    content: "";
    display: block;
    width: 100%;
    height: 100%;
    background: var(--bg);
    opacity: 0;
    position: absolute;
    top: 0;
    left: 0;
    pointer-events: none;
}

.day:hover::after {
    opacity: 0.2;
}

.top {
    display: flex;
    align-items:center;
    justify-content:center;
    padding: 0.35em;
    font-family: 'Lilita One', cursive;
    font-size: min(calc(1em + 1vw), calc(1em + 2vh));
    color: var(--top-text);
    text-align: center;
    vertical-align: baseline;
    border-radius: calc(1em + 1vh) calc(1em + 1vh) 0 0;
}

.content {
    border-radius: 0 0 calc(1em + 1vh) calc(1em + 1vh);
}

.info {
    /* height: 49vh; */
    height: calc(49vh - 0.4vw);
    font-family: 'Indie Flower', cursive;
    font-weight: 500;
    font-size: 1.2rem;
    padding: 0.5rem 0.5rem 0 1rem;
    overflow-y: scroll;
    background-color: #ffffff;
    background-size: 1.5rem 1.5rem;
    background-image:  repeating-linear-gradient(0deg, #d7d7d780, #d7d7d780 1px, #ffffff 1px, #ffffff);
}

::-webkit-scrollbar {
    width: 8px;
}

::-webkit-scrollbar-track-piece:end {
    background: transparent;
    margin-bottom: 1.5vh; 
}

::-webkit-scrollbar-thumb {
    border-radius: 8px;
    background: #c2c9d2;
}

.frame {
    padding: 0 0.25em 0.25em 0.25em;
    height: calc(100% - min(calc(1em + 1vw), calc(1em + 2vh)) - 2em);
}

.frame-square {
    background-color: var(--top1);
}

.frame-dotted {
    background-color: var(--top2);
}

.square {
    background-position: center;
    background-color: var(--top1);
    background-image:  linear-gradient(var(--top1-aux) 2px, transparent 2px), linear-gradient(90deg, var(--top1-aux) 2px, transparent 2px);
    background-size: 1em 0.9em, 1em 0.9em;
}

.dotted {
    background-position: center;
    background-color: var(--top2);
    background: radial-gradient(circle, transparent 20%, var(--top2) 20%, var(--top2) 80%, transparent 80%, transparent), radial-gradient(circle, transparent 20%, var(--top2) 20%, var(--top2) 80%, transparent 80%, transparent) 17.5px 17.5px, linear-gradient(var(--top2-aux) 1.4px, transparent 1.4px) 0 -0.7px, linear-gradient(90deg, var(--top2-aux) 1.4px, var(--top2-aux) 1.4px) -0.7px 0;
    background-size: 35px 35px, 35px 35px, 17.5px 17.5px, 17.5px 17.5px;
}

#calendario {
    width: 30vw;
    height: 40vh;

    margin: 0;
    margin-left: 1em;
    margin-bottom: 1em;
    border-style: solid;
    border-color: var(--top1);
    border-radius: 1em;
}

.ion-color-same {
    --ion-color-base: var(--top1);
    --ion-color-contrast: white;
}
  
ion-datetime {
    --background: var(--bg);
    
    border-radius: 16px;
    margin-right: 5em;
}

table {
    height: 100%;
    width: 100%;
}

#inf {
    padding: 0;
    margin: 0;
    height: 15rem;
}

#frase {
    margin: 3rem;
    font-family: 'Dancing Script', 'Lilita One', cursive;
    color: var(--top-text);
}

#frase-container {
    width: 100%;
    height: 100%;
    text-align: center;
}

#fondo-frase {
    display: flex;
    height: calc(100% - 1em);
    width: calc(100% - 2em);
    max-height: calc(100% - 1em);
    max-width: calc(100% - 2em);
    margin: 1em;
    margin-top: 0;
    align-items:center;
    justify-content:center;
}

ion-datetime::part(datetime-year){
    background: red;
}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Document</title>
    <script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.esm.js"></script>
    <script nomodule src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.js"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core/css/ionic.bundle.css" />
    
    <link rel="stylesheet" href="style.css">
    
    <script>
      
      function valueChanged() {
        const calendario = document.getElementById('calendario');
        if (calendario.value.length != 7) {
          const last = calendario.value[calendario.value.length - 1];
          let date = new Date(last);
          
          date = getMonday(date);
          selectFromMonday(date);
        }
      }

      function getMonday(date) {
        switch (date.getDay()) {
          case 2:
            date.setDate(date.getDate() - 1);
            break;
          case 3:
            date.setDate(date.getDate() - 2);
            break;
          case 4:
            date.setDate(date.getDate() - 3);
            break;
          case 5:
            date.setDate(date.getDate() - 4);
            break;
          case 6:
            date.setDate(date.getDate() - 5);
            break;
          case 0:
            date.setDate(date.getDate() - 6);
            break;
        }
        return date;
      }

      async function selectFromMonday(date) {
        let calendario = document.getElementById('calendario');
        let diaIds = ['lunes', 'martes', 'miercoles', 'jueves', 'viernes', 'sabado', 'domingo'];
        let value = [];
        for (let i = 0; i < 7; i++) {
          const dia = document.getElementById(diaIds[i]);
          value.push(date.getFullYear() + "-" + parseInt(date.getMonth()+1) + "-" + date.getDate());
          dia.getElementsByClassName('fecha')[0].value = value[value.length - 1];
          dia.getElementsByClassName('content info')[0].innerHTML = "";
          date.setDate(date.getDate() + 1);
        }
        calendario.value = value;
      }
    </script>
  </head>
  <body>
    <table id="tabla">
    <tr id="sup"><td>
    <div class="week">
      <div class="day" id="lunes">
        <input type="hidden" class="fecha">
        <div class="top square">LUNES</div>
        <div class="content frame frame-square">
          <div class="content info"></div>
        </div>
      </div>
      <div class="day" id="martes">
        <input type="hidden" class="fecha">
        <div class="top dotted">MARTES</div>
        <div class="content frame frame-dotted">
          <div class="content info"></div>
        </div>
      </div>
      <div class="day" id="miercoles">
        <input type="hidden" class="fecha">
        <div class="top square">MIÉRCOLES</div>
        <div class="content frame frame-square">
          <div class="content info"></div>
        </div>
      </div>
      <div class="day" id="jueves">
        <input type="hidden" class="fecha">
        <div class="top dotted">JUEVES</div>
        <div class="content frame frame-dotted">
          <div class="content info"></div>
        </div>
      </div>
      <div class="day" id="viernes">
        <input type="hidden" class="fecha">
        <div class="top square">VIERNES</div>
        <div class="content frame frame-square">
          <div class="content info"></div>
        </div>
      </div>
      <div class="day" id="sabado">
        <input type="hidden" class="fecha">
        <div class="top dotted">SÁBADO</div>
        <div class="content frame frame-dotted">
          <div class="content info"></div>
        </div>
      </div>
      <div class="day" id="domingo">
        <input type="hidden" class="fecha">
        <div class="top square">DOMINGO</div>
        <div class="content frame frame-square">
          <div class="content info"></div>
        </div>
      </div>
    </div>
    </td></tr>

    <tr><td id="inf">
      <table><tr><td>
        <ion-datetime id="calendario"
          presentation="date"
          locale="es-ES"
          first-day-of-week="1"
          min="1970-01-01T00:00:00"
          max="2200-12-31T23:59:59"
          size="cover"
          color="same"
          multiple="true">
        </ion-datetime>
      </td><td id="frase-container">
        <div id="fondo-frase">
          <h1 id="frase"></h1>
        </div>
      </td></tr></table>
    </td>
    </tr>
  </table>

  <script>
    
    let today = new Date();
    //today.setTime( today.getTime() - today.getTimezoneOffset() * 60 * 1000 );
    let monday = getMonday(today);
    selectFromMonday(monday);
    
    let calen = document.getElementById('calendario');
    calen.addEventListener('ionChange', function () {
      valueChanged()
    });
  </script>
  </body>
</html>


Solution

  • Okay, I finally found the problem. I initially used the Date.toISOString() method and splitted the result to get just the date, but since it gave me a date outside my timezone I had to do it manually.

    So the thing is that when I used my own method, I did not take into account that for 1 digit days (or months) it adds a zero before so that it's 2 digits. For example, for October 3 2022 I got 2022-10-3 with my method and 2022-10-03 with the other one, and that was what caused the problems.