WordPress form dengan TailwindCSS dan Alpine JS

Form wordpress yang dibuat dari awal dengan mengaplikasikan tailwindcss untuk styling nya dan alpine js , file ini adalah bagian dari sebuah theme wordpress yang sedang saya coba bangun tanpa menggunakan Jquery dan bootstrap css yang umumnya digunakan

Halaman Villa Page

<!-- template-parts/villa-page -->
<div class="md:flex md:container md:mx-auto md:-mt-24">
<div class="w-full md:w-7/12">
<main class="p-5 text-sm text-gray-500">
    <div class="villadetail">
    <?php if (have_posts()) : while (have_posts()) : the_post(); ?>
    <h1 class="md:text-2xl text-md md:text-white font-medium uppercase md:text-3xl tracking-wider">
       <?php the_title() ?>
    </h1>
    <div class="text-gray-500 text-xs my-10" aria-label="Breadcrumb">
        <ol class="list-none p-0 inline-flex">
            <li class="flex items-center">
                <a href="<?php echo esc_url( home_url( '/' ) ); ?>">Home</a><svg class="fill-current w-3 h-3 mx-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"/></svg>
            </li>
            <li class="flex items-center">
                <a href="<?php echo esc_url( home_url( '/villas/' ) ); ?>">Villas</a>
            </li>
        </ol> 
    </div>
    <?php the_content() ?>
    <?php endwhile; endif; ?>
    </div>
