projects/cobbler-frontend/src/app/common/multi-select/multi-select.component.ts

Implements

ControlValueAccessor Validator OnChanges

Metadata

Relationships

Index

Properties
Methods
Inputs

Inputs

availableOptions
Type : string[] | null
Default value : null
hint
Type : string
label
Type : string
Default value : ''
section
Type : string
Default value : ''

Methods

addOption
addOption()
Returns : void
buildFormGroup
buildFormGroup(options: string[], checked: unknown)
Parameters :
Name Type Optional Default value
options string[] No
checked unknown No false
Returns : void
changeValues
changeValues(event: MatCheckboxChange, option: string)
Parameters :
Name Type Optional
event MatCheckboxChange No
option string No
Returns : void
ngOnChanges
ngOnChanges(changes: SimpleChanges)
Parameters :
Name Type Optional
changes SimpleChanges No
Returns : void
registerOnChange
registerOnChange(fn: any)
Parameters :
Name Type Optional
fn any No
Returns : void
registerOnTouched
registerOnTouched(fn: any)
Parameters :
Name Type Optional
fn any No
Returns : void
registerOnValidatorChange
registerOnValidatorChange(fn: () => void)
Parameters :
Name Type Optional
fn function No
Returns : void
setDisabledState
setDisabledState(isDisabled: boolean)
Parameters :
Name Type Optional
isDisabled boolean No
Returns : void
updateFormGroup
updateFormGroup(options: string[], checked: unknown)
Parameters :
Name Type Optional Default value
options string[] No
checked unknown No false
Returns : void
validate
validate(control: AbstractControl)
Parameters :
Name Type Optional
control AbstractControl No
Returns : ValidationErrors | null
writeValue
writeValue(obj: string[])
Parameters :
Name Type Optional
obj string[] No
Returns : void

Properties

Private currentValue
Type : string[]
Default value : []
Readonly dialog
Type : unknown
Default value : inject(MatDialog)
isDisabled
Type : unknown
Default value : true
matSelectOptionsFormGroup
Type : FormGroup<{ }>
Default value : new FormGroup({})
multiSelectOptions
Type : Array<string>
Default value : []
onChange
Type : any
onTouched
Type : any
import {
  AfterViewInit,
  Component,
  ElementRef,
  inject,
  Input,
  OnChanges,
  OnInit,
  SimpleChanges,
  ViewChild,
} from '@angular/core';
import {
  AbstractControl,
  ControlValueAccessor,
  FormControl,
  FormGroup,
  NG_VALIDATORS,
  NG_VALUE_ACCESSOR,
  ReactiveFormsModule,
  ValidationErrors,
  Validator,
} from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import {
  MatCheckboxChange,
  MatCheckboxModule,
} from '@angular/material/checkbox';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatListModule } from '@angular/material/list';
import { MatSelectModule } from '@angular/material/select';
import { DialogTextInputComponent } from '../dialog-text-input/dialog-text-input.component';
import { DialogBoxSelectComponent } from '../dialog-box-select/dialog-box-select.component';
import { filter, fromEvent, Subject, takeUntil } from 'rxjs';
import { HelpButtonComponent } from '../help-button/help-button.component';

