import { Directive, Input, Output, HostBinding, EventEmitter, ElementRef, Renderer2, OnDestroy, OnInit } from '@angular/core'; import { Observable, Subject } from 'rxjs'; @Directive({ selector: '[fDropdown]', exportAs: 'fDropdown' }) export class FDropdownDirective implements OnDestroy, OnInit { toggleElement: any; documentClickEventListener: any; selfDefineEventListener: any; private _seflEl: HTMLElement; // tslint:disable-next-line:no-input-rename private _internalOpen = false; // 计算宽度 private _calculateMenu = false; // 纠正位置的参照 有window和parent两个值 @Input() rectifyReference; // 纠正纵向位置的参照 @Input() rectifyReferenceV; // 是否自动纠正位置 @Input() autoRectify = false; // 在外面强制控制关闭状态 @Input('forceState') set forceState(value: Array) { if (value && value.length) { this.close(); } } @Input('open') set openState(value: boolean) { this._internalOpen = value[0]; this.isOpenState.next(value[0]); } @Output() dpChangeEvent = new EventEmitter(); private _placement = 'bottom'; // 记录 position @Input() isSubDP = false; // 是否是子下拉 @Input('placement') set placement(value) { // 如果相等 if (!value) { return; } // if (value !== this._placement) { const newClsName = this._getClsName(value); const oldClsName = this._getClsName(this._placement); this.render.removeClass(this._seflEl, oldClsName); this.render.addClass(this._seflEl, newClsName); this._placement = value; } } get placement(): string { return this._placement; } // @HostBinding('class.show') get isOpen(): boolean { return this._internalOpen; } private isOpenState = new Subject(); @HostBinding('class.dropdown-submenu') get submenuCls(): boolean { return this.isSubDP; } @HostBinding('class.dropdown') get dropdownCls(): boolean { return !this.isSubDP; } private ngZone; constructor( elementRef: ElementRef, private render: Renderer2 ) { this._seflEl = elementRef.nativeElement; this.isOpenState.subscribe((state: boolean) => { if (this._internalOpen !== state) { this._internalOpen = state; if (this._internalOpen) { this.render.addClass(this._seflEl, 'show'); } else { this.render.removeClass(this._seflEl, 'show'); } } }); } ngOnInit() { } getNativeElement() { return this._seflEl; } private bindDocumentEvents() { // this.ngzone.runOutsideAngular(() => { this.documentClickEventListener = this.onDocumentClick.bind(this); document.addEventListener('click', this.documentClickEventListener); // 绑定被操作native时触发 this.selfDefineEventListener = this.onSelfDefineHandler.bind(this); this._seflEl.addEventListener('selfClose', this.selfDefineEventListener); // }); } private onSelfDefineHandler() { this.close(); } private unbindDocumentEvents() { if (this.documentClickEventListener) { document.removeEventListener( 'click', this.documentClickEventListener ); this.documentClickEventListener = null; } if (this.selfDefineEventListener) { this._seflEl.removeEventListener( 'selfClose', this.selfDefineEventListener ); this.selfDefineEventListener = null; } } private onDocumentClick(event: MouseEvent) { // 如果已经关闭,不需要再响应 if (!this._internalOpen) { return; } if (event.button !== 2 && !this.isEventFromToggle(event)) { this.close(); } } // 判断menu展开时是否要计算 needToCalculate() { return this.autoRectify && this._calculateMenu; } resetCalculate(value) { this._calculateMenu = value; } open() { if (!this._internalOpen) { this.closeSiblingDropdowns(); this._calculateMenu = true; this.render.addClass(this._seflEl, 'show'); this.isOpenState.next(true); this.dpChangeEvent.emit(true); // 执行绑定事件 this.bindDocumentEvents(); } } close() { if (this._internalOpen) { this._calculateMenu = false; this.render.removeClass(this._seflEl, 'show'); this.isOpenState.next(false); this.dpChangeEvent.emit(false); this.unbindDocumentEvents(); } } toggle() { if (this._internalOpen) { this.close(); } else { this.open(); } } getOpenState() { return this.isOpenState; } ngOnDestroy() { this.isOpenState.unsubscribe(); this.unbindDocumentEvents(); } private closeSiblingDropdowns() { let dpParentEl = this._seflEl.parentNode; if (dpParentEl && dpParentEl['className'].indexOf('dropdown-menu') > -1) { let dropdowns = dpParentEl.querySelectorAll('[fDropdown]'); if (dropdowns && dropdowns.length > 1) { for (var k = 0; k < dropdowns.length; k++) { if (dropdowns[k].className.indexOf('show')) { // 触发事件试试 this.compatibleDispatchEvent(dropdowns[k], 'selfClose'); // dropdowns[k].dispatchEvent(new Event('selfClose')); } } } } } private compatibleDispatchEvent(eventEl, eventName) { var event; if (typeof Event === "function") { event = new Event(eventName); } else { event = document.createEvent("Event"); event.initEvent(eventName, false, false); } eventEl.dispatchEvent(event); } private _getClsName(position: string) { let className = ''; switch (position) { case 'top-right': case 'top': // 朝上,朝上-朝右 className = 'dropup'; break; case 'top-left': // 朝上-朝左 className = 'dropup-left'; break; case 'left-bottom': case 'left': // 横向——朝左——朝下 className = 'dropleft'; break; case 'left-top': // 横向——朝左——朝上 className = 'dropleft-up'; break; case 'right-bottom': case 'right': // 横向——朝右——朝下 className = 'dropright'; break; case 'right-top': // 横向——朝右——朝上 className = 'dropright-up'; break; case 'bottom-left': // 朝下——朝左 className = 'dropdown-left'; break; case 'bottom-right': className = 'dropdown'; break; default: // 朝下,朝下——朝右 className = 'dropdown'; } return className; } private isEventFromToggle(event: MouseEvent) { const result = this.toggleElement && this.toggleElement.contains(event.target); return result; } getRectifyReferenceEl() { var resultWidth = window.innerWidth, resultHeight = window.innerHeight; // 横向计算 if (this.rectifyReference) { resultWidth = this.rectifyReference.getBoundingClientRect().right; } // 纵向计算 if (this.rectifyReferenceV) { resultWidth = this.rectifyReference.getBoundingClientRect().bottom; } return { width: resultWidth, height: resultHeight }; } } // export enum FDropdownPlacement{ // "top", "top-left", "top-right", "bottom", "bottom-left", "bottom-right", // * "left", "left-top", "left-bottom", "right", "right-top", "right-bottom" // }