</main>
</div>
<!--sidebar -->
<div class="w-full md:w-5/12">
<aside class="p-5">
<div class="bg-white w-full m-auto boder-1 border-dashed border-gray-100 shadow-md rounded-lg overflow-hidden">
    <div class="p-4 border-2">
        <h2 class="mb-1 text-gray-700 font-semibold text-sm pb-3 uppercase tracking-wide">
            Reservation
        </h2>
        <p class="text-sm pb-3">Questions?</p>
        <div class="flex">
            <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="24" height="24" viewBox="0 0 24 24" style=" fill:#000000;"><path d="M 12.011719 2 C 6.5057187 2 2.0234844 6.478375 2.0214844 11.984375 C 2.0204844 13.744375 2.4814687 15.462563 3.3554688 16.976562 L 2 22 L 7.2324219 20.763672 C 8.6914219 21.559672 10.333859 21.977516 12.005859 21.978516 L 12.009766 21.978516 C 17.514766 21.978516 21.995047 17.499141 21.998047 11.994141 C 22.000047 9.3251406 20.962172 6.8157344 19.076172 4.9277344 C 17.190172 3.0407344 14.683719 2.001 12.011719 2 z M 12.009766 4 C 14.145766 4.001 16.153109 4.8337969 17.662109 6.3417969 C 19.171109 7.8517969 20.000047 9.8581875 19.998047 11.992188 C 19.996047 16.396187 16.413812 19.978516 12.007812 19.978516 C 10.674812 19.977516 9.3544062 19.642812 8.1914062 19.007812 L 7.5175781 18.640625 L 6.7734375 18.816406 L 4.8046875 19.28125 L 5.2851562 17.496094 L 5.5019531 16.695312 L 5.0878906 15.976562 C 4.3898906 14.768562 4.0204844 13.387375 4.0214844 11.984375 C 4.0234844 7.582375 7.6067656 4 12.009766 4 z M 8.4765625 7.375 C 8.3095625 7.375 8.0395469 7.4375 7.8105469 7.6875 C 7.5815469 7.9365 6.9355469 8.5395781 6.9355469 9.7675781 C 6.9355469 10.995578 7.8300781 12.182609 7.9550781 12.349609 C 8.0790781 12.515609 9.68175 15.115234 12.21875 16.115234 C 14.32675 16.946234 14.754891 16.782234 15.212891 16.740234 C 15.670891 16.699234 16.690438 16.137687 16.898438 15.554688 C 17.106437 14.971687 17.106922 14.470187 17.044922 14.367188 C 16.982922 14.263188 16.816406 14.201172 16.566406 14.076172 C 16.317406 13.951172 15.090328 13.348625 14.861328 13.265625 C 14.632328 13.182625 14.464828 13.140625 14.298828 13.390625 C 14.132828 13.640625 13.655766 14.201187 13.509766 14.367188 C 13.363766 14.534188 13.21875 14.556641 12.96875 14.431641 C 12.71875 14.305641 11.914938 14.041406 10.960938 13.191406 C 10.218937 12.530406 9.7182656 11.714844 9.5722656 11.464844 C 9.4272656 11.215844 9.5585938 11.079078 9.6835938 10.955078 C 9.7955938 10.843078 9.9316406 10.663578 10.056641 10.517578 C 10.180641 10.371578 10.223641 10.267562 10.306641 10.101562 C 10.389641 9.9355625 10.347156 9.7890625 10.285156 9.6640625 C 10.223156 9.5390625 9.737625 8.3065 9.515625 7.8125 C 9.328625 7.3975 9.131125 7.3878594 8.953125 7.3808594 C 8.808125 7.3748594 8.6425625 7.375 8.4765625 7.375 z"></path></svg> 
            <p class="text-sm ml-2 text-gray-600">+62 361 0000 000</p>
        </div>

        <div class="flex">
            <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="24" height="24" viewBox="0 0 50 50" style=" fill:#000000;"><path d="M 14 3.9902344 C 8.4886661 3.9902344 4 8.4789008 4 13.990234 L 4 35.990234 C 4 41.501568 8.4886661 45.990234 14 45.990234 L 36 45.990234 C 41.511334 45.990234 46 41.501568 46 35.990234 L 46 13.990234 C 46 8.4789008 41.511334 3.9902344 36 3.9902344 L 14 3.9902344 z M 14 5.9902344 L 36 5.9902344 C 40.430666 5.9902344 44 9.5595687 44 13.990234 L 44 35.990234 C 44 40.4209 40.430666 43.990234 36 43.990234 L 14 43.990234 C 9.5693339 43.990234 6 40.4209 6 35.990234 L 6 13.990234 C 6 9.5595687 9.5693339 5.9902344 14 5.9902344 z M 18.048828 11.035156 C 16.003504 10.946776 14.45113 11.723922 13.474609 12.658203 C 12.986349 13.125343 12.633832 13.625179 12.392578 14.091797 C 12.151324 14.558415 11.998047 14.943108 11.998047 15.443359 C 11.998047 15.398799 11.987059 15.632684 11.980469 15.904297 C 11.973869 16.17591 11.97507 16.542045 12 16.984375 C 12.04996 17.869036 12.199897 19.065677 12.597656 20.484375 C 13.393174 23.321771 15.184446 27.043821 19.070312 30.929688 C 22.95618 34.815554 26.678014 36.606575 29.515625 37.402344 C 30.93443 37.800228 32.130881 37.949937 33.015625 38 C 33.457997 38.02503 33.822105 38.026091 34.09375 38.019531 C 34.365395 38.012931 34.601049 38.001953 34.556641 38.001953 C 35.056892 38.001953 35.441585 37.848676 35.908203 37.607422 C 36.374821 37.366168 36.874657 37.013651 37.341797 36.525391 C 38.276078 35.54887 39.053222 33.996496 38.964844 31.951172 C 38.922907 30.975693 38.381316 30.111858 37.582031 29.599609 C 36.96435 29.203814 36.005458 28.589415 34.753906 27.789062 C 33.301811 26.861299 31.44451 26.795029 29.929688 27.625 L 30.015625 27.582031 L 28.837891 28.087891 L 28.751953 28.148438 C 28.465693 28.349428 28.111154 28.386664 27.789062 28.251953 C 26.886813 27.874649 25.480985 27.133329 24.173828 25.826172 C 22.866671 24.519015 22.125351 23.113186 21.748047 22.210938 C 21.613336 21.888845 21.650568 21.534307 21.851562 21.248047 L 21.912109 21.162109 L 22.417969 19.984375 L 22.375 20.070312 C 23.204764 18.555868 23.140248 16.698619 22.210938 15.246094 C 21.410584 13.994542 20.796186 13.03565 20.400391 12.417969 C 19.888142 11.618684 19.02431 11.077096 18.048828 11.035156 z M 17.962891 13.033203 C 18.243409 13.045263 18.533045 13.209378 18.716797 13.496094 C 19.113001 14.114413 19.727696 15.07377 20.527344 16.324219 C 21.058033 17.153694 21.09533 18.243821 20.621094 19.109375 L 20.597656 19.152344 L 20.115234 20.279297 L 20.214844 20.097656 C 19.623835 20.939396 19.505055 22.032514 19.902344 22.982422 C 20.35304 24.060173 21.214923 25.695392 22.759766 27.240234 C 24.304608 28.785077 25.939827 29.64696 27.017578 30.097656 C 27.967486 30.494945 29.060604 30.376165 29.902344 29.785156 L 29.720703 29.884766 L 30.847656 29.402344 L 30.890625 29.378906 C 31.755801 28.904877 32.845877 28.944375 33.675781 29.474609 L 33.675781 29.472656 C 34.92623 30.272304 35.885587 30.886999 36.503906 31.283203 C 36.790622 31.466955 36.954736 31.756591 36.966797 32.037109 C 37.032417 33.555785 36.504954 34.506599 35.896484 35.142578 C 35.59225 35.460568 35.262335 35.691348 34.990234 35.832031 C 34.718133 35.972715 34.457889 36.001953 34.556641 36.001953 C 34.373232 36.001953 34.276633 36.013981 34.046875 36.019531 C 33.817117 36.025131 33.509144 36.025436 33.128906 36.003906 C 32.368431 35.960876 31.318757 35.831053 30.054688 35.476562 C 27.526547 34.767581 24.137509 33.168759 20.484375 29.515625 C 16.831241 25.862491 15.232169 22.473167 14.523438 19.945312 C 14.169071 18.681386 14.039037 17.631464 13.996094 16.871094 C 13.974624 16.490908 13.974899 16.18286 13.980469 15.953125 C 13.986069 15.72339 13.998047 15.626918 13.998047 15.443359 C 13.998047 15.542109 14.027287 15.281867 14.167969 15.009766 C 14.308652 14.737665 14.539432 14.40775 14.857422 14.103516 C 15.493401 13.495046 16.444215 12.967581 17.962891 13.033203 z"></path></svg>
            <p class="text-sm ml-2 text-gray-600">+62 888 0000 000</p>
        </div>
        <p class="text-sm pt-5 text-gray-600">
            information : [email protected]
        </p>
        <div class="mt-8 mb-3">
            <div class="relative" x-data="{ toggle:null }"> 

            <button type="button"
            @click="toggle !== 1 ? toggle = 1 : toggle = null"
            x-ref="reservationbutton"
            class="px-4 py-2 bg-blue-600 shadow-lg border rounded-lg text-white uppercase text-sm tracking-wider focus:outline-none focus:shadow-outline hover:bg-blue-800 active:bg-blue-400">
            Reservation
            </button>

            <div class="relative overflow-hidden transition-all max-h-0 duration-700"
            x-ref="reservation"
            :style="toggle == 1 ? 'max-height: '+ $refs.reservation.scrollHeight + 'px' :''"
            >          
                <div class="p-2">
                <p class="text-xs">
                 This is only an enquiry regarding the villa; it does not constitute a reservation.
                 <br />Fields (*) required.
                </p>
                <!--reservation-form-->
                <?php get_template_part( 'template-parts/reservation-form');?>
                </div>
            </div>
            </div>
        </div>
    </div>
