The suggestion (of LArry I.A). Replacing :id="date.name" with :id="date.id" is not working. Btw is I use date.id as ID I also need in the label bind the field :for from :for=: "date.name" to :for="date.id"
So Issue still not solved.
Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.
Hi All,
I made a simple code for Laravel with alpine and tailwind to have a simple clickeable calendar. It seems to be working. All seems to be working: clicking to the next month and showing the dates are working. If I click on the day of the next month, day day value is properly, but the month and year is not reflected. All values in my array seems to be good. In the radio iput box i added the following code:
<input
@click="processDate(date);"
type="radio"
x-model="SelectedDate"
:value="date.name"
:id="date.name"
:disabled="date.disabled"
name="date_calendar"
class="sr-only"
>
Is was expecting that the ID value of the input woould be the same as value. But this is not the case. I see in the generated HTML code:
<input @click="processDate(date);" type="radio" x-model="SelectedDate" :value="date.name" :id="date.name" :disabled="date.disabled" name="date_calendar" class="sr-only" value="2023-3-25" id="2023-4-25">
As you can see the value and ID are not the same. What am I missing?
The code code of this component is:
<div
x-data="calandar()"
x-init="[initDate(), getNoOfDays()]"
id="calandar-container"
class="md:pr-7"
>
<div class="mb-8">
<h2>Date of the training</h2>
<div class="text-sm text-gray-500">Select the day that the training will start.</div>
</div>
<div class="flex items-center">
<div class="flex-auto font-medium text-sm text-gray-900">
<span x-text="MONTH_NAMES[month]"></span>
<span x-text="year"></span>
</div>
<button
@click="previousMonth(); getNoOfDays();"
type="button"
class="-my-1.5 flex flex-none items-center justify-center p-1.5 text-gray-400 hover:text-gray-500"
:class="{ 'invisible': hasMonthBack(month, year) == true }"
>
<span class="sr-only">Previous month</span>
<!-- Heroicon name: mini/chevron-left -->
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor"
aria-hidden="true">
<path fill-rule="evenodd"
d="M12.79 5.23a.75.75 0 01-.02 1.06L8.832 10l3.938 3.71a.75.75 0 11-1.04 1.08l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 011.06.02z"
clip-rule="evenodd"/>
</svg>
</button>
<button
@click="nextMonth(); getNoOfDays()"
type="button"
class="-my-1.5 -mr-1.5 ml-2 flex flex-none items-center justify-center p-1.5 text-gray-400 hover:text-gray-500"
>
<span class="sr-only">Next month</span>
<!-- Heroicon name: mini/chevron-right -->
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor"
aria-hidden="true">
<path fill-rule="evenodd"
d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
clip-rule="evenodd"/>
</svg>
</button>
</div>
<!-- Add day Header -->
<div
class="mt-10 grid grid-cols-7 text-center leading-6 text-gray-500 lowercase">
<template x-for="(day, index) in DAYS" :key="index">
<div x-text="day"></div>
</template>
</div>
<!-- Add the days -->
<div class="mt-2 grid grid-cols-7">
<!-- Add days beofre today (not clickeable) -->
<template x-for="blankday in blankdays">
<div></div>
</template>
<template x-for="(date, dateIndex) in no_of_days" :key="dateIndex">
<label
class="mx-auto flex h-6 w-6 items-center justify-center rounded-full mt-2 text-gray-400"
:for="date.name"
:class="{ 'text-th-orange font-bold hover:bg-th-orange hover:text-white': isToday(date) == true,
'text-gray-400 cursor-not-allowed hover:text-gray-400 hover:bg-transparent ': isBeforeToday(date) == true,
'hover:bg-th-orange hover:text-white': isToday(date) == false,
'bg-th-orange text-white': date.checked === true,
'text-gray-400': isWeekend(date) == true,
}"
>
<input
@click="processDate(date);"
type="radio"
x-model="SelectedDate"
:value="date.name"
:id="date.name"
:disabled="date.disabled"
name="date_calendar"
class="sr-only"
>
<div x-text="date.id"></div>
</label>
</template>
</div>
<!-- Check to see the selected date -->
<p class="mt-4">Date selected: <code x-text="SelectedDate"></code></p>
@error('date_calendar')
<div class="mt-2 font-medium text-sm text-red-600">{{ __('error.start-date') }}</div>
@enderror
</div>
<script>
const MONTH_NAMES = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
const DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
function calandar() {
return {
month: '',
year: '',
SelectedDate: '',
no_of_days: [],
dayID: '',
blankdays: [],
days: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
initDate() {
let today = new Date();
this.month = today.getMonth();
this.year = today.getFullYear();
this.datepickerValue = new Date(this.year, this.month, today.getDate()).toDateString();
},
isToday(date) {
const today = new Date();
const d = new Date(date.name);
return today.toDateString() === d.toDateString();
},
isWeekend(date) {
const d = new Date(date.name);
let isWeekendDay = false;
let dayNumber = d.getDay();
if (dayNumber === 0 || dayNumber === 6) {
isWeekendDay = true;
}
return isWeekendDay;
},
isBeforeToday(date) {
const today = new Date();
const d = new Date(date.name);
if (d < today) {
date.disabled = true;
}
return d < today;
},
hasMonthBack(month, year) {
const todayMonth = new Date().getMonth();
const todayYear = new Date().getFullYear();
const todayMonthYear = todayMonth * 10000 + todayYear;
const currentMonthYear = this.month * 10000 + this.year;
let noLastmonth = false;
if (currentMonthYear === todayMonthYear) {
noLastmonth = true;
}
return noLastmonth;
},
get previousDayDates() {
return this.no_of_days.filter(date => date.clicked === false);
},
processDate(date) {
if (!date.disabled) {
date.clicked = true;
this.previousDayDates.forEach(reset => reset.checked = false);
date.checked = true;
date.clicked = false;
}
},
previousMonth() {
if (this.month === 0) {
this.month = 11;
this.year--;
} else {
this.month--;
}
},
nextMonth() {
if (this.month === 11) {
this.month = 0;
this.year++;
} else {
this.month++;
}
},
getNoOfDays() {
let daysInMonth = new Date(this.year, this.month + 1, 0).getDate();
// find where to start calendar day of week
let dayOfWeek = new Date(this.year, this.month).getDay() - 1;
if (dayOfWeek < 0) {
dayOfWeek = 6;
}
// Get bland day (before last momnth
let blankdaysArray = [];
for (let i = 1; i <= dayOfWeek; i++) {
blankdaysArray.push(i);
}
// PLace days in Array
let daysArray = [];
for (let i = 1; i <= daysInMonth; i++) {
/* valueToPush:
Array: { id, name, year, month, day, checked, clicked, disabled }
Values:
id: id og the day
name: total formatted day (e.g. 2023-04-21)
year: year of the day (e.g. 2023) ! just for testing
month: month of the day (e.g. 4) ! just for testing
day: day of the day (e.g. 21) ! just for testing
checked: Is the the current swelected date?
clicked: has this date been clicked?
disabled: this date has been disabled.
*/
let valueToPush = {};
valueToPush.id = i;
valueToPush.name = this.year + "-" + (this.month + 1) + "-" + i;
valueToPush.year = this.year;
valueToPush.month = this.month + 1;
valueToPush.day = i;
valueToPush.checked = false;
valueToPush.clicked = false;
valueToPush.disabled = false;
daysArray.push(valueToPush);
}
this.blankdays = blankdaysArray;
this.no_of_days = daysArray;
}
}
}
</script>
For people who are interested, I optimized to code a little bit so that only one input has been used.
If anyone had optimized suggestion, please share with me. I am still learning:
<div
x-data="calandar()"
x-init="[initDate(), getNoOfDays()]"
:aria-labelledby="$id('calandar-container')"
id="calandar-container"
class="md:pr-7"
>
<div class="mb-8">
<h2>Date of the training</h2>
<div class="text-sm text-gray-500">Select the day that the training will start.</div>
</div>
<div class="flex items-center">
<div class="flex-auto font-medium text-sm text-gray-900">
<span x-text="MONTH_NAMES[month]"></span>
<span x-text="year"></span>
</div>
<button
@click="previousMonth(); getNoOfDays();"
type="button"
class="-my-1.5 flex flex-none items-center justify-center p-1.5 text-gray-400 hover:text-gray-500"
:class="{ 'invisible': hasMonthBack(month, year) == true }"
>
<span class="sr-only">Previous month</span>
<!-- Heroicon name: mini/chevron-left -->
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor"
aria-hidden="true">
<path fill-rule="evenodd"
d="M12.79 5.23a.75.75 0 01-.02 1.06L8.832 10l3.938 3.71a.75.75 0 11-1.04 1.08l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 011.06.02z"
clip-rule="evenodd"/>
</svg>
</button>
<button
@click="nextMonth(); getNoOfDays()"
type="button"
class="-my-1.5 -mr-1.5 ml-2 flex flex-none items-center justify-center p-1.5 text-gray-400 hover:text-gray-500"
>
<span class="sr-only">Next month</span>
<!-- Heroicon name: mini/chevron-right -->
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor"
aria-hidden="true">
<path fill-rule="evenodd"
d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
clip-rule="evenodd"/>
</svg>
</button>
</div>
<!-- Add day Header -->
<div
class="mt-10 grid grid-cols-7 text-center leading-6 text-gray-500 lowercase">
<template x-for="(dayname, index) in DAYS_NAMES" :key="index">
<div x-text="dayname"></div>
</template>
</div>
<!-- Add the days -->
<div class="mt-2 grid grid-cols-7">
<!-- Add days before today (not clickable) -->
<template x-for="blankday in blankdays">
<div></div>
</template>
<template x-for="(date, dateIndex) in no_of_days" :key="dateIndex">
<label
class="mx-auto flex h-6 w-6 items-center justify-center rounded-full mt-2 text-gray-400"
role="none"
:class="{ 'text-th-orange font-bold hover:bg-th-orange hover:text-white': isToday(date) == true,
'text-gray-400 cursor-not-allowed hover:text-gray-400 hover:bg-transparent ': isBeforeToday(date) == true,
'hover:bg-th-orange hover:text-white': isToday(date) == false,
'bg-th-orange text-white': date.checked === true,
'text-gray-400': isWeekend(date) == true,
}"
>
<div
:id="date.id"
@click="StartDate = processDate(date)"
x-model="StartDate"
x-text="date.id">
</div>
</label>
</template>
</div>
<!-- Check to see the selected date -->
<p class="sr-only">Date selected: <code x-text="StartDate"></code></p>
<input
name="StartDate"
id="StartDate"
type="hidden"
role="none"
:value="StartDate">
@error('date_calendar')
<div class="mt-2 font-medium text-sm text-red-600">{{ __('error.start-date') }}</div>
@enderror
</div>
<script>
const MONTH_NAMES = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
const DAYS_NAMES = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
function calandar() {
return {
month: '',
year: '',
StartDate: '',
no_of_days: [],
dayID: '',
blankdays: [],
dayNames: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
initDate() {
let today = new Date();
this.month = today.getMonth();
this.year = today.getFullYear();
this.datepickerValue = new Date(this.year, this.month, today.getDate()).toDateString();
},
isToday(date) {
const today = new Date();
const d = new Date(date.name);
return today.toDateString() === d.toDateString();
},
isWeekend(date) {
const d = new Date(date.name);
let isWeekendDay = false;
let dayNumber = d.getDay();
if (dayNumber === 0 || dayNumber === 6) {
isWeekendDay = true;
}
return isWeekendDay;
},
isBeforeToday(date) {
const today = new Date();
const d = new Date(date.name);
if (d < today) {
date.disabled = true;
}
return d < today;
},
hasMonthBack(month, year) {
const todayMonth = new Date().getMonth();
const todayYear = new Date().getFullYear();
const todayMonthYear = todayMonth * 10000 + todayYear;
const currentMonthYear = this.month * 10000 + this.year;
let noLastmonth = false;
if (currentMonthYear === todayMonthYear) {
noLastmonth = true;
}
return noLastmonth;
},
get previousDayDates() {
return this.no_of_days.filter(date => date.clicked === false);
},
processDate(date) {
console.log(date.value);
if (!date.disabled) {
if (!date.checked) {
date.clicked = true;
this.previousDayDates.forEach(reset => reset.checked = false);
date.checked = true;
date.clicked = false;
return date.value;
}
}
return date.value;
},
previousMonth() {
if (this.month === 0) {
this.month = 11;
this.year--;
} else {
this.month--;
}
},
nextMonth() {
if (this.month === 11) {
this.month = 0;
this.year++;
} else {
this.month++;
}
},
getNoOfDays() {
let daysInMonth = new Date(this.year, this.month + 1, 0).getDate();
// find where to start calendar day of week
let dayOfWeek = new Date(this.year, this.month).getDay() - 1;
if (dayOfWeek < 0) {
dayOfWeek = 6;
}
// Get bland day (before last momnth
let blankdaysArray = [];
for (let i = 1; i <= dayOfWeek; i++) {
blankdaysArray.push(i);
}
// PLace days in Array
let daysArray = [];
for (let i = 1; i <= daysInMonth; i++) {
/* valueToPush:
Array: { id, name, year, month, day, checked, clicked, disabled }
Values:
id: id og the day
name: total formatted day (e.g. 2023-04-21)
value: value of he date
checked: Is the the current swelected date?
clicked: has this date been clicked?
disabled: this date has been disabled.
*/
let valueToPush = {};
valueToPush.id = i;
valueToPush.name = this.year + "-" + (this.month + 1) + "-" + i;
valueToPush.value = this.year + "-" + (this.month + 1) + "-" + i;
valueToPush.checked = false;
valueToPush.clicked = false;
valueToPush.disabled = false;
daysArray.push(valueToPush);
}
this.blankdays = blankdaysArray;
this.no_of_days = daysArray;
}
}
}
</script>
Please or to participate in this conversation.