UNPKG

coupdoeil

Version:

Javascript for Ruby on Rails Coupdoeil gem

134 lines (112 loc) 5.51 kB
import {FETCH_DELAY_MS, OPENING_DELAY_MS, POPOVER_CLASS_NAME} from "./config" import {preloadedContentElement, triggeredOnClick} from "./attributes" import {getPopoverContentHTML, setPopoverContentHTML} from "./cache" import {extractOptionsFromElement} from "./options_parser" import {positionPopover} from "./positioning" import {enter} from "el-transition" import {addToCurrents} from "./current" import {cancelClosingRequest, clear as clearPopover} from "./closing" import {lazyLoadPopoverContent} from "./lazy_loading"; import {executeOnNextFrameIfStillOpening, fetchPopoverContent} from "./utils"; async function loadPopoverContentHTML(controller, options, delayOptions) { return new Promise((resolve) => { setTimeout(async () => { // If opening has been canceled then this function aborts. Note that if lazy loading is enable it will still // finish to load the HTML to the content cache. if (!controller.coupdoeilElement.openingPopover) return resolve() // If the cache option is set to true and the content has already been fetched then this function aborts. if (getPopoverContentHTML(controller) && options.cache) return resolve() let html if (options.loading === "preload") { // If loading option is set to 'preload', the preloaded content is present in DOM, // nested in the coup-doeil element, in a template tag html = preloadedContentElement(controller).innerHTML } else if (options.loading === "lazy") { // If loading option is set to 'lazy', the HTML is loaded first with the 'lazy' param set to true, // to require the temporary/loader content of the popover. // At the same time, a second call is triggered with 'lazyLoadPopoverContent' to fetch the actual popover // content and update it when received. html = await fetchPopoverContent(controller, options.loading === "lazy") lazyLoadPopoverContent(controller, options) } else if (options.loading === "async") { html = await fetchPopoverContent(controller) } // Once the HTML has been retrieved by any of the loading mode, the content cache is updated. setPopoverContentHTML(controller, html) resolve() }, delayOptions.fetch) }) } export async function openPopover(controller, { parent, beforeDisplay }) { if (controller.isOpen) { return cancelClosingRequest(controller) } if (parent) { controller.parent = parent parent.children.add(controller) } const options = extractOptionsFromElement(controller.coupdoeilElement) const delays = getDelayOptionsForController(controller, options) const openingDelay = new Promise(resolve => setTimeout(resolve, delays.opening)) const fetchDelay = loadPopoverContentHTML(controller, options, delays) await Promise.all([fetchDelay, openingDelay]) const parentIsClosedOrClosing = controller.parent && (controller.parent.isClosed || controller.parent.closingRequest) // but if opening has been canceled (nullified), the wait still happens, so we need to check again if (controller.coupdoeilElement.openingPopover && !parentIsClosedOrClosing) { await display(controller, options, beforeDisplay) } } async function display(controller, options, beforeDisplay) { if (controller.isOpen) return; cancelClosingRequest(controller) if (controller.card) { controller.card.remove() } controller.card = buildPopoverElement(controller, options) document.body.appendChild(controller.card) if (options.animation) { controller.card.dataset.animation = options.animation } executeOnNextFrameIfStillOpening(controller, async () => { await positionPopover(controller.coupdoeilElement, controller.card, options) // popover can be closed while waiting for positioning promise to resolve if (controller.card === null) { return clearPopover(controller) } // see buildPopoverElement() about next 2 lines controller.card.classList.add('hidden') controller.card.style.removeProperty('visibility') executeOnNextFrameIfStillOpening(controller, async () => { if (beforeDisplay) { beforeDisplay(controller) } // // adding again the card to make sure it is in the map, could be better addToCurrents(controller.coupdoeilElement) delete controller.coupdoeilElement.openingPopover controller.coupdoeilElement.dataset.popoverOpen = true await enter(controller.card, 'popover') }) }) } function getDelayOptionsForController(controller, options) { if (options.openingDelay === false || triggeredOnClick(controller)) { return { fetch: 0, opening: 0 } } return { fetch: FETCH_DELAY_MS, opening: OPENING_DELAY_MS } } function buildPopoverElement(controller, options) { const el = document.createElement('div') el.setAttribute('role', 'dialog') el.classList.add(POPOVER_CLASS_NAME) el.style.cssText = 'position: absolute; left: 0; top: 0;' el.innerHTML = getPopoverContentHTML(controller) el.popoverController = controller el.coupdoeilElement = controller.coupdoeilElement el.close = function() { this.coupdoeilElement.closePopover() } el.dataset.placement = options.placement // Initial style is not .hidden (no display: none;) and visibility: 'hidden'; so the card is inserted // in DOM the without being visible. // This allows the browser to compute its actual size so the positioning is computed correctly. el.style.visibility = 'hidden' return el }