[sdc] rebase update
[sdc.git] / catalog-ui / src / app / ng2 / components / popover / popover-content.component.ts
1 /*-
2  * ============LICENSE_START=======================================================
3  * SDC
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
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
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=========================================================
19  */
20
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";
24
25 @Component({
26     selector: "popover-content",
27     templateUrl:'./popover-content.component.html',
28     styleUrls:['popover-content.component.less']
29 })
30 export class PopoverContentComponent implements AfterViewInit, OnDestroy {
31
32     @Input() public title: string;
33     @Input() public buttons:ButtonsModelMap;
34
35     @Input()
36     content: string;
37
38     @Input()
39     placement: "top"|"bottom"|"left"|"right"|"auto"|"auto top"|"auto bottom"|"auto left"|"auto right" = "bottom";
40
41     @Input()
42     animation: boolean = true;
43
44     @Input()
45     closeOnClickOutside: boolean = false;
46
47     @Input()
48     closeOnMouseOutside: boolean = false;
49
50     @Input()
51     hideArrow: boolean = false;
52
53     @ViewChild("popoverDiv")
54     popoverDiv: ElementRef;
55
56     buttonsNames:Array<string>;
57
58     popover: PopoverComponent;
59     onCloseFromOutside = new EventEmitter();
60     top: number = -10000;
61     left: number = -10000;
62     isIn: boolean = false;
63     displayType: string = "none";
64     effectivePlacement: string;
65
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;
70         this.hide();
71         this.onCloseFromOutside.emit(undefined);
72     };
73
74
75     constructor(protected element: ElementRef,
76                 protected cdr: ChangeDetectorRef,
77                 protected renderer: Renderer) {
78     }
79
80     listenClickFunc: any;
81     listenMouseFunc: any;
82
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));
89
90         this.show();
91         this.cdr.detectChanges();
92     }
93
94     ngOnDestroy() {
95         if (this.closeOnClickOutside)
96             this.listenClickFunc();
97         if (this.closeOnMouseOutside)
98             this.listenMouseFunc();
99     }
100
101     // -------------------------------------------------------------------------
102     // Public Methods
103     // -------------------------------------------------------------------------
104
105     show(): void {
106         if (!this.popover || !this.popover.getElement())
107             return;
108
109         const p = this.positionElements(this.popover.getElement(), this.popoverDiv.nativeElement, this.placement);
110         this.displayType = "block";
111         this.top = p.top;
112         this.left = p.left;
113         this.isIn = true;
114     }
115
116     hide(): void {
117         this.top = -10000;
118         this.left = -10000;
119         this.isIn = true;
120         this.popover.hide();
121     }
122
123     hideFromPopover() {
124         this.top = -10000;
125         this.left = -10000;
126         this.isIn = true;
127     }
128
129     // -------------------------------------------------------------------------
130     // Protected Methods
131     // -------------------------------------------------------------------------
132
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;
140
141         this.effectivePlacement = pos0 = this.getEffectivePlacement(pos0, hostEl, targetEl);
142
143         let shiftWidth: any = {
144             center: function (): number {
145                 return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2;
146             },
147             left: function (): number {
148                 return hostElPos.left;
149             },
150             right: function (): number {
151                 return hostElPos.left + hostElPos.width - targetElWidth;
152             }
153         };
154
155         let shiftHeight: any = {
156             center: function (): number {
157                 return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2;
158             },
159             top: function (): number {
160                 return hostElPos.top;
161             },
162             bottom: function (): number {
163                 return hostElPos.top + hostElPos.height - targetElHeight;
164             }
165         };
166
167         let targetElPos: { top: number, left: number };
168         switch (pos0) {
169             case "right":
170                 targetElPos = {
171                     top: shiftHeight[pos1](),
172                     left: hostElPos.left + hostElPos.width
173                 };
174                 break;
175
176             case "left":
177                 targetElPos = {
178                     top: shiftHeight[pos1](),
179                     left: hostElPos.left - targetElWidth
180                 };
181                 break;
182
183             case "bottom":
184                 targetElPos = {
185                     top: hostElPos.top + hostElPos.height,
186                     left: shiftWidth[pos1]()
187                 };
188                 break;
189
190             default:
191                 targetElPos = {
192                     top: hostElPos.top - targetElHeight,
193                     left: shiftWidth[pos1]()
194                 };
195                 break;
196         }
197
198         return targetElPos;
199     }
200
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;
209         }
210
211         const boundingClientRect = nativeEl.getBoundingClientRect();
212         return {
213             width: boundingClientRect.width || nativeEl.offsetWidth,
214             height: boundingClientRect.height || nativeEl.offsetHeight,
215             top: elBCR.top - offsetParentBCR.top,
216             left: elBCR.left - offsetParentBCR.left
217         };
218     }
219
220     protected offset(nativeEl: any): { width: number, height: number, top: number, left: number } {
221         const boundingClientRect = nativeEl.getBoundingClientRect();
222         return {
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)
227         };
228     }
229
230     protected getStyle(nativeEl: HTMLElement, cssProp: string): string {
231         if ((nativeEl as any).currentStyle) // IE
232             return (nativeEl as any).currentStyle[cssProp];
233
234         if (window.getComputedStyle)
235             return (window.getComputedStyle as any)(nativeEl)[cssProp];
236
237         // finally try and get inline style
238         return (nativeEl.style as any)[cssProp];
239     }
240
241     protected isStaticPositioned(nativeEl: HTMLElement): boolean {
242         return (this.getStyle(nativeEl, "position") || "static" ) === "static";
243     }
244
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;
249         }
250         return offsetParent || window.document;
251     }
252
253     protected getEffectivePlacement(placement: string, hostElement: HTMLElement, targetElement: HTMLElement): string {
254         const placementParts = placement.split(" ");
255         if (placementParts[0] !== "auto") {
256             return placement;
257         }
258
259         const hostElBoundingRect = hostElement.getBoundingClientRect();
260
261         const desiredPlacement = placementParts[1] || "bottom";
262
263         if (desiredPlacement === "top" && hostElBoundingRect.top - targetElement.offsetHeight < 0) {
264             return "bottom";
265         }
266         if (desiredPlacement === "bottom" && hostElBoundingRect.bottom + targetElement.offsetHeight > window.innerHeight) {
267             return "top";
268         }
269         if (desiredPlacement === "left" && hostElBoundingRect.left - targetElement.offsetWidth < 0) {
270             return "right";
271         }
272         if (desiredPlacement === "right" && hostElBoundingRect.right + targetElement.offsetWidth > window.innerWidth) {
273             return "left";
274         }
275
276         return desiredPlacement;
277     }
278 }