Javascript for booking widget

  • Implementation with Vanilla JavaScript (no jQuery).
  • using Pikaday for date picker (less than 5kb minified and gzipped)
  • using Day.js for date manipulation (fast 2kB alternative to Moment.js with the same modern API)

Based on the following assumptions:

  • form method is POST
  • convert display dates to submitted dates using YYYYMMDD
  • submitted field names are: – date_from
    • date_to
    • num_nights
    • num_adults
    • num_children

Examples

Client sites using a booking widget and notes about respective implementation.

Site Implementation Booking System
https://platinumsuitesresort.com/ Using TailwindCSS and converted to plain JS from original version which was using jQuery RoomKeyPMS
https://mountrobsoninn.com/ Using TailwindCSS and converted to plain JS from original version which was using jQuery WebRezPro
https://mountaineerlodge.com/ Using Foundation, Pikaday and Moment.js (a larger date manipulation library) WebRezPro
https://fairmontcreek.ca/ Using Foundation, Pikaday and Moment.js (a larger date manipulation library) WebRezPro
https://summerlandresorthotel.com/ Using Foundation, Pikaday and Moment.js (a larger date manipulation library) TravelClick
  • Handle: @booking-widget
  • Preview:
  • Filesystem Path: components/02-components/booking-widget/booking-widget.hbs
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/pikaday/css/pikaday.css">

<div class="bg-gray-500">
    <form id="booking-widget" class="max-w-5xl mx-auto px-4">
        <div class="flex flex-wrap content-between py-4 -mx-2">
            <div class="w-full sm:w-1/3 md:flex-1 mb-4 px-2">
                <label class="block uppercase tracking-wide text-gray-200 text-xs font-bold mb-2" for="ArrivalDate">
                    Arrival
                </label>
                <div class="relative">
                    <input class="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" id="ArrivalDate" name="arrival" type="text" placeholder="Arrival Date" autocomplete="off" readonly="">
                    <input type="hidden" id="date_from" name="date_from">
                    <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-brown-500">
                        <svg class="h-4 w-5 fill-current text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                            <path d="M17 3h-1v2h-3V3H7v2H4V3H3c-1.101 0-2 .9-2 2v12c0 1.1.899 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 14H3V9h14v8zM6.5 1h-2v3.5h2V1zm9 0h-2v3.5h2V1z"></path>
                        </svg>
                    </div>
                </div>
            </div>
            <div class="w-full sm:w-1/3 md:flex-1 mb-4 px-2">
                <label class="block uppercase tracking-wide text-gray-200 text-xs font-bold mb-2" for="DepartureDate">
                    Departure
                </label>
                <div class="relative">
                    <input class="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" id="DepartureDate" name="departure" type="text" placeholder="Departure Date" autocomplete="off" readonly="">
                    <input type="hidden" id="date_to" name="date_to">
                    <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-brown-500">
                        <svg class="h-4 w-5 fill-current text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                            <path d="M17 3h-1v2h-3V3H7v2H4V3H3c-1.101 0-2 .9-2 2v12c0 1.1.899 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 14H3V9h14v8zM6.5 1h-2v3.5h2V1zm9 0h-2v3.5h2V1z"></path>
                        </svg>
                    </div>
                </div>
            </div>
            <div class="w-full sm:w-1/3 md:w-40 mb-4 px-2">
                <label class="block uppercase tracking-wide text-gray-200 text-xs font-bold mb-2" for="adults">
                    Guests
                </label>
                <div class="relative text-gray-500">
                    <select id="adults" class="block appearance-none w-full bg-gray-100 border border-gray-200 text-gray-700 py-3 px-4 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-gray-500" name="adults">
                        <option value="1">1</option>
                        <option value="2" selected="selected">2</option>
                        <option value="3">3</option>
                        <option value="4">4</option>
                        <option value="5">5</option>
                        <option value="6">6</option>
                    </select>
                    <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-brown-700">
                        <svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                            <path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"></path>
                        </svg>
                    </div>
                </div>
            </div>
            <div class="w-full md:flex-1 mb-4 px-2 flex flex-col justify-end">
                <button type="submit" class="block bg-gray-700 hover:bg-gray-900 focus:bg-gray-800 text-white font-bold py-3 px-4 rounded focus:outline-none focus:shadow-outline" onclick="ga('send','event','Outgoing','Booking System')">
                    Search Availability
                </button>
            </div>
        </div>
    </form>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.9.4/dayjs.min.js" integrity="sha512-XZSHSEFj4QeE0G4pwy4tToyAhF2VXoEcF9CP0t1PSZMP2XHhEEB9PjM9knsdzcEKbi6GRMazdt8tJadz0JTKIQ==" crossOrigin="anonymous"></script>

