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).
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 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
@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>
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.