@WidgetComponent

nm-dynamic-form-fields-component

File

src/app/shared/widgets/dynamic-form/dynamic-form-component/dynamic-form-fields.component.ts

Implements

OnDestroy

Metadata

changeDetection ChangeDetectionStrategy.OnPush
selector nm-dynamic-form-fields-component
styleUrls dynamic-form-fields.component.scss
templateUrl ./dynamic-form-fields.component.html

Index

Widget inputs
Widget outputs
Properties
Methods
Inputs
Outputs

Constructor

constructor(cdr: ChangeDetectorRef, widgetFrameService: WidgetframeService, currentLocaleService: CurrentLocaleService, dateAdapter: DateAdapter, owldateTimeAdapter: DateTimeAdapter, translateService: TranslateService, owlDateTimeIntl: OwlDateTimeIntl, dialogService: DialogService, appContext: AppContext, validationService: ValidationService)
Parameters :
Name Type Optional
cdr ChangeDetectorRef no
widgetFrameService WidgetframeService no
currentLocaleService CurrentLocaleService no
dateAdapter DateAdapter<Date> no
owldateTimeAdapter DateTimeAdapter<any> no
translateService TranslateService no
owlDateTimeIntl OwlDateTimeIntl no
dialogService DialogService no
appContext AppContext no
validationService ValidationService no

Inputs

addFields

Adds fields dynamically to the form.

configuration

Sets dynamic form configurations.

Type: DynamicFormConfiguration

dialogContext

Sets dialog context for field used with page-dialog type fields.

fields

Sets dynamic form fields.

Type: DynamicFormField[]

labelSetter

Sets field label.

lookupOptions

Sets lookup options for lookup types.

reloadLookup

Reloads field lookup options.

removeAllFields

Removes all fields dynamically from the form.

removeFields

Removes fields dynamically from the form.

reset

Resets all form fields.

resetFormFields

Resets all form fields.

resetLookup

Resets field lookup options.

setDisabled

Enables\Disables field.

setHidden

Shows\Hides field.

triggerValidation

Triggers validation for form fields.

valueSetter

Sets field value.

Outputs

droppedValue

Emits the dropped value when using drag and drop featuer.

$event type: EventEmitter
init

Emits when initialization is finished for all fields and emits also before form destroy.

$event type: EventEmitter
onClick

Emits when any field is clicked.

$event type: EventEmitter
onEnterPressed

Emits when enter is pressed.

$event type: EventEmitter
valid

Emits validation result when any field value changed.

$event type: EventEmitter
value

Emits when any field value changed.

$event type: EventEmitter

Methods