@Component({
  selector: 'cobbler-multi-select',
  imports: [
    MatFormFieldModule,
    MatSelectModule,
    ReactiveFormsModule,
    MatListModule,
    MatCheckboxModule,
    MatButtonModule,
    MatIconModule,
    MatCardModule,
    MatInputModule,
    HelpButtonComponent,
  ],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      multi: true,
      useExisting: MultiSelectComponent,
    },
    {
      provide: NG_VALIDATORS,
      multi: true,
      useExisting: MultiSelectComponent,
    },
  ],
  templateUrl: './multi-select.component.html',
  styleUrl: './multi-select.component.scss',
})
export class MultiSelectComponent
  implements ControlValueAccessor, Validator, OnChanges
{
  @Input() label = '';
  @Input() availableOptions: string[] | null = null; // options come from API response, user can't add any more (used for mgmt classes)
  @Input() section: string = '';
  @Input() hint?: string;

  multiSelectOptions: Array<string> = [];
  matSelectOptionsFormGroup: FormGroup<{}> = new FormGroup({});
  private currentValue: string[] = [];
  onChange: any;
  onTouched: any;
  readonly dialog = inject(MatDialog);
  isDisabled = true;

  ngOnChanges(changes: SimpleChanges): void {
    if (
      changes['availableOptions'] &&
      !changes['availableOptions'].firstChange
    ) {
      this.writeValue(this.currentValue);
    }
  }

  buildFormGroup(options: string[], checked = false): void {
    options.forEach((value) => {
      this.matSelectOptionsFormGroup.addControl(
        value,
        new FormControl({ value: checked, disabled: this.isDisabled }),
      );
    });
  }

  updateFormGroup(options: string[], checked = false): void {
    options.forEach((value) => {
      this.matSelectOptionsFormGroup.get(value).setValue(checked);
    });
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  setDisabledState(isDisabled: boolean): void {
    this.isDisabled = isDisabled;
    if (this.isDisabled) {
      this.matSelectOptionsFormGroup.disable();
    } else {
      this.matSelectOptionsFormGroup.enable();
    }
  }

  writeValue(obj: string[]): void {
    this.currentValue = obj || [];
    this.multiSelectOptions = this.currentValue;
    this.buildFormGroup(this.multiSelectOptions);
    this.multiSelectOptions.forEach((opt) => {
      this.matSelectOptionsFormGroup.get(opt)?.setValue(true);
    });
  }

  registerOnValidatorChange(fn: () => void): void {}

  validate(control: AbstractControl): ValidationErrors | null {
    return undefined;
  }

  changeValues(event: MatCheckboxChange, option: string) {
    if (!event.checked) {
      // Remove the checkbox if it's clicked
      this.matSelectOptionsFormGroup.removeControl(option);
      this.multiSelectOptions = this.multiSelectOptions.filter(
        (o) => o !== option,
      );
      this.currentValue = this.currentValue.filter((o) => o !== option);
    }

    const options = Object.keys(this.matSelectOptionsFormGroup.controls).filter(
      (key) => {
        const control = this.matSelectOptionsFormGroup.get(key);
        return control instanceof FormControl && control.value;
      },
    );

    this.onTouched(options);
    this.onChange(options);
  }

  addOption(): void {
    let dialogRef: MatDialogRef<any, any>;
    // Dialog with select for management class
    if (this.section === 'mgmt_classes') {
      dialogRef = this.dialog.open(DialogBoxSelectComponent, {
        data: {
          // Filter the array to avoid duplication of options
          options: (this.availableOptions ?? []).filter(
            (o) => !this.currentValue.includes(o) && o !== undefined,
          ),
        },
      });
    } else {
      dialogRef = this.dialog.open(DialogTextInputComponent);
    }

    dialogRef.afterClosed().subscribe((result) => {
      if (result) {
        const newOptions = Array.from(this.currentValue);
        newOptions.push(result);
        this.onChange(newOptions);
        this.onTouched();
        this.writeValue(newOptions);
      }
    });
  }
}
<mat-card appearance="outlined">
  <mat-card-header class="card-header">
    <mat-card-title>{{ label }}</mat-card-title>
    @if (hint) {
      <cobbler-help-button [hint]="hint" />
    }
  </mat-card-header>
  @if (multiSelectOptions.length === 0) {
    <p style="text-align: center" i18n="@@common.multi-select.empty">
      Empty list of options
    </p>
  } @else {
    <!-- TODO: See https://github.com/cobbler/cobbler-web/issues/270
    <mat-form-field style="margin: 0 1em">
      <mat-label>Search for option</mat-label>
      <input matInput type="text">
    </mat-form-field>
    -->
    <ng-container [formGroup]="matSelectOptionsFormGroup">
      @for (option of multiSelectOptions; track option) {
        <mat-checkbox
          (change)="changeValues($event, option)"
          [formControlName]="option"
          [value]="option"
          >{{ option }}</mat-checkbox
        >
      }
    </ng-container>
  }
  <button
    mat-button
    [disabled]="isDisabled"
    (click)="addOption()"
    i18n="@@common.multi-select.add-option"
  >
    Add option
  </button>
</mat-card>

./multi-select.component.scss

:host {
  display: block;
  margin-bottom: 16px;
}

.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""