import Element from './element'; import { CLASS_BTN_DISABLED, CLASS_CLOSE_BTN, CLASS_NEXT_STEP_BTN, CLASS_POPOVER_DESCRIPTION, CLASS_POPOVER_FOOTER, CLASS_POPOVER_TIP, CLASS_POPOVER_TITLE, CLASS_PREV_STEP_BTN, ID_POPOVER, POPOVER_HTML, } from '../common/constants'; /** * Popover that is displayed on top of the highlighted element */ export default class Popover extends Element { /** * @param {Object} options * @param {Window} window * @param {Document} document */ constructor(options, window, document) { super(); this.options = Object.assign({ isFirst: true, isLast: true, totalCount: 1, currentIndex: 0, doneBtnText: 'Done', closeBtnText: 'Close', nextBtnText: 'Next →', prevBtnText: '← Previous', }, options); this.window = window; this.document = document; this.makeNode(); this.hide(); } /** * Prepares the dom element for popover */ makeNode() { let popover = this.document.getElementById(ID_POPOVER); if (!popover) { popover = Popover.createFromString(POPOVER_HTML); document.body.appendChild(popover); } this.node = popover; this.tipNode = popover.querySelector(`.${CLASS_POPOVER_TIP}`); this.titleNode = popover.querySelector(`.${CLASS_POPOVER_TITLE}`); this.descriptionNode = popover.querySelector(`.${CLASS_POPOVER_DESCRIPTION}`); this.footerNode = popover.querySelector(`.${CLASS_POPOVER_FOOTER}`); this.nextBtnNode = popover.querySelector(`.${CLASS_NEXT_STEP_BTN}`); this.prevBtnNode = popover.querySelector(`.${CLASS_PREV_STEP_BTN}`); this.closeBtnNode = popover.querySelector(`.${CLASS_CLOSE_BTN}`); } /** * Turn a string into a node * @param {String} htmlString to convert * @return {Node} Converted node element */ static createFromString(htmlString) { const div = document.createElement('div'); div.innerHTML = htmlString.trim(); // Change this to div.childNodes to support multiple top-level nodes return div.firstChild; } /** * Gets the size for popover * @returns {{height: number, width: number}} */ getSize() { return { height: Math.max(this.node.scrollHeight, this.node.offsetHeight), width: Math.max(this.node.scrollWidth, this.node.offsetWidth), }; } hide() { this.node.style.display = 'none'; } reset() { this.node.style.display = 'block'; this.node.style.left = '0'; this.node.style.top = '0'; this.node.style.bottom = ''; this.node.style.right = ''; // Remove the positional classes from tip this.node .querySelector(`.${CLASS_POPOVER_TIP}`) .className = CLASS_POPOVER_TIP; } /** * Shows the popover at the given position * @param {Position} position */ show(position) { this.reset(); // Set the title and descriptions this.titleNode.innerHTML = this.options.title; this.descriptionNode.innerHTML = this.options.description; this.renderButtons(); // Position the popover around the given position switch (this.options.position) { case 'left': this.positionOnLeft(position); break; case 'right': this.positionOnRight(position); break; case 'top': this.positionOnTop(position); break; case 'bottom': this.positionOnBottom(position); break; case 'auto': default: this.autoPosition(position); break; } } /** * Enables, disables buttons, sets the text and * decides if to show them or not */ renderButtons() { this.nextBtnNode.innerHTML = this.options.nextBtnText; this.prevBtnNode.innerHTML = this.options.prevBtnText; this.closeBtnNode.innerHTML = this.options.closeBtnText; // If there was only one item, hide the buttons if (!this.options.totalCount || this.options.totalCount === 1) { this.footerNode.style.display = 'none'; return; } this.footerNode.style.display = 'block'; if (this.options.isFirst) { this.prevBtnNode.classList.add(CLASS_BTN_DISABLED); } else { this.prevBtnNode.classList.remove(CLASS_BTN_DISABLED); } if (this.options.isLast) { this.nextBtnNode.innerHTML = this.options.doneBtnText; } else { this.nextBtnNode.innerHTML = this.options.nextBtnText; } } /** * Shows the popover on the left of the given position * @param {Position} elementPosition */ positionOnLeft(elementPosition) { const popoverWidth = this.getSize().width; const popoverMargin = this.options.padding + 10; // adding 10 to give it a little distance from the element this.node.style.left = `${elementPosition.left - popoverWidth - popoverMargin}px`; this.node.style.top = `${elementPosition.top - this.options.padding}px`; this.node.style.right = ''; this.node.style.bottom = ''; this.tipNode.classList.add('right'); } /** * Shows the popover on the right of the given position * @param {Position} elementPosition */ positionOnRight(elementPosition) { const popoverMargin = this.options.padding + 10; // adding 10 to give it a little distance from the element this.node.style.left = `${elementPosition.right + popoverMargin}px`; this.node.style.top = `${elementPosition.top - this.options.padding}px`; this.node.style.right = ''; this.node.style.bottom = ''; this.tipNode.classList.add('left'); } /** * Shows the popover on the top of the given position * @param {Position} elementPosition */ positionOnTop(elementPosition) { const popoverHeight = this.getSize().height; const popoverMargin = this.options.padding + 10; // adding 10 to give it a little distance from the element this.node.style.top = `${elementPosition.top - popoverHeight - popoverMargin}px`; this.node.style.left = `${elementPosition.left - this.options.padding}px`; this.node.style.right = ''; this.node.style.bottom = ''; this.tipNode.classList.add('bottom'); } /** * Shows the popover on the bottom of the given position * @param {Position} elementPosition */ positionOnBottom(elementPosition) { const popoverMargin = this.options.padding + 10; // adding 10 to give it a little distance from the element this.node.style.top = `${elementPosition.bottom + popoverMargin}px`; this.node.style.left = `${elementPosition.left - this.options.padding}px`; this.node.style.right = ''; this.node.style.bottom = ''; this.tipNode.classList.add('top'); } /** * Automatically positions the popover around the given position * such that the element and popover remain in view * @todo add the left and right positioning decisions * @param {Position} elementPosition */ autoPosition(elementPosition) { const pageSize = this.getFullPageSize(); const popoverSize = this.getSize(); const pageHeight = pageSize.height; const popoverHeight = popoverSize.height; const popoverMargin = this.options.padding + 10; // adding 10 to give it a little distance from the element const pageHeightAfterPopOver = elementPosition.bottom + popoverHeight + popoverMargin; // If adding popover would go out of the window height, then show it to the top if (pageHeightAfterPopOver >= pageHeight) { this.positionOnTop(elementPosition); } else { this.positionOnBottom(elementPosition); } } }