class Popover extends View { static get POPOVER_SCREEN_PADDING() { return 12; } static get POPOVER_ARROW_SIZE() { return 15; } /** * The constructor accepts a DOM element as a parameter. This is the content view of the popover. */ constructor(element = null) { super(element); this.id = this.element.getAttribute("id"); this.disableBackground = this.element.hasAttribute("disable-background"); this.contentElement = document.createElement("DIV"); this.contentElement.className = "popover-content"; var navigationBar = null; while (this.element.children.length > 0) { //Pick out the navigation bar. if (this.element.firstChild.nodeName.toLowerCase() == "navigation-bar") { navigationBar = this.element.firstChild; } this.contentElement.appendChild(this.element.firstChild); } //Move the navigation bar to the popover element if there is one. if (navigationBar) { this.element.appendChild(navigationBar); } this.element.appendChild(this.contentElement); this.arrowElement = document.createElement("DIV"); this.arrowElement.className = "popover-arrow-container"; this.arrowBoxElement = document.createElement("DIV"); this.arrowBoxElement.className = "popover-arrow-box"; this.arrowTipElement = document.createElement("DIV"); this.arrowTipElement.className = "popover-arrow-tip"; this.arrowBoxElement.appendChild(this.arrowTipElement); this.arrowElement.appendChild(this.arrowBoxElement); this.element.appendChild(this.arrowElement); this.element.addEventListener("mousedown", this.onPopoverClicked.bind(this)); document.body.addEventListener("mousedown", this.onBodyClicked.bind(this)); } /** * The build function is called in the constructor when no element was passed as the base. * Override it to create custom subviews or manipulate the root element. * Don't forget to return the element in the end! */ build(element) { return element; } /** * Called once the view has been processed by UIKit during the view controller initialization. */ viewDidLoad() { } /** * Called everytime before the view is shown. * The layout is already done at this step, so it's safe to use scrollWidth and scrollHeight and what not. */ viewWillAppear() { } /** * Called everytime after the view is shown. */ viewDidAppear() { } /** * Called everytime before the view is hidden. */ viewWillDisappear() { } /** * Called everytime after the view is hidden. */ viewDidDisappear() { } /** * Shows the popover for the specified element. * Will automatically position the popover to be located next to the element on screen. * You can optionally specify the side of the element on which the popover should appear. */ showForElement(element, side = "right") { this.prepareForShowing(); this.pointAtElement(element, side); this.completeShowing(); } /** * Shows the popover pointing to the given position on the specified side or "right" if no side was specified. */ showForPosition(x, y, side = "right") { this.prepareForShowing(); this.pointToPosition(x, y, side); this.completeShowing(); } /** * Shows the popover at the cursor position. */ showForCursor(side = "right") { this.prepareForShowing(); this.pointToCursor(side); this.completeShowing(); } prepareForShowing() { //Store the showing time. this.showedAt = new Date().getTime(); this.element.classList.add("preparing"); if (this.hidingTimeout) { clearTimeout(this.hidingTimeout); this.hidingTimeout = null; } if (this.endDismissingTimeout) { clearTimeout(this.endDismissingTimeout); this.endDismissingTimeout = null; this.endDismissing(); } UIKit.activeScene.showPopover(this); } completeShowing() { this.element.classList.remove("preparing"); this.visible = true; if (this.autohide) { this.hidingTimeout = setTimeout(this.dismiss.bind(this), this.hideDelay * 1000); } } /** * Makes the popover point at the given element. */ pointAtElement(element, side = "right") { this.pointingElement = element; var elementBounds = element.getBoundingClientRect(); var popoverBounds = this.element.getBoundingClientRect(); //Calculate the absolute center position of the element on the screen. var elementCenterX = elementBounds.left + (elementBounds.width / 2); var elementCenterY = elementBounds.top + (elementBounds.height / 2); //Evaluate the target position based on the side. var targetPosition = { x: 0, y: 0 }; if (side == "right") { targetPosition = { x: elementBounds.left + elementBounds.width, y: elementCenterY }; } else if (side == "left") { targetPosition = { x: elementBounds.left, y: elementCenterY }; } else if (side == "top") { targetPosition = { x: elementCenterX, y: elementBounds.top }; } else if (side == "bottom") { targetPosition = { x: elementCenterX, y: elementBounds.top + elementBounds.height }; } this.pointToPosition(targetPosition.x, targetPosition.y, side); } /** * Makes the popover point at a position on screen. */ pointToPosition(x, y, side = "right") { this.pointingPosition = { x, y }; this.side = side; var popoverBounds = this.element.getBoundingClientRect(); var targetPosition = { x: 0, y: 0 }; if (side == "right") { targetPosition = { x: x + Popover.POPOVER_ARROW_SIZE, y: y - (popoverBounds.height / 2) }; } else if (side == "left") { targetPosition = { x: x - popoverBounds.width - Popover.POPOVER_ARROW_SIZE, y: y - (popoverBounds.height / 2) }; } else if (side == "top") { targetPosition = { x: x - (popoverBounds.width / 2), y: y - popoverBounds.height - Popover.POPOVER_ARROW_SIZE }; } else if (side == "bottom") { targetPosition = { x: x - (popoverBounds.width / 2), y: y + Popover.POPOVER_ARROW_SIZE }; } //Make sure the target position stays within the window. var windowEdgeOffsetX = 0; var windowEdgeOffsetY = 0; if (targetPosition.x < Popover.POPOVER_SCREEN_PADDING) { windowEdgeOffsetX = Popover.POPOVER_SCREEN_PADDING - targetPosition.x; targetPosition.x = Popover.POPOVER_SCREEN_PADDING; } else if (targetPosition.x + popoverBounds.width > window.innerWidth - Popover.POPOVER_SCREEN_PADDING) { windowEdgeOffsetX = targetPosition.x - (window.innerWidth - Popover.POPOVER_SCREEN_PADDING - popoverBounds.width); targetPosition.x = window.innerWidth - Popover.POPOVER_SCREEN_PADDING - popoverBounds.width; } if (targetPosition.y < Popover.POPOVER_SCREEN_PADDING) { windowEdgeOffsetY = Popover.POPOVER_SCREEN_PADDING - targetPosition.y; targetPosition.y = Popover.POPOVER_SCREEN_PADDING; } else if (targetPosition.y + popoverBounds.height > window.innerHeight - Popover.POPOVER_SCREEN_PADDING) { windowEdgeOffsetY = targetPosition.y - (window.innerHeight - Popover.POPOVER_SCREEN_PADDING - popoverBounds.height); targetPosition.y = window.innerHeight - Popover.POPOVER_SCREEN_PADDING - popoverBounds.height; } //Move the arrow out of the screen if it leaves the popover bounds anyways. if ((windowEdgeOffsetX > 0 && windowEdgeOffsetX > popoverBounds.width * 0.5 - 25) || (windowEdgeOffsetX < 0 && windowEdgeOffsetX < popoverBounds.width * -0.5 + 25)) { windowEdgeOffsetX = 20000000; } if ((windowEdgeOffsetY > 0 && windowEdgeOffsetY > popoverBounds.height * 0.5 - 25) || (windowEdgeOffsetY < 0 && windowEdgeOffsetY < popoverBounds.height * -0.5 + 25)) { windowEdgeOffsetY = 20000000; } //Apply the window edge offset reversedly to the arrow. this.arrowElement.style.marginLeft = windowEdgeOffsetX + "px"; this.arrowElement.style.marginTop = windowEdgeOffsetY + "px"; //Position the popover absolutely. this.element.style.left = targetPosition.x + "px"; this.element.style.top = targetPosition.y + "px"; this.element.className = side; } pointToCursor(side = "right") { this.pointToPosition(UIKit.mousePosition.x, UIKit.mousePosition.y, side); } /** * Dismisses the popover. */ dismiss(animated = true) { UIKit.activeScene.dismissPopover(this, animated); } /** * This is called when a click is performed inside the popover. * Used to stop the event from propagating to the body to prevent the popover from being hidden. */ onPopoverClicked(event) { event.stopPropagation(); } onBodyClicked(event) { //Do nothing if the popover has only been visible for less than one hundred milliseconds. if (this.showedAt >= new Date().getTime() - 100) { return; } if (this.visible) { this.dismiss(); } } } UIKit.registerPopoverType(Popover, "popover");