addChip
addChip(event: MatChipInputEvent, field: DynamicFormField)
Parameters :
Name Type Optional
event MatChipInputEvent no
field DynamicFormField no
Returns : void
Public booleanValueChanged
booleanValueChanged(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : void
Public changePassword
changePassword(field: )
Parameters :
Name Optional
field no
Returns : void
Public clearField
clearField(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : void
Private findField
findField(identifier: string)
Parameters :
Name Type Optional
identifier string no
Returns : any
Public getButtonActions
getButtonActions(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : Observable<Content[]>
Public getCategoryUrl
getCategoryUrl(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : any
Public getClass
getClass(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : any
Public getDescriptionField
getDescriptionField(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : string
Public getIdentifierField
getIdentifierField(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : string
Public getInteractions
getInteractions(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : literal type | null
Public getLocalizedValue
getLocalizedValue(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : string
Public getMenuActions
getMenuActions(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : Observable<Content[]>
getPreferredPosition
getPreferredPosition(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : any
handlePaste
handlePaste(event: ClipboardEvent)
Parameters :
Name Type Optional
event ClipboardEvent no
Returns : void
Public includeSubCategoriesValueChanged
includeSubCategoriesValueChanged(field: DynamicFormField, value: any)
Parameters :
Name Type Optional
field DynamicFormField no
value any no
Returns : void
Private initFields
initFields(fields: any[])
Parameters :
Name Type Optional
fields any[] no
Returns : void
Private initializeButtonActions
initializeButtonActions(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : void
Private initializeMenuActions
initializeMenuActions(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : void
Public loadLookupData
loadLookupData(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : void
ngOnDestroy
ngOnDestroy()
Returns : void
Public ngOnInit
ngOnInit()
Returns : void
onClick
onClick(event: , field: DynamicFormField)
Parameters :
Name Type Optional
event no
field DynamicFormField no
Returns : void
onEditorValueChange
onEditorValueChange(field: DynamicFormField, value: any)
Parameters :
Name Type Optional
field DynamicFormField no
value any no
Returns : void
onEnterPressed
onEnterPressed(event: )
Parameters :
Name Optional
event no
Returns : void
Public onExternalModelChange
onExternalModelChange(field: DynamicFormField, value: any)
Parameters :
Name Type Optional
field DynamicFormField no
value any no
Returns : void
Public onFileUpload
onFileUpload(field: , uploadedFile: any)
Parameters :
Name Type Optional
field no
uploadedFile any no
Returns : void
onFocusIn
onFocusIn(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : void
onFocusOut
onFocusOut(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : void
Public onItemDropped
onItemDropped(ev: , field: )
Parameters :
Name Optional
ev no
field no
Returns : void
onKeyPressed
onKeyPressed(event: KeyboardEvent, field: )
Parameters :
Name Type Optional
event KeyboardEvent no
field no
Returns : boolean
onKeyPressedDatePicker
onKeyPressedDatePicker(event: )
Parameters :
Name Optional
event no
Returns : void
onKeyPressedDateTimePicker
onKeyPressedDateTimePicker(event: )
Parameters :
Name Optional
event no
Returns : void
onTriggerValidation
onTriggerValidation()
Returns : void
onValueChange
onValueChange(field: DynamicFormField, isEdit: boolean)
Parameters :
Name Type Optional Default value
field DynamicFormField no
isEdit boolean no true
Returns : void
Public openPageDialog
openPageDialog(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : void
Public openSelectDialog
openSelectDialog(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : void
Public openUserRightDialog
openUserRightDialog(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : void
Public openWorklistDialog
openWorklistDialog(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : void
Private pageDialog
pageDialog(field: DynamicFormField, module: string, identifier: string, context: any, minWidth: string)
Parameters :
Name Type Optional
field DynamicFormField no
module string no
identifier string no
context any no
minWidth string no
Returns : void
removeChip
removeChip(chip: any, field: DynamicFormField)
Parameters :
Name Type Optional
chip any no
field DynamicFormField no
Returns : void
resetDateTime
resetDateTime(field: , event: )
Parameters :
Name Optional
field no
event no
Returns : void
Private setFieldDefaultValue
setFieldDefaultValue(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : void
Private setFieldDescription
setFieldDescription(field: DynamicFormField, value: any)
Parameters :
Name Type Optional
field DynamicFormField no
value any no
Returns : void
Private setLookupOptions
setLookupOptions(field: , value: )
Parameters :
Name Optional
field no
value no
Returns : void
Private updateLocale
updateLocale(locale: string, fields: DynamicFormField[])
Parameters :
Name Type Optional
locale string no
fields DynamicFormField[] no
Returns : void
Private validateCategorySelector
validateCategorySelector(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : boolean
Private validateFields
validateFields()
Returns : boolean
Private validateLocalizedText
validateLocalizedText(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : boolean
Private validatePasswords
validatePasswords(field: DynamicFormField)
Parameters :
Name Type Optional
field DynamicFormField no
Returns : boolean
Private validateSourceCodeEditor
validateSourceCodeEditor(field: )
Parameters :
Name Optional
field no
Returns : any

Properties

Public addFieldsChannel
addFieldsChannel:
Default value : new Subject<DynamicFormField[]>()
Public buttonActions
buttonActions: literal type
Type : literal type
Default value : {}
Public currentLocale
currentLocale: any
Type : any
datePicker
datePicker: OwlDateTimeComponent<Date>
Type : OwlDateTimeComponent<Date>
Decorators : ViewChild
dateTimePicker
dateTimePicker: OwlDateTimeComponent<any>
Type : OwlDateTimeComponent<any>
Decorators : ViewChild
Public description
description: object
Type : object
Default value : {}
Public dialogContextChannel
dialogContextChannel:
Default value : new Subject<{ field: string; context: any }>()
Public dialogSelectedItems
dialogSelectedItems: object
Type : object
Default value : {}
Public editorConfig
editorConfig: any
Type : any
Default value : DEFAULT_TEXT_EDITOR_CONFIG
Public labelSetterChannel
labelSetterChannel:
Default value : new Subject<{ field: string; value: any }>()
Public lookupOptions
lookupOptions: object
Type : object
Default value : {}
Public lookupOptionsInputChannel
lookupOptionsInputChannel:
Default value : new Subject<any>()
Public menuActions
menuActions: literal type
Type : literal type
Default value : {}
Public passwordConfirmation
passwordConfirmation: object
Type : object
Default value : {}
Public reloadLookupChannel
reloadLookupChannel:
Default value : new Subject<string>()
Public removeAllFieldsChannel
removeAllFieldsChannel:
Default value : new Subject<any>()
Public removeFieldsChannel
removeFieldsChannel:
Default value : new Subject<string[]>()
Public resetChannel
resetChannel:
Default value : new Subject<any>()
Public resetDateValue
resetDateValue:
Default value : new Subject<any>()
Public resetFormFieldsChannel
resetFormFieldsChannel:
Default value : new Subject<any>()
Public resetLookupChannel
resetLookupChannel:
Default value : new Subject<string>()
Public separatorKeysCodes
separatorKeysCodes: number[]
Type : number[]
Default value : [ENTER, COMMA]
Public setDisabledChannel
setDisabledChannel:
Default value : new Subject<{ field: string; value: boolean }>()
Public setHiddenChannel
setHiddenChannel:
Default value : new Subject<{ field: string; value: boolean }>()
Public showPassword
showPassword: boolean
Type : boolean
Default value : false
Public sourceCodeValidations
sourceCodeValidations: object
Type : object
Default value : {}
Public triggerValidationChannel
triggerValidationChannel:
Default value : new Subject()
Public unsubscribe
unsubscribe:
Default value : NgUnsubscribe.create()
Public value
value: object
Type : object
Default value : {}
Public valueSetterChannel
valueSetterChannel:
Default value : new Subject<{ field: string; value: any }>()

Accessors

lookupOptionsInput
setlookupOptionsInput(value: )

Sets lookup options for lookup types.

Parameters :
Name Optional
value no
Returns : void
resetLookup
setresetLookup(value: )

Resets field lookup options.

Parameters :
Name Optional
value no
Returns : void
valueSetter
setvalueSetter(value: )

Sets field value.

Parameters :
Name Optional
value no
Returns : void
labelSetter
setlabelSetter(value: )

Sets field label.

Parameters :
Name Optional
value no
Returns : void
setDisabled
setsetDisabled(value: )

Enables\Disables field.

Parameters :
Name Optional
value no
Returns : void
dialogContext
setdialogContext(value: )

Sets dialog context for field used with page-dialog type fields.

Parameters :
Name Optional
value no
Returns : void
resetFormFields
setresetFormFields(value: )

Resets all form fields.

Parameters :
Name Optional
value no
Returns : void
setHidden
setsetHidden(value: )

Shows\Hides field.

Parameters :
Name Optional
value no
Returns : void
reset
setreset(value: )

Resets all form fields.

Parameters :
Name Optional
value no
Returns : void
reloadLookup
setreloadLookup(value: )

Reloads field lookup options.

Parameters :
Name Optional
value no
Returns : void
addFields
setaddFields(value: )

Adds fields dynamically to the form.

Parameters :
Name Optional
value no
Returns : void
removeFields
setremoveFields(value: )

Removes fields dynamically from the form.

Parameters :
Name Optional
value no
Returns : void
removeAllFields
setremoveAllFields(value: )

Removes all fields dynamically from the form.

Parameters :
Name Optional
value no
Returns : void
triggerValidation
settriggerValidation(value: )

Triggers validation for form fields.

Parameters :
Name Optional
value no
Returns : void
import {
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  OnDestroy,
  Input,
  Output,
  EventEmitter,
  ViewChild,
} from "@angular/core";
import { EMPTY, Observable, Subject } from "rxjs";
import { NgUnsubscribe } from "../../../ng-unsubscribe";
import { map, takeUntil, withLatestFrom } from "rxjs/operators";
import { WidgetframeService } from "../../widgetframe/widgetframe.service";
import { SelectItemsDialogComponent } from "../../../components/select-items-dialog/select-items-dialog.component";
import { CurrentLocaleService } from "../../../components/i18n/currentLocale.service";
import { DEFAULT_TEXT_EDITOR_CONFIG } from "../../../components/tiny-text-editor/tiny-text-editor.component";
import { BaseConfiguration } from "../../widgetframe/widgetframe.component";
import { DataValidation } from "../../../components/validation/validation.service";
import { ValidationError } from "../../../components/validation/validators";
import { TranslateService } from "@ngx-translate/core";
import { OwlDateTimeComponent, OwlDateTimeIntl } from "ng-pick-datetime";
import { DateAdapter } from "@angular/material/core";
import { getOrDefault } from "../../widget.configuration";
import {
  angularWidgetBridgeInput,
  stripPasteContent,
} from "../../../components/util/util.service";
import { PageDialogComponent } from "../../../components/dialog/page-dialog.component";
import { COMMA, ENTER } from "@angular/cdk/keycodes";
import { MatChipInputEvent } from "@angular/material/chips";
import { AppContext } from "../../../components/app-context/app.context";
import { Content, Selectors } from "../../../components/app-context/api";
import { filter } from "rxjs/operators";
import { ValidationService } from "../../../components/validation/validation.service";
import { DateTimeAdapter } from "ng-pick-datetime";
import { WidgetForPipe } from "../../../widgets/container/widget-for.pipe";
import { DialogService } from "../../../components/dialog";

declare var contextPath: string;

@Component({
  selector: "nm-dynamic-form-fields-component",
  templateUrl: "./dynamic-form-fields.component.html",
  styleUrls: ["./dynamic-form-fields.component.scss"],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DynamicFormFieldsComponent implements OnDestroy {
  public unsubscribe = NgUnsubscribe.create();

  public editorConfig: any = DEFAULT_TEXT_EDITOR_CONFIG;

  public lookupOptions = {};
  public dialogSelectedItems = {};
  public value = {};
  public description = {};
  public passwordConfirmation = {};
  public sourceCodeValidations = {};

  public menuActions: { [key: string]: Observable<Content[]> } = {};
  public buttonActions: { [key: string]: Observable<Content[]> } = {};

  public resetDateValue = new Subject<any>();
  public showPassword: boolean = false;
  public separatorKeysCodes: number[] = [ENTER, COMMA];
  public currentLocale: any;

  /**
   * Sets dynamic form fields.
   */
  @Input()
  public fields: DynamicFormField[];

  /**
   * Sets dynamic form configurations.
   */
  @Input()
  public configuration: DynamicFormConfiguration;

  /**
   * Sets lookup options for lookup types.
   */
  @Input("lookupOptions")
  public set lookupOptionsInput(value) {
    angularWidgetBridgeInput(
      value,
      this.lookupOptionsInputChannel,
      this.unsubscribe
    );
  }

  /**
   * Resets field lookup options.
   */
  @Input("resetLookup")
  public set resetLookup(value) {
    angularWidgetBridgeInput(value, this.resetLookupChannel, this.unsubscribe);
  }

  /**
   * Sets field value.
   */
  @Input("valueSetter")
  public set valueSetter(value) {
    angularWidgetBridgeInput(value, this.valueSetterChannel, this.unsubscribe);
  }

  /**
   * Sets field label.
   */
  @Input("labelSetter")
  public set labelSetter(value) {
    angularWidgetBridgeInput(value, this.labelSetterChannel, this.unsubscribe);
  }

  /**
   * Enables\Disables field.
   */
  @Input("setDisabled")
  public set setDisabled(value) {
    angularWidgetBridgeInput(value, this.setDisabledChannel, this.unsubscribe);
  }

  /**
   * Sets dialog context for field used with page-dialog type fields.
   */
  @Input("dialogContext")
  public set dialogContext(value) {
    angularWidgetBridgeInput(
      value,
      this.dialogContextChannel,
      this.unsubscribe
    );
  }

  /**
   * Resets all form fields.
   */
  @Input("resetFormFields")
  public set resetFormFields(value) {
    angularWidgetBridgeInput(
      value,
      this.resetFormFieldsChannel,
      this.unsubscribe
    );
  }

  /**
   * Shows\Hides field.
   */
  @Input("setHidden")
  public set setHidden(value) {
    angularWidgetBridgeInput(value, this.setHiddenChannel, this.unsubscribe);
  }

  /**
   * Resets all form fields.
   */
  @Input("reset")
  public set reset(value) {
    angularWidgetBridgeInput(value, this.resetChannel, this.unsubscribe);
  }

  /**
   * Reloads field lookup options.
   */
  @Input("reloadLookup")
  public set reloadLookup(value) {
    angularWidgetBridgeInput(value, this.reloadLookupChannel, this.unsubscribe);
  }

  /**
   * Adds fields dynamically to the form.
   */
  @Input("addFields")
  public set addFields(value) {
    angularWidgetBridgeInput(value, this.addFieldsChannel, this.unsubscribe);
  }

  /**
   * Removes fields dynamically from the form.
   */
  @Input("removeFields")
  public set removeFields(value) {
    angularWidgetBridgeInput(value, this.removeFieldsChannel, this.unsubscribe);
  }

  /**
   * Removes all fields dynamically from the form.
   */
  @Input("removeAllFields")
  public set removeAllFields(value) {
    angularWidgetBridgeInput(
      value,
      this.removeAllFieldsChannel,
      this.unsubscribe
    );
  }

  /**
   * Triggers validation for form fields.
   */
  @Input("triggerValidation")
  public set triggerValidation(value) {
    angularWidgetBridgeInput(
      value,
      this.triggerValidationChannel,
      this.unsubscribe
    );
  }

  @ViewChild("datepicker", { static: false })
  datePicker: OwlDateTimeComponent<Date>;
  @ViewChild("datetimepicker", { static: false })
  dateTimePicker: OwlDateTimeComponent<any>;

  public lookupOptionsInputChannel = new Subject<any>();
  public resetLookupChannel = new Subject<string>();
  public reloadLookupChannel = new Subject<string>();
  public valueSetterChannel = new Subject<{ field: string; value: any }>();
  public labelSetterChannel = new Subject<{ field: string; value: any }>();
  public setDisabledChannel = new Subject<{ field: string; value: boolean }>();
  public dialogContextChannel = new Subject<{ field: string; context: any }>();
  public resetFormFieldsChannel = new Subject<any>();
  public setHiddenChannel = new Subject<{ field: string; value: boolean }>();
  public resetChannel = new Subject<any>();
  public addFieldsChannel = new Subject<DynamicFormField[]>();
  public removeFieldsChannel = new Subject<string[]>();
  public removeAllFieldsChannel = new Subject<any>();
  public triggerValidationChannel = new Subject();

  /**
   * Emits when any field value changed.
   */
  @Output("value")
  public valueOutputEmitter = new EventEmitter<any>();

  /**
   * Emits validation result when any field value changed.
   */
  @Output("valid")
  public validOutputEmitter = new EventEmitter<any>();

  /**
   * Emits when any field is clicked.
   */
  @Output("onClick")
  public clickOutputEmitter = new EventEmitter<any>();

  /**
   * Emits when enter is pressed.
   */
  @Output("onEnterPressed")
  public enterPressedOutputEmitter = new EventEmitter<any>();

  /**
   * Emits when initialization is finished for all fields and emits also before form destroy.
   */
  @Output("init")
  public initOutputEmitter = new EventEmitter<any>();

  /**
   * Emits the dropped value when using drag and drop featuer.
   */
  @Output("droppedValue")
  public droppedValueEmitter = new EventEmitter<any>();

  constructor(
    protected cdr: ChangeDetectorRef,
    protected widgetFrameService: WidgetframeService,
    protected currentLocaleService: CurrentLocaleService,
    protected dateAdapter: DateAdapter<Date>,
    protected owldateTimeAdapter: DateTimeAdapter<any>,
    protected translateService: TranslateService,
    protected owlDateTimeIntl: OwlDateTimeIntl,
    protected dialogService: DialogService,
    protected appContext: AppContext,
    protected validationService: ValidationService
  ) {}

  public ngOnInit() {
    if (!this.fields) {
      return;
    }

    this.initFields(this.fields);

    this.reloadLookupChannel
      .pipe(takeUntil(this.unsubscribe))
      .subscribe((identifier) => {
        let field = this.fields.find((entry) => entry.field === identifier);
        if (!field) {
          field = this.fields.find((entry) => entry.type === "multi-toggler");
          if (field) {
            field = field.items.find((item) => item.field === identifier);
          }
          if (!field) {
            console.error(
              `[ReloadLookup] Cant find field with identifier ${identifier}`
            );
            return;
          }
        }
        this.loadLookupData(field);
      });

    this.addFieldsChannel
      .pipe(
        withLatestFrom(this.currentLocaleService.getCurrentLocale()),
        takeUntil(this.unsubscribe)
      )
      .subscribe((data) => {
        const fields = data[0];
        const locale = data[1];
        this.fields.push(...fields);
        this.initFields(fields);
        this.updateLocale(locale, fields);
        this.cdr.markForCheck();
      });

    this.valueSetterChannel
      .pipe(takeUntil(this.unsubscribe))
      .subscribe((data) => {
        if (!this.value) {
          this.value = {};
        }

        const field = this.findField(data.field);
        if (!field) {
          return;
        }

        this.setFieldDescription(field, data.value);
        this.value[field.field] = data.value;
        this.onValueChange(field, false);
        this.cdr.markForCheck();
      });

    this.labelSetterChannel
      .pipe(takeUntil(this.unsubscribe))
      .subscribe((data) => {
        const field = this.findField(data.field);
        if (!field) {
          return;
        }

        if (field.label != null) {
          field.label = data.value;
        }

        this.cdr.markForCheck();
      });

    this.setDisabledChannel
      .pipe(takeUntil(this.unsubscribe))
      .subscribe((data) => {
        const field = this.findField(data.field);
        if (!field) {
          return;
        }

        field.disabled = data.value;
        this.cdr.markForCheck();
      });

    this.dialogContextChannel
      .pipe(takeUntil(this.unsubscribe))
      .subscribe((data) => {
        const field = this.findField(data.field);
        if (!field) {
          return;
        }

        field.dialogContext = data.context;
        this.cdr.markForCheck();
      });

    this.resetFormFieldsChannel
      .pipe(takeUntil(this.unsubscribe))
      .subscribe((data) => {
        this.fields.forEach((field) => {
          if (field.type === "category-selector") {
            field.includeSubCategories = false;
            this.cdr.markForCheck();
          }
        });
        Object.keys(this.value).forEach((field) => {
          this.value[field] = null;
          this.description[field] = null;
          this.cdr.markForCheck();
          this.resetDateValue.next(true);
        });
        this.dialogSelectedItems = {};
      });

    this.setHiddenChannel
      .pipe(takeUntil(this.unsubscribe))
      .subscribe((data) => {
        const field = this.fields.find((entry) => entry.field === data.field);
        if (!field) {
          console.error("Unable to find field " + data.field);
          return;
        }
        field.hidden = data.value;

        this.cdr.markForCheck();
      });

    this.lookupOptionsInputChannel
      .pipe(takeUntil(this.unsubscribe))
      .subscribe((data) => {
        this.setLookupOptions(data.field, data.value);
      });

    this.resetChannel.pipe(takeUntil(this.unsubscribe)).subscribe((reset) => {
      this.value = {};
      this.dialogSelectedItems = {};
      this.description = {};
      this.cdr.markForCheck();
    });

    this.resetLookupChannel
      .pipe(takeUntil(this.unsubscribe))
      .subscribe((data) => {
        this.lookupOptions[data] = [];
        this.value[data] = null;
        this.cdr.markForCheck();
      });

    this.removeFieldsChannel
      .pipe(takeUntil(this.unsubscribe))
      .subscribe((fields) => {
        this.fields = this.fields.filter((f) => fields.indexOf(f.field) === -1);
        fields.forEach((f) => delete this.value[f]);
        this.cdr.markForCheck();
      });

    this.removeAllFieldsChannel
      .pipe(takeUntil(this.unsubscribe))
      .subscribe((data) => {
        this.fields = [];
        this.value = {};
        this.description = {};
        this.cdr.markForCheck();
      });

    this.triggerValidationChannel
      .pipe(takeUntil(this.unsubscribe))
      .subscribe((data) => this.onTriggerValidation());

    this.currentLocaleService
      .getCurrentLocale()
      .pipe(takeUntil(this.unsubscribe))
      .subscribe((locale) => {
        this.updateLocale(locale, this.fields);
      });

    this.initOutputEmitter.next({ init: true });
  }

  private findField(identifier: string) {
    let field = this.fields.find((entry) => entry.field === identifier);
    if (!field) {
      field = this.fields.find((entry) => entry.type === "multi-toggler");
      if (field) {
        field = field.items.find((item) => item.field === identifier);
      }
      if (!field) {
        console.error(
          `Unable to find field with identifier ${identifier} `,
          this.fields
        );
        return;
      }
    }

    return field;
  }

  private initFields(fields: any[]) {
    this.fields.sort((a, b) => {
      if (!a.order) {
        a.order = 0;
      }
      if (!b.order) {
        b.order = 0;
      }
      return a.order - b.order;
    });

    fields.forEach((field) => {
      if (field.chipRemovable === undefined || field.chipRemovable === null) {
        field.chipRemovable = true;
      }

      if (field.clearable == null) {
        field.clearable = true;
      }

      if (field.type === "date") {
        if (!field.dateFormat) {
          field.dateFormat = "dd.mm.yyyy";
        }
      } else if (field.type === "text") {
        if (field.height) {
          //This will only work if all editors are supposed to have the same height..
          this.editorConfig = Object.assign(
            { height: field.height },
            this.editorConfig
          );
        }
      } else if (
        field.type === "lookup" ||
        field.type === "multi-lookup" ||
        field.type === "context"
      ) {
        this.loadLookupData(field);
      } else if (field.type === "category-selector") {
        if (field.publication) {
          this.value[field.field] = {
            publication: field.publication,
          };
        }
      } else if (field.type === "boolean-indeterminate") {
        this.value[field.field] = field.defaultValue;
      } else if (field.type === "password-confirmation") {
        if (field.hideConfirmationField === undefined) {
          field.hideConfirmationField = true;
        }
      } else if (field.type === "radio") {
        this.loadLookupData(field);
      } else if (field.type === "source-code-editor") {
        this.value[field.field] = field.defaultValue;
      }

      field.showHint = !field.hintOnFocus;

      this.setFieldDefaultValue(field);
      this.onValueChange(field, false);
    });

    this.currentLocaleService.getCurrentLocale().subscribe((locale) => {
      this.currentLocale = locale;
    });
  }

  private setFieldDefaultValue(field: DynamicFormField) {
    if (!field.defaultValue) {
      return;
    }

    this.value[field.field] = field.defaultValue;
  }

  private setFieldDescription(field: DynamicFormField, value: any) {
    if (!field) {
      return;
    }

    if (field.type === "user-right") {
      this.description[field.field] = value
        ? value.identifier + " (" + value.pimRef + ")"
        : null;
    } else if (field.type === "worklist") {
      this.description[field.field] = value ? value.name : null;
    } else if (field.type === "page-dialog") {
      this.description[field.field] = value
        ? value[field.dialogDescription]
        : null;
    }
  }

  private setLookupOptions(field, value) {
    this.lookupOptions[field] = value;
    this.cdr.markForCheck();
  }

  public getClass(field: DynamicFormField) {
    return String(field.field);
  }

  public getIdentifierField(field: DynamicFormField) {
    if (field.identifierField) {
      return field.identifierField;
    }
    return "identifier";
  }

  public getDescriptionField(field: DynamicFormField) {
    if (field.descriptionField) {
      return field.descriptionField;
    }
    return "description";
  }

  public loadLookupData(field: DynamicFormField) {
    if (field.lookupUrl) {
      this.widgetFrameService.getData(field.lookupUrl).subscribe((data) => {
        if (data._embedded) {
          if (data.type) {
            data = data._embedded[data.type];
          } else {
            const key = Object.keys(data._embedded)[0];
            data = data._embedded[key];
          }
        }
        if (field.lookupAllowEmpty) {
          const emptyEntry = {};
          emptyEntry[this.getIdentifierField(field)] = "";
          emptyEntry[this.getDescriptionField(field)] = "";
          data.unshift(emptyEntry);
        }
        this.setLookupOptions(field.field, data);
      });
    } else if (field.lookupOptions) {
      this.setLookupOptions(field.field, field.lookupOptions);
    }
  }

  ngOnDestroy(): void {
    this.initOutputEmitter.next({ init: false, value: this.value });
    this.unsubscribe.destroy();
  }

  onValueChange(field: DynamicFormField, isEdit: boolean = true) {
    this.onTriggerValidation();

    this.valueOutputEmitter.next({
      value: this.value,
      lastChange: field.field,
      isEdit: isEdit,
    });
  }

  onTriggerValidation() {
    const valid = this.validateFields();
    this.validOutputEmitter.next(valid);
  }

  onEditorValueChange(field: DynamicFormField, value: any) {
    this.value[field.field] = value.value;
    this.sourceCodeValidations[field.field] = value.isValid;
    this.onValueChange(field);
  }

  private validateFields(): boolean {
    this.validationService.validateDynamicFormFields(this.fields, this.value);

    let valid = true;
    for (const field of this.fields) {
      // the validation service updates the errors to field.errors
      // if field.errors is undefined or has no entries, the field is valid
      if (Array.isArray(field.errors) && field.errors.length > 0) {
        valid = false;
        break;
      }

      if (!field.required) {
        continue;
      }

      const value = this.value[field.field];

      if (
        value === undefined ||
        value === null ||
        value === "" ||
        value.length === 0
      ) {
        valid = false;
        break;
      }

      if (!this.validateLocalizedText(field)) {
        valid = false;
        break;
      }

      if (!this.validateCategorySelector(field)) {
        valid = false;
        break;
      }

      if (!this.validatePasswords(field)) {
        valid = false;
        break;
      }

      if (!this.validateSourceCodeEditor(field)) {
        valid = false;
        break;
      }
    }

    return valid;
  }

  private validateSourceCodeEditor(field) {
    if (field.type !== "source-code-editor") {
      return true;
    }
    return this.sourceCodeValidations[field.field];
  }

  private validateLocalizedText(field: DynamicFormField): boolean {
    if (!field.localizedText) {
      return true;
    }

    const value = this.value[field.field];

    let validLocalizedText = false;
    Object.keys(value).forEach((locale) => {
      if (locale && value[locale].trim()) {
        validLocalizedText = true;
      }
    });

    return validLocalizedText;
  }

  private validateCategorySelector(field: DynamicFormField): boolean {
    if (field.type !== "category-selector") {
      return true;
    }

    const value = this.value[field.field];

    if (!value.publication || !value.category || value.category.length === 0) {
      return false;
    }

    return true;
  }

  private validatePasswords(field: DynamicFormField): boolean {
    if (field.type !== "password") {
      return true;
    }

    const password = this.value[field.field];
    const confirmPassword = this.passwordConfirmation[field.field];

    if (!password || !confirmPassword) {
      return false;
    }

    return password === confirmPassword;
  }

  onClick(event, field: DynamicFormField) {
    event.stopPropagation();
    this.clickOutputEmitter.next(field);
  }

  onEnterPressed(event) {
    if (event.target) {
      // due to ngModelOptions: force blur event to trigger setting of asset-search´s formValue
      event.target.blur();
      this.enterPressedOutputEmitter.next(event.target.value);
    }
  }

  onKeyPressedDatePicker(event) {
    const key = event.key.toLowerCase();
    switch (key) {
      case "space":
      case "spacebar":
      case " ":
      case "enter":
      case "down":
      case "arrowdown":
        this.datePicker.open();
        this.onEnterPressed(event);
    }
  }

  onKeyPressedDateTimePicker(event) {
    const key = event.key.toLowerCase();
    switch (key) {
      case "space":
      case "spacebar":
      case " ":
      case "enter":
      case "down":
      case "arrowdown":
        this.dateTimePicker.open();
        this.onEnterPressed(event);
    }
  }

  public onExternalModelChange(field: DynamicFormField, value: any) {
    this.value[field.field] = value;
    this.onValueChange(field);
  }

  public openSelectDialog(field: DynamicFormField) {
    const dialogConfig = {
      minWidth: "900px",
      minHeight: "750px",
      data: {
        title: field.label,
        itemsUrl: field.lookupUrl,
        itemType: field.selectDialogItemType,
        itemTypeId: this.getIdentifierField(field),
        itemTypeDescription: this.getDescriptionField(field),
        additionalProperties: field.additionalProperties
          ? field.additionalProperties
          : [],
        preselectedItems: this.dialogSelectedItems[field.field],
        excludePreselected: false,
        allowNoSelection: true,
      },
    };

    let dialogRef = this.dialogService.open(
      SelectItemsDialogComponent,
      dialogConfig
    );

    dialogRef.afterClosed().subscribe((selectedItems) => {
      if (!selectedItems) {
        return;
      }
      if (selectedItems.length === 0) {
        this.value[field.field] = null;
        this.description[field.field] = null;
        this.dialogSelectedItems[field.field] = null;
      } else if (selectedItems.length > 0) {
        this.dialogSelectedItems[field.field] = selectedItems;
        let values = [];
        let descriptions = [];
        selectedItems.forEach((item) => {
          values.push(item[this.getIdentifierField(field)]);
          descriptions.push(item[this.getDescriptionField(field)]);
        });
        this.value[field.field] = values.toString();
        this.description[field.field] = descriptions.join(", ");
        this.onValueChange(field);
      }
      this.cdr.markForCheck();
    });
  }

  public openUserRightDialog(field: DynamicFormField) {
    const context = { userRightsUrl: field.url };
    this.pageDialog(field, "core", "select-userRight", context, "650px");
  }

  public openWorklistDialog(field: DynamicFormField) {
    this.pageDialog(field, "core", "select-worklist", null, "650px");
  }

  public openPageDialog(field: DynamicFormField) {
    this.pageDialog(
      field,
      field.dialogModule,
      field.dialogIdentifier,
      field.dialogContext,
      "900px"
    );
  }

  private pageDialog(
    field: DynamicFormField,
    module: string,
    identifier: string,
    context: any,
    minWidth: string
  ) {
    if (field.disabled) {
      return;
    }

    const dialogRef = this.dialogService.open(PageDialogComponent, {
      minWidth: minWidth,
      height: "700px",
      data: {
        title: field.label,
        acceptEnabled: true,
        customAcceptIcon: true,
        acceptIcon: "check",
        acceptText: "button.accept",
        cancelEnabled: true,
        cancelText: "button.cancel",
        module: module,
        identifier: identifier,
        context: context,
      },
    });

    dialogRef.afterClosed().subscribe((data) => {
      if (!data) {
        return;
      }

      this.setFieldDescription(field, data);
      this.value[field.field] = data;
      this.onValueChange(field);
      this.cdr.markForCheck();
    });
  }

  public clearField(field: DynamicFormField) {
    this.value[field.field] = null;
    this.description[field.field] = null;
    this.dialogSelectedItems[field.field] = null;

    this.onValueChange(field);
    this.cdr.markForCheck();
  }

  private updateLocale(locale: string, fields: DynamicFormField[]) {
    fields.forEach((field) => {
      if (field.translations) {
        const translations = field.translations;
        if (translations[locale]) {
          field.label = translations[locale];
        }
      }
      if (field.hintTranslations) {
        const translations = field.hintTranslations;
        if (translations[locale]) {
          field.hint = translations[locale];
        }
      }
    });
    this.dateAdapter.setLocale(locale);
    this.owldateTimeAdapter.setLocale(locale);
    this.owlDateTimeIntl.cancelBtnLabel =
      this.translateService.instant("button.cancel");
    this.owlDateTimeIntl.setBtnLabel =
      this.translateService.instant("button.accept");
  }

  public includeSubCategoriesValueChanged(field: DynamicFormField, value: any) {
    if (this.value[field.field]) {
      this.value[field.field].includeSubCategories = value;
    } else {
      this.value[field.field] = { includeSubCategories: value };
    }
    field.includeSubCategories = value;
  }

  public getCategoryUrl(field: DynamicFormField) {
    return getOrDefault(field.categoryUrl, "");
  }

  public booleanValueChanged(field: DynamicFormField) {
    switch (this.value[field.field]) {
      case true: {
        this.value[field.field] = false;
        break;
      }
      case false: {
        this.value[field.field] = null;
        break;
      }
      case undefined:
      case null: {
        this.value[field.field] = true;
        break;
      }
    }

    this.onValueChange(field);
  }

  addChip(event: MatChipInputEvent, field: DynamicFormField): void {
    const input = event.input;
    const value = event.value;

    if ((value || "").trim()) {
      if (this.value[field.field]) {
        this.value[field.field].push({
          identifier: value.trim(),
          description: value.trim(),
        });
      } else {
        this.value[field.field] = [
          { identifier: value.trim(), description: value.trim() },
        ];
      }

      this.onValueChange(field);
    }

    if (input) {
      input.value = "";
    }
  }

  removeChip(chip: any, field: DynamicFormField): void {
    if (!this.value[field.field]) {
      return;
    }

    const index = this.value[field.field].indexOf(chip);

    if (index >= 0) {
      this.value[field.field].splice(index, 1);
      this.onValueChange(field);
    }
  }

  public onItemDropped(ev, field) {
    const dropValueIdentifier = getOrDefault(
      field.dropValueIdentifier,
      "identifier"
    );
    const myValue = ev.dragData.source[dropValueIdentifier];
    this.droppedValueEmitter.emit(myValue);
    const cursorPosition = ev.owner.element.nativeElement.selectionStart;

    if (this.value[field.field]) {
      var firstHalf = this.value[field.field].substring(0, cursorPosition);
      if (firstHalf) {
        firstHalf += " ";
      }
      var secondHalf = this.value[field.field].substring(
        cursorPosition,
        this.value[field.field].length
      );
      if (secondHalf) {
        secondHalf = " " + secondHalf;
      }

      this.value[field.field] = firstHalf + myValue + secondHalf;
    } else {
      this.value[field.field] = myValue;
    }
    this.onValueChange(field);
  }

  public onFileUpload(field, uploadedFile: any) {
    if (uploadedFile) {
      this.value[field.field] = uploadedFile;
    } else {
      this.value[field.field] = null;
    }
    this.onValueChange(field);
  }

  public changePassword(field) {
    field.hideConfirmationField = !field.hideConfirmationField;
    field.required = !field.required;
    field.disabled = !field.disabled;
    if (field.hideConfirmationField) {
      this.value[field.field] = null;
      this.passwordConfirmation[field.field] = null;
    }
    this.onValueChange(field);
    this.clickOutputEmitter.next(field);
  }

  public getLocalizedValue(field: DynamicFormField): string {
    if (!field || this.value[field.field] == null) {
      return "";
    }

    if (field.localizedText) {
      return this.value[field.field][this.currentLocale];
    }
    return this.value[field.field];
  }

  private initializeMenuActions(field: DynamicFormField) {
    if (field.menuInteractions) {
      this.menuActions[field.field] = this.appContext.browserContext
        .subscribe(field.menuInteractions)
        .pipe(
          takeUntil(this.unsubscribe),
          filter((contents) => !!contents),
          map((contents) => {
            const interactions = contents
              .filter(
                (entry) =>
                  entry.showAsButton == null || entry.showAsButton === false
              )
              .sort((l, r) => (r.order || 0) - (l.order || 0));
            return interactions;
          })
        );
    } else {
      this.menuActions[field.field] = EMPTY;
    }
  }

  private initializeButtonActions(field: DynamicFormField) {
    if (field.menuInteractions) {
      this.buttonActions[field.field] = this.appContext.browserContext
        .subscribe(field.menuInteractions)
        .pipe(
          takeUntil(this.unsubscribe),
          filter((contents) => !!contents),
          map((contents) => {
            return contents
              .filter((entry) => entry?.showAsButton === true)
              .sort((l, r) => (r.order || 0) - (l.order || 0));
          })
        );
    } else {
      this.buttonActions[field.field] = EMPTY;
    }
  }

  public getMenuActions(field: DynamicFormField): Observable<Content[]> {
    if (this.menuActions[field.field]) {
      return this.menuActions[field.field];
    }

    if (field.menuInteractions) {
      this.initializeMenuActions(field);
    } else {
      this.menuActions[field.field] = EMPTY;
    }
    return this.menuActions[field.field];
  }

  public getButtonActions(field: DynamicFormField): Observable<Content[]> {
    if (this.buttonActions[field.field]) {
      return this.buttonActions[field.field];
    }

    if (field.menuInteractions) {
      this.initializeButtonActions(field);
    } else {
      this.buttonActions[field.field] = EMPTY;
    }

    return this.buttonActions[field.field];
  }

  public getInteractions(
    field: DynamicFormField
  ): { menu: Observable<Content[]>; buttons: Observable<Content[]> } | null {
    if (field.menuInteractions == null) {
      return null;
    }

    return {
      menu: this.getMenuActions(field),
      buttons: this.getButtonActions(field),
    };
  }

  onKeyPressed(event: KeyboardEvent, field) {
    if (field.tab && event.code !== "Tab") {
      event.preventDefault();
      return false;
    }
    return;
  }

  onFocusOut(field: DynamicFormField) {
    field.showHint = !field.hintOnFocus;
  }

  onFocusIn(field: DynamicFormField) {
    field.showHint = true;
  }

  getPreferredPosition(field: DynamicFormField) {
    return field.additionalProperties &&
      field.additionalProperties.preferredPosition
      ? field.additionalProperties.preferredPosition
      : ["right-below", "right", "bottom"];
  }

  resetDateTime(field, event) {
    event.stopPropagation();
    this.value[field.field] = null;
    this.onValueChange(field);
  }

  handlePaste(event: ClipboardEvent) {
    stripPasteContent(event);
  }
}

export interface DynamicFormConfiguration extends BaseConfiguration {
  /**
   * Array of dynamic form fields to be added in the form.
   */
  fields: DynamicFormField[];
}

export interface DynamicFormField {
  /**
   * Type of the field. supported types are: boolean, boolean-indeterminate, category-selector,
   * chip-list, date, date-time, file-upload, heading, lookup, multi-lookup,  number,
   * number-decimal, password, plain, select-dialog, text, textarea, toggler, multi-toggler, user-right, worklist, source-code-editor
   */
  type: string;

  /**
   * Unique field identifier.
   */
  field: string;

  /**
   * collection of all data for multi-components
   */
  items?: DynamicFormField[];

  /**
   * Localized key for field label
   */
  label: string;

  /**
   * URL to load lookup options from. used with these types: lookup, multi-lookup, select-dialog.
   * (See also lookupOptions)
   */
  lookupUrl?: string;

  /**
   * Array of static lookup options. used with these types: lookup, multi-lookup, select-dialog.
   * (See also lookupUrl)
   */
  lookupOptions?: any[];

  /**
   * Allows empty selection for lookup types. @default(false)
   */
  lookupAllowEmpty?: boolean;

  /**
   * Item type used for select-dialog type.
   */
  selectDialogItemType?: string;

  /**
   * Enables\Disables the field.
   */
  disabled?: boolean;

  /**
   * Makes the field required or optional.
   */
  required?: boolean;

  /**
   * Identifier field of the object. it is used with 'lookup' and 'multi-lookup' types
   * to define identifier field. @default(identifier)
   */
  identifierField?: string;

  /**
   * Description field of the object. it is used with 'lookup' and 'multi-lookup' types
   * to define description field. @default(description)
   */
  descriptionField?: string;

  /**
   * Field height. used with 'text' type.
   */
  height?: number;

  /**
   * Hides or shows the field.
   */
  hidden?: boolean;

  /**
   * Order of the field in the form. @default(0)
   */
  order?: number;

  /**
   * If not set the version select lookup will be shown,
   * otherwise the provided version will be used and the category select lookup only will be shown.
   * Used with type 'category-selector'.
   */
  publication?: string;

  /**
   * Allows multiple category selection. Used with type 'category-selector'. @default(false)
   */
  multiCategorySelect?: boolean;

  /**
   * URL used to load version from.
   * default is 'category-select-publications' for publications
   * and 'category-select-asset-trees' for asset-trees
   * Used with type 'category-selector'.
   */
  categoryUrl?: string;

  /**
   * Label translation in other locales.
   */
  translations?: any;

  /**
   * hint translation in other locales.
   */
  hintTranslations?: any;

  /**
   * Validations to be applied on field input.
   */
  validations?: DataValidation[];

  /**
   * Date format. used with 'date' type. @default(dd.mm.yyyy)
   */
  dateFormat?: string;

  /**
   * Shows\Hides include sub-categories checkbox. used with 'category-selector' type. @default(false)
   */
  showIncludeSubCategories?: boolean;

  /**
   * Includes sub-categories value. used with 'category-selector' type.
   */
  includeSubCategories?: boolean;

  /**
   * Applies channel types filter on versions. supported values are PURCHASE, SALES, ARCHIVE.
   * used with 'category-selector' type.
   */
  filterChannelTypes?: string[];

  /**
   * Array of additional properties to view in select dialog list. used with 'select-dialog' type.
   */
  additionalProperties?: any;

  /**
   * List folder type. used with 'worklist' type.
   */
  listfolderType?: string;

  /**
   * List folder data type. used with 'worklist' type.
   */
  dataType?: string;

  /**
   * Shows floating label for the field. @default(false)
   */
  floatingLabel?: boolean;

  /**
   * The target tab. used in dynamic forms with mutliple tabs.
   * supported tabs are localizedtext, listFolder, userRights
   */
  tab?: string;

  /**
   * This property shouldn't be added in configuration. It is only used internally.
   */
  value?: any;

  /**
   * Initial value of the field.
   */
  defaultValue?: any;

  /**
   * Makes the field Readonly or editable. @default(false)
   */
  readonly?: boolean;

  /**
   * Sets autosize property for type 'textarea'.
   */
  autosize?: boolean;

  /**
   * Shows clear selection button. used with 'lookup' and 'multi-lookup' types. @default(true)
   */
  clearable?: boolean;

  /**
   * Sets maximum characters length for text types.
   */
  maxLength?: number;

  /**
   * URL to load user rights. used with 'user-right' type.
   */
  url?: string;

  /**
   * Comma-separated list of supported file types. used with 'file-upload' type.
   */
  supportedFileTypes?: string;

  /**
   * File upload url. used with 'file-upload type.
   */
  fileUploadUrl?: string;

  /**
   * Sets field as localized text. @default(false)
   */
  localizedText?: boolean;

  /**
   * Identifiers of the interactions that are supposed to be loaded for the menu.
   */
  menuInteractions?: Selectors;

  /**
   * List of field validation errors.
   */
  errors?: ValidationError[];

  /**
   * Array of supported MIME types. used with 'file-upload' type.
   */
  supportedMimeTypes?: string[];

  /**
   * Makes chips removable in 'chip-list' type. @default(true)
   */
  chipRemovable?: boolean;

  /**
   * Shows\Hides edit password button. used with 'password' type. @default(false)
   */
  showEditpasswordButton?: boolean;

  /**
   * Hides\Shows second password confirmation field. used with 'password' type. @default(true)
   */
  hideConfirmationField?: boolean;

  /**
   * Localized key for password confirmation lable. used with 'password' type.
   */
  confirmationLabel?: string;

  /**
   * Auto focus field.
   */
  autofocus?: boolean;

  /**
   * Localized key for field hint.
   */
  hint?: string;

  /**
   * Shows hint on focus.
   */
  hintOnFocus?: boolean;

  /**
   * This property shouldn't be added in configuration. It is only used internally.
   */
  showHint?: boolean;

  /**
   * Identifier field that will be used to get the value while dropping it in the textarea field.
   */
  dropValueIdentifier?: string;

  /**
   * min rows for textarea field
   */
  autosizeMinRows?: string;

  /**
   * Source code programming language. It is used with type = 'source-code-editor'.
   */
  sourceCodeLanguage?: string;

  /**
   * Dialog module for page-dialog type.
   */
  dialogModule?: string;

  /**
   * Dialog identifier for page-dialog type.
   */
  dialogIdentifier?: string;

  /**
   * Dialog data for page-dialog type.
   */
  dialogContext?: any;

  /**
   * Dialog data property used to set the dialog description when closed for page-dialog type.
   */
  dialogDescription?: any;

  /**
   * Enable/Disable editor minimap for source-code-editor type.
   */
  enableMinimap?: boolean;

  /**
   * Triggers the display of Tiny editor implmentation in its onInit callback
   */
  showEditorOnInit?: boolean;
}
<form>
  <div
    *ngFor="let field of fields"
    class="nm-dynamicFormFields nm-dynamicFormFields__formElement form-element {{
      field.required ? 'required' : 'optional'
    }} {{ field.hint && field.showHint ? 'hint' : '' }}"
    (click)="onClick($event, field)"
  >
    <ng-container *ngIf="!field.hidden" [ngSwitch]="field.type">
      <ng-container *ngSwitchCase="'context'">
        <nm-context-selector
          [contexts]="lookupOptions[field.field]"
          [placeholder]="field.label | translate"
          [field]="field.identifierField"
          [(ngModel)]="value[field.field]"
          [name]="field.field"
          [disabled]="field.disabled"
          [clearable]="field.clearable"
          (ngModelChange)="onValueChange(field)"
          [nmAutofocus]="field.autofocus"
          tabIndex="0"
        >
        </nm-context-selector>

        <div class="non-mat-form-field-hint" *ngIf="field.hint">
          {{ field.hint | translate }}
        </div>
        <ng-container
          *ngTemplateOutlet="interactionOutlet; context: { field: field }"
        ></ng-container>
      </ng-container>

      <!-- Lookup -->

      <ng-container *ngSwitchCase="'lookup'">
        <mat-form-field
          class="nm-lookup-field"
          [ngClass]="getClass(field)"
          (focusin)="onFocusIn(field)"
          (focusout)="onFocusOut(field)"
        >
          <mat-label *ngIf="field.floatingLabel">
            {{ field.label | translate }}
          </mat-label>
          <nm-combo
            class="combo"
            [options]="lookupOptions[field.field]"
            [valueKey]="getIdentifierField(field)"
            [displayKey]="getDescriptionField(field)"
            [filterPlaceholder]="'placeholder.search' | translate"
            [placeholder]="field.label | translate"
            [disabled]="field.disabled"
            [clearable]="field.clearable"
            [name]="field.field"
            [nmAutofocus]="field.autofocus"
            [(ngModel)]="value[field.field]"
            (ngModelChange)="onValueChange(field)"
            tabIndex="0"
          >
          </nm-combo>
          <mat-hint *ngIf="field.hint && field.showHint">{{
            field.hint | translate
          }}</mat-hint>
        </mat-form-field>
        <ng-container
          *ngTemplateOutlet="interactionOutlet; context: { field: field }"
        ></ng-container>
      </ng-container>

      <!-- Multi-Lookup-->
      <ng-container *ngSwitchCase="'multi-lookup'">
        <mat-form-field
          class="nm-multi-lookup-field"
          [ngClass]="getClass(field)"
          (focusin)="onFocusIn(field)"
          (focusout)="onFocusOut(field)"
        >
          <mat-label *ngIf="field.floatingLabel">
            {{ field.label | translate }}
          </mat-label>
          <nm-combo
            class="combo"
            [options]="lookupOptions[field.field]"
            [valueKey]="getIdentifierField(field)"
            [displayKey]="getDescriptionField(field)"
            [filterPlaceholder]="'placeholder.search' | translate"
            [placeholder]="field.label | translate"
            [disabled]="field.disabled"
            [clearable]="field.clearable"
            [multiple]="true"
            [name]="field.field"
            [nmAutofocus]="field.autofocus"
            [(ngModel)]="value[field.field]"
            (ngModelChange)="onValueChange(field)"
            tabIndex="0"
          >
          </nm-combo>
          <mat-hint *ngIf="field.hint && field.showHint">{{
            field.hint | translate
          }}</mat-hint>
        </mat-form-field>
        <ng-container
          *ngTemplateOutlet="interactionOutlet; context: { field: field }"
        ></ng-container>
      </ng-container>

      <!-- Date -->
      <ng-container *ngSwitchCase="'date'">
        <mat-form-field
          class="full-width nm-dynamicFormFields__date nm-date-time-input"
          [ngClass]="getClass(field)"
          (click)="datepicker.open()"
          (focusin)="onFocusIn(field)"
          (focusout)="onFocusOut(field)"
        >
          <mat-label *ngIf="field.floatingLabel"
            >{{ field.label | translate }}
          </mat-label>
          <input
            autocomplete="off"
            matInput
            [owlDateTime]="datepicker"
            [(ngModel)]="value[field.field]"
            [ngModelOptions]="{ updateOn: 'blur' }"
            (ngModelChange)="onValueChange(field)"
            placeholder="{{ field.label | translate }}"
            [disabled]="field.disabled"
            [name]="field.field"
            (keydown)="onKeyPressedDatePicker($event)"
          />
          <button
            mat-icon-button
            color="primary"
            matSuffix
            class="remove-action"
            *ngIf="value[field.field] && !field.readonly && !field.disabled"
            (click)="resetDateTime(field, $event)"
            tabIndex="-1"
          >
            <mat-icon color="primary" class="fade-in">close</mat-icon>
          </button>
          <nm-dynamic-form-errors
            *ngIf="field.errors"
            [errors]="field.errors"
            [field]="field"
          ></nm-dynamic-form-errors>
          <mat-hint *ngIf="field.hint && field.showHint">{{
            field.hint | translate
          }}</mat-hint>
          <ng-container
            *ngTemplateOutlet="interactionOutlet; context: { field: field }"
          ></ng-container>
        </mat-form-field>

        <owl-date-time #datepicker [pickerType]="'calendar'"></owl-date-time>
      </ng-container>

      <!-- plain -->
      <mat-form-field
        *ngSwitchCase="'plain'"
        [ngClass]="getClass(field)"
        (focusin)="onFocusIn(field)"
        (focusout)="onFocusOut(field)"
      >
        <mat-label *ngIf="field.floatingLabel"
          >{{ field.label | translate }}
        </mat-label>
        <input
          matInput
          type="text"
          [placeholder]="field.label | translate"
          [ngModel]="getLocalizedValue(field)"
          (ngModelChange)="onExternalModelChange(field, $event)"
          [ngModelOptions]="{ updateOn: 'blur' }"
          [disabled]="field.disabled"
          [readonly]="field.readonly"
          [maxlength]="field.maxLength"
          [name]="field.field"
          (keydown.enter)="onEnterPressed($event)"
          (keydown)="onKeyPressed($event, field)"
          [nmAutofocus]="field.autofocus"
          [matTooltip]="getLocalizedValue(field)"
          [matTooltipDisabled]="!field.disabled || !field.readonly"
        />
        <nm-dynamic-form-errors
          *ngIf="field.errors"
          [errors]="field.errors"
          [field]="field"
        ></nm-dynamic-form-errors>

        <button
          color="primary"
          mat-icon-button
          *ngIf="field.tab"
          matSuffix
          class="fade-in nm-dynamicFormFields__moreIcon"
        >
          <mat-icon>more_horiz</mat-icon>
        </button>
        <button
          *ngIf="field.clearable && !field.disabled && value[field.field]"
          mat-button
          matSuffix
          mat-icon-button
          aria-label="Clear"
          color="primary"
          class="
            mat-body mat-form-field-suffix
            nm-dynamicFormFields__clearButton
          "
          (click)="clearField(field); $event.stopPropagation()"
          tabIndex="-1"
        >
          <mat-icon>close</mat-icon>
        </button>

        <mat-hint *ngIf="field.hint && field.showHint">{{
          field.hint | translate
        }}</mat-hint>
        <ng-container
          *ngTemplateOutlet="interactionOutlet; context: { field: field }"
        ></ng-container>
      </mat-form-field>

      <!-- heading -->
      <div
        *ngSwitchCase="'heading'"
        [ngClass]="[getClass(field), 'nm-heading']"
      >
        {{ field.label | translate }}{{ value[field.field] }}
      </div>

      <!-- number -->
      <mat-form-field
        *ngSwitchCase="'number'"
        [ngClass]="getClass(field)"
        (focusin)="onFocusIn(field)"
        (focusout)="onFocusOut(field)"
      >
        <mat-label *ngIf="field.floatingLabel"
          >{{ field.label | translate }}
        </mat-label>
        <input
          matInput
          type="number"
          [placeholder]="field.label | translate"
          [(ngModel)]="value[field.field]"
          (ngModelChange)="onValueChange(field)"
          [ngModelOptions]="{ updateOn: 'blur' }"
          [disabled]="field.disabled"
          [name]="field.field"
          (keydown.enter)="onEnterPressed($event)"
          [nmAutofocus]="field.autofocus"
          [matTooltip]="value[field.field]"
          [matTooltipDisabled]="!field.disabled || !field.readonly"
        />
        <nm-dynamic-form-errors
          *ngIf="field.errors"
          [errors]="field.errors"
          [field]="field"
        ></nm-dynamic-form-errors>

        <button
          color="primary"
          mat-icon-button
          *ngIf="field.tab"
          matSuffix
          class="fade-in nm-dynamicFormFields__moreIcon"
        >
          <mat-icon>more_horiz</mat-icon>
        </button>

        <button
          *ngIf="field.clearable && !field.disabled && value[field.field]"
          mat-button
          matSuffix
          mat-icon-button
          aria-label="Clear"
          color="primary"
          class="
            mat-body mat-form-field-suffix
            nm-dynamicFormFields__clearButton
          "
          (click)="clearField(field); $event.stopPropagation()"
          tabIndex="-1"
        >
          <mat-icon>close</mat-icon>
        </button>

        <mat-hint *ngIf="field.hint && field.showHint">{{
          field.hint | translate
        }}</mat-hint>
        <ng-container
          *ngTemplateOutlet="interactionOutlet; context: { field: field }"
        ></ng-container>
      </mat-form-field>

      <!-- boolean -->
      <mat-checkbox
        color="primary"
        *ngSwitchCase="'boolean'"
        class="nm-dynamicFormFields__checkbox"
        [ngClass]="getClass(field)"
        [(ngModel)]="value[field.field]"
        name="default"
        (ngModelChange)="onValueChange(field)"
        [disabled]="field.disabled"
        [name]="field.field"
      >
        <span> {{ field.label | translate }} </span>
        <ng-container
          *ngTemplateOutlet="interactionOutlet; context: { field: field }"
        ></ng-container>
      </mat-checkbox>

      <!-- textarea -->
      <mat-form-field
        *ngSwitchCase="'textarea'"
        [ngClass]="getClass(field)"
        (focusin)="onFocusIn(field)"
        (focusout)="onFocusOut(field)"
      >
        <mat-label *ngIf="field.floatingLabel"
          >{{ field.label | translate }}
        </mat-label>
        <textarea
          (dropped)="onItemDropped($event, field)"
          (ngModelChange)="onExternalModelChange(field, $event)"
          [(ngModel)]="value[field.field]"
          [disabled]="field.disabled"
          [mat-autosize]="field.autosize"
          [maxlength]="field.maxLength"
          [name]="field.field"
          [nmAutofocus]="field.autofocus"
          [ngModelOptions]="{ updateOn: 'blur' }"
          [placeholder]="field.label | translate"
          [readonly]="field.readonly"
          cdkTextareaAutosize
          igxDrop
          matAutosizeMaxRows="20"
          [matAutosizeMinRows]="
            field.autosizeMinRows ? field.autosizeMinRows : 2
          "
          matInput
          spellcheck="true"
          [matTooltip]="value[field.field]"
          [matTooltipDisabled]="!field.disabled || !field.readonly"
          (paste)="handlePaste($event)"
        >
        {{ value[field.field] }}
      </textarea
        >
        <nm-dynamic-form-errors
          *ngIf="field.errors"
          [errors]="field.errors"
          [field]="field"
        ></nm-dynamic-form-errors>
        <button
          *ngIf="field.clearable && !field.disabled && value[field.field]"
          mat-button
          matSuffix
          mat-icon-button
          aria-label="Clear"
          color="primary"
          class="
            mat-body mat-form-field-suffix
            nm-dynamicFormFields__clearButton
          "
          (click)="clearField(field); $event.stopPropagation()"
          tabIndex="-1"
        >
          <mat-icon>close</mat-icon>
        </button>
        <mat-hint *ngIf="field.hint && field.showHint">{{
          field.hint | translate
        }}</mat-hint>

        <ng-container
          *ngTemplateOutlet="interactionOutlet; context: { field: field }"
        ></ng-container>
      </mat-form-field>

      <!--text -->
      <ng-container *ngSwitchCase="'text'">
        <nm-tiny-text-editor
          [ngClass]="getClass(field)"
          [disabled]="field.disabled"
          (valueChanged)="onExternalModelChange(field, $event)"
          [value]="value[field.field]"
          [showEditorOnInit]="field.showEditorOnInit"
        >
        </nm-tiny-text-editor>

        <ng-container
          *ngTemplateOutlet="interactionOutlet; context: { field: field }"
        ></ng-container>
      </ng-container>

      <!-- Source code editor -->
      <ng-container *ngSwitchCase="'source-code-editor'">
        <div class="nm-dynamicFormFields__sourceCodeEditor">
          <nm-source-code-editor
            [ngClass]="getClass(field)"
            [disabled]="field.disabled"
            [enableMinimap]="field.enableMinimap"
            [code]="value[field.field]"
            [sourceCodeLanguage]="field.sourceCodeLanguage"
            (valueChanged)="onEditorValueChange(field, $event)"
          >
          </nm-source-code-editor>
          <ng-container
            *ngTemplateOutlet="interactionOutlet; context: { field: field }"
          ></ng-container>
        </div>
      </ng-container>

      <!-- select-dialog -->
      <mat-form-field
        *ngSwitchCase="'select-dialog'"
        [ngClass]="getClass(field)"
        (focusin)="onFocusIn(field)"
        (focusout)="onFocusOut(field)"
      >
        <mat-label *ngIf="field.floatingLabel"
          >{{ field.label | translate }}
        </mat-label>
        <input
          matInput
          type="text"
          [placeholder]="field.label | translate"
          [ngModel]="description[field.field]"
          (ngModelChange)="onValueChange(field)"
          readonly
          [name]="field.field"
        />
        <button
          mat-icon-button
          color="primary"
          matSuffix
          (click)="openSelectDialog(field)"
          [disabled]="field.disabled"
        >
          <mat-icon
            class="
              fade-in
              nm-dynamicFormFields__moreIcon nm-dynamicFormFields__moreIcon
            "
          >
            more_horiz
          </mat-icon>
        </button>
        <button
          *ngIf="field.clearable && !field.disabled && value[field.field]"
          mat-button
          matSuffix
          mat-icon-button
          aria-label="Clear"
          color="primary"
          class="
            mat-body mat-form-field-suffix
            nm-dynamicFormFields__clearButton
          "
          (click)="clearField(field); $event.stopPropagation()"
          tabIndex="-1"
        >
          <mat-icon>close</mat-icon>
        </button>
        <mat-hint *ngIf="field.hint && field.showHint">{{
          field.hint | translate
        }}</mat-hint>

        <ng-container
          *ngTemplateOutlet="interactionOutlet; context: { field: field }"
        ></ng-container>
      </mat-form-field>

      <!-- Category-selector -->
      <ng-container *ngSwitchCase="'category-selector'">
        <mat-label
          class="nm-dynamicFormFields__categorySelectorLabel"
          *ngIf="!field.floatingLabel"
        >
          {{ field.label | translate }}
        </mat-label>
        <nm-category-select-control
          [placeholder]="field.placeholder | translate"
          [categoryPlaceholder]="field.categoryPlaceholder | translate"
          [value]="value[field.field]"
          (change)="onExternalModelChange(field, $event)"
          [localStorageRecovery]="false"
          [showPublicationDropdown]="!field.publication"
          [multi]="field.multiCategorySelect"
          [showIncludeSubCategories]="field.showIncludeSubCategories"
          [includeSubCategories]="field.includeSubCategories"
          [categoryUrl]="field.categoryUrl"
          (includeSubCategoriesChange)="
            includeSubCategoriesValueChanged(field, $event)
          "
          [filterChannelTypes]="field.filterChannelTypes"
          [preferredPosition]="getPreferredPosition(field)"
          [autofocus]="field.autofocus"
        >
        </nm-category-select-control>
        <ng-container
          *ngTemplateOutlet="interactionOutlet; context: { field: field }"
        ></ng-container>
      </ng-container>
      <!-- toggle / toggler -->
      <ng-container *ngSwitchCase="'toggler'">
        <mat-slide-toggle
          [ngClass]="getClass(field)"
          [(ngModel)]="value[field.field]"
          (ngModelChange)="onValueChange(field)"
          [disabled]="field.disabled"
          [name]="field.field"
          >{{ field.label | translate }}
        </mat-slide-toggle>
        <ng-container
          *ngTemplateOutlet="interactionOutlet; context: { field: field }"
        ></ng-container>
      </ng-container>
      <!-- multi-toggle / multiToggler -->
      <ng-container *ngSwitchCase="'multi-toggler'">
        <ng-container *ngFor="let item of field.items">
          <mat-slide-toggle
            class="nm-dynamicFormFields__multi-toggler"
            [ngClass]="getClass(field)"
            [(ngModel)]="value[item.field]"
            (ngModelChange)="onValueChange(item)"
            [disabled]="item.disabled"
            [name]="item.field"
            >{{ item.label | translate }}
          </mat-slide-toggle>
        </ng-container>
      </ng-container>

      <!-- user-right -->
      <mat-form-field
        *ngSwitchCase="'user-right'"
        [ngClass]="getClass(field)"
        (focusin)="onFocusIn(field)"
        (focusout)="onFocusOut(field)"
      >
        <mat-label *ngIf="field.floatingLabel"
          >{{ field.label | translate }}
        </mat-label>
        <button
          mat-icon-button
          color="primary"
          matSuffix
          class="remove-action"
          *ngIf="description[field.field]"
          (click)="clearField(field)"
          [disabled]="field.disabled"
          tabIndex="-1"
        >
          <mat-icon class="fade-in">close</mat-icon>
        </button>
        <input
          matInput
          type="text"
          [placeholder]="field.label | translate"
          [ngModel]="description[field.field]"
          (ngModelChange)="onValueChange(field)"
          (click)="openUserRightDialog(field)"
          [name]="field.field"
          readonly
        />
        <button
          mat-icon-button
          color="primary"
          matSuffix
          (click)="openUserRightDialog(field)"
          [disabled]="field.disabled"
        >
          <mat-icon class="fade-in nm-dynamicFormFields__moreIcon"
            >more_horiz</mat-icon
          >
        </button>
        <mat-hint *ngIf="field.hint && field.showHint">{{
          field.hint | translate
        }}</mat-hint>

        <ng-container
          *ngTemplateOutlet="interactionOutlet; context: { field: field }"
        ></ng-container>
      </mat-form-field>

      <!-- worklist -->
      <mat-form-field
        *ngSwitchCase="'worklist'"
        [ngClass]="getClass(field)"
        (focusin)="onFocusIn(field)"
        (focusout)="onFocusOut(field)"
      >
        <mat-label *ngIf="field.floatingLabel"
          >{{ field.label | translate }}
        </mat-label>
        <button
          mat-icon-button
          color="primary"
          matSuffix
          class="remove-action"
          *ngIf="description[field.field]"
          (click)="clearField(field)"
          [disabled]="field.disabled"
        >
          <mat-icon class="fade-in">close</mat-icon>
        </button>
        <input
          matInput
          type="text"
          [placeholder]="field.label | translate"
          [ngModel]="description[field.field]"
          (ngModelChange)="onValueChange(field)"
          (click)="openWorklistDialog(field)"
          [name]="field.field"
          readonly
          [disabled]="field.disabled"
        />
        <button
          mat-icon-button
          color="primary"
          matSuffix
          (click)="openWorklistDialog(field)"
          [disabled]="field.disabled"
        >
          <mat-icon class="fade-in nm-dynamicFormFields__moreIcon"
            >more_horiz</mat-icon
          >
        </button>
        <mat-hint *ngIf="field.hint && field.showHint">{{
          field.hint | translate
        }}</mat-hint>
        <ng-container
          *ngTemplateOutlet="interactionOutlet; context: { field: field }"
        ></ng-container>
      </mat-form-field>

      <!-- number-decimal -->
      <mat-form-field
        *ngSwitchCase="'number-decimal'"
        [ngClass]="getClass(field)"
        (focusin)="onFocusIn(field)"
        (focusout)="onFocusOut(field)"
      >
        <mat-label *ngIf="field.floatingLabel"
          >{{ field.label | translate }}
        </mat-label>
        <input
          matInput
          type="number"
          [placeholder]="field.label | translate"
          [(ngModel)]="value[field.field]"
          (ngModelChange)="onValueChange(field)"
          [ngModelOptions]="{ updateOn: 'blur' }"
          [disabled]="field.disabled"
          [name]="field.field"
          step="0.01"
          (keydown.enter)="onEnterPressed($event)"
        />
        <nm-dynamic-form-errors
          *ngIf="field.errors"
          [errors]="field.errors"
          [field]="field"
        ></nm-dynamic-form-errors>
        <button
          *ngIf="field.clearable && !field.disabled && value[field.field]"
          mat-button
          matSuffix
          mat-icon-button
          aria-label="Clear"
          color="primary"
          class="
            mat-body mat-form-field-suffix
            nm-dynamicFormFields__clearButton
          "
          (click)="clearField(field); $event.stopPropagation()"
          tabIndex="-1"
        >
          <mat-icon>close</mat-icon>
        </button>
        <mat-hint *ngIf="field.hint && field.showHint">{{
          field.hint | translate
        }}</mat-hint>

        <ng-container
          *ngTemplateOutlet="interactionOutlet; context: { field: field }"
        ></ng-container>
      </mat-form-field>

      <!-- Date-time -->
      <ng-container *ngSwitchCase="'date-time'">
        <mat-form-field
          class="full-width nm-date-time-input"
          [ngClass]="getClass(field)"
          (click)="datetimepicker.open()"
          (focusin)="onFocusIn(field)"
          (focusout)="onFocusOut(field)"
        >
          <mat-label *ngIf="field.floatingLabel"
            >{{ field.label | translate }}
          </mat-label>
          <input
            autocomplete="off"
            matInput
            [owlDateTime]="datetimepicker"
            [(ngModel)]="value[field.field]"
            [ngModelOptions]="{ updateOn: 'blur' }"
            (ngModelChange)="onValueChange(field)"
            placeholder="{{ field.label | translate }}"
            [disabled]="field.disabled"
            [name]="field.field"
            (keydown)="onKeyPressedDateTimePicker($event)"
          />
          <button
            mat-icon-button
            color="primary"
            matSuffix
            class="remove-action"
            *ngIf="value[field.field] && !field.readonly && !field.disabled"
            (click)="resetDateTime(field, $event)"
            tabIndex="-1"
          >
            <mat-icon color="primary" class="fade-in">close</mat-icon>
          </button>
          <nm-dynamic-form-errors
            *ngIf="field.errors"
            [errors]="field.errors"
            [field]="field"
          ></nm-dynamic-form-errors>
          <mat-hint *ngIf="field.hint && field.showHint">{{
            field.hint | translate
          }}</mat-hint>
          <ng-container
            *ngTemplateOutlet="interactionOutlet; context: { field: field }"
          ></ng-container>
        </mat-form-field>

        <owl-date-time #datetimepicker></owl-date-time>
      </ng-container>

      <!-- boolean-indeterminate -->
      <mat-checkbox
        *ngSwitchCase="'boolean-indeterminate'"
        name="default"
        color="primary"
        class="align-checkbox nm-boolean-indeterminate-field"
        [ngClass]="getClass(field)"
        [ngModel]="value[field.field]"
        [indeterminate]="value[field.field] === null"
        (ngModelChange)="booleanValueChanged(field)"
        [disabled]="field.disabled"
        [name]="field.field"
      >
        <span> {{ field.label | translate }} </span>

        <ng-container
          *ngTemplateOutlet="interactionOutlet; context: { field: field }"
        ></ng-container>
      </mat-checkbox>

      <!-- radio -->
      <mat-radio-group
        *ngSwitchCase="'radio'"
        color="primary"
        class="nm-dynamicFormFields__radioButtonGroup"
        [ngClass]="getClass(field)"
        [(ngModel)]="value[field.field]"
        (change)="onValueChange(field)"
        [disabled]="field.disabled"
        [name]="field.field"
      >
        <div class="nm-dynamicFormFields__radioButtonClear">
          <mat-label class="mat-label" *ngIf="field.floatingLabel">
            {{ field.label | translate }}
          </mat-label>
          <button
            *ngIf="field.clearable && !field.disabled && value[field.field]"
            mat-button
            matSuffix
            mat-icon-button
            aria-label="Clear"
            color="primary"
            class="
              mat-body mat-form-field-suffix
              nm-dynamicFormFields__clearButton
            "
            (click)="clearField(field); $event.stopPropagation()"
            tabIndex="-1"
          >
            <mat-icon>close</mat-icon>
          </button>
        </div>

        <mat-radio-button
          class="nm-dynamicFormFields__radioButton"
          *ngFor="let option of lookupOptions[field.field]"
          [disabled]="!option.enabled"
          [value]="option.value"
        >
          {{ option.description | translate }}
        </mat-radio-button>
        <nm-dynamic-form-errors
          *ngIf="field.errors"
          [errors]="field.errors"
          [field]="field"
        >
        </nm-dynamic-form-errors>

        <mat-hint *ngIf="field.hint && field.showHint">{{
          field.hint | translate
        }}</mat-hint>
      </mat-radio-group>

      <!-- chip-list -->
      <mat-form-field
        *ngSwitchCase="'chip-list'"
        class="nm-chip-list-field"
        [ngClass]="getClass(field)"
        (focusin)="onFocusIn(field)"
        (focusout)="onFocusOut(field)"
      >
        <mat-label *ngIf="field.floatingLabel">{{
          field.label | translate
        }}</mat-label>
        <mat-chip-list #chipList>
          <mat-chip
            *ngFor="let chip of value[field.field]"
            [id]="chip[getIdentifierField(field)]"
            [removable]="true"
            (removed)="removeChip(chip, field)"
          >
            <span>{{ chip[getDescriptionField(field)] }}</span>
            <mat-icon *ngIf="field.chipRemovable" matChipRemove
              >cancel
            </mat-icon>
          </mat-chip>
          <input
            [matChipInputFor]="chipList"
            [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
            [matChipInputAddOnBlur]="true"
            (matChipInputTokenEnd)="addChip($event, field)"
            [disabled]="field.disabled"
            [readonly]="field.readonly"
            [name]="field.field"
            [nmAutofocus]="field.autofocus"
            autocomplete="off"
          />
        </mat-chip-list>
        <button
          color="primary"
          mat-icon-button
          *ngIf="field.tab"
          matSuffix
          class="fade-in nm-dynamicFormFields__moreIcon"
        >
          <mat-icon>more_horiz</mat-icon>
        </button>
        <button
          *ngIf="field.clearable && !field.disabled && value[field.field]"
          mat-button
          matSuffix
          mat-icon-button
          aria-label="Clear"
          color="primary"
          class="
            mat-body mat-form-field-suffix
            nm-dynamicFormFields__clearButton
          "
          (click)="clearField(field); $event.stopPropagation()"
          tabIndex="-1"
        >
          <mat-icon>close</mat-icon>
        </button>
        <mat-hint *ngIf="field.hint && field.showHint">{{
          field.hint | translate
        }}</mat-hint>

        <ng-container
          *ngTemplateOutlet="interactionOutlet; context: { field: field }"
        ></ng-container>
      </mat-form-field>

      <!-- password -->
      <ng-container
        *ngSwitchCase="'password'"
        [ngClass]="getClass(field)"
        (focusin)="onFocusIn(field)"
        (focusout)="onFocusOut(field)"
      >
        <mat-form-field>
          <mat-label *ngIf="field.floatingLabel"
            >{{ field.label | translate }}
          </mat-label>
          <input
            autocomplete="off"
            matInput
            [type]="showPassword ? 'text' : 'password'"
            [placeholder]="field.label | translate"
            [(ngModel)]="value[field.field]"
            (ngModelChange)="onValueChange(field)"
            [ngModelOptions]="{ updateOn: 'blur' }"
            [disabled]="field.disabled"
            [readonly]="field.readonly"
            [name]="field.field"
            (keydown.enter)="onEnterPressed($event)"
          />
          <button
            mat-icon-button
            color="primary"
            matSuffix
            (click)="showPassword = !showPassword"
          >
            <mat-icon
              >{{ showPassword ? "visibility" : "visibility_off" }}
            </mat-icon>
          </button>
          <button
            mat-icon-button
            color="primary"
            matSuffix
            *ngIf="field.showEditpasswordButton"
            (click)="changePassword(field)"
          >
            <mat-icon class="fade-in">edit</mat-icon>
          </button>
          <nm-dynamic-form-errors
            *ngIf="field.errors"
            [errors]="field.errors"
            [field]="field"
          ></nm-dynamic-form-errors>
          <mat-hint *ngIf="field.hint && field.showHint">{{
            field.hint | translate
          }}</mat-hint>

          <ng-container
            *ngTemplateOutlet="interactionOutlet; context: { field: field }"
          ></ng-container>
        </mat-form-field>
        <div [hidden]="field.hideConfirmationField">
          <mat-form-field>
            <mat-label *ngIf="field.floatingLabel"
              >{{ field.confirmationLabel | translate }}
            </mat-label>
            <input
              autocomplete="off"
              matInput
              type="password"
              [(ngModel)]="passwordConfirmation[field.field]"
              (ngModelChange)="onValueChange(field)"
              [disabled]="field.disabled"
              [readonly]="field.readonly"
              [name]="field.field"
              (keydown.enter)="onEnterPressed($event)"
            />
          </mat-form-field>
        </div>
      </ng-container>

      <!-- file-upload -->
      <ng-container *ngSwitchCase="'file-upload'">
        <nm-upload-file
          [ngClass]="getClass(field)"
          [supportedFileTypes]="field.supportedFileTypes"
          [supportedMimeTypes]="field.supportedMimeTypes"
          (fileContent)="onFileUpload(field, $event)"
          [disabled]="field.disabled"
          [uploadUrl]="field.fileUploadUrl"
          [value]="value[field.field]"
        >
        </nm-upload-file>

        <ng-container
          *ngTemplateOutlet="interactionOutlet; context: { field: field }"
        ></ng-container>
        <button
          *ngIf="field.clearable && !field.disabled && value[field.field]"
          mat-button
          matSuffix
          mat-icon-button
          aria-label="Clear"
          color="primary"
          class="
            mat-body mat-form-field-suffix
            nm-dynamicFormFields__clearButton
          "
          (click)="clearField(field); $event.stopPropagation()"
        >
          <mat-icon>close</mat-icon>
        </button>
      </ng-container>

      <!-- page-dialog -->
      <mat-form-field
        *ngSwitchCase="'page-dialog'"
        [ngClass]="getClass(field)"
        (focusin)="onFocusIn(field)"
        (focusout)="onFocusOut(field)"
      >
        <mat-label *ngIf="field.floatingLabel"
          >{{ field.label | translate }}
        </mat-label>
        <input
          matInput
          type="text"
          [placeholder]="field.label | translate"
          [ngModel]="description[field.field]"
          (ngModelChange)="onValueChange(field)"
          readonly
          [name]="field.field"
        />
        <button
          mat-icon-button
          color="primary"
          matSuffix
          (click)="openPageDialog(field)"
          [disabled]="field.disabled"
        >
          <mat-icon
            class="
              fade-in
              nm-dynamicFormFields__moreIcon nm-dynamicFormFields__moreIcon
            "
          >
            more_horiz
          </mat-icon>
        </button>
        <button
          *ngIf="field.clearable && !field.disabled && value[field.field]"
          mat-button
          matSuffix
          mat-icon-button
          aria-label="Clear"
          color="primary"
          class="
            mat-body mat-form-field-suffix
            nm-dynamicFormFields__clearButton
          "
          (click)="clearField(field); $event.stopPropagation()"
          tabIndex="-1"
        >
          <mat-icon>close</mat-icon>
        </button>
        <mat-hint *ngIf="field.hint && field.showHint">{{
          field.hint | translate
        }}</mat-hint>

        <ng-container
          *ngTemplateOutlet="interactionOutlet; context: { field: field }"
        ></ng-container>
      </mat-form-field>

      <div *ngSwitchDefault>Not configured: {{ field.type }}</div>
    </ng-container>
  </div>
</form>

<ng-template #interactionOutlet let-field="field">
  <nm-interaction-menu-bar
    *ngIf="field.menuInteractions"
    [menu]="getMenuActions(field) | async"
    [buttons]="getButtonActions(field) | async"
    [buttonActionTemplate]="buttonAction"
    [menuConfigurationTemplate]="field.customMenuTemplate ? menuTemplate : ''"
    [param]="{ value: value, field: field }"
  >
  </nm-interaction-menu-bar>
</ng-template>

<ng-template
  #buttonAction
  let-onClick="onClick"
  let-configuration="configuration"
  let-param="param"
  let-disabled="disabled"
>
  <nm-button
    [disabled]="disabled | async"
    (fireEvent)="onClick($event)"
    [buttonType]="
      configuration.buttonType ? configuration.buttonType : 'mat-button'
    "
    [icon]="configuration.icon"
    [buttonColor]="configuration.buttonColor ? configuration.buttonColor : ''"
    [placeholder]="configuration.description | translate"
  >
  </nm-button>
</ng-template>

<ng-template
  #menuTemplate
  let-onClick="onClick"
  let-configuration="configuration"
  let-components="param.field.templateComponents"
>
  <nm-container
    *ngFor="let component of components"
    [configuration]="component | widgetFor: component.configuration"
    [id]="component.id"
  >
  </nm-container>
</ng-template>
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""