1 import {AfterContentInit, Component, ElementRef, EventEmitter, forwardRef, HostBinding, HostListener, Input, OnInit, Output, Renderer2, ViewChild} from '@angular/core';
2 import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
3 import {Observable} from 'rxjs/Observable';
5 import {BooleanFieldValue} from '../core/boolean-field-value';
6 import {NumberWrapperParseFloat} from '../core/number-wrapper-parse';
7 import {UUID} from '../core/uuid';
11 export const PX_TEXT_INPUT_CONTROL_VALUE_ACCESSOR: any = {
12 provide: NG_VALUE_ACCESSOR,
13 useExisting: forwardRef(() => PlxTextInputComponent),
18 selector: 'plx-text-input',
19 templateUrl: 'text-input.html',
20 styleUrls: ['text-input.less'],
21 host: {'style': 'display: inline-block;'},
22 providers: [PX_TEXT_INPUT_CONTROL_VALUE_ACCESSOR]
25 export class PlxTextInputComponent implements ControlValueAccessor, OnInit,
27 private _focused: boolean = false;
28 private _value: any = '';
29 /** Callback registered via registerOnTouched (ControlValueAccessor) */
30 private _onTouchedCallback: () => void = noop;
31 /** Callback registered via registerOnChange (ControlValueAccessor) */
32 private _onChangeCallback: (_: any) => void = noop;
34 /** Readonly properties. */
36 return this._value === null || this._value === '';
38 get inputId(): string {
41 get isShowHintLabel() {
42 return this._focused && this.hintLabel !== null;
45 return this.type === 'number' ? 'text' : this.type;
48 @Input() id: string = `plx-input-${UUID.UUID()}`;
49 @Input() name: string = null;
50 @Input() hintLabel: string = null;
51 @Input() lang: string = 'zh';
52 @Input() @BooleanFieldValue() disabled: boolean = false;
54 @Input() numberShowSpinner = true;
55 @Input() max: string|number = null;
56 @Input() maxLength: number = 64;
57 @Input() min: string|number = null;
58 @Input() minLength: number = null;
59 @Input() placeholder: string = '';
60 @Input() @BooleanFieldValue() readOnly: boolean = false;
61 @Input() @BooleanFieldValue() required: boolean = false;
62 @Input() @BooleanFieldValue() notShowOption: boolean = true;
63 @Input() type: string = 'text';
64 @Input() tabIndex: number = null;
65 @Input() pattern: string = null;
67 @Input() @BooleanFieldValue() shortInput: boolean = false;
68 @Input() unit: string = null;
69 @Input() unitOptions: string[] = null;
70 @Input() prefix: string = null;
71 @Input() suffixList: string[] = null;
73 // @Input() precision: number = 0;
74 @Input() step: number = 1;
75 @Input() width: string = '400px';
76 @Input() unitWidth: string = '45px';
77 @Input() unitOptionWidth: string = '84px';
78 @Input() prefixWidth: string = '70px';
79 @Input() historyList: string[];
81 @Input() prefixOptions: string[] = [];
82 @Input() prefixOptionWidth: string = '84px';
84 @Input() @BooleanFieldValue() passwordSwitch: boolean = false;
86 @ViewChild('input') inputViewChild: ElementRef;
87 @ViewChild('inputOutter') pxTextInputElement: ElementRef;
89 @HostBinding('class.input-invalid') selectClass: boolean = true;
91 isDisabledUp: boolean = false;
92 isDisabledDown: boolean = false;
93 displayDataList: string[];
94 currentPrecision: number = 0;
95 keyPattern: RegExp = /[0-9\-]/;
97 /[a-zA-Z]|[\u4e00-\u9fa5]|[\uff08\uff09\u300a\u300b\u2014\u2014\uff1a\uff1b\uff0c\u201c\u201d\u2018\u2019\+\=\{\}\u3001\u3002\u3010\u3011\<\>\uff01\uff1f\/\|]/g;
99 optionalLabel: string = null;
100 hasSelection = false;
101 _precision: number = 0;
104 isOpenDataList: boolean = false;
105 dataListClicked: boolean = false;
106 isOpenSuffixList: boolean = false;
107 suffixListClicked: boolean = false;
110 isShowUnitOption: boolean = false;
111 unitOptionClicked: boolean = false;
113 prefixOptionClicked: boolean = false;
114 isShowPrefixOption = false;
116 showPassword: boolean = false;
128 isPwdSwithHover: boolean = false;
129 isPwdSwithClick: boolean = false;
131 _autoFocus: boolean = false;
133 set autoFocus(value: boolean) {
134 this._autoFocus = value;
137 if (this._autoFocus) {
139 that.inputViewChild.nativeElement.focus();
144 return this._autoFocus;
148 set precision(value: string) {
149 this._precision = parseInt(value);
156 if (this.prefixOptions && this.prefixOptions.length > 0) {
157 if (this.unitOptions && this.unitOptions.length > 0) {
158 return `calc(${this.width} - ${this.prefixOptionWidth} - ${this.unitOptionWidth})`;
159 } else if (this.unit !== null) {
160 return `calc(${this.width} - ${this.prefixOptionWidth} - ${this.unitWidth})`;
162 return `calc(${this.width} - ${this.prefixOptionWidth})`;
166 if (this.unit !== null && this.prefix !== null) {
167 return `calc(${this.width} - ${this.unitWidth} - ${this.prefixWidth})`;
170 if (this.unit !== null) {
171 return `calc(${this.width} - ${this.unitWidth})`;
174 if (!!this.unitOptions && this.unitOptions.length !== 0 &&
175 this.prefix !== null) {
176 return `calc(${this.width} - ${this.unitOptionWidth} - ${
181 if (!!this.unitOptions && this.unitOptions.length !== 0) {
182 return `calc(${this.width} - ${this.unitOptionWidth})`;
184 if (this.prefix !== null) {
185 return `calc(${this.width} - ${this.prefixWidth})`;
192 return this.unit !== null;
194 get hasUnitOption() {
195 return this.showUnit !== undefined;
198 return this.prefix !== null;
200 get hasPrefixOption() {
201 return this.prefixOptions && this.prefixOptions.length > 0;
204 return this._focused;
207 private _blurEmitter: EventEmitter<FocusEvent> =
208 new EventEmitter<FocusEvent>();
209 private _focusEmitter: EventEmitter<FocusEvent> =
210 new EventEmitter<FocusEvent>();
211 private click = new EventEmitter<any>();
212 private unitChange = new EventEmitter<any>();
213 @Output() public prefixChange = new EventEmitter<any>();
216 get onBlur(): Observable<FocusEvent> {
217 return this._blurEmitter.asObservable();
221 get onFocus(): Observable<FocusEvent> {
222 return this._focusEmitter.asObservable();
225 @HostListener('focus')
227 this.renderer.addClass(this.el.nativeElement, 'input-focus');
228 this.renderer.removeClass(this.el.nativeElement, 'input-blur');
231 @HostListener('blur')
233 this.renderer.addClass(this.el.nativeElement, 'input-blur');
234 this.renderer.removeClass(this.el.nativeElement, 'input-focus');
239 v = this.filterZhChar(v);
240 v = this._convertValueForInputType(v);
241 if (v !== this._value) {
243 if (this.type === 'number') {
244 if (this._value === '') {
245 this._onChangeCallback(null);
246 } else if (isNaN(this._value)) {
247 this._onChangeCallback(this._value);
249 this._onChangeCallback(NumberWrapperParseFloat(this._value));
252 this._onChangeCallback(this._value);
261 private el: ElementRef, private renderer: Renderer2) {}
264 if (this.shortInput) {
265 this.width = '120px';
266 this.unitWidth = '40px';
267 this.prefixWidth = '40px';
269 this.translateLabel();
271 if (!!this.unitOptions && this.unitOptions.length !== 0) {
272 this.showUnit = this.unitOptions[0];
275 if (!!this.prefixOptions && this.prefixOptions.length !== 0) {
276 this.showPrefix = this.prefixOptions[0];
280 ngAfterContentInit() {
281 if (this.pxTextInputElement) {
282 Array.from(this.pxTextInputElement.nativeElement.childNodes)
283 .forEach((node: HTMLElement) => {
284 if (node.nodeType === 3) {
285 this.pxTextInputElement.nativeElement.removeChild(node);
290 private translateLabel() {
291 if (this.lang === 'zh') {
292 this.optionalLabel = '(可选)';
294 this.optionalLabel = '(Optional)';
298 _handleFocus(event: FocusEvent) {
299 this._focused = true;
300 this._focusEmitter.emit(event);
303 _handleBlur(event: FocusEvent) {
304 this._focused = false;
305 this._onTouchedCallback();
306 this._blurEmitter.emit(event);
309 _checkValueLimit(value: any) {
310 if (this.type === 'number') {
311 if ((value === '' || value === undefined || value === null) &&
316 NumberWrapperParseFloat(value) < NumberWrapperParseFloat(this.min)) {
320 NumberWrapperParseFloat(value) > NumberWrapperParseFloat(this.max)) {
330 this.value = this._checkValueLimit(this.value);
331 this.displayValue = this.value;
334 _handleChange(event: Event) {
335 this.value = (<HTMLInputElement>event.target).value;
336 this._onTouchedCallback();
340 this.dataListClicked = true;
341 if (this.historyList) {
343 this.filterOption(this.value);
345 this.displayDataList = this.historyList;
346 if (!this.isOpenDataList) {
347 this.isOpenDataList = true;
353 _handleClick(event: Event) {
354 if (this.isShowUnitOption) {
355 this.isShowUnitOption = false;
357 this.click.emit(event);
359 if (this.historyList) {
364 _handleSelect(event: Event) { // 输入框文本被选中时处理
365 if (!this.hasSelection) {
366 this.hasSelection = true;
369 deleteSelection() { // 删除选中文本,
370 document.getSelection().deleteFromDocument();
371 this.hasSelection = false;
374 _onWindowClick(event: Event) {
375 if (this.historyList) {
376 if (!this.dataListClicked) {
377 this.isOpenDataList = false;
379 this.dataListClicked = false;
382 if (this.suffixList) {
383 if (!this.suffixListClicked) {
384 this.isOpenSuffixList = false;
386 this.suffixListClicked = false;
389 if (this.unitOptions) {
390 if (!this.unitOptionClicked) {
391 this.isShowUnitOption = false;
393 this.unitOptionClicked = false;
396 if (this.prefixOptions) {
397 if (!this.prefixOptionClicked) {
398 this.isShowPrefixOption = false;
400 this.prefixOptionClicked = false;
404 _showPrefixOption(event: Event) {
405 this.isShowPrefixOption = !this.isShowPrefixOption;
406 this.prefixOptionClicked = true;
409 _showUnitOption(event: Event) {
410 this.isShowUnitOption = !this.isShowUnitOption;
411 this.unitOptionClicked = true;
414 _setUnit(unitValue: string) {
415 this.unitOptionClicked = true;
416 this.showUnit = unitValue;
417 this.unitChange.emit(unitValue);
418 this.isShowUnitOption = false;
421 _setPrefix(value: string) {
422 if (value !== this.showPrefix) {
423 this.showPrefix = value;
424 this.prefixChange.emit(value);
426 this.prefixOptionClicked = true;
427 this.isShowPrefixOption = false;
430 filterOption(value: any) {
431 this.displayDataList = [];
432 this.displayDataList = this.historyList.filter((data: string) => {
433 return data.toLowerCase().indexOf(value.toLowerCase()) > -1;
436 this.isOpenDataList = this.displayDataList.length !== 0;
439 concatValueAndSuffix(value: string) {
441 that.displayDataList = [];
444 that.displayDataList = [];
445 } else if (value.trim().toLowerCase().indexOf(mark) === -1) {
446 that.displayDataList.push(value);
447 that.suffixList.map((item: string) => {
448 let tempValue = value + mark + item;
449 that.displayDataList.push(tempValue);
452 that.suffixList.map((item: string) => {
453 let tempValue = value.split(mark)[0] + mark + item;
454 that.displayDataList.push(tempValue);
456 that.displayDataList = that.displayDataList.filter((item: string) => {
457 return item.trim().toLowerCase().indexOf(value) > -1;
461 that.isOpenSuffixList = that.displayDataList.length !== 0;
464 _handleInput(event: any) {
465 let inputValue = event.target.value.trim().toLowerCase();
467 if (this.historyList) {
468 this.filterOption(inputValue);
471 if (this.suffixList) {
472 this.concatValueAndSuffix(inputValue);
476 chooseInputData(data: any) {
477 this.displayValue = data;
480 this.isOpenDataList = false;
481 this.dataListClicked = true;
482 this.isOpenSuffixList = false;
483 this.suffixListClicked = true;
486 writeValue(value: any) {
487 this.displayValue = this._checkValueLimit(value);
488 this._value = this.displayValue;
491 registerOnChange(fn: any) {
492 this._onChangeCallback = fn;
495 registerOnTouched(fn: any) {
496 this._onTouchedCallback = fn;
499 private getConvertValue(v: any) {
500 this.currentPrecision = v.toString().split('.')[1].length;
501 if (this.currentPrecision === 0) { // 输入小数点,但小数位数为0时
504 if (this.currentPrecision <
505 this._precision) { // 输入小数点,且小数位数不为0
506 return this.toFixed(v, this.currentPrecision);
508 return this.toFixed(v, this._precision);
512 private filterZhChar(v: any) {
513 if (this.type === 'number') {
514 let reg = /[^0-9.-]/;
515 while (reg.test(v)) {
516 v = v.replace(reg, '');
522 private _convertValueForInputType(v: any): any {
525 if (v === '' || v === '-') {
529 if (v.toString().indexOf('.') === -1) { // 整数
530 return this.toFixed(v, 0);
532 return this.getConvertValue(v);
540 private toFixed(value: number, precision: number) {
541 return Number(value).toFixed(precision);
544 repeat(interval: number, dir: number) {
545 let i = interval || 500;
548 this.timer = setTimeout(() => {
549 this.repeat(40, dir);
557 clearInterval(this.timer);
562 let step = this.step * dir;
563 let currentValue = this._convertValueForInputType(this.value) || 0;
565 this.value = Number(currentValue) + step;
567 if (this.maxLength !== null &&
568 this.value.toString().length > this.maxLength) {
569 this.value = currentValue;
572 if (this.min !== null && this.value <= NumberWrapperParseFloat(this.min)) {
573 this.value = this.min;
574 this.isDisabledDown = true;
577 if (this.max !== null && this.value >= NumberWrapperParseFloat(this.max)) {
578 this.value = this.max;
579 this.isDisabledUp = true;
581 this.displayValue = this.value;
582 this._onChangeCallback(NumberWrapperParseFloat(this.value));
585 onUpButtonMousedown(event: Event, input: HTMLInputElement) {
586 if (!this.disabled && this.type === 'number') {
588 this.repeat(null, 1);
589 event.preventDefault();
593 onUpButtonMouseup(event: Event) {
594 if (!this.disabled) {
599 onUpButtonMouseleave(event: Event) {
600 if (!this.disabled) {
605 onDownButtonMousedown(event: Event, input: HTMLInputElement) {
606 if (!this.disabled && this.type === 'number') {
608 this.repeat(null, -1);
609 event.preventDefault();
613 onDownButtonMouseup(event: Event) {
614 if (!this.disabled) {
619 onDownButtonMouseleave(event: Event) {
620 if (!this.disabled) {
625 onInputKeydown(event: KeyboardEvent) {
626 if (this.type === 'number') {
627 if (event.which === 229) {
628 event.preventDefault();
630 } else if (event.which === 38) {
632 event.preventDefault();
633 } else if (event.which === 40) {
635 event.preventDefault();
639 onInputKeyPress(event: KeyboardEvent) {
640 let inputChar = String.fromCharCode(event.charCode);
641 if (this.type === 'number') {
642 if (event.which === 8) {
646 // if (!this.isValueLimit()) {
647 // this.handleSelection(event);
650 if (inputChar === '-' && this.min !== null &&
651 NumberWrapperParseFloat(this.min) >= 0) {
652 event.preventDefault();
655 if (this.isIllegalNumberInputChar(event) ||
656 this.isIllegalIntergerInput(inputChar)) {
657 event.preventDefault();
660 if (this.isIllegalFloatInput(
661 inputChar)) { // 当该函数返回true时,执行两种情景
662 this.handleSelection(event);
664 if (this.hasSelection) { // 文本被选中,执行文本替换
665 this.deleteSelection();
670 private handleSelection(event: any) {
671 if (this.hasSelection) { // 文本被选中,执行文本替换
672 this.deleteSelection();
673 } else { // 无选中文本,阻止非法输入
674 event.preventDefault();
677 // private isValueLimit() {
678 // if (this.min !== null && NumberWrapperParseFloat(this.value) !== 0 &&
679 // this.value <= NumberWrapperParseFloat(this.min)) {
682 // if (this.max !== null && NumberWrapperParseFloat(this.value) !== 0 &&
683 // this.value >= NumberWrapperParseFloat(this.max)) {
689 private isIllegalNumberInputChar(event: KeyboardEvent) {
690 /* 8:backspace, 46:. */
691 return !this.keyPattern.test(String.fromCharCode(event.charCode)) &&
692 event.which !== 46 && event.which !== 0;
695 private isIllegalIntergerInput(inputChar: string) {
696 return this._precision === 0 &&
697 (inputChar === '.' ||
698 (this._value && this._value && this._value.toString().length > 0 &&
702 private isIllegalFloatInput(inputChar: string) {
703 return this._precision > 0 && this._value &&
704 ((this._value.toString().length > 0 && inputChar === '-') ||
705 ((this._value.toString() === '' ||
706 this._value.toString().indexOf('.') > 0) &&
707 inputChar === '.') ||
708 (this._value.toString().indexOf('.') > 0 &&
709 this._value.toString().split('.')[1].length === this._precision));
712 onInput(event: Event, inputValue: string) {
713 this.value = inputValue;
718 if (this.value === undefined || this.value === null || this.value === '') {
724 isDisabledUpCaret() {
725 if (this.isEmptyValue()) {
729 (NumberWrapperParseFloat(this.value) >=
730 NumberWrapperParseFloat(this.max))) {
736 isDisabledDownCaret() {
737 if (this.isEmptyValue()) {
741 (NumberWrapperParseFloat(this.value) <=
742 NumberWrapperParseFloat(this.min))) {
748 _handleMouseEnterUp() {
749 this.isDisabledUp = this.isDisabledUpCaret();
752 _handleMouseEnterDown() {
753 this.isDisabledDown = this.isDisabledDownCaret();
756 public switch(): void {
757 this.showPassword = !this.showPassword;
758 this.showPassword?this.inputViewChild.nativeElement.type =
759 'text':this.inputViewChild.nativeElement.type = 'password';
762 private setPasswordTooltip(): void {
763 this.tooltipTexts[this.lang][this.showPassword.toString()]