import {
Component,
Directive,
Input,
Output,
EventEmitter,
ChangeDetectionStrategy,
OnInit,
OnDestroy,
Injector,
Renderer,
ComponentRef,
ElementRef,
TemplateRef,
ViewContainerRef,
ComponentFactoryResolver,
NgZone, ViewEncapsulation
} from '@angular/core';
import {listenToTriggers} from '../util/triggers';
import {positionElements, getPlacement} from '../util/positioning';
import {PopupService} from '../util/popup';
import {PlxTooltipConfig} from './plx-tooltip-config';
let nextId = 0;
@Component({
selector: 'plx-tooltip-window',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
host: {'[class]': '"plx-tooltip show plx-tooltip-" + placement', 'role': 'tooltip', '[id]': 'id'},
template: `
`,
styleUrls: ['./plx-tooltip.less']
})
export class PlxTooltipWindow {
@Input() public placement: 'top' | 'bottom' | 'left' | 'right' = 'top';
@Input() public id: string;
}
/**
* A lightweight, extensible directive for fancy tooltip creation.
*/
@Directive({selector: '[plxTooltip]', exportAs: 'plxTooltip'})
export class PlxTooltip implements OnInit, OnDestroy {
/**
* Placement of a tooltip. Accepts: "top", "bottom", "left", "right"
*/
@Input() public placement: 'top' | 'bottom' | 'left' | 'right';
/**
* Specifies events that should trigger. Supports a space separated list of event names.
*/
@Input() public triggers: string;
/**
* A selector specifying the element the tooltip should be appended to.
* Currently only supports "body".
*/
@Input() public container: string;
/**
* Emits an event when the tooltip is shown
*/
@Output() public shown = new EventEmitter();
/**
* Emits an event when the tooltip is hidden
*/
@Output() public hidden = new EventEmitter();
private _plxTooltip: string | TemplateRef;
private _plxTooltipWindowId = `plx-tooltip-${nextId++}`;
private _popupService: PopupService;
private _windowRef: ComponentRef;
private _unregisterListenersFn;
private _zoneSubscription: any;
constructor(private _elementRef: ElementRef, private _renderer: Renderer, injector: Injector,
componentFactoryResolver: ComponentFactoryResolver, viewContainerRef: ViewContainerRef, config: PlxTooltipConfig,
ngZone: NgZone) {
this.placement = config.placement;
this.triggers = config.triggers;
this.container = config.container;
this._popupService = new PopupService(
PlxTooltipWindow, injector, viewContainerRef, _renderer, componentFactoryResolver);
this._zoneSubscription = ngZone.onStable.subscribe(() => {
if (this._windowRef) {
positionElements(
this._elementRef.nativeElement, this._windowRef.location.nativeElement, this.placement,
this.container === 'body');
let tmpPlace = getPlacement(this._elementRef.nativeElement, this._windowRef.location.nativeElement, this.placement);
this._windowRef.instance.placement = tmpPlace;
this._windowRef.changeDetectorRef.detectChanges();
}
});
}
/**
* Content to be displayed as tooltip. If falsy, the tooltip won't open.
*/
@Input()
set plxTooltip(value: string | TemplateRef) {
this._plxTooltip = value;
if (!value && this._windowRef) {
this.close();
}
}
get plxTooltip() {
return this._plxTooltip;
}
/**
* Opens an element’s tooltip. This is considered a “manual” triggering of the tooltip.
* The context is an optional value to be injected into the tooltip template when it is created.
*/
public open(context?: any) {
if (!this._windowRef && this._plxTooltip) {
this._windowRef = this._popupService.open(this._plxTooltip, context);
// let tmpPlace = getPlacement(this._elementRef.nativeElement, this._windowRef.location.nativeElement, this.placement);
this._windowRef.instance.placement = this.placement;
this._windowRef.instance.id = this._plxTooltipWindowId;
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'aria-describedby', this._plxTooltipWindowId);
if (this.container === 'body') {
window.document.querySelector(this.container).appendChild(this._windowRef.location.nativeElement);
}
// we need to manually invoke change detection since events registered via
// Renderer::listen() - to be determined if this is a bug in the Angular itself
this._windowRef.changeDetectorRef.markForCheck();
this.shown.emit();
}
}
/**
* Closes an element’s tooltip. This is considered a “manual” triggering of the tooltip.
*/
public close(): void {
if (this._windowRef !== null) {
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'aria-describedby', null);
this._popupService.close();
this._windowRef = null;
this.hidden.emit();
}
}
/**
* Toggles an element’s tooltip. This is considered a “manual” triggering of the tooltip.
*/
public toggle(): void {
if (this._windowRef) {
this.close();
} else {
this.open();
}
}
/**
* Returns whether or not the tooltip is currently being shown
*/
public isOpen(): boolean {
return !!this._windowRef;
}
public ngOnInit() {
this._unregisterListenersFn = listenToTriggers(
this._renderer, this._elementRef.nativeElement, this.triggers, this.open.bind(this), this.close.bind(this),
this.toggle.bind(this));
}
public ngOnDestroy() {
this.close();
this._unregisterListenersFn();
this._zoneSubscription.unsubscribe();
}
}