2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
21 import {Component, Input, Output, AfterViewInit, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, EventEmitter, Renderer } from "@angular/core";
22 import {ButtonsModelMap} from "app/models";
23 import {PopoverComponent} from "./popover.component";
26 selector: "popover-content",
27 templateUrl:'./popover-content.component.html',
28 styleUrls:['popover-content.component.less']
30 export class PopoverContentComponent implements AfterViewInit, OnDestroy {
32 @Input() public title: string;
33 @Input() public buttons:ButtonsModelMap;
39 placement: "top"|"bottom"|"left"|"right"|"auto"|"auto top"|"auto bottom"|"auto left"|"auto right" = "bottom";
42 animation: boolean = true;
45 closeOnClickOutside: boolean = false;
48 closeOnMouseOutside: boolean = false;
51 hideArrow: boolean = false;
53 @ViewChild("popoverDiv")
54 popoverDiv: ElementRef;
56 buttonsNames:Array<string>;
58 popover: PopoverComponent;
59 onCloseFromOutside = new EventEmitter();
61 left: number = -10000;
62 isIn: boolean = false;
63 displayType: string = "none";
64 effectivePlacement: string;
66 onDocumentMouseDown = (event: any) => {
67 const element = this.element.nativeElement;
68 if (!element || !this.popover) return;
69 if (element.contains(event.target) || this.popover.getElement().contains(event.target)) return;
71 this.onCloseFromOutside.emit(undefined);
75 constructor(protected element: ElementRef,
76 protected cdr: ChangeDetectorRef,
77 protected renderer: Renderer) {
83 ngAfterViewInit(): void {
84 this.buttonsNames = Object.keys(this.buttons);
85 if (this.closeOnClickOutside)
86 this.listenClickFunc = this.renderer.listenGlobal("document", "mousedown", (event: any) => this.onDocumentMouseDown(event));
87 if (this.closeOnMouseOutside)
88 this.listenMouseFunc = this.renderer.listenGlobal("document", "mouseover", (event: any) => this.onDocumentMouseDown(event));
91 this.cdr.detectChanges();
95 if (this.closeOnClickOutside)
96 this.listenClickFunc();
97 if (this.closeOnMouseOutside)
98 this.listenMouseFunc();
101 // -------------------------------------------------------------------------
103 // -------------------------------------------------------------------------
106 if (!this.popover || !this.popover.getElement())
109 const p = this.positionElements(this.popover.getElement(), this.popoverDiv.nativeElement, this.placement);
110 this.displayType = "block";
129 // -------------------------------------------------------------------------
131 // -------------------------------------------------------------------------
133 protected positionElements(hostEl: HTMLElement, targetEl: HTMLElement, positionStr: string, appendToBody: boolean = false): { top: number, left: number } {
134 let positionStrParts = positionStr.split("-");
135 let pos0 = positionStrParts[0];
136 let pos1 = positionStrParts[1] || "center";
137 let hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl);
138 let targetElWidth = targetEl.offsetWidth;
139 let targetElHeight = targetEl.offsetHeight;
141 this.effectivePlacement = pos0 = this.getEffectivePlacement(pos0, hostEl, targetEl);
143 let shiftWidth: any = {
144 center: function (): number {
145 return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2;
147 left: function (): number {
148 return hostElPos.left;
150 right: function (): number {
151 return hostElPos.left + hostElPos.width - targetElWidth;
155 let shiftHeight: any = {
156 center: function (): number {
157 return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2;
159 top: function (): number {
160 return hostElPos.top;
162 bottom: function (): number {
163 return hostElPos.top + hostElPos.height - targetElHeight;
167 let targetElPos: { top: number, left: number };
171 top: shiftHeight[pos1](),
172 left: hostElPos.left + hostElPos.width
178 top: shiftHeight[pos1](),
179 left: hostElPos.left - targetElWidth
185 top: hostElPos.top + hostElPos.height,
186 left: shiftWidth[pos1]()
192 top: hostElPos.top - targetElHeight,
193 left: shiftWidth[pos1]()
201 protected position(nativeEl: HTMLElement): { width: number, height: number, top: number, left: number } {
202 let offsetParentBCR = { top: 0, left: 0 };
203 const elBCR = this.offset(nativeEl);
204 const offsetParentEl = this.parentOffsetEl(nativeEl);
205 if (offsetParentEl !== window.document) {
206 offsetParentBCR = this.offset(offsetParentEl);
207 offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
208 offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
211 const boundingClientRect = nativeEl.getBoundingClientRect();
213 width: boundingClientRect.width || nativeEl.offsetWidth,
214 height: boundingClientRect.height || nativeEl.offsetHeight,
215 top: elBCR.top - offsetParentBCR.top,
216 left: elBCR.left - offsetParentBCR.left
220 protected offset(nativeEl: any): { width: number, height: number, top: number, left: number } {
221 const boundingClientRect = nativeEl.getBoundingClientRect();
223 width: boundingClientRect.width || nativeEl.offsetWidth,
224 height: boundingClientRect.height || nativeEl.offsetHeight,
225 top: boundingClientRect.top + (window.pageYOffset || window.document.documentElement.scrollTop),
226 left: boundingClientRect.left + (window.pageXOffset || window.document.documentElement.scrollLeft)
230 protected getStyle(nativeEl: HTMLElement, cssProp: string): string {
231 if ((nativeEl as any).currentStyle) // IE
232 return (nativeEl as any).currentStyle[cssProp];
234 if (window.getComputedStyle)
235 return (window.getComputedStyle as any)(nativeEl)[cssProp];
237 // finally try and get inline style
238 return (nativeEl.style as any)[cssProp];
241 protected isStaticPositioned(nativeEl: HTMLElement): boolean {
242 return (this.getStyle(nativeEl, "position") || "static" ) === "static";
245 protected parentOffsetEl(nativeEl: HTMLElement): any {
246 let offsetParent: any = nativeEl.offsetParent || window.document;
247 while (offsetParent && offsetParent !== window.document && this.isStaticPositioned(offsetParent)) {
248 offsetParent = offsetParent.offsetParent;
250 return offsetParent || window.document;
253 protected getEffectivePlacement(placement: string, hostElement: HTMLElement, targetElement: HTMLElement): string {
254 const placementParts = placement.split(" ");
255 if (placementParts[0] !== "auto") {
259 const hostElBoundingRect = hostElement.getBoundingClientRect();
261 const desiredPlacement = placementParts[1] || "bottom";
263 if (desiredPlacement === "top" && hostElBoundingRect.top - targetElement.offsetHeight < 0) {
266 if (desiredPlacement === "bottom" && hostElBoundingRect.bottom + targetElement.offsetHeight > window.innerHeight) {
269 if (desiredPlacement === "left" && hostElBoundingRect.left - targetElement.offsetWidth < 0) {
272 if (desiredPlacement === "right" && hostElBoundingRect.right + targetElement.offsetWidth > window.innerWidth) {
276 return desiredPlacement;