9b5a01e9e9f7b79571a6fcd469f75e07e5afb5fc
[sdc/sdc-workflow-designer.git] /
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';
4
5 import {BooleanFieldValue} from '../core/boolean-field-value';
6 import {NumberWrapperParseFloat} from '../core/number-wrapper-parse';
7 import {UUID} from '../core/uuid';
8
9 const noop = () => {};
10
11 export const PX_TEXT_INPUT_CONTROL_VALUE_ACCESSOR: any = {
12   provide: NG_VALUE_ACCESSOR,
13   useExisting: forwardRef(() => PlxTextInputComponent),
14   multi: true
15 };
16
17 @Component({
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]
23 })
24
25 export class PlxTextInputComponent implements ControlValueAccessor, OnInit,
26                                              AfterContentInit {
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;
33
34   /** Readonly properties. */
35   get empty() {
36     return this._value === null || this._value === '';
37   }
38   get inputId(): string {
39     return `${this.id}`;
40   }
41   get isShowHintLabel() {
42     return this._focused && this.hintLabel !== null;
43   }
44   get inputType() {
45     return this.type === 'number' ? 'text' : this.type;
46   }
47
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;
53
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;
66
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;
72
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[];
80
81         @Input() prefixOptions: string[] = [];
82         @Input() prefixOptionWidth: string = '84px';
83
84         @Input() @BooleanFieldValue() passwordSwitch: boolean = false;
85
86   @ViewChild('input') inputViewChild: ElementRef;
87   @ViewChild('inputOutter') pxTextInputElement: ElementRef;
88
89   @HostBinding('class.input-invalid') selectClass: boolean = true;
90
91   isDisabledUp: boolean = false;
92   isDisabledDown: boolean = false;
93   displayDataList: string[];
94   currentPrecision: number = 0;
95   keyPattern: RegExp = /[0-9\-]/;
96   langPattern: RegExp =
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;
98   timer: any;
99   optionalLabel: string = null;
100   hasSelection = false;
101   _precision: number = 0;
102   displayValue: any;
103
104   isOpenDataList: boolean = false;
105   dataListClicked: boolean = false;
106   isOpenSuffixList: boolean = false;
107   suffixListClicked: boolean = false;
108
109   showUnit: string;
110   isShowUnitOption: boolean = false;
111   unitOptionClicked: boolean = false;
112
113         prefixOptionClicked: boolean = false;
114         isShowPrefixOption = false;
115         showPrefix: string;
116         showPassword: boolean = false;
117         tooltipText: string;
118         tooltipTexts = {
119                 'zh': {
120                         'true': '隐藏',
121                         'false': '显示',
122                 },
123                 'en': {
124                         'true': 'hidden',
125                         'false': 'show',
126                 }
127         };
128   isPwdSwithHover: boolean = false;
129   isPwdSwithClick: boolean = false;
130
131   _autoFocus: boolean = false;
132   @Input()
133   set autoFocus(value: boolean) {
134     this._autoFocus = value;
135
136     const that = this;
137     if (this._autoFocus) {
138       setTimeout(() => {
139         that.inputViewChild.nativeElement.focus();
140       }, 0);
141     }
142   }
143   get autoFocus() {
144     return this._autoFocus;
145   }
146
147   @Input()
148   set precision(value: string) {
149     this._precision = parseInt(value);
150   }
151   get precision() {
152     return this._value;
153   }
154
155   get inputWidth() {
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})`;
161             } else {
162                     return `calc(${this.width} - ${this.prefixOptionWidth})`;
163             }
164     }
165
166     if (this.unit !== null && this.prefix !== null) {
167       return `calc(${this.width} - ${this.unitWidth} - ${this.prefixWidth})`;
168     }
169
170     if (this.unit !== null) {
171       return `calc(${this.width} - ${this.unitWidth})`;
172     }
173
174     if (!!this.unitOptions && this.unitOptions.length !== 0 &&
175         this.prefix !== null) {
176       return `calc(${this.width} - ${this.unitOptionWidth} - ${
177                                                                this.prefixWidth
178                                                              })`;
179     }
180
181     if (!!this.unitOptions && this.unitOptions.length !== 0) {
182       return `calc(${this.width} - ${this.unitOptionWidth})`;
183     }
184     if (this.prefix !== null) {
185       return `calc(${this.width} - ${this.prefixWidth})`;
186     }
187     return this.width;
188   }
189
190
191   get hasUnit() {
192     return this.unit !== null;
193   }
194   get hasUnitOption() {
195     return this.showUnit !== undefined;
196   }
197   get hasPrefix() {
198     return this.prefix !== null;
199   }
200         get hasPrefixOption() {
201                 return this.prefixOptions && this.prefixOptions.length > 0;
202         }
203   get isFocus() {
204     return this._focused;
205   }
206
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>();
214
215   @Output('blur')
216   get onBlur(): Observable<FocusEvent> {
217     return this._blurEmitter.asObservable();
218   }
219
220   @Output('focus')
221   get onFocus(): Observable<FocusEvent> {
222     return this._focusEmitter.asObservable();
223   }
224
225   @HostListener('focus')
226   onHostFocus() {
227     this.renderer.addClass(this.el.nativeElement, 'input-focus');
228     this.renderer.removeClass(this.el.nativeElement, 'input-blur');
229   }
230
231   @HostListener('blur')
232   onHostBlur() {
233     this.renderer.addClass(this.el.nativeElement, 'input-blur');
234     this.renderer.removeClass(this.el.nativeElement, 'input-focus');
235   }
236
237   @Input()
238   set value(v: any) {
239     v = this.filterZhChar(v);
240     v = this._convertValueForInputType(v);
241     if (v !== this._value) {
242       this._value = v;
243       if (this.type === 'number') {
244         if (this._value === '') {
245           this._onChangeCallback(null);
246         } else if (isNaN(this._value)) {
247           this._onChangeCallback(this._value);
248         } else {
249           this._onChangeCallback(NumberWrapperParseFloat(this._value));
250         }
251       } else {
252         this._onChangeCallback(this._value);
253       }
254     }
255   }
256   get value(): any {
257     return this._value;
258   }
259
260   constructor(
261       private el: ElementRef, private renderer: Renderer2) {}
262
263   ngOnInit() {
264     if (this.shortInput) {
265       this.width = '120px';
266       this.unitWidth = '40px';
267       this.prefixWidth = '40px';
268     }
269     this.translateLabel();
270
271     if (!!this.unitOptions && this.unitOptions.length !== 0) {
272       this.showUnit = this.unitOptions[0];
273     }
274
275           if (!!this.prefixOptions && this.prefixOptions.length !== 0) {
276                   this.showPrefix = this.prefixOptions[0];
277           }
278   }
279
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);
286             }
287           });
288     }
289   }
290   private translateLabel() {
291     if (this.lang === 'zh') {
292       this.optionalLabel = '(可选)';
293     } else {
294       this.optionalLabel = '(Optional)';
295     }
296   }
297
298   _handleFocus(event: FocusEvent) {
299     this._focused = true;
300     this._focusEmitter.emit(event);
301   }
302
303   _handleBlur(event: FocusEvent) {
304     this._focused = false;
305     this._onTouchedCallback();
306     this._blurEmitter.emit(event);
307   }
308
309   _checkValueLimit(value: any) {
310     if (this.type === 'number') {
311       if ((value === '' || value === undefined || value === null) &&
312           !this.required) {
313         return '';
314       } else if (
315           this.min !== null &&
316           NumberWrapperParseFloat(value) < NumberWrapperParseFloat(this.min)) {
317         return this.min;
318       } else if (
319           this.max !== null &&
320           NumberWrapperParseFloat(value) > NumberWrapperParseFloat(this.max)) {
321         return this.max;
322       } else {
323         return value;
324       }
325     }
326     return value;
327   }
328
329   _checkValue() {
330     this.value = this._checkValueLimit(this.value);
331     this.displayValue = this.value;
332   }
333
334   _handleChange(event: Event) {
335     this.value = (<HTMLInputElement>event.target).value;
336     this._onTouchedCallback();
337   }
338
339   openDataList() {
340     this.dataListClicked = true;
341     if (this.historyList) {
342       if (this.value) {
343         this.filterOption(this.value);
344       } else {
345         this.displayDataList = this.historyList;
346         if (!this.isOpenDataList) {
347           this.isOpenDataList = true;
348         }
349       }
350     }
351   }
352
353   _handleClick(event: Event) {
354     if (this.isShowUnitOption) {
355       this.isShowUnitOption = false;
356     }
357     this.click.emit(event);
358
359     if (this.historyList) {
360       this.openDataList();
361     }
362   }
363
364   _handleSelect(event: Event) {  // 输入框文本被选中时处理
365     if (!this.hasSelection) {
366       this.hasSelection = true;
367     }
368   }
369   deleteSelection() {  // 删除选中文本,
370     document.getSelection().deleteFromDocument();
371     this.hasSelection = false;
372   }
373
374   _onWindowClick(event: Event) {
375     if (this.historyList) {
376       if (!this.dataListClicked) {
377         this.isOpenDataList = false;
378       }
379       this.dataListClicked = false;
380     }
381
382     if (this.suffixList) {
383       if (!this.suffixListClicked) {
384         this.isOpenSuffixList = false;
385       }
386       this.suffixListClicked = false;
387     }
388
389     if (this.unitOptions) {
390       if (!this.unitOptionClicked) {
391         this.isShowUnitOption = false;
392       }
393       this.unitOptionClicked = false;
394     }
395
396         if (this.prefixOptions) {
397                 if (!this.prefixOptionClicked) {
398                         this.isShowPrefixOption = false;
399                 }
400                 this.prefixOptionClicked = false;
401         }
402   }
403
404         _showPrefixOption(event: Event) {
405                 this.isShowPrefixOption = !this.isShowPrefixOption;
406                 this.prefixOptionClicked = true;
407         }
408
409   _showUnitOption(event: Event) {
410     this.isShowUnitOption = !this.isShowUnitOption;
411     this.unitOptionClicked = true;
412   }
413
414   _setUnit(unitValue: string) {
415     this.unitOptionClicked = true;
416     this.showUnit = unitValue;
417     this.unitChange.emit(unitValue);
418     this.isShowUnitOption = false;
419   }
420
421         _setPrefix(value: string) {
422                 if (value !== this.showPrefix) {
423                         this.showPrefix = value;
424                         this.prefixChange.emit(value);
425                 }
426                 this.prefixOptionClicked = true;
427                 this.isShowPrefixOption = false;
428         }
429
430   filterOption(value: any) {
431     this.displayDataList = [];
432     this.displayDataList = this.historyList.filter((data: string) => {
433       return data.toLowerCase().indexOf(value.toLowerCase()) > -1;
434     });
435
436     this.isOpenDataList = this.displayDataList.length !== 0;
437   }
438
439   concatValueAndSuffix(value: string) {
440     const that = this;
441     that.displayDataList = [];
442     let mark = '@';
443     if (value === '') {
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);
450       });
451     } else {
452       that.suffixList.map((item: string) => {
453         let tempValue = value.split(mark)[0] + mark + item;
454         that.displayDataList.push(tempValue);
455       });
456       that.displayDataList = that.displayDataList.filter((item: string) => {
457         return item.trim().toLowerCase().indexOf(value) > -1;
458       });
459     }
460
461     that.isOpenSuffixList = that.displayDataList.length !== 0;
462   }
463
464   _handleInput(event: any) {
465     let inputValue = event.target.value.trim().toLowerCase();
466
467     if (this.historyList) {
468       this.filterOption(inputValue);
469     }
470
471     if (this.suffixList) {
472       this.concatValueAndSuffix(inputValue);
473     }
474   }
475
476   chooseInputData(data: any) {
477     this.displayValue = data;
478     this.value = data;
479
480     this.isOpenDataList = false;
481     this.dataListClicked = true;
482     this.isOpenSuffixList = false;
483     this.suffixListClicked = true;
484   }
485
486   writeValue(value: any) {
487     this.displayValue = this._checkValueLimit(value);
488     this._value = this.displayValue;
489   }
490
491   registerOnChange(fn: any) {
492     this._onChangeCallback = fn;
493   }
494
495   registerOnTouched(fn: any) {
496     this._onTouchedCallback = fn;
497   }
498
499   private getConvertValue(v: any) {
500     this.currentPrecision = v.toString().split('.')[1].length;
501     if (this.currentPrecision === 0) {  // 输入小数点,但小数位数为0时
502       return v;
503     }
504     if (this.currentPrecision <
505         this._precision) {  // 输入小数点,且小数位数不为0
506       return this.toFixed(v, this.currentPrecision);
507     } else {
508       return this.toFixed(v, this._precision);
509     }
510   }
511
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, '');
517                     }
518     }
519     return v;
520   }
521
522   private _convertValueForInputType(v: any): any {
523     switch (this.type) {
524       case 'number': {
525         if (v === '' || v === '-') {
526           return v;
527         }
528
529         if (v.toString().indexOf('.') === -1) {  // 整数
530           return this.toFixed(v, 0);
531         } else {
532           return this.getConvertValue(v);
533         }
534       }
535       default:
536         return v;
537     }
538   }
539
540   private toFixed(value: number, precision: number) {
541     return Number(value).toFixed(precision);
542   }
543
544   repeat(interval: number, dir: number) {
545     let i = interval || 500;
546
547     this.clearTimer();
548     this.timer = setTimeout(() => {
549       this.repeat(40, dir);
550     }, i);
551
552     this.spin(dir);
553   }
554
555   clearTimer() {
556     if (this.timer) {
557       clearInterval(this.timer);
558     }
559   }
560
561   spin(dir: number) {
562     let step = this.step * dir;
563     let currentValue = this._convertValueForInputType(this.value) || 0;
564
565     this.value = Number(currentValue) + step;
566
567     if (this.maxLength !== null &&
568         this.value.toString().length > this.maxLength) {
569       this.value = currentValue;
570     }
571
572     if (this.min !== null && this.value <= NumberWrapperParseFloat(this.min)) {
573       this.value = this.min;
574       this.isDisabledDown = true;
575     }
576
577     if (this.max !== null && this.value >= NumberWrapperParseFloat(this.max)) {
578       this.value = this.max;
579       this.isDisabledUp = true;
580     }
581     this.displayValue = this.value;
582     this._onChangeCallback(NumberWrapperParseFloat(this.value));
583   }
584
585   onUpButtonMousedown(event: Event, input: HTMLInputElement) {
586     if (!this.disabled && this.type === 'number') {
587       input.focus();
588       this.repeat(null, 1);
589       event.preventDefault();
590     }
591   }
592
593   onUpButtonMouseup(event: Event) {
594     if (!this.disabled) {
595       this.clearTimer();
596     }
597   }
598
599   onUpButtonMouseleave(event: Event) {
600     if (!this.disabled) {
601       this.clearTimer();
602     }
603   }
604
605   onDownButtonMousedown(event: Event, input: HTMLInputElement) {
606     if (!this.disabled && this.type === 'number') {
607       input.focus();
608       this.repeat(null, -1);
609       event.preventDefault();
610     }
611   }
612
613   onDownButtonMouseup(event: Event) {
614     if (!this.disabled) {
615       this.clearTimer();
616     }
617   }
618
619   onDownButtonMouseleave(event: Event) {
620     if (!this.disabled) {
621       this.clearTimer();
622     }
623   }
624
625   onInputKeydown(event: KeyboardEvent) {
626     if (this.type === 'number') {
627       if (event.which === 229) {
628         event.preventDefault();
629         return;
630       } else if (event.which === 38) {
631         this.spin(1);
632         event.preventDefault();
633       } else if (event.which === 40) {
634         this.spin(-1);
635         event.preventDefault();
636       }
637     }
638   }
639   onInputKeyPress(event: KeyboardEvent) {
640     let inputChar = String.fromCharCode(event.charCode);
641     if (this.type === 'number') {
642       if (event.which === 8) {
643         return;
644       }
645
646       // if (!this.isValueLimit()) {
647       //   this.handleSelection(event);
648       // }
649
650       if (inputChar === '-' && this.min !== null &&
651           NumberWrapperParseFloat(this.min) >= 0) {
652         event.preventDefault();
653         return;
654       }
655       if (this.isIllegalNumberInputChar(event) ||
656           this.isIllegalIntergerInput(inputChar)) {
657         event.preventDefault();
658         return;
659       }
660       if (this.isIllegalFloatInput(
661               inputChar)) {  // 当该函数返回true时,执行两种情景
662         this.handleSelection(event);
663       }
664       if (this.hasSelection) {  // 文本被选中,执行文本替换
665         this.deleteSelection();
666       }
667     }
668   }
669
670   private handleSelection(event: any) {
671     if (this.hasSelection) {  // 文本被选中,执行文本替换
672       this.deleteSelection();
673     } else {  // 无选中文本,阻止非法输入
674       event.preventDefault();
675     }
676   }
677   // private isValueLimit() {
678   //   if (this.min !== null && NumberWrapperParseFloat(this.value) !== 0 &&
679   //       this.value <= NumberWrapperParseFloat(this.min)) {
680   //     return false;
681   //   }
682   //   if (this.max !== null && NumberWrapperParseFloat(this.value) !== 0 &&
683   //       this.value >= NumberWrapperParseFloat(this.max)) {
684   //     return false;
685   //   }
686   //   return true;
687   // }
688
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;
693   }
694
695   private isIllegalIntergerInput(inputChar: string) {
696     return this._precision === 0 &&
697         (inputChar === '.' ||
698          (this._value && this._value && this._value.toString().length > 0 &&
699           inputChar === '-'));
700   }
701
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));
710   }
711
712   onInput(event: Event, inputValue: string) {
713     this.value = inputValue;
714   }
715
716   //处理鼠标经过上下箭头时,样式设置
717   isEmptyValue() {
718     if (this.value === undefined || this.value === null || this.value === '') {
719       return true;
720     }
721     return false;
722   }
723
724   isDisabledUpCaret() {
725     if (this.isEmptyValue()) {
726       return true;
727     } else if (
728         this.max !== null &&
729         (NumberWrapperParseFloat(this.value) >=
730          NumberWrapperParseFloat(this.max))) {
731       return true;
732     }
733     return false;
734   }
735
736   isDisabledDownCaret() {
737     if (this.isEmptyValue()) {
738       return true;
739     } else if (
740         this.min !== null &&
741         (NumberWrapperParseFloat(this.value) <=
742          NumberWrapperParseFloat(this.min))) {
743       return true;
744     }
745     return false;
746   }
747
748   _handleMouseEnterUp() {
749     this.isDisabledUp = this.isDisabledUpCaret();
750   }
751
752   _handleMouseEnterDown() {
753     this.isDisabledDown = this.isDisabledDownCaret();
754   }
755
756   public switch(): void {
757           this.showPassword = !this.showPassword;
758           this.showPassword?this.inputViewChild.nativeElement.type =
759                   'text':this.inputViewChild.nativeElement.type = 'password';
760   }
761
762         private setPasswordTooltip(): void {
763                 this.tooltipTexts[this.lang][this.showPassword.toString()]
764         }
765 }