Project

General

Profile

1
import {
2
  Component, ElementRef,
3
  EventEmitter, HostListener,
4
  Input,
5
  OnChanges,
6
  OnDestroy,
7
  OnInit,
8
  Output,
9
  SimpleChanges,
10
  ViewChild
11
} from "@angular/core";
12
import {AbstractControl, FormArray, FormControl} from "@angular/forms";
13
import {HelperFunctions} from "../../utils/HelperFunctions.class";
14
import {Observable, of, Subscription} from "rxjs";
15
import {MatSelect} from "@angular/material/select";
16
import {MatAutocompleteSelectedEvent} from "@angular/material/autocomplete";
17
import {map, startWith} from "rxjs/operators";
18
import {MatChipInputEvent} from "@angular/material/chips";
19

    
20

    
21
export interface Option {
22
  icon?: string,
23
  iconClass?: string,
24
  value: any,
25
  label: string
26
}
27

    
28
@Component({
29
  selector: '[dashboard-input]',
30
  template: `
31
    <div *ngIf="label && type != 'checkbox'"
32
         class="uk-text-bold uk-form-label uk-margin-small-bottom">{{label + (required ? ' *' : '')}}</div>
33
    <div *ngIf="hint" class="uk-margin-bottom uk-form-hint">{{hint}}</div>
34
    <div class="uk-grid uk-flex" [ngClass]="'uk-flex-' + flex" [class.uk-grid-small]="gridSmall" uk-grid>
35
      <ng-content></ng-content>
36
      <div [class.uk-hidden]="hideControl" class="uk-width-expand uk-position-relative"
37
           [class.uk-flex-first]="!extraLeft">
38
        <ng-template [ngIf]="icon && formControl.enabled">
39
          <span class="uk-text-muted" [ngClass]="iconLeft?('left'):'right'">
40
            <icon [name]="icon"></icon>
41
          </span>
42
        </ng-template>
43
        <ng-template [ngIf]="formControl.disabled">
44
          <span class="uk-text-muted left">
45
            <icon [name]="'lock'"></icon>
46
          </span>
47
        </ng-template>
48
        <ng-template [ngIf]="type === 'text'">
49
          <div [ngClass]="inputClass"
50
               [class.uk-form-danger]="formControl.invalid && formControl.touched"
51
               [attr.uk-tooltip]="formControl.disabled?'title: This field is not editable; pos: bottom-left':''">
52
            <input class="uk-input" [placeholder]="placeholder" [formControl]="formControl">
53
          </div>
54
        </ng-template>
55
        <ng-template [ngIf]="type === 'textarea'">
56
          <div [ngClass]="inputClass" class="uk-padding-remove-right"
57
               [class.uk-form-danger]="formControl.invalid && formControl.touched"
58
               [attr.uk-tooltip]="formControl.disabled?'title: This field is not editable; pos: bottom-left':''">
59
              <textarea class="uk-textarea"
60
                        [rows]="rows" [placeholder]="placeholder"
61
                        [formControl]="formControl">
62
              </textarea>
63
              <div class="tools" [class.focused]="focused">
64
                <ng-content select="[options]"></ng-content>
65
              </div>
66
          </div>
67
        </ng-template>
68
        <ng-template [ngIf]="type === 'select'">
69
          <div [ngClass]="inputClass"
70
               [attr.uk-tooltip]="formControl.disabled?'title: This field is not editable; pos: bottom-left':null"
71
               [class.clickable]="formControl.enabled"
72
               [class.uk-form-danger]="formControl.invalid && formControl.touched" (click)="openSelect()">
73
            <mat-form-field class="uk-width-1-1">
74
              <mat-select #select [required]="required" [value]="null"
75
                          (openedChange)="stopPropagation()" [formControl]="formControl"
76
                          [disableOptionCentering]="true">
77
                <mat-option *ngIf="placeholder" class="uk-hidden" [value]="''">{{placeholder}}</mat-option>
78
                <mat-option *ngFor="let option of options" [value]="option.value">
79
                  {{option.label}}
80
                </mat-option>
81
              </mat-select>
82
            </mat-form-field>
83
          </div>
84
        </ng-template>
85
        <ng-template [ngIf]="type === 'chips'">
86
          <div [ngClass]="inputClass"
87
               [attr.uk-tooltip]="formControl.disabled?'title: This field is not editable; pos: bottom-left':null"
88
               [class.clickable]="formControl.enabled"
89
               [class.uk-form-danger]="formControl.invalid && formControl.touched" (click)="openSelect()">
90
            <mat-form-field class="uk-width-1-1">
91
              <mat-chip-list #chipList>
92
                <mat-chip *ngFor="let chip of formAsArray.controls; let i=index" [selectable]="false"
93
                          [removable]="removable" [attr.uk-tooltip]="getLabel(chip.value)">
94
                  <span class="uk-width-expand uk-text-truncate" [class.uk-text-small]="smallChip">{{getLabel(chip.value)}}</span>
95
                  <icon name="remove_circle" class="mat-chip-remove" [ratio]="smallChip?0.8:1" (click)="removed(i)"></icon>
96
                </mat-chip>
97
                <div class="uk-width-expand uk-position-relative chip-input">
98
                  <input #searchInput class="uk-width-1-1" [formControl]="searchControl" [matAutocomplete]="auto"
99
                         [matChipInputFor]="chipList"
100
                         [matChipInputAddOnBlur]="addExtraChips && searchControl.value"
101
                         (matChipInputTokenEnd)="add($event)">
102
                  <div *ngIf="placeholder && !searchControl.value" class="placeholder uk-width-1-1"
103
                       (click)="searchInput.focus()">{{placeholder}}</div>
104
                </div>
105
              </mat-chip-list>
106
              <mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)" [class]="panelClass" [panelWidth]="panelWidth">
107
                <mat-option *ngFor="let option of filteredOptions | async" [value]="option.value">
108
                  {{option.label}}
109
                </mat-option>
110
              </mat-autocomplete>
111
            </mat-form-field>
112
          </div>
113
        </ng-template>
114
        <span *ngIf="formControl.invalid && formControl.errors.error"
115
              class="uk-text-danger input-message">{{formControl.errors.error}}</span>
116
        <span *ngIf="warning" class="uk-text-warning input-message">{{warning}}</span>
117
        <span *ngIf="note" class="input-message">{{note}}</span>
118
      </div>
119
    </div>
120
    <mat-checkbox *ngIf="type === 'checkbox'" [formControl]="formControl">{{label}}</mat-checkbox>
121
  `,
122
  styleUrls: ['input.component.css']
123
})
124
export class InputComponent implements OnInit, OnDestroy, OnChanges {
125
  /** Basic information */
126
  @Input('formInput') formControl: AbstractControl;
127
  @Input('type') type: 'text' | 'textarea' | 'select' | 'checkbox' | 'chips' = 'text';
128
  @Input('label') label: string;
129
  @Input('rows') rows: number = 3;
130
  /** Select | chips available options */
131
  @Input('options') options: Option[];
132
  @Input('hint') hint = null;
133
  @Input('placeholder') placeholder = '';
134
  @Input() inputClass: string = 'input-box';
135
  /** Extra element Right or Left of the input */
136
  @Input() extraLeft: boolean = true;
137
  @Input() gridSmall: boolean = false;
138
  @Input() hideControl: boolean = false;
139
  @Input() flex: 'middle' | 'top' | 'bottom' = 'middle';
140
  /** Icon Right or Left on the input */
141
  @Input() icon: string = null;
142
  @Input() iconLeft: boolean = false;
143
  /** Custom messages */
144
  @Input() warning: string = null;
145
  @Input() note: string = null;
146
  /** Chip options */
147
  @Input() removable: boolean = true;
148
  @Input() addExtraChips: boolean = false;
149
  @Input() smallChip: boolean = false;
150
  @Input() panelWidth: number = 300;
151
  @Input() panelClass: string = null;
152
  @Output() focusEmitter: EventEmitter<boolean> = new EventEmitter<boolean>();
153
  /** Internal basic information */
154
  public required: boolean = false;
155
  private initValue: any;
156
  /** Chips */
157
  public filteredOptions: Observable<Option[]>;
158
  public searchControl: FormControl;
159
  private subscriptions: any[] = [];
160
  @ViewChild('select') select: MatSelect;
161
  @ViewChild('searchInput') searchInput: ElementRef;
162
  focused: boolean = false;
163
  
164
  constructor(private elementRef: ElementRef) {
165
  }
166
  
167
  @HostListener('document:click', ['$event'])
168
  clickOut(event) {
169
    this.focused = !!this.elementRef.nativeElement.contains(event.target);
170
    this.focusEmitter.emit(this.focused);
171
  }
172
  
173
  ngOnInit(): void {
174
    this.reset();
175
  }
176
  
177
  ngOnChanges(changes: SimpleChanges) {
178
    if (changes.formControl) {
179
      this.reset();
180
    }
181
  }
182
  
183
  get formAsArray(): FormArray {
184
    return (<FormArray>this.formControl);
185
  }
186
  
187
  reset() {
188
    this.unsubscribe();
189
    this.initValue = HelperFunctions.copy(this.formControl.value);
190
    if (this.options && this.type === 'chips') {
191
      this.filteredOptions = of(this.options);
192
      this.searchControl = new FormControl('');
193
      this.filteredOptions = this.searchControl.valueChanges.pipe(startWith(''),
194
        map(option => this.filter(option)));
195
    }
196
    if (this.formControl && this.formControl.validator) {
197
      let validator = this.formControl.validator({} as AbstractControl);
198
      this.required = (validator && validator.required);
199
    }
200
    this.subscriptions.push(this.formControl.valueChanges.subscribe(value => {
201
      value = (value === '') ? null : value;
202
      if (this.initValue === value || (this.initValue === '' && value === null)) {
203
        this.formControl.markAsPristine();
204
      }
205
    }));
206
    if (!this.formControl.value) {
207
      this.formControl.setValue('');
208
    }
209
  }
210
  
211
  unsubscribe() {
212
    this.subscriptions.forEach(subscription => {
213
      if (subscription instanceof Subscription) {
214
        subscription.unsubscribe();
215
      }
216
    });
217
  }
218
  
219
  openSelect() {
220
    if (this.select) {
221
      this.select.open();
222
    }
223
  }
224
  
225
  ngOnDestroy(): void {
226
    this.unsubscribe();
227
  }
228
  
229
  stopPropagation() {
230
    event.stopPropagation();
231
  }
232
  
233
  removed(index: number) {
234
    this.formAsArray.removeAt(index);
235
    this.formAsArray.markAsDirty();
236
    this.searchControl.setValue('');
237
    this.searchInput.nativeElement.focus();
238
    this.searchInput.nativeElement.value = '';
239
    this.stopPropagation();
240
  }
241
  
242
  selected(event: MatAutocompleteSelectedEvent): void {
243
    this.formAsArray.push(new FormControl(event.option.value));
244
    this.formAsArray.markAsDirty();
245
    this.searchControl.setValue('');
246
    this.searchInput.nativeElement.focus();
247
    this.searchInput.nativeElement.value = '';
248
    this.stopPropagation();
249
  }
250
  
251
  private filter(value: string): Option[] {
252
    let options = this.options.filter(option => !this.formAsArray.value.find(value => option.value === value));
253
    if (!value || value.length == 0) {
254
      return [];
255
    }
256
    const filterValue = value.toString().toLowerCase();
257
    return options.filter(option => option.label.toLowerCase().indexOf(filterValue) != -1);
258
  }
259
  
260
  add(event: MatChipInputEvent) {
261
    if (this.addExtraChips && event.value) {
262
      this.stopPropagation();
263
      this.formAsArray.push(new FormControl(event.value));
264
      this.formAsArray.markAsDirty();
265
      this.searchControl.setValue('');
266
      this.searchInput.nativeElement.value = '';
267
    }
268
  }
269
  
270
  getLabel(value: any) {
271
    let option = this.options.find(option => option.value === value);
272
    return (option) ? option.label : value;
273
  }
274
}
(2-2/3)