nm-dynamic-form-fields-component
src/app/shared/widgets/dynamic-form/dynamic-form-component/dynamic-form-fields.component.ts
changeDetection | ChangeDetectionStrategy.OnPush |
selector | nm-dynamic-form-fields-component |
styleUrls | dynamic-form-fields.component.scss |
templateUrl | ./dynamic-form-fields.component.html |
constructor(cdr: ChangeDetectorRef, widgetFrameService: WidgetframeService, currentLocaleService: CurrentLocaleService, dateAdapter: DateAdapter
|
|||||||||||||||||||||||||||||||||
Parameters :
|
addFields
|
Adds fields dynamically to the form. |
configuration
|
Sets dynamic form configurations.
Type: |
dialogContext
|
Sets dialog context for field used with page-dialog type fields. |
fields
|
Sets dynamic form fields.
Type: |
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. |
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
|
addChip | |||||||||
addChip(event: MatChipInputEvent, field: DynamicFormField)
|
|||||||||
Parameters :
Returns :
void
|
Public booleanValueChanged | ||||||
booleanValueChanged(field: DynamicFormField)
|
||||||
Parameters :
Returns :
void
|
Public changePassword | ||||
changePassword(field: )
|
||||
Parameters :
Returns :
void
|
Public clearField | ||||||
clearField(field: DynamicFormField)
|
||||||
Parameters :
Returns :
void
|
Private findField | ||||||
findField(identifier: string)
|
||||||
Parameters :
Returns :
any
|
Public getButtonActions | ||||||
getButtonActions(field: DynamicFormField)
|
||||||
Parameters :
Returns :
Observable<Content[]>
|
Public getCategoryUrl | ||||||
getCategoryUrl(field: DynamicFormField)
|
||||||
Parameters :
Returns :
any
|
Public getClass | ||||||
getClass(field: DynamicFormField)
|
||||||
Parameters :
Returns :
any
|
Public getDescriptionField | ||||||
getDescriptionField(field: DynamicFormField)
|
||||||
Parameters :
Returns :
string
|
Public getIdentifierField | ||||||
getIdentifierField(field: DynamicFormField)
|
||||||
Parameters :
Returns :
string
|
Public getInteractions | ||||||
getInteractions(field: DynamicFormField)
|
||||||
Parameters :
Returns :
literal type | null
|
Public getLocalizedValue | ||||||
getLocalizedValue(field: DynamicFormField)
|
||||||
Parameters :
Returns :
string
|
Public getMenuActions | ||||||
getMenuActions(field: DynamicFormField)
|
||||||
Parameters :
Returns :
Observable<Content[]>
|
getPreferredPosition | ||||||
getPreferredPosition(field: DynamicFormField)
|
||||||
Parameters :
Returns :
any
|
handlePaste | ||||||
handlePaste(event: ClipboardEvent)
|
||||||
Parameters :
Returns :
void
|
Public includeSubCategoriesValueChanged | |||||||||
includeSubCategoriesValueChanged(field: DynamicFormField, value: any)
|
|||||||||
Parameters :
Returns :
void
|
Private initFields | ||||||
initFields(fields: any[])
|
||||||
Parameters :
Returns :
void
|
Private initializeButtonActions | ||||||
initializeButtonActions(field: DynamicFormField)
|
||||||
Parameters :
Returns :
void
|
Private initializeMenuActions | ||||||
initializeMenuActions(field: DynamicFormField)
|
||||||
Parameters :
Returns :
void
|
Public loadLookupData | ||||||
loadLookupData(field: DynamicFormField)
|
||||||
Parameters :
Returns :
void
|
ngOnDestroy |
ngOnDestroy()
|
Returns :
void
|
Public ngOnInit |
ngOnInit()
|
Returns :
void
|
onClick | |||||||||
onClick(event: , field: DynamicFormField)
|
|||||||||
Parameters :
Returns :
void
|
onEditorValueChange | |||||||||
onEditorValueChange(field: DynamicFormField, value: any)
|
|||||||||
Parameters :
Returns :
void
|
onEnterPressed | ||||
onEnterPressed(event: )
|
||||
Parameters :
Returns :
void
|
Public onExternalModelChange | |||||||||
onExternalModelChange(field: DynamicFormField, value: any)
|
|||||||||
Parameters :
Returns :
void
|
Public onFileUpload | |||||||||
onFileUpload(field: , uploadedFile: any)
|
|||||||||
Parameters :
Returns :
void
|
onFocusIn | ||||||
onFocusIn(field: DynamicFormField)
|
||||||
Parameters :
Returns :
void
|
onFocusOut | ||||||
onFocusOut(field: DynamicFormField)
|
||||||
Parameters :
Returns :
void
|
Public onItemDropped | ||||||
onItemDropped(ev: , field: )
|
||||||
Parameters :
Returns :
void
|
onKeyPressed | |||||||||
onKeyPressed(event: KeyboardEvent, field: )
|
|||||||||
Parameters :
Returns :
boolean
|
onKeyPressedDatePicker | ||||
onKeyPressedDatePicker(event: )
|
||||
Parameters :
Returns :
void
|
onKeyPressedDateTimePicker | ||||
onKeyPressedDateTimePicker(event: )
|
||||
Parameters :
Returns :
void
|
onTriggerValidation |
onTriggerValidation()
|
Returns :
void
|
onValueChange | ||||||||||||
onValueChange(field: DynamicFormField, isEdit: boolean)
|
||||||||||||
Parameters :
Returns :
void
|
Public openPageDialog | ||||||
openPageDialog(field: DynamicFormField)
|
||||||
Parameters :
Returns :
void
|
Public openSelectDialog | ||||||
openSelectDialog(field: DynamicFormField)
|
||||||
Parameters :
Returns :
void
|
Public openUserRightDialog | ||||||
openUserRightDialog(field: DynamicFormField)
|
||||||
Parameters :
Returns :
void
|
Public openWorklistDialog | ||||||
openWorklistDialog(field: DynamicFormField)
|
||||||
Parameters :
Returns :
void
|
Private pageDialog | ||||||||||||||||||
pageDialog(field: DynamicFormField, module: string, identifier: string, context: any, minWidth: string)
|
||||||||||||||||||
Parameters :
Returns :
void
|
removeChip | |||||||||
removeChip(chip: any, field: DynamicFormField)
|
|||||||||
Parameters :
Returns :
void
|
resetDateTime | ||||||
resetDateTime(field: , event: )
|
||||||
Parameters :
Returns :
void
|
Private setFieldDefaultValue | ||||||
setFieldDefaultValue(field: DynamicFormField)
|
||||||
Parameters :
Returns :
void
|
Private setFieldDescription | |||||||||
setFieldDescription(field: DynamicFormField, value: any)
|
|||||||||
Parameters :
Returns :
void
|
Private setLookupOptions | ||||||
setLookupOptions(field: , value: )
|
||||||
Parameters :
Returns :
void
|
Private updateLocale | |||||||||
updateLocale(locale: string, fields: DynamicFormField[])
|
|||||||||
Parameters :
Returns :
void
|
Private validateCategorySelector | ||||||
validateCategorySelector(field: DynamicFormField)
|
||||||
Parameters :
Returns :
boolean
|
Private validateFields |
validateFields()
|
Returns :
boolean
|
Private validateLocalizedText | ||||||
validateLocalizedText(field: DynamicFormField)
|
||||||
Parameters :
Returns :
boolean
|
Private validatePasswords | ||||||
validatePasswords(field: DynamicFormField)
|
||||||
Parameters :
Returns :
boolean
|
Private validateSourceCodeEditor | ||||
validateSourceCodeEditor(field: )
|
||||
Parameters :
Returns :
any
|
Public addFieldsChannel |
addFieldsChannel:
|
Default value : new Subject<DynamicFormField[]>()
|
Public buttonActions |
buttonActions:
|
Type : literal type
|
Default value : {}
|
Public currentLocale |
currentLocale:
|
Type : any
|
datePicker |
datePicker:
|
Type : OwlDateTimeComponent<Date>
|
Decorators : ViewChild
|
dateTimePicker |
dateTimePicker:
|
Type : OwlDateTimeComponent<any>
|
Decorators : ViewChild
|
Public description |
description:
|
Type : object
|
Default value : {}
|
Public dialogContextChannel |
dialogContextChannel:
|
Default value : new Subject<{ field: string; context: any }>()
|
Public dialogSelectedItems |
dialogSelectedItems:
|
Type : object
|
Default value : {}
|
Public editorConfig |
editorConfig:
|
Type : any
|
Default value : DEFAULT_TEXT_EDITOR_CONFIG
|
Public labelSetterChannel |
labelSetterChannel:
|
Default value : new Subject<{ field: string; value: any }>()
|
Public lookupOptions |
lookupOptions:
|
Type : object
|
Default value : {}
|
Public lookupOptionsInputChannel |
lookupOptionsInputChannel:
|
Default value : new Subject<any>()
|
Public menuActions |
menuActions:
|
Type : literal type
|
Default value : {}
|
Public passwordConfirmation |
passwordConfirmation:
|
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:
|
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:
|
Type : boolean
|
Default value : false
|
Public sourceCodeValidations |
sourceCodeValidations:
|
Type : object
|
Default value : {}
|
Public triggerValidationChannel |
triggerValidationChannel:
|
Default value : new Subject()
|
Public unsubscribe |
unsubscribe:
|
Default value : NgUnsubscribe.create()
|
Public value |
value:
|
Type : object
|
Default value : {}
|
Public valueSetterChannel |
valueSetterChannel:
|
Default value : new Subject<{ field: string; value: any }>()
|
lookupOptionsInput | ||||
setlookupOptionsInput(value: )
|
||||
Sets lookup options for lookup types.
Parameters :
Returns :
void
|
resetLookup | ||||
setresetLookup(value: )
|
||||
Resets field lookup options.
Parameters :
Returns :
void
|
valueSetter | ||||
setvalueSetter(value: )
|
||||
Sets field value.
Parameters :
Returns :
void
|
labelSetter | ||||
setlabelSetter(value: )
|
||||
Sets field label.
Parameters :
Returns :
void
|
setDisabled | ||||
setsetDisabled(value: )
|
||||
Enables\Disables field.
Parameters :
Returns :
void
|
dialogContext | ||||
setdialogContext(value: )
|
||||
Sets dialog context for field used with page-dialog type fields.
Parameters :
Returns :
void
|
resetFormFields | ||||
setresetFormFields(value: )
|
||||
Resets all form fields.
Parameters :
Returns :
void
|
setHidden | ||||
setsetHidden(value: )
|
||||
Shows\Hides field.
Parameters :
Returns :
void
|
reset | ||||
setreset(value: )
|
||||
Resets all form fields.
Parameters :
Returns :
void
|
reloadLookup | ||||
setreloadLookup(value: )
|
||||
Reloads field lookup options.
Parameters :
Returns :
void
|
addFields | ||||
setaddFields(value: )
|
||||
Adds fields dynamically to the form.
Parameters :
Returns :
void
|
removeFields | ||||
setremoveFields(value: )
|
||||
Removes fields dynamically from the form.
Parameters :
Returns :
void
|
removeAllFields | ||||
setremoveAllFields(value: )
|
||||
Removes all fields dynamically from the form.
Parameters :
Returns :
void
|
triggerValidation | ||||
settriggerValidation(value: )
|
||||
Triggers validation for form fields.
Parameters :
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>