Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Ginius's avatar

Dont get proper value'from bind parameter when updating a date to next month in Alpine.JS

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>

0 likes
3 replies
Ginius's avatar

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.

Ginius's avatar

Solved the issue. I forgot to give back the selected date. COde need to be changed to:

I need to chenge the @click from:

@click="processDate(date);"

to

@click="SelectedDate= processDate(date);"

and in the function ProcessDate(date) add at the end of the function:

return date.value;

This means the dunction processDate will be:

processDate(date) {
                if (!date.disabled) {
                    date.clicked = true;
                    this.previousDayDates.forEach(reset => reset.checked = false);
                    date.checked = true;
                    date.clicked = false;
                }
                return date.value;
            },
Ginius's avatar
Ginius
OP
Best Answer
Level 5

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.