</div>
<!--Gallery -->
<?php get_template_part( 'template-parts/gallery');?>
</aside>
</div>
</div>

Reservation-form PHP

<form 
    method="post" 
    id="form-reservation" 
    data-nonce="<?php echo wp_create_nonce('submit_reservation_form_nonce');?>"
    @submit.prevent="submitForm"
    x-data="ReservationForm"
>
<!--modal success -->
<?php get_template_part('template-parts/success-modal');?>

<input type="hidden" name="subject" value="Reservation for <?php the_title() ?>" />
<div class="mt-8 max-w-md">
     <div class="grid grid-cols-1 gap-6 text-sm">
        <label class="block">
            <span class="text-gray-700">Full name *</span>
            <input type="text"
            class="mt-1 block w-full rounded-md border-gray-300 shadow-sm text-sm 
            focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
            name="fullname" autocomplete="off" placeholder="" x-model="formData.fullname" x-ref="fullname"/>
            <p 
            class="text-xs text-red-600"
            x-text="errors.fullname?'Please fill fullname':''" 
            >
            </p><span class="text-lg font-bold"></span>
        </label>
        <label class="block">
            <span class="text-gray-700">Email address *</span>
            <input type="email"
            class="mt-1 block w-full rounded-md border-gray-300 shadow-sm text-sm
                focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 placeholder-gray-400"
                name="email" autocomplete="off"
                placeholder="[email protected]"
                x-model="formData.email" 
                x-ref="email"
            />
            <p 
            class="text-xs text-red-600"
            x-text="errors.email?'Please fill email':''"
            >
            </p>
        </label>
        <label class="block">
            <span class="text-gray-700">Phone *</span>
            <input type="text"                   
                class="mt-1 block w-full rounded-md border-gray-300 shadow-sm text-sm
                focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
             name="phone" placeholder="" x-model="formData.phone" x-ref="phone"/>
            <p 
            class="text-xs text-red-600"
            x-text="errors.phone?'Please fill phone':''"
            >
            </p>
        </label>
        <div class="relative" @keydown.escape="datepicker.closeDatepicker()" @click.away="datepicker.closeDatepicker()">
        <label class="block">
            <span class="text-gray-700">Check IN</span>
            <input type="text"                
            class="mt-1 block w-full rounded-md border-gray-300 shadow-sm
                focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 text-sm"
                name="checkin" autocomplete="off"
                @click="datepicker.endToShow = 'from'; datepicker.initDate(); datepicker.showDatepicker = true" 
                x-model="datepicker.dateFromValue" x-ref="checkin"
                :class="{'font-semibold': datepicker.endToShow == 'from' }"
                />
        </label>
        <p 
          class="text-xs text-red-600"
          x-text="errors.checkin?'Please select date':''"
        >
        </p>
        <label class="block">
            <span class="text-gray-700">Check Out</span>
            <input type="text"
                class="mt-1 block w-full rounded-md border-gray-300 shadow-sm text-sm
                focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
                name="checkout" autocomplete="off"
                @click="datepicker.endToShow = 'to'; datepicker.initDate(); datepicker.showDatepicker = true" 
                x-model="datepicker.dateToValue" 
                :class="{'font-semibold': datepicker.endToShow == 'to' }"
                />
        </label>
        <?php get_template_part( 'template-parts/date-modal');?>
        </div>
        <label class="block">
            <span class="text-gray-700">Number of guests</span>
            <input type="number" min="0" step="1"             
            class="mt-1 block w-full rounded-md border-gray-300 shadow-sm text-sm
                focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
                name="number_of_guest" placeholder="" x-model="formData.number_of_guest"/>
        </label>
        <label class="block">
            <span class="text-gray-700">Request</span>
            <textarea                
                class="mt-1 block w-full rounded-md border-gray-300 shadow-sm text-sm
                focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 placeholder-gray-400"
                rows="6" 
                name="request"
                placeholder="Please advise flight details for airport pick-up, or estimated arrival time at Villa, if travelling with infant(s), and any special requests, eg kid’s equipment / dietary needs / villa set-up."
                x-model="formData.request"
            >

