import {
Component, OnInit, OnDestroy, Input, ViewChild, ElementRef, Renderer, AfterViewInit
} from '@angular/core';
import { Observable, Subject, Subscription, BehaviorSubject } from 'rxjs/Rx';
@Component({
selector: 'pop-over',
styles: [
`.pop-over-content {
position: absolute;
transition: opacity 0.2s ease-in-out;
background: #FFFFFF;
}`
],
template: `
`
})
export class PopOverComponent implements OnInit, OnDestroy, AfterViewInit {
private showOnSubscription: Subscription;
private hideOnSubscription: Subscription;
private originalParent: Node;
private clickSubscription: Subscription;
@Input('show-on')
set showOn(value: Observable){
this.showOnSubscription && this.showOnSubscription.unsubscribe();
value && (this.showOnSubscription = value.subscribe(this.show.bind(this)));
};
@Input('hide-on')
set hideOn(value: Observable){
this.hideOnSubscription && this.hideOnSubscription.unsubscribe();
value && (this.hideOnSubscription = value.subscribe(this.hide.bind(this)));
};
@Input('keep-on-click-outside') keepOnClickOutside: boolean;
@Input('anchor-to') anchorTo: boolean|Node = false;
@Input() my: string;
@Input() at: string;
@Input('x-offset') xOffset: number = 0;
@Input('y-offset') yOffset: number = 0;
@Input('content-class') contentClass: string;
@ViewChild('popOverContent') content: any;
visible$: Subject = new BehaviorSubject(false);
constructor(private elRef: ElementRef, private renderer: Renderer) {
}
ngOnInit() {
}
ngOnDestroy() {
this.hideOnSubscription && this.hideOnSubscription.unsubscribe();
this.showOnSubscription && this.showOnSubscription.unsubscribe();
}
ngAfterViewInit(): any {
this.renderer.setElementStyle(this.content.nativeElement, 'opacity', '0');
this.renderer.setElementStyle(this.content.nativeElement, 'visibility', 'hidden');
return undefined;
}
private isPosition(parts: Array, type: string): boolean {
return parts.indexOf(type) >= 0;
}
private computeAtPosition(target: any, position: string, xOffset: number = 0, yOffset: number = 0): [number, number] {
let x: number;
let y: number;
let positionParts = (position || '').split(/\s+/);
if (positionParts.length > 2) {
console.warn('bad position: ', position);
}
let targetPosition = target.getBoundingClientRect();
if (this.isPosition(positionParts, 'left')) {
x = targetPosition.left - xOffset;
} else if (this.isPosition(positionParts, 'right')) {
x = targetPosition.right + xOffset;
} else {
x = (targetPosition.left + targetPosition.right) / 2;
}
if (this.isPosition(positionParts, 'top')) {
y = targetPosition.top - yOffset;
} else if (this.isPosition(positionParts, 'bottom')) {
y = targetPosition.bottom + yOffset;
} else {
y = (targetPosition.top + targetPosition.bottom) / 2;
}
return [x, y];
}
private computePosition(el: any, event: MouseEvent): [number, number] {
let baseX: number;
let baseY: number;
let boundryMargin: number = 5;
let bodyPosition = el.ownerDocument.body.getClientRects()[0];
if (this.anchorTo) {
let target: any = (this.anchorTo instanceof Node ? this.anchorTo: event.target);
[baseX, baseY] = this.computeAtPosition(target, this.at);
} else {
[baseX, baseY] = [event.clientX, event.clientY];
}
let [offsetX, offsetY] = this.computeAtPosition(el,
(this.my) || '', this.xOffset, this.yOffset);
let elPosition = el.getBoundingClientRect();
let pagePosition = el.ownerDocument.body.getBoundingClientRect();
let documentElement = el.ownerDocument.documentElement;
offsetX = offsetX - elPosition.left + pagePosition.left;
offsetY = offsetY - elPosition.top + pagePosition.top;
return [
Math.max(boundryMargin - bodyPosition.left, Math.min(
baseX - offsetX,
documentElement.clientWidth - bodyPosition.left - elPosition.width - boundryMargin)),
Math.max(boundryMargin - bodyPosition.top, Math.min(
baseY - offsetY,
documentElement.clientHeight - bodyPosition.top - elPosition.height - boundryMargin))
];
}
hide() {
this.renderer.setElementStyle(this.content.nativeElement, 'opacity', '0');
this.renderer.setElementStyle(this.content.nativeElement, 'visibility', 'hidden');
this.visible$.next(false);
this.originalParent && this.originalParent.appendChild(this.content.nativeElement);
}
show(event: MouseEvent) {
this.clickSubscription && this.clickSubscription.unsubscribe();
this.visible$.next(true);
let el = this.content.nativeElement;
this.originalParent = el.parentNode;
el.ownerDocument.body.appendChild(el);
this.renderer.setElementStyle(this.content.nativeElement, 'visibility', 'inherit');
Observable.timer(0).take(1).subscribe(() => {
var [x, y] = this.computePosition(el, event);
this.renderer.setElementStyle(el, 'top', y + 'px');
this.renderer.setElementStyle(el, 'left', x + 'px');
this.renderer.setElementStyle(el, 'opacity', '1');
if (!this.keepOnClickOutside) {
this.clickSubscription = Observable.fromEvent(el.ownerDocument, 'click')
.skipUntil(Observable.timer(0))
.filter((md: MouseEvent) => !el.contains(md.target))
.take(1)
.subscribe((v) => (this.hide()))
}
})
}
toggle(event: MouseEvent) {
this.visible$
.take(1)
.subscribe((visible: boolean) => (visible ? this.hide() : this.show(event)));
}
}