// Based on https://stackoverflow.com/a/15289883 function computeDateDifferenceInHours (date1, date2) { const msPerHour = 60 * 60 * 1000; return Math.floor(Math.abs(date2.getTime() - date1.getTime()) / msPerHour); } class Calendar { constructor (calendarParameters = {}) { this.containerNode = calendarParameters.containerNode !== undefined ? calendarParameters.containerNode : $("#cal-container"); this.eventContainerNode = null; this.timeSlotsContainerNode = null; this.eventDetailsContainerNode = null; this.startDate = calendarParameters.startDate !== undefined ? calendarParameters.startDate : new Date(); this.endDate = calendarParameters.endDate !== undefined ? calendarParameters.endDate : new Date(Date.now() + (24 * 60 * 60 * 1000)); this.nbHoursToDisplay = 0; this.firstHourToDisplay = 0; this.endHourToDisplay = 0; this.events = []; this.subscriptionURLFormat = calendarParameters.subscriptionURLFormat !== undefined ? calendarParameters.subscriptionURLFormat : ""; this.csrfToken = calendarParameters.csrfToken !== undefined ? calendarParameters.csrfToken : ""; // Map from locations to their CSS styles this.locationStyles = new Map(); this.init(); } init () { this.updateHoursToDisplay(); this.createTimeSlotContainer(); this.createEventContainer(); this.createEventDetailsContainer(); this.updateEventContainerGridStyle(); this.createTimeSlots(); this.createEvents(); this.createLocationStyles(); this.applyLocationStylesAsCSS(); this.updateEventLocationStyleID(); this.sortEventNodesByEndTimeAndLocation(); } // Date change setStartDate (newStartDate) { this.startDate = newStartDate; this.updateHoursToDisplay(); this.updateEventContainerGridStyle(); this.updateTimeSlots(); this.updateEventVisibilities(); } setEndDate (newEndDate) { this.endDate = newEndDate; this.updateHoursToDisplay(); this.updateEventContainerGridStyle(); this.updateTimeSlots(); this.updateEventVisibilities(); } updateHoursToDisplay () { this.startHourToDisplay = this.startDate.getHours(); this.endHourToDisplay = this.endDate.getHours(); this.nbHoursToDisplay = computeDateDifferenceInHours(this.startDate, this.endDate); } // Time slots createTimeSlotContainer () { this.timeSlotsContainerNode = $("
") .addClass("cal-time-slot-container") .appendTo(this.containerNode); } createTimeSlots () { // Populate the container hour by hour let self = this; function getHourStringToDisplay (index, hour) { if (index === self.nbHoursToDisplay - 1 || hour === 23) { return ""; } if (hour >= 10) { return hour + 1; } else { return " " + (hour + 1); } } for (let i = 0; i < this.nbHoursToDisplay; i++) { let hour = (this.startHourToDisplay + i) % 24; // Time slot hour let timeSlotHourNode = $("
") .addClass("cal-time-slot-hour") .css({ "grid-column-start": `${i + 1}`, "grid-column-end" : "span 1", "grid-row-start" : "1", "grid-row-end" : "1" }) .html(getHourStringToDisplay(i, hour)) .prependTo(this.timeSlotsContainerNode); // Time slot block let timeSlotBlockNode = $("
") .addClass("cal-time-slot") .css({ "grid-column-start": `${i + 1}`, "grid-column-end" : "span 1", "grid-row-start" : "2", "grid-row-end" : "2" }) .appendTo(this.timeSlotsContainerNode); if (i > 0 && hour === 0) { timeSlotHourNode.addClass("cal-new-day"); timeSlotBlockNode.addClass("cal-new-day"); } } } updateTimeSlots () { this.timeSlotsContainerNode.empty(); this.createTimeSlots(); } getHourSlotWidth () { return this.timeSlotsContainerNode.width() / this.nbHoursToDisplay; } // Events createEventContainer () { this.eventContainerNode = $("
") .addClass("cal-event-container") .appendTo(this.containerNode); } createEvents () { // Move all event nodes into the event container let eventElements = this.containerNode.find(".cal-event"); eventElements.appendTo(this.eventContainerNode); // Create event objects from them all for (let element of eventElements) { let newEvent = new Event($(element), this); this.events.push(newEvent); } } updateEventContainerGridStyle () { this.eventContainerNode.css("grid-template-columns", `repeat(${this.nbHoursToDisplay}, ${100 / this.nbHoursToDisplay }%)`); } updateEventVisibilities () { for (let event of this.events) { event.updateVisibility(); } } // Event details createEventDetailsContainer () { this.eventDetailsContainerNode = $("
") .addClass("cal-details-container") .appendTo(this.containerNode); } // Location styles createLocationStyles () { let locationIndices = new Map(); for (let event of this.events) { if (! locationIndices.has(event.location)) { locationIndices.set(event.location, [...locationIndices.keys()].length); } } let nbUniqueLocations = [...locationIndices.keys()].length; let styleID = 0; for (let [location, index] of locationIndices.entries()) { let hue = (index / (nbUniqueLocations + 1)) * 255; styleID += 1; this.locationStyles.set(location, { id: styleID, normal: [ `background-color: hsl(${hue}, 60%, 80%);`, `border-color: hsl(${hue}, 50%, 50%);`, `color: #000;` ], hover: [ `background-color: hsl(${hue}, 65%, 85%);`, `border-color: hsl(${hue}, 52%, 55%);`, `color: #000;` ], subscribed: [ `background-color: hsl(${hue}, 75%, 75%);`, `border-color: hsl(${hue}, 60%, 50%);`, `color: #000;` ], selected: [ `background-color: hsl(${hue}, 45%, 50%);`, `border-color: hsl(${hue}, 40%, 35%);`, `color: #FFF;` ] }); } } applyLocationStylesAsCSS () { let styleNode = $("