resevationform.js

'use strict';

window.ReservationForm  = function () {

    const formReservation = document.getElementById('form-reservation');
    let nonce = formReservation.dataset.nonce;
    let fields = ['fullname','email','phone','checkin','checkout','number_of_guest','request'];
    let datepicker = new datePicker();

    return {

        buttonLabel: 'Submit',
        loading: false,
        status: false,

        modalHeaderText:'',
        modalBodyText:'',

        datepicker,

        //create formdata objects from array fields
        formData: Object.assign({}, ...Object.entries({...fields}).map(([a,b]) => ({ [b]: '' }))),

        //create errors objects from array fields
        errors : Object.assign({}, ...Object.entries({...fields}).map(([a,b]) => ({ [b]: false }))),

        isEmail(email){
            const rgx = /^(([^<>()[].,;:s@"]+(.[^<>()[].,;:s@"]+)*)|(".+"))@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}])|(([a-zA-Z-0-9]+.)+[a-zA-Z]{2,}))$/
            return rgx.test(email);
        },

        validation(q,w){
            var validate=false;
            if (!q){
                this.errors[w] = true;
                this.$refs[w].scrollIntoView();
                validate=true;
            }else{
                this.errors[w]= false;
            }
            return validate;
        },

        submitForm() {  
            //compensate height of reservation when error messages display
            this.$refs.reservation.style.maxHeight = '100%';

            //validations
            if(
                this.validation(this.formData.fullname.length>0, 'fullname')||
                this.validation(this.isEmail(this.formData.email), 'email')||
                this.validation(this.formData.phone.length > 0, 'phone')||
                this.validation(this.datepicker.dateFromValue.length > 0, 'checkin')
            ){ return; }

            this.formData.checkin = this.datepicker.dateFromValue;
            this.formData.checkout = this.datepicker.dateToValue;

            this.buttonLabel = 'Submitting...';
            this.loading = true;

            fetch( SiteParameters.ajax_url, {
                method: "POST",
                headers: { 
                    'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
                    'Cache-Control': 'no-cache',
                    'Accept': 'application/json' 
                },
                credentials: 'same-origin',
                body: 'action=submit_reservation_form&nonce='+ nonce +'&formdata='+ JSON.stringify( this.formData ) ,
            })
            .then( async (response) => {
                let data = await response.json();

                if (data.status == 'true') {
                    this.$nextTick(() => {
                        //scroll up reservation form container
                        this.$refs.reservationbutton.dispatchEvent(new Event("click"));
                        this.status = true;
                        this.modalHeaderText = "Thank You!";
                        this.modalBodyText = "Your form have been successfully submited!";
                    });
                 } else { 
                    throw new Error("Your registration failed");
                 }
                 //clear forms data after form submitted
                 Object.keys(this.formData).forEach(key => this.formData[key]='');
                 //clear submitted date picker
                 this.datepicker.unsetDateValues();

            })
            .catch(() => {

            })
            .finally(() => {
                this.loading = false;
                this.buttonLabel = 'Submit'
                this.status = false;
                document.getElementById("form-reservation").reset();
            });        
        },
    }
}

