Source

client/ux/booking/booking.js

/**
 * Class Booking
 * 
 * The booking component shows a timeline from a start date to an end date.
 * 
 * Each day displays clickable slots.
 * Slots can be defined by a range of hours (example: 8 - 18), or a specific array of slots, like this:
 * slots: [
 *      {
 *          name: "Morning",
 *          startTime: 8,
 *          endTime: 12
 *      },
 *      {
 *          name: "Afternoon",
 *          startTime: 14,
 *          endTime: 17
 *      }
 *  ]
 * 
 * @ignore
 */
kiss.ux.Booking = class Booking extends kiss.ui.Component {
    constructor() {
        super()
    }

    /**
     * Generates a Booking from a JSON config
     * @param {object} config - JSON config
     * @returns {HTMLElement}
     * 
     * @example:
     * createBooking({
     *      startDate: new Date(),
     *      endDate: new Date("2020-10-09"),
     *      startTime: 8,
     *      endTime: 18
     * })
     */
    init(config = {}) {
        super.init(config)
        const id = this.id

        // Adjust starting & ending dates
        this.startDate = (config.startDate) ? new Date(config.startDate) : new Date()
        this.endDate = new Date(this.startDate)

        // Adjust starting & ending times
        this.startTime = config.startTime || 0
        this.endTime = config.endTime || 23

        // Set a specific list of slots
        this.slots = this.config.slots || null
        
        if (config.endDate) this.endDate = new Date(config.endDate)
        else this.endDate.setDate(this.endDate.getDate() + 7)

        // Adjust date format
        this.dateRenderer = config.dateRenderer || this.renderDate

        // Assembly
        this.innerHTML = `<div class="booking">${this.renderBooking()}</div>`
        this.booking = this.querySelector(".booking")

        // Set properties
        this.hours = this.querySelector(".booking-hours") || {}
        this.days = this.querySelector(".booking-days") || {}

        this._setProperties(config, [
            [
                ["display"],
                [this.style]
            ],
            [
                ["flex", "position", "top", "left", "width", "height", "margin", "padding", "background", "backgroundColor", "borderColor", "borderRadius", "borderStyle", "borderWidth", "boxShadow"],
                [this.booking.style]
            ]
        ])

        // Manage the click on a slot
        this.onclick = function(event) {
            let slot = event.target.parentNode.parentNode.querySelector(".booking-day-slot")
            let date = slot.getAttribute("date")
            let start = slot.getAttribute("start")
            let end = slot.getAttribute("end")

            if (typeof this.config.action === "function") this.config.action({
                date: date,
                start: start,
                end: end
            })
        }

        return this
    }

    /**
     * Render the booking within a start date and an end date
     */
    renderBooking() {
        let date = this.startDate
        let week = ""
        
        for (let day = 0; date < this.endDate; day++) {
            date.setDate(date.getDate() + 1)
            week += this.renderDay(date)
        }
        return week
    }

    /**
     * Render a day in the booking.
     * It's composed of a name (ex: 1 septembre 2020)
     * @param {*} date 
     */
    renderDay(date) {
        let day = date.getDay()
        let isWeekEnd = (day === 6) || (day === 0)

        //if (isWeekEnd) return ""

        return `<div class="booking-day" day="${date.toISO()}">
                    <div class="booking-day-header">${this.dateRenderer(date)}</div>
                    <div class="booking-day-details">${this.renderDayDetails(date, isWeekEnd)}</div>
                </div>`.removeExtraSpaces()
    }

    /**
     * 
     * @param {*} date 
     * @param {*} isWeekEnd 
     */
    renderDayDetails(date, isWeekEnd) {
        let slots = ""
        if (isWeekEnd) return `<div class="booking-day-weeked"></div>`

        // Some specific slots have been setup:
        if (this.slots) {
            return this.slots.map(slot => {
                let txtHourStart = slot.startTime.toString().padStart(2, 0) + ":00"
                let txtHourEnd = slot.endTime.toString().padStart(2, 0) + ":00"
                return this.renderDaySlot(date, txtHourStart, txtHourEnd, slot.name)
            }).join('')
        }

        // No specific slots have been setup, we render a set of hours:
        for (let hour = this.startTime; hour <= this.endTime; hour++) {
            let txtHourStart = hour.toString().padStart(2, 0) + ":00"
            let txtHourEnd = (hour + 1).toString().padStart(2, 0) + ":00"

            slots += this.renderDaySlot(date, txtHourStart, txtHourEnd, "")
        }
        return slots
    }

    /**
     * 
     * @param {*} slotDate 
     * @param {*} slotStart 
     * @param {*} slotEnd 
     * @param {*} slotName 
     */
    renderDaySlot(slotDate, slotStart, slotEnd, slotName) {
        return  `<div class="booking-day-slot" date="${slotDate.toISO()}" start="${slotStart}" end="${slotEnd}">
                    <div class="booking-day-slot-time">${slotStart + " - " + slotEnd}</div>
                    <div class="booking-day-slot-name">${slotName}</div>
                </div>`.removeExtraSpaces()
    }

    /**
     * Render the hours within a day, within a range
     * 
     * @param {number} start 
     * @param {number} end 
     */
    renderHours = function (start, end) {
        let hours = ""
        for (let hour = start; hour <= end; hour++) {
            hours += `<div class="booking-day-hour">${hour.toString().padStart(2, 0)}</div>`
        }
        return hours
    }  
    
    /**
     * Render the date seen at the top of each day in the booking
     * 
     * @param {date} date 
     */
    renderDate(date) {
        return date.toISO()

        /*
        // With Moment.js
        let day = date.getDate()
        let momentDate = moment(date.toISO())
        let weekDay = momentDate.format("dddd")
        let month = momentDate.format("MMMM")
        let year = momentDate.format("YYYY")
        return weekDay + "<br>" + day + " " + month + "<br>" + year
        */
    }    

    /**
     * Update the start / end dates of the booking
     * 
     * @param {date} startDate 
     * @param {date} endDate 
     */
    updateRange(startDate, endDate) {
        this.startDate = new Date(startDate)
        this.endDate = new Date(endDate)
        this.booking.innerHTML = this.renderBooking()
    }
}

// Create a Custom Element and add a shortcut to create it
customElements.define("a-booking", kiss.ux.Booking)

/**
 * Shorthand to create a new Booking component. See [kiss.ux.Booking](kiss.ux.Booking.html)
 * 
 * @param {object} config
 * @returns HTMLElement
 */
const createBooking = (config) => document.createElement("a-booking").init(config)

;