skip to Main Content

I have a datepicker with tailwindcss and alpinejs and it works as expected:

this is my index.html file:

<!doctype html>
<html lang="en">
<head>
    <title>Datepicker</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>
    <style>
        [x-cloak] {display: none !important;}
    </style>
</head>
<body class="flex justify-center">
<div class="flex flex-col my-10 space-y-5">
    <button onclick="addToFirstDiv()" class="text-white bg-red-800 p-1 rounded w-64">add to div#first</button>
    <button onclick="addToSecondDiv()" class="text-white bg-red-800 p-1 rounded w-64">add to div#second</button>

    <div id="first">
        <!-- datepicker -->
        <div x-data="datepicker()" x-init="[initDate(), initDatepicker()]" x-cloak>
            <div class="w-64 rounded-lg ring-1 ring-gray-100 shadow-lg">
                <div class="px-3 py-2">
                    <!-- header -->
                    <div class="flex justify-between items-center mb-2">
                        <div class="text-sm font-bold text-gray-800">
                            <span x-text="MONTH_NAMES[month]"></span>
                            <span x-text="year"></span>
                        </div>
                        <div class="text-gray-500 space-x-1">
                            <button type="button" class="transition ease-in-out duration-100 inline-flex p-1 rounded" :class="month === currentMonth && year === currentYear ? 'opacity-25' : 'hover:bg-gray-100'" :disabled="month === currentMonth && year === currentYear" @click="previousMonth()">
                                <svg class="h-4 w-4 inline-flex"  fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
                                </svg>
                            </button>
                            <button type="button" class="transition ease-in-out duration-100 inline-flex hover:bg-gray-100 p-1 rounded" @click="nextMonth()">
                                <svg class="h-4 w-4 inline-flex"  fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
                                </svg>
                            </button>
                        </div>
                    </div>

                    <div class="grid grid-cols-7 text-center -mx-1.5 text-gray-500 mb-2" style="font-size: 10px">
                        <!-- days of week -->
                        <span>M</span>
                        <span>T</span>
                        <span>W</span>
                        <span>T</span>
                        <span>F</span>
                        <span>S</span>
                        <span>S</span>
                    </div>

                    <div class="grid grid-cols-7 text-center mb-3 -mx-1.5">
                        <template x-for="blankDay in blankDays">
                            <span class="w-6 h-6 mb-1"></span>
                        </template>
                        <template x-for="(date, dateIndex) in daysOfMonth" :key="dateIndex">
                            <span x-text="date" @click="getDateValue(date)" class="w-6 h-6 mx-auto mb-1 flex justify-center items-center text-xs rounded-full" :class="[isPassedDay(date) ? 'opacity-25' : 'cursor-pointer transition ease-in-out duration-100 hover:bg-gray-100', isToday(date) ? 'text-red-800 font-bold' : 'text-gray-700']"></span>
                        </template>
                    </div>

                </div>
            </div>
        </div>

    </div>

    <div id="second">

    </div>

    <script src="script.js"></script>
    <script>
        function addToFirstDiv() {
            document.getElementById('first').innerHTML += `<span>added-to-first-div</span>`;
        }

        function addToSecondDiv() {
            document.getElementById('second').innerHTML += `<span>added-to-second-div</span>`;
        }
    </script>
</div>
</body>
</html>

and this is my script.js:

const MONTH_NAMES = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

function datepicker() {
    return {
        timestampOfToday: null,
        currentMonth: null,
        currentYear: null,
        month: null,
        year: null,
        daysOfMonth: [],
        blankDays: [],

        initDate() {
            const today = new Date();
            this.currentMonth = today.getMonth();
            this.currentYear = today.getFullYear();
            this.month = this.currentMonth;
            this.year = this.currentYear;
            this.timestampOfToday = new Date(this.year, this.month, today.getDate()) / 1000;
        },

        isToday(date) {
            const today = new Date();
            const d = new Date(this.year, this.month, date);

            return today.toDateString() === d.toDateString();
        },

        isPassedDay(date)
        {
            const timestampOfDate = new Date(this.year, this.month, date) / 1000;
            return timestampOfDate < this.timestampOfToday;
        },

        getDateValue(date) {
            if (this.isPassedDay(date)) {return;}
            let selectedDate = new Date(this.year, this.month, date);

            selectedDate = `${selectedDate.getFullYear()}-${selectedDate.getMonth() + 1}-${selectedDate.getDate()}`;

            console.log(selectedDate)
        },

        initDatepicker() {
            let daysInMonth = new Date(this.year, this.month + 1, 0).getDate();
            let firstDayOfWeek = new Date(this.year, this.month).getDay(); // find where to start calendar day of week
            let blankDaysArray = [];
            let daysOfMonthArray = [];

            if (firstDayOfWeek === 0) {
                firstDayOfWeek = 7;
            }

            for ( let i = 2; i <= firstDayOfWeek; i++) {
                blankDaysArray.push(i);
            }

            for ( let i = 1; i <= daysInMonth; i++) {
                daysOfMonthArray.push(i);
            }

            this.blankDays = blankDaysArray;
            this.daysOfMonth = daysOfMonthArray;
        },

        previousMonth() {
            if (this.month === this.currentMonth && this.year === this.currentYear) {return;}
            if (this.month  === 0) {
                this.year--;
                this.month = 11;
            } else {
                this.month--;
            }
            this.initDatepicker();
        },

        nextMonth() {
            if (this.month === 11) {
                this.year++;
                this.month = 0;
            } else {
                this.month++;
            }
            this.initDatepicker();
        }
    }
}

my problem is when I click on first button to add an element after Alpine element, it’ll cause several errors and datepicker loses its functionality.

Why is this happening and how can I fix this?

Thank you very much for your help.

2

Answers


  1. In your HTML, add an x-data attribute to the first div to initialize the Alpine data

    <div id="first" x-data="datepicker()" x-init="[initDate(), initDatepicker()]" x-cloak>
       <!-- ... your datepicker content ... -->
    </div>
    

    in addToFirstDiv function, you’re manipulating the DOM instead of Apline data. you can add something like the following code in order to fix it:

    Alpine.store('datepicker').daysOfMonth.push('added-to-first-div');
    

    i hope this helps

    Login or Signup to reply.
  2. I believe the issue is with the way you’re using the innerHTML method. One way to fix the problem is to use the appendChild method instead:

    function addToFirstDiv() {
        const newElement = document.createElement("span");
        newElement.innerHTML = "added-to-first-div";
        const firstDiv = document.getElementById("first");
        firstDiv.appendChild(newElement);
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search