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;
}
}