<script src="https://cdn.jsdelivr.net/npm/pikaday/pikaday.js"></script>
  • Content:
    /*
      Javascript for booking widget
      - using Pikaday for date widget
      - using Day.js for date manipulation
    
      Based on the following assumptions:
      - form method is POST
      - convert display dates to submitted dates using YYYYMMDD
      - submitted field names are:
            - date_from
            - date_to
            - num_nights
            - num_adults
            - num_children
    */
    
    
    // Date picker for arrival date. On select, set minDepartureDate + minNights for departure date widget
    const minNights = 2;
    const arrival = new Pikaday({
        field: document.querySelector('#ArrivalDate'),
        format: 'YYYY-MM-DD',
        minDate: new Date(dayjs().add(1, 'day')),
        onSelect() {
            let selectedDate = new Date(this.getDate());
            const minDepartureDate = new Date(
                selectedDate.getFullYear(),
                selectedDate.getMonth(),
                selectedDate.getDate() + minNights
            );
            depart.setMinDate(minDepartureDate);
            // set depart calendar to selected date, important when not in same month
            depart.gotoDate(selectedDate);
            // format date for submission
            return arrivalDateSubmitFormat = dayjs(this.getDate()).format('YYYYMMDD');
        },
    });
    
    // Date picker for departure date.
    const departure_date = '';
    const depart = new Pikaday({
        field: document.querySelector('#DepartureDate'),
        format: 'YYYY-MM-DD',
        minDate: new Date(dayjs().add(1 + minNights, 'day')),
        onSelect() {
            const selectedDate = new Date(this.getDate());
            const maxArrivalDate = new Date(
                selectedDate.getFullYear(),
                selectedDate.getMonth(),
                selectedDate.getDate() - minNights
            );
            arrival.setMaxDate(maxArrivalDate);
            return departureDateSubmitFormat = dayjs(this.getDate()).format('YYYYMMDD');
    
        },
    });
    
    // Date conversion before submission
    
    const bookingWidget = document.querySelector('#booking-widget');
    const arrivalDisplayField = document.querySelector('#ArrivalDate')
    const departureDisplayField = document.querySelector('#DepartureDate')
    
    const date_from = document.querySelector('#date_from')
    const date_to = document.querySelector('#date_to')
    
    if (bookingWidget) {
        bookingWidget.addEventListener("submit", function(event) {
    
            // set the value of hidden fields to dates reformatted for submission
            date_from.value = arrivalDateSubmitFormat;
            date_to.value = departureDateSubmitFormat;
    
            // remove the name attribute or our arrival and departure display fields to avoid submitting
            arrivalDisplayField.removeAttribute('name');
            departureDisplayField.removeAttribute('name');
    
            // to test values without submitting form
            // event.preventDefault();
            // console.log('date_from: ' + date_from.value);
            // console.log('date_to: ' + date_to.value);
        });
    }
  • URL: /components/raw/booking-widget/booking-widget.js
  • Filesystem Path: components/02-components/booking-widget/booking-widget.js
  • Size: 2.8 KB
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/pikaday/css/pikaday.css">

<div class="bg-gray-500">
  <form id="booking-widget" class="max-w-5xl mx-auto px-4">
    <div class="flex flex-wrap content-between py-4 -mx-2">
      <div class="w-full sm:w-1/3 md:flex-1 mb-4 px-2">
        <label class="block uppercase tracking-wide text-gray-200 text-xs font-bold mb-2" for="ArrivalDate">
          Arrival
        </label>
        <div class="relative">
          <input class="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
                 id="ArrivalDate" name="arrival" type="text"
                 placeholder="Arrival Date" autocomplete="off" readonly="">
          <input type="hidden" id="date_from" name="date_from">
          <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-brown-500">
            <svg class="h-4 w-5 fill-current text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
              <path d="M17 3h-1v2h-3V3H7v2H4V3H3c-1.101 0-2 .9-2 2v12c0 1.1.899 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 14H3V9h14v8zM6.5 1h-2v3.5h2V1zm9 0h-2v3.5h2V1z"></path>
            </svg>
          </div>
        </div>
      </div>
      <div class="w-full sm:w-1/3 md:flex-1 mb-4 px-2">
        <label class="block uppercase tracking-wide text-gray-200 text-xs font-bold mb-2" for="DepartureDate">
          Departure
        </label>
        <div class="relative">
          <input class="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
                 id="DepartureDate" name="departure" type="text"
                 placeholder="Departure Date" autocomplete="off" readonly="">
          <input type="hidden" id="date_to" name="date_to">
          <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-brown-500">
            <svg class="h-4 w-5 fill-current text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
              <path d="M17 3h-1v2h-3V3H7v2H4V3H3c-1.101 0-2 .9-2 2v12c0 1.1.899 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 14H3V9h14v8zM6.5 1h-2v3.5h2V1zm9 0h-2v3.5h2V1z"></path>
            </svg>
          </div>
        </div>
      </div>
      <div class="w-full sm:w-1/3 md:w-40 mb-4 px-2">
        <label class="block uppercase tracking-wide text-gray-200 text-xs font-bold mb-2" for="adults">
          Guests
        </label>
        <div class="relative text-gray-500">
          <select id="adults"
                  class="block appearance-none w-full bg-gray-100 border border-gray-200 text-gray-700 py-3 px-4 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
                  name="adults">
            <option value="1">1</option>
            <option value="2" selected="selected">2</option>
            <option value="3">3</option>
            <option value="4">4</option>
            <option value="5">5</option>
            <option value="6">6</option>
          </select>
          <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-brown-700">
            <svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
              <path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"></path>
            </svg>
          </div>
        </div>
      </div>
      <div class="w-full md:flex-1 mb-4 px-2 flex flex-col justify-end">
        <button type="submit"
                class="block bg-gray-700 hover:bg-gray-900 focus:bg-gray-800 text-white font-bold py-3 px-4 rounded focus:outline-none focus:shadow-outline"
                onclick="ga('send','event','Outgoing','Booking System')">
          Search Availability
        </button>
      </div>
    </div>
  </form>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.9.4/dayjs.min.js"
        integrity="sha512-XZSHSEFj4QeE0G4pwy4tToyAhF2VXoEcF9CP0t1PSZMP2XHhEEB9PjM9knsdzcEKbi6GRMazdt8tJadz0JTKIQ=="
        crossOrigin="anonymous"></script>

<script src="https://cdn.jsdelivr.net/npm/pikaday/pikaday.js"></script>
/* No context defined. */