journal-to-canvas-slideshow/scripts/classes/PopoverGenerator.js

271 lines
10 KiB
JavaScript

"use strict";
import { HelperFunctions } from "./HelperFunctions.js";
import { universalInterfaceActions as UIA } from "../data/Universal-Actions.js";
export class Popover {
static defaultElementData = {
popoverElement: {
target: null,
hideEvents: [],
},
sourceElement: {
target: null,
hideEvents: [],
},
parentElement: {
target: null,
hideEvents: [],
},
};
static async processPopoverData(
sourceElement,
parentElement,
templateData,
elementData = Popover.defaultElementData,
sourceEvent = ""
) {
// -- RENDER THE POPOVER
elementData.parentElement.target = parentElement;
elementData.sourceElement.target = sourceElement;
let elementDataArray = Object.keys(elementData).map((key) => {
let newData = elementData[key];
newData.name = key;
return newData;
});
let popover = await Popover.createAndPositionPopover(
templateData,
elementDataArray,
sourceEvent
);
return popover;
}
/**
*
* @param {Event} event - the event (usually hover) that generated the tooltip
* @param {JQueryObject} $html - the html of the entire app
* @param {String} dataElementSelector - a string to select the parent element with the relevant data for this tooltip
*/
static async generateTooltip(event, $html, dataElementSelector, sourceEvent) {
if (!$html.jquery) $html = $($html);
let sourceElement = event.currentTarget;
let parentDataElement = sourceElement.closest(dataElementSelector);
let templateData = Popover.createTemplateData(parentDataElement, "tooltip", {
content: sourceElement.dataset.tooltipText,
});
let popover = await Popover.processPopoverData(
sourceElement,
$html,
templateData,
{
...Popover.defaultElementData,
},
sourceEvent
);
let eventString = "mouseenter mouseleave";
let selectorString = `[data-tooltip], .popover[data-popover-id="tooltip"]`;
$html
.off(eventString, selectorString)
.on(eventString, selectorString, async (event) => {
let { type, currentTarget } = event;
let isMouseOver = type === "mouseover" || type === "mouseenter";
!currentTarget.jquery && (currentTarget = $(currentTarget));
//if our current target is our source element or the popover itself
if (currentTarget.is($(sourceElement)) || currentTarget.is($(popover))) {
if (!popover.hoveredElements) popover.hoveredElements = [];
let { hoveredElements } = popover;
let isInArray = Popover.JqObjectInArray(
hoveredElements,
currentTarget
);
if (isMouseOver) {
//if we're already tracking it, remove it
if (!isInArray) {
hoveredElements.push(currentTarget);
} else {
}
popover.hoveredElements = hoveredElements;
} else {
if (isInArray) {
//if we're already tracking it, remove it
hoveredElements = hoveredElements.filter(
(el) => !el.is(currentTarget)
);
}
popover.hoveredElements = hoveredElements;
if (popover.hoveredElements.length === 0) {
await Popover.hideAndDeletePopover(popover);
}
}
}
});
}
static JqObjectInArray(objectArray, searchObject) {
let isIncluded = false;
objectArray.forEach((obj) => {
if (obj.is(searchObject)) {
isIncluded = true;
}
});
return isIncluded;
}
static createTemplateData(parentLI, partialName, context = {}) {
let template = { frameId: "", id: "", type: "" };
let dataset = filterObject($(parentLI).data(), template);
if (!dataset) {
dataset = {};
}
let popoverId = partialName;
dataset.popoverId = popoverId; //to keep there from being multiples of the same popover
return {
passedPartial: partialName,
dataset: dataset,
passedPartialContext: context,
};
}
static validateInput(inputValue, validationType, onInvalid = "") {
let valid = false;
switch (validationType) {
case "image":
valid = HelperFunctions.isImage(inputValue);
break;
default:
valid = inputValue !== undefined;
break;
}
return valid;
}
/**
* Generates a popover (tooltip, etc), and positions it from the source element
* boundingClientRect data
* @param {Object} templateData - the data to be passed to the popover
* @param {Application} parentApp - the parent application rendering the popover
* @param {HTMLElement} sourceElement - the element that is the "source" of the popover (a button, input, etc.)
*/
static async createAndPositionPopover(
templateData,
elementDataArray = [],
sourceEvent = ""
) {
let elements = elementDataArray.map((data) => data.target);
let [popoverElement, sourceElement, parentElement] = elements; //destructure the passed-in elements
let boundingRect = sourceElement.getBoundingClientRect();
let popoverTemplate = game.JTCS.templates["popover"];
// popoverElement = parentElement.find(`.popover[data-popover-id="${templateData.dataset.popoverId}"]`);
let another = parentElement.find(`.popover`);
popoverElement = parentElement.find(
`.popover[data-popover-id="${templateData.dataset.popoverId}"]`
);
let areTheSame = another.is(popoverElement);
if (another && !areTheSame) {
await Popover.hideAndDeletePopover(another);
}
if (popoverElement.length === 0) {
//if it doesn't already exist, create it
let renderedHTML = await renderTemplate(popoverTemplate, templateData);
parentElement.append(renderedHTML);
popoverElement = parentElement.find(
`.popover[data-popover-id="${templateData.dataset.popoverId}"`
);
UIA.fade(popoverElement);
}
let popoverData = elementDataArray.find((data) => data.name === "popoverElement");
popoverData.target = popoverElement;
popoverElement.css({ position: "absolute" });
popoverElement.offset({
top: boundingRect.top + boundingRect.height,
left: boundingRect.left,
});
popoverElement.focus({ focusVisible: true });
//set up a "Click Out" event handler
let popoverId = templateData.dataset.popoverId;
// //hideEvents should be list of events to hide the popover on (like blur, change, mouseout, etc)
// elementDataArray.forEach((data) => {
// let targetElement = data.target;
// data.hideEvents.forEach((eventData) => {
// let handler;
// let selector;
// let eventName;
// let options;
// if (typeof eventData === "string") {
// //if it's a simple string, just set the handler to immediaetly hide the popover on this event
// eventName = eventData;
// handler = async (event) => await Popover.hideAndDeletePopover(popoverElement);
// selector = "*";
// } else if (typeof eventData === "object") {
// //if it's an object, we'll want to do something (like validate input) first before hiding
// eventName = eventData.eventName;
// //pass the popover element and the hide function to the wrapperFunction
// options = {
// ...eventData.options,
// popover: popoverElement,
// hideFunction: Popover.hideAndDeletePopover,
// };
// handler = async (event, options) => {
// await eventData.wrapperFunction(event, options);
// };
// selector = eventData.selector;
// }
// $(targetElement)
// .off(eventName, selector)
// .on(eventName, selector, async (event) => await handler(event, options));
// });
// });
// let popoverID = popoverElement.data().popoverId;
$(document)
.off("click")
.on("click", async (event) => {
//make sure the button that originated the click wasn't
//the same one being handled by this document
if (Popover.isOutsideClick(event, sourceElement)) {
await Popover.hideAndDeletePopover(popoverElement);
}
});
return popoverElement;
}
static isOutsideClick(event, sourceElement) {
let wasOnPopover = $(event.target).closest(".popover").length > 0;
let wasOnSourceElement = $(event.target).is($(sourceElement));
if (wasOnPopover || wasOnSourceElement) {
//click was on the popover
return false;
}
//if our click is outside of our popover element
return true;
}
static async hideAndDeletePopover(popoverElement) {
if (popoverElement.timeout) {
//if the popover is already counting down to a timeout, cancel it
clearTimeout(popoverElement.timeout);
}
let popoverTimeout = setTimeout(() => {
UIA.fade(popoverElement, { isFadeOut: true });
}, 400);
//save that timeout's id on the popover
popoverElement.timeout = popoverTimeout;
}
}