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 { AfterViewInit, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnDestroy, Output, Renderer, ViewChild } 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: string[];
58 popover: PopoverComponent;
59 onCloseFromOutside = new EventEmitter();
62 isIn: boolean = false;
63 displayType: string = 'none';
64 effectivePlacement: string;
69 constructor(protected element: ElementRef,
70 protected cdr: ChangeDetectorRef,
71 protected renderer: Renderer) {
74 onDocumentMouseDown = (event: any) => {
75 const element = this.element.nativeElement;
76 if (!element || !this.popover) { return; }
77 if (element.contains(event.target) || this.popover.getElement().contains(event.target)) { return; }
79 this.onCloseFromOutside.emit(undefined);
82 ngAfterViewInit(): void {
84 this.buttonsNames = Object.keys(this.buttons);
86 if (this.closeOnClickOutside) {
87 this.listenClickFunc = this.renderer.listenGlobal('document', 'mousedown', (event: any) => this.onDocumentMouseDown(event));
89 if (this.closeOnMouseOutside) {
90 this.listenMouseFunc = this.renderer.listenGlobal('document', 'mouseover', (event: any) => this.onDocumentMouseDown(event));
93 this.cdr.detectChanges();
97 if (this.closeOnClickOutside) {
98 this.listenClickFunc();
100 if (this.closeOnMouseOutside) {
101 this.listenMouseFunc();
105 // -------------------------------------------------------------------------
107 // -------------------------------------------------------------------------
110 if (!this.popover || !this.popover.getElement()) {
114 const p = this.positionElements(this.popover.getElement(), this.popoverDiv.nativeElement, this.placement);
115 this.displayType = 'block';
129 this.displayType = 'none';
133 // -------------------------------------------------------------------------
135 // -------------------------------------------------------------------------
137 protected positionElements(hostEl: HTMLElement, targetEl: HTMLElement, positionStr: string, appendToBody: boolean = false): { top: number, left: number } {
138 const positionStrParts = positionStr.split('-');
139 let pos0 = positionStrParts[0];
140 const pos1 = positionStrParts[1] || 'center';
141 const hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl);
142 const targetElWidth = targetEl.offsetWidth;
143 const targetElHeight = targetEl.offsetHeight;
145 this.effectivePlacement = pos0 = this.getEffectivePlacement(pos0, hostEl, targetEl);
147 const shiftWidth: any = {
149 return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2;
152 return hostElPos.left;
155 return hostElPos.left + hostElPos.width - targetElWidth;
159 const shiftHeight: any = {
161 return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2;
164 return hostElPos.top;
167 return hostElPos.top + hostElPos.height - targetElHeight;
171 let targetElPos: { top: number, left: number };
175 top: shiftHeight[pos1](),
176 left: hostElPos.left + hostElPos.width
182 top: shiftHeight[pos1](),
183 left: hostElPos.left - targetElWidth
189 top: hostElPos.top + hostElPos.height,
190 left: shiftWidth[pos1]()
196 top: hostElPos.top - targetElHeight,
197 left: shiftWidth[pos1]()
205 protected position(nativeEl: HTMLElement): { width: number, height: number, top: number, left: number } {
206 let offsetParentBCR = { top: 0, left: 0 };
207 const elBCR = this.offset(nativeEl);
208 const offsetParentEl = this.parentOffsetEl(nativeEl);
209 if (offsetParentEl !== window.document) {
210 offsetParentBCR = this.offset(offsetParentEl);
211 offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
212 offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
215 const boundingClientRect = nativeEl.getBoundingClientRect();
217 width: boundingClientRect.width || nativeEl.offsetWidth,
218 height: boundingClientRect.height || nativeEl.offsetHeight,
219 top: elBCR.top - offsetParentBCR.top,
220 left: elBCR.left - offsetParentBCR.left
224 protected offset(nativeEl: any): { width: number, height: number, top: number, left: number } {
225 const boundingClientRect = nativeEl.getBoundingClientRect();
227 width: boundingClientRect.width || nativeEl.offsetWidth,
228 height: boundingClientRect.height || nativeEl.offsetHeight,
229 top: boundingClientRect.top + (window.pageYOffset || window.document.documentElement.scrollTop),
230 left: boundingClientRect.left + (window.pageXOffset || window.document.documentElement.scrollLeft)
234 protected getStyle(nativeEl: HTMLElement, cssProp: string): string {
235 if ((nativeEl as any).currentStyle) { // IE
236 return (nativeEl as any).currentStyle[cssProp];
239 if (window.getComputedStyle) {
240 return (window.getComputedStyle as any)(nativeEl)[cssProp];
243 // finally try and get inline style
244 return (nativeEl.style as any)[cssProp];
247 protected isStaticPositioned(nativeEl: HTMLElement): boolean {
248 return (this.getStyle(nativeEl, 'position') || 'static' ) === 'static';
251 protected parentOffsetEl(nativeEl: HTMLElement): any {
252 let offsetParent: any = nativeEl.offsetParent || window.document;
253 while (offsetParent && offsetParent !== window.document && this.isStaticPositioned(offsetParent)) {
254 offsetParent = offsetParent.offsetParent;
256 return offsetParent || window.document;
259 protected getEffectivePlacement(placement: string, hostElement: HTMLElement, targetElement: HTMLElement): string {
260 const placementParts = placement.split(' ');
261 if (placementParts[0] !== 'auto') {
265 const hostElBoundingRect = hostElement.getBoundingClientRect();
267 const desiredPlacement = placementParts[1] || 'bottom';
269 if (desiredPlacement === 'top' && hostElBoundingRect.top - targetElement.offsetHeight < 0) {
272 if (desiredPlacement === 'bottom' && hostElBoundingRect.bottom + targetElement.offsetHeight > window.innerHeight) {
275 if (desiredPlacement === 'left' && hostElBoundingRect.left - targetElement.offsetWidth < 0) {
278 if (desiredPlacement === 'right' && hostElBoundingRect.right + targetElement.offsetWidth > window.innerWidth) {
282 return desiredPlacement;