import { LanguageTextboxService } from './language-textbox.service'; import { HostListener, OnChanges, SimpleChanges } from '@angular/core'; /* * @Author: 疯狂秀才(Lucas Huang) * @LastEditors: 疯狂秀才(Lucas Huang) * @Company: Inspur * @Version: v0.0.1 * @Date: 2019-03-12 15:46:47 * @LastEditTime: 2019-04-08 18:46:53 */ import { Component, OnInit, Input, ViewChild, ComponentRef, ElementRef, ComponentFactoryResolver, Injector, ApplicationRef, NgZone, Renderer2, ViewEncapsulation, forwardRef, Optional, Self } from '@angular/core'; import { NG_VALUE_ACCESSOR, ControlValueAccessor, NgControl, RequiredValidator } from '@angular/forms'; import { InputGroupComponent } from '@farris/ui-input-group'; import { LanguageTextPanelComponent } from './language-textbox-panel.component'; import { LanguageItem, LanguageData } from './types'; import { LocaleService } from '@farris/ui-locale'; import { ChangeDetectorRef, OnDestroy } from '@angular/core'; export const LANGUAGE_TEXTBOX_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => LanguageTextboxComponent), multi: true }; export interface LanguageTextMaxLength { [langCode: string]: number; } @Component({ selector: 'language-textbox', template: ` `, styles: [ ` .farris-language-textbox .input-group-text { padding-right: 3px; } .farris-language-textbox .input-group-text .language-text { padding-right: 3px; } ` ], providers: [LANGUAGE_TEXTBOX_VALUE_ACCESSOR], encapsulation: ViewEncapsulation.None }) export class LanguageTextboxComponent implements OnInit, OnDestroy, ControlValueAccessor, OnChanges { @Input() disabled = false; @Input() readonly = false; @Input() editable = false; @Input() enableClear = false; @Input() languages: LanguageItem[] = []; @Input() currentLanguage: string; @Input() panelHeight = 260; @Input() panelWidth = 365; @Input() maxWords: LanguageTextMaxLength = null; /** * 面板输入框根据LOCALE_ID 自动获得焦点 * 设为 false 时,仅第1个获得焦点 */ @Input() autoFocus = true; @ViewChild('input') input: InputGroupComponent; @Input() openOnFocus = true; groupIcon = ''; comboPanelRef: ComponentRef; currentLanguageItem: LanguageItem = undefined; private data: LanguageData = {}; private globalListener: () => void; private ngCtrl: NgControl; value = ''; onKeyDownHandler: any; localeService: LocaleService; onTextBoxclickHandler = null; lts: LanguageTextboxService = null; onModelChange = (obj?: any) => { }; onModelTouched = (obj?: any) => { }; constructor( private el: ElementRef, private cfr: ComponentFactoryResolver, private injector: Injector, private ngZone: NgZone, private applicationRef: ApplicationRef, private renderer: Renderer2, @Optional() @Self() private requiredValidator: RequiredValidator, private cd: ChangeDetectorRef) { this.localeService = this.injector.get(LocaleService); this.lts = this.injector.get(LanguageTextboxService, null); if (!this.lts) { this.lts = new LanguageTextboxService(); } this.lts.hide$.subscribe( e => { this.hideDropDownPanel(); }); } ngOnInit(): void { if (!this.editable) { this.editable = this.languages && this.languages.length && this.languages.length === 1; } this.bindLanguageInfo(this.languages); this.onTextBoxclickHandler = this.renderer.listen(this.input.textbox.nativeElement, 'click', (e) => { e.stopPropagation(); if (this.editable) { this.hideDropDownPanel(); return; } if (!this.comboPanelRef) { this.showDropDownPanel(); } }); this.ngCtrl = this.injector.get(NgControl, null); this.registerKeyDown(); } ngOnChanges(changes: SimpleChanges) { if (changes.languages && !changes.languages.isFirstChange()) { this.bindLanguageInfo(changes.languages.currentValue); this.setValue(); } } private bindLanguageInfo(languages: LanguageItem[]) { if (languages && languages.length) { if (!this.currentLanguage) { const runtimeLanguageCode = this.localeService.localeId; if (runtimeLanguageCode) { const defaultLang = languages.find(l => l.code === runtimeLanguageCode); if (defaultLang) { this.currentLanguage = defaultLang.code; this.currentLanguageItem = defaultLang; } else { if (languages.length) { this.currentLanguage = languages[0].code; this.currentLanguageItem = languages[0]; } } } else { console.warn('当前上下文环境未取到语言代码。'); } } else { this.currentLanguageItem = this.getLanguageItem(this.currentLanguage); } } else { // throw new Error(`Can not find the '[languages]' data.`); console.warn(`Please set the '[languages]' data.`); } } ngOnDestroy() { if (this.onKeyDownHandler) { this.onKeyDownHandler(); } this.hideDropDownPanel(); if (this.onTextBoxclickHandler) { this.onTextBoxclickHandler(); } } private registerKeyDown() { const textEl = this.input.textbox.nativeElement; this.onKeyDownHandler = this.renderer.listen(textEl, 'keydown', this.onKeyDown.bind(this)); // this.ngZone.runOutsideAngular(() => { // }); } private onKeyDown($event: KeyboardEvent) { if ($event) { $event.stopPropagation(); const keyCode = $event.keyCode; // F2 if (keyCode === 113) { this.showDropDownPanel(); } if (keyCode !== 38 && keyCode !== 40) { return; } const languageItemIndex = this.languages.findIndex(l => l.code === this.currentLanguageItem.code); switch (keyCode) { case 38: if (languageItemIndex === 0) { this.currentLanguageItem = this.languages[this.languages.length - 1]; } else { this.currentLanguageItem = this.languages[languageItemIndex - 1]; } this.currentLanguage = this.currentLanguageItem.code; break; case 40: if (languageItemIndex === this.languages.length - 1) { this.currentLanguageItem = this.languages[0]; } else { this.currentLanguageItem = this.languages[languageItemIndex + 1]; } this.currentLanguage = this.currentLanguageItem.code; break; } this.setValue(); this.cd.markForCheck(); this.cd.detectChanges(); // this.input.cd.detectChanges(); setTimeout( () => { this.input.setFocusToEnd(); }); } } onFocus($event) { $event.stopPropagation(); if (this.openOnFocus) { this.showDropDownPanel(); } } onBlur($event) { $event.stopPropagation(); // this.hideDropDownPanel(); } onIconClick($event) { if ($event.originalEvent) { $event.originalEvent.stopPropagation(); $event.originalEvent.preventDefault(); } if (!this.comboPanelRef) { this.showDropDownPanel(); } return false; } private getCliecntRect() { const rect = this.input.el.nativeElement.getBoundingClientRect(); const winWidth = window.innerWidth; const winHeight = window.innerHeight; let posleft = rect.left; if (winWidth - posleft < this.panelWidth) { posleft = posleft + rect.width - this.panelWidth; } let postop = rect.top; if (winHeight - postop < this.panelHeight) { postop = postop - rect.height; } else { postop = postop + rect.height; } return { left: posleft, // + rect.width - this.panelWidth top: postop, height: this.panelHeight, width: this.panelWidth }; } private updateLangOrder() { let idx = -1; const curritem = this.languages.find((n, i) => { const f = n.code === this.localeService.localeId; if (f) { idx = i; } return f; }); if (curritem) { const _item = Object.assign({}, curritem); this.languages.splice(idx, 1); this.languages.unshift(_item); } } showDropDownPanel() { if (!this.languages || this.languages.length === 1) { return; } if (this.readonly || this.disabled) { return; } if (!this.comboPanelRef) { const compFac = this.cfr.resolveComponentFactory(LanguageTextPanelComponent); this.comboPanelRef = compFac.create(this.injector); this.applicationRef.attachView(this.comboPanelRef.hostView); Object.assign(this.comboPanelRef.instance, this.getCliecntRect()); const panelRefEl = this.comboPanelRef.location.nativeElement; document.body.appendChild(panelRefEl); this.renderer.addClass(panelRefEl, 'overlay-pane'); // this.renderer.setStyle(panelRefEl, 'width', '100vw'); // this.renderer.setStyle(panelRefEl, 'height', '100vh'); this.renderer.setStyle(panelRefEl, 'z-index', '99999999'); this.renderer.setStyle(panelRefEl, 'left', '0'); this.renderer.setStyle(panelRefEl, 'top', '0'); this.globalListener = this.registerListenClick(); // 根据上下文语言,自动调整语言列表的顺序,将当前语言设为第1个 this.updateLangOrder(); this.comboPanelRef.instance.items = this.languages; this.comboPanelRef.instance.languageInputRef = this; this.comboPanelRef.instance.data = {...this.data}; this.comboPanelRef.instance.currentItem = this.currentLanguageItem; this.comboPanelRef.instance.maxWords = this.maxWords; this.comboPanelRef.instance.itemClick.subscribe((data: LanguageData) => { // this.currentLanguageItem = item; // this.currentLanguage = item.code; this.data = {...data}; this.hideDropDownPanel(); this.setValue(); }); this.comboPanelRef.instance.hidePanel.subscribe( () => { this.hideDropDownPanel(); }); this.comboPanelRef.instance.showPanel.subscribe( (panelIns: LanguageTextPanelComponent) => { panelIns.setInputFocus(); }); this.comboPanelRef.instance.show(this); this.lts.setActivePane(this); } else { this.hideDropDownPanel(); } } private getLanguageItem(code: string) { return this.languages.find(l => l.code === code); } onClear() { } onChanges(val?: any) { this.data[this.currentLanguage] = this.value; this.onModelChange({...this.data}); this.onModelTouched({...this.data}); } setValue() { if (this.data) { this.value = this.data[this.currentLanguage] || ''; } else { this.value = ''; } this.onChanges(); this.cd.detectChanges(); } private removeGlobalListener() { if (this.globalListener) { this.globalListener(); this.globalListener = null; } } private iframeEventHandle(action: 'addEventListener' | 'removeEventListener', fn) { const iframes = Array.from(document.querySelectorAll('iframe')); if (iframes && iframes.length) { for (const iframe of iframes) { const iframeDoc = iframe.contentDocument; if (iframeDoc) { iframeDoc[action]('mousedown', fn); iframeDoc[action]('mousewheel', fn); } } } } private registerListenClick() { const removePanel = (event: any) => { if (!this.comboPanelRef) { return; } const targets = [this.comboPanelRef.location.nativeElement, this.el.nativeElement]; if (targets.some(t => t.contains(event.target))) { return; } this.hideDropDownPanel(); }; document.body.addEventListener('mousedown', removePanel, true); document.body.addEventListener('mousewheel', removePanel, true); this.iframeEventHandle('addEventListener', removePanel); return () => { document.body.removeEventListener('mousedown', removePanel, true); document.body.removeEventListener('mousewheel', removePanel, true); this.iframeEventHandle('removeEventListener', removePanel); }; // return this.renderer.listen('document', 'click', (event: any) => { // if (!this.comboPanelRef) { // return; // } // const targets = [this.comboPanelRef.location.nativeElement, this.el.nativeElement]; // if (targets.some(t => t.contains(event.target))) { // return; // } // this.hideDropDownPanel(); // }); } hideDropDownPanel() { if (this.comboPanelRef && this.comboPanelRef.instance) { // if (this.comboPanelRef.instance.opened) { // this.comboPanelRef.instance.hide(); // } this.comboPanelRef.instance.opened = false; const componentEl = this.comboPanelRef.location.nativeElement; if (componentEl.parentNode) { componentEl.parentNode.removeChild(componentEl); } this.removeGlobalListener(); this.ngZone.runOutsideAngular(() => { this.input.focus(); setTimeout(() => { if (this.comboPanelRef) { this.comboPanelRef.destroy(); this.comboPanelRef = null; } }, 100); }); } } writeValue(obj: any): void { if (obj && Object.keys(obj).length) { this.data = obj; this.value = obj[this.currentLanguage]; } else { this.data = {}; this.value = ''; } } registerOnChange(fn: any): void { this.onModelChange = fn; } registerOnTouched(fn: any): void { this.onModelTouched = fn; } setDisabledState?(isDisabled: boolean): void { this.disabled = isDisabled; } }