success-modal.php pop up window container sebagai alert window setelah fetch ajax success return true

 <!-- modal starts -->
    <div 
    x-cloak
    x-show.transition="status" 
    class="fixed inset-0 z-30 flex items-center justify-center overflow-auto bg-black bg-opacity-50"
    >
    <div 
    class="max-w-3xl px-6 py-4 mx-auto text-left bg-white rounded shadow-lg"
    @click.away="status = false"
    x-transition:enter="motion-safe:ease-out duration-300"
    x-transition:enter-start="opacity-0 scale-90"
    x-transition:enter-end="opacity-100 scale-100"
    >
        <div class="bg-white rounded px-8 py-8 max-w-lg mx-auto">
            <div class="flex items-center justify-between">
            <h5 class="mr-3 text-black max-w-none" x-text="modalHeaderText"></h5>          
            <span class="mr-3 text-black max-w-none" x-text="modalBodyText"></span>          
          <button type="button" class="z-50 cursor-pointer" @click="status = false">
            <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
          </svg>
          </button>
            </div>
        </div>         
    </div>
    </div>
<!-- modal ends -->

datepicker.js , date range component yang di implementasikan kedalam form

const MONTH_NAMES = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
const DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];

window.datePicker  = function () {
            return {
                MONTH_NAMES, 
                DAYS ,
                showDatepicker: false,
                dateFromYmd: '',
                dateToYmd: '',
                outputDateFromValue: '',
                outputDateToValue: '',
                dateFromValue: '',
                dateToValue: '',
                currentDate: null,
                dateFrom: null,
                dateTo: null,
                endToShow: '',
                selecting: false,
                month: '',
                year: '',
                no_of_days: [],
                blankdays: [],

                convertFromYmd(dateYmd) {
                    const year = Number(dateYmd.substr(0, 4));
                    const month = Number(dateYmd.substr(5, 2)) - 1;
                    const date = Number(dateYmd.substr(8, 2));

                    return new Date(year, month, date);
                },

                convertToYmd(dateObject) {
                    const year = dateObject.getFullYear();
                    const month = dateObject.getMonth() + 1;
                    const date = dateObject.getDate();

                    return year + "-" + ('0' + month).slice(-2) + "-" +  ('0' + date).slice(-2);
                },

                initDate() {
                    this.selecting = ( this.endToShow === 'to' && this.dateTo ) || ( this.endToShow === 'from' && this.dateFrom);
                    if ( ! this.dateFrom ) {
                        if ( this.dateFromYmd ) {
                            this.dateFrom = this.convertFromYmd( this.dateFromYmd );
                        }
                    }
                    if ( ! this.dateTo ) {
                        if ( this.dateToYmd ) {
                            this.dateTo = this.convertFromYmd( this.dateToYmd );
                        }
                    }
                    if ( ! this.dateFrom ) {
                        this.dateFrom = this.dateTo;
                    }
                    if ( ! this.dateTo ) {
                        this.dateTo = this.dateFrom;
                    }
                    if ( this.endToShow === 'from' && this.dateFrom ) {
                        this.currentDate = this.dateFrom;
                    } else if ( this.endToShow === 'to' && this.dateTo ) {
                        this.currentDate = this.dateTo;
                    } else {
                        this.currentDate = new Date();
                    }
                    currentMonth = this.currentDate.getMonth();
                    currentYear = this.currentDate.getFullYear();
                    if ( this.month !== currentMonth || this.year !== currentYear ) {
                        this.month = currentMonth;
                        this.year = currentYear;
                        this.getNoOfDays();
                    }
                    this.setDateValues();
                },

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

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

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

                    if ( !this.dateFrom ) {
                        return false;
                    }

                    return d.getTime() === this.dateFrom.getTime();
                },

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

                    if ( !this.dateTo ) {
                        return false;
                    }

                    return d.getTime() === this.dateTo.getTime();
                },

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

                    return d > this.dateFrom && d < this.dateTo;
                },

                outputDateValues() {
                    if (this.dateFrom) {
                        this.outputDateFromValue = this.dateFrom.toDateString();
                        this.dateFromYmd = this.convertToYmd(this.dateFrom);
                    }
                    if (this.dateTo) {
                        this.outputDateToValue = this.dateTo.toDateString();
                        this.dateToYmd = this.convertToYmd(this.dateTo);
                    }
                },

                setDateValues() {
                    if (this.dateFrom) {
                        this.dateFromValue = this.dateFrom.toDateString();
                    }
                    if (this.dateTo) {
                        this.dateToValue = this.dateTo.toDateString();
                    }
                },

                getDateValue(date, temp) {
                    // if we are in mouse over mode but have not started selecting a range, there is nothing more to do.
                    if (temp && !this.selecting) {
                        return;
                    }
                    let selectedDate = new Date(this.year, this.month, date);
                    if ( this.endToShow === 'from' ) {
                        this.dateFrom = selectedDate;
                        if ( ! this.dateTo ) {
                            this.dateTo = selectedDate;
                        } else if ( selectedDate > this.dateTo ) {
                            this.endToShow = 'to';
                            this.dateFrom = this.dateTo;
                            this.dateTo = selectedDate;
                        }
                    } else if ( this.endToShow === 'to' ) {
                        this.dateTo = selectedDate;
                        if ( ! this.dateFrom ) {
                            this.dateFrom = selectedDate;
                        } else if ( selectedDate < this.dateFrom ) {
                            this.endToShow = 'from';
                            this.dateTo = this.dateFrom;
                            this.dateFrom = selectedDate;
                        }
                    }
                    this.setDateValues();

                    if (!temp) {
                        if (this.selecting) {
                            this.outputDateValues();
                            this.closeDatepicker();
                        }
                        this.selecting = !this.selecting;
                    }
                },

                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();
                    let blankdaysArray = [];
                    for ( var i=1; i <= dayOfWeek; i++) {
                        blankdaysArray.push(i);
                    }

                    let daysArray = [];
                    for ( var i=1; i <= daysInMonth; i++) {
                        daysArray.push(i);
                    }

                    this.blankdays = blankdaysArray;
                    this.no_of_days = daysArray;
                },

                closeDatepicker() {
                    this.endToShow = '';
                    this.showDatepicker = false;
                },

                unsetDateValues() {
                    this.dateFromYmd= '';
                    this.dateToYmd= '';
                    this.outputDateFromValue= '';
                    this.outputDateToValue= '';
                    this.dateFromValue= '';
                    this.dateToValue= '';
                    this.currentDate= null;
                    this.dateFrom= null;
                    this.dateTo= null;
                    this.endToShow= '';
                    this.selecting= false;
                },

            }
        }

