/* * © 2017 Stratio Big Data Inc., Sucursal en España. * * This software is licensed under the Apache License, Version 2.0. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the terms of the License for more details. * * SPDX-License-Identifier: Apache-2.0. */ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input, OnChanges, OnDestroy, OnInit, ViewChildren, Output, EventEmitter } from '@angular/core'; import { ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; import { Subscription } from 'rxjs'; import { StTextareaError } from './st-textarea.error.model'; /** * @description {Component} [Textare] * * The textarea component is for use normally inside a form, you can use too outside a form like a template driven form. * * @example * * {html} * * ``` * * * ``` * */ @Component({ selector: 'st-textarea', templateUrl: './st-textarea.component.html', styleUrls: ['./st-textarea.component.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => StTextareaComponent), multi: true }, { provide: NG_VALIDATORS, useExisting: forwardRef(() => StTextareaComponent), multi: true } ], changeDetection: ChangeDetectionStrategy.OnPush }) export class StTextareaComponent implements ControlValueAccessor, OnChanges, OnInit, OnDestroy { /** @Input {string} [placeholder=''] The text that appears as placeholder of the textarea. It is empty by default */ @Input() placeholder: string = ''; /** @Input {string} [name=''] Name of the textarea */ @Input() name: string = ''; /** @Input {string} [label=''] Label to show over the textarea. It is empty by default */ @Input() label: string = ''; /** @Input {StTextareaError} [errors=''] Error to show for each error case, if you don\'t provide this parameter, * the default behaviour is only to change color without message */ @Input() errors: StTextareaError; /** @Input {string} [qaTag=''] Id for QA test */ @Input() qaTag: string; /** @Input {boolean} [forceValidations=false] If you specify it to 'true', the textarea checks the errors before being modified by user */ @Input() forceValidations: boolean = false; /** @Input {string} [contextualHelp=''] It will be displayed when user clicks on the info button */ @Input() contextualHelp: string; /** @Input {string} [maxLength=''] Define a max-length for textarea field */ @Input() maxLength: number; /** @Input {boolean} [isFocused=false] If true, the textarea will be focused on view init. */ @Input() isFocused: boolean = false; /** @Input {number} [cols=''] Define textarea number of cols */ @Input() cols: number; /** @Input {number} [rows=''] Define textarea number of rows */ @Input() rows: number; /** @Input {string} [wrap='soft'] Define type of wrap as html standard */ @Input() wrap: string = 'soft'; /** @Input {string} [default=] Default value of textarea */ @Input() default: string; /** @Output {} [blur] Notify when user leaves a field */ @Output() blur: EventEmitter = new EventEmitter(); @ViewChildren('textarea') vc: any; public isDisabled: boolean = false; // To check disable public focus: boolean = false; public internalControl: FormControl; public errorMessage: string = undefined; public showErrorValue: boolean = false; private sub: Subscription; private valueChangeSub: Subscription; private internalTextareaModel: any = ''; constructor(private _cd: ChangeDetectorRef) { } onChange = (_: any) => { }; onTouched = () => { }; validate(control: FormControl): any { if (this.sub) { this.sub.unsubscribe(); } this.sub = control.statusChanges.subscribe(() => this.checkErrors(control)); } ngOnChanges(change: any): void { if (this.forceValidations) { this.writeValue(this.internalControl.value); } this._cd.markForCheck(); } ngOnInit(): void { this.internalControl = new FormControl(this.internalTextareaModel); this.valueChangeSub = this.internalControl.valueChanges.subscribe((value) => this.onChange(value)); this.showErrorValue = this.showError(); } ngAfterViewInit(): void { if (this.isFocused) { this.focus = true; this.vc.first.nativeElement.focus(); } } ngOnDestroy(): void { if (this.valueChangeSub) { this.valueChangeSub.unsubscribe(); } if (this.sub) { this.sub.unsubscribe(); } } // When value is received from outside writeValue(value: any): void { this.internalControl.setValue(value); this.internalTextareaModel = value; } // Registry the change function to propagate internal model changes registerOnChange(fn: (_: any) => void): void { this.onChange = fn; } // Registry the touch function to propagate internal touch events TODO: make this function. registerOnTouched(fn: () => void): void { this.onTouched = fn; } setDisabledState(disable: boolean): void { this.isDisabled = disable; if (this.isDisabled && this.internalControl && this.internalControl.enabled) { this.internalControl.disable(); } else if (!this.isDisabled && this.internalControl && this.internalControl.disabled) { this.internalControl.enable(); } this._cd.markForCheck(); } showError(): boolean { return this.errorMessage !== undefined && (!this.internalControl.pristine || this.forceValidations) && !this.focus && !this.isDisabled; } /** Style functions */ onFocus(event: Event): void { this.focus = true; this.showErrorValue = this.showError(); } onFocusOut(event: Event, emitEvent: boolean): void { this.focus = false; if (emitEvent) { this.blur.emit(); } this.showErrorValue = this.showError(); } displayResetButton(): boolean { return this.default !== undefined && this.internalControl.dirty && this.internalControl.value !== this.default; } resetToDefault(): void { this.writeValue(this.default); this._cd.markForCheck(); } // When status change call this function to check if have errors private checkErrors(control: FormControl): void { let errors: { [key: string]: any } = control.errors; this.errorMessage = this.getErrorMessage(errors); this.showErrorValue = this.showError(); this._cd.markForCheck(); } // Get error message in function of error list. private getErrorMessage(errors: { [key: string]: any }): string { if (!errors) { return undefined; } if (!this.errors) { return ''; } if (errors.hasOwnProperty('required')) { return this.errors.required || this.errors.generic || ''; } if (errors.hasOwnProperty('minlength')) { return this.errors.minLength || this.errors.generic || ''; } if (errors.hasOwnProperty('maxlength')) { return this.errors.maxLength || this.errors.generic || ''; } if (errors.hasOwnProperty('pattern')) { return this.errors.pattern || this.errors.generic || ''; } return ''; } }