datemodal php, pop up window yang menampung tabel tanggal dari datepicker.js

<div 
class="absolute bg-white mt-0 rounded-lg shadow p-4  w-full z-80" 
x-show.transition="datepicker.showDatepicker"
>
<div class="flex flex-col items-center">
    <div class="w-full flex justify-between items-center mb-2">

    <div>
        <span x-text="datepicker.MONTH_NAMES[datepicker.month]" class="text-sm font-bold text-gray-800"></span>
        <span x-text="datepicker.year" class="ml-1 text-sm text-gray-600 font-normal"></span>
    </div>

    <div>
        <button 
            type="button"
            class="transition ease-in-out duration-100 inline-flex cursor-pointer hover:bg-gray-200 p-1 rounded-full" 
            @click="if (datepicker.month == 0) {datepicker.year--; datepicker.month=11;} else {datepicker.month--;} datepicker.getNoOfDays()">
            <svg class="h-4 w-4 text-gray-500 inline-flex"  fill="none" viewBox="0 0 20 20" stroke="currentColor">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
            </svg>  
        </button>        
        <button 
            type="button"
            class="transition ease-in-out duration-100 inline-flex cursor-pointer hover:bg-gray-200 p-1 rounded-full" 
            @click="if (datepicker.month == 11) {datepicker.year++; datepicker.month=0;} else {datepicker.month++;} datepicker.getNoOfDays()">
            <svg class="h-4 w-4 text-gray-500 inline-flex"  fill="none" viewBox="0 0 20 20" stroke="currentColor">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
            </svg>  
        </button>
    </div>            
    </div>
    <div class="w-full flex flex-wrap mb-3 -mx-1">
        <template x-for="(day, index) in datepicker.DAYS" :key="index">    
            <div style="width: 14.26%" class="px-1">
            <div
                x-text="day" 
                class="text-gray-800 font-medium text-center text-xs"
            ></div>
            </div>
        </template>
    </div>
    <div class="flex flex-wrap -mx-1">
        <template x-for="blankday in datepicker.blankdays">
        <div 
        style="width: 14.28%"
        class="text-center border p-1 border-transparent text-sm"   
        ></div>
        </template> 
        <template x-for="(date, dateIndex) in datepicker.no_of_days" :key="dateIndex"> 
            <div style="width: 14.28%" >
                <div
                    @click="datepicker.getDateValue(date, false)"
                    /*@mouseenter="datepicker.getDateValue(date, true)"*/
                    x-text="date"
                    class="p-0 cursor-pointer text-center text-xs leading-loose transition ease-in-out duration-100"
                    :class="{'font-bold': datepicker.isToday(date) == true, 'bg-blue-800 text-white rounded-l-full': datepicker.isDateFrom(date) == true, 'bg-blue-800 text-white rounded-r-full': datepicker.isDateTo(date) == true, 'bg-blue-200': datepicker.isInRange(date) == true }"  
                ></div>
            </div>
        </template>
    </div>
    <div>
        <span class="transition ease-in-out duration-100 inline-flex cursor-pointer hover:bg-gray-200 p-1" @click="datepicker.unsetDateValues()">Clear</span>
    </div>
    </div>
</div>

kode php untuk memproses data yang dikirimkan dari form

/******* SENDING MAIL *******/
    function wpdocs_set_html_mail_content_type() {
        return 'text/html';
    }
    add_filter('wp_mail_content_type', 'wpdocs_set_html_mail_content_type' );
    add_action('wp_ajax_submit_reservation_form', 'submit_reservation_form');
    add_action('wp_ajax_nopriv_submit_reservation_form', 'submit_reservation_form');

    function submit_reservation_form() {

        $nonce = $_REQUEST['nonce'];
        if (! wp_verify_nonce($nonce , 'submit_reservation_form_nonce') ) {
            die('No naughty business please.');
        }
        else{
            $formdata = stripslashes($_POST['formdata']); 
            $formdata = json_decode($formdata , true);

            $fullname = $formdata['fullname'];
            $email = sanitize_email($formdata['email']);
            $subject = $formdata['subject'];
            $phone = $formdata['phone'];
            $checkin = $formdata['checkin'];
            $checkout = $formdata['checkout'];
            $number_of_guest = $formdata['number_of_guest'];
            $request = $formdata['request'];

            //$recipient_email = base64_decode($formdata['sendto']);

            /* Email Message */
            $body = "<h2>Reservation Message</h2>";
            $body .= "<strong>Villa Name:</strong> ".$subject."<br/>";
            $body .= "<strong>Nama:</strong> ".$fullname."<br/>";
            $body .= "<strong>E-mail:</strong> ".$email."<br/>";
            $body .= "<strong>Phone:</strong> ".$phone."<br/>";
            $body .= "<strong>Check in date:</strong> ".$checkin."<br/>";
            $body .= "<strong>Check out date:</strong> ".$checkout."<br/>";
            $body .= "<strong>Number of guest:</strong> ".$number_of_guest."<br/>";
            $body .= "<strong>Request:</strong> ".$request."<br/>";

            if( $_SERVER['REMOTE_ADDR'] === '127.0.0.1' ){
                $result['status'] = 'true';
            } else{

                if( function_exists('wp_mail') ) {

                    $headers = 'From: ' . $fullname . ' <' . $email . '>' . "rn";

                    if(wp_mail($recipient_email, $subject, $body, $headers)) {
                        $result['status'] = 'true';
                    } else {
                        $result['status'] = 'false';
                    }

                } else {
                    $result['status'] = 'false';
                }

            }

            echo json_encode($result);

            die();

        }
    }

kode php untuk memasukan ajax url parameter yg dibutuhkan sebagai alamat url pada fetch di reservationform js

 /* Make site url available to JS scripts */
        $site_parameters = array(
            'site_url' => get_site_url(),
            'theme_directory' => get_template_directory_uri(),
            'ajax_url' => admin_url('admin-ajax.php')
        );
        wp_localize_script( 'tw-js', 'SiteParameters', $site_parameters );

kode php untuk memasukan source javascript dalam element dengan menambahkan attribute defer

if( is_page_template( 'villa-page.php' )){
 //add script into theme and inject defer attributes in the <script>s
    $scripts_ids = array('datepicker','reservation-form','alpinejs');
    $scripts_srcs = array('datepicker.js','reservationform.js','alpinejs.js');

    foreach( array_combine( $scripts_ids , $scripts_srcs ) as $id => $src) {
        echo wp_get_script_tag( 
            array(
            'id'=> $id,
            'src'=> get_template_directory_uri( ).'/dist/js/'. $src,
            'defer'=> true,
            )
        ); 
    }

}

untuk mengkompilasi script reservationform dan datepicker menggunakan laravel mix ditambahkan kedalam webpack.mix.js

let mix = require('laravel-mix');

mix.setPublicPath('./dist');
mix.postCss('src/css/theme.css','css')
.options(
    {
        postCss:[
            require('tailwindcss')('tailwind.config.js')
        ],
        processCssUrls: false
    }
).postCss('src/css/tiny-slider.css','css');

mix.js('src/js/main.js','js')
.js('src/js/tiny-slider.js','js')
.js('src/js/alpinejs.js','js')
.js('src/js/reservationform.js','js')
.js('src/js/datepicker.js','js');