Description
Dialog to configure additional details for the "inherited-field"
default value strategy, working in combination with the admin components.
Example
|
isInvalidMapping
|
Type : boolean
|
Default value : false
|
|
|
|
mappingEnabled
|
Type : unknown
|
Default value : signal(false)
|
|
|
If the user explicitly enabled the optional value mapping functionality
|
|
selectedSourceValueField
|
Type : WritableSignal<string | null>
|
|
|
The currently selected "sourceValueField" on the related entity
|
|
sourceValueEntityType
|
Type : EntityConstructor
|
|
|
The entity type of the related entity that triggers the updates
|
|
sourceValueFieldSchema
|
Type : Signal<EntitySchemaField | undefined>
|
Default value : computed(
() => {
const fieldId = this.selectedSourceValueField();
if (!fieldId) return;
return this.sourceValueEntityType.schema.get(fieldId);
},
)
|
|
|
|
targetFieldConfig
|
Type : FormFieldConfig
|
|
|
The full schema of the field for which this default value is configured
|
|
valueMappingOptions
|
Type : Signal<literal type[]>
|
Default value : computed(() => {
if (
this.sourceValueFieldSchema()?.dataType !==
ConfigurableEnumDatatype.dataType
) {
// only configurable-enum fields supported currently
return [];
}
const enumEntity = this.configurableEnumService.getEnum(
this.sourceValueFieldSchema().additional,
);
const values = enumEntity?.values ?? [];
return values.map((sourceValue) => {
const sourceValueRaw = sourceValue.id; // database format of the source value
// select existing mapping value if available
let selectedMappedValue: any = this.value.valueMapping?.[sourceValueRaw];
if (selectedMappedValue) {
selectedMappedValue = this.schemaService.valueToEntityFormat(
selectedMappedValue,
this.targetFieldConfig,
);
}
const formControl = new FormControl(selectedMappedValue);
const formGroup = new FormGroup({
[this.targetFieldConfig.id]: formControl,
});
return {
sourceValue,
sourceValueRaw,
form: {
formGroup,
} as unknown as EntityForm<Entity>,
};
});
})
|
|
|
The possible values of the selected sourceValueField that can be mapped to custom target values.
Currently mapping only supported for ConfigurableEnum fields.
|
import { EntityForm } from "#src/app/core/common-components/entity-form/entity-form";
import { EntityFieldEditComponent } from "#src/app/core/entity/entity-field-edit/entity-field-edit.component";
import { EntityFieldLabelComponent } from "#src/app/core/entity/entity-field-label/entity-field-label.component";
import {
Component,
computed,
inject,
Signal,
signal,
WritableSignal,
ChangeDetectionStrategy,
} from "@angular/core";
import { FormControl, FormGroup } from "@angular/forms";
import { MatButtonModule } from "@angular/material/button";
import { MatOptionModule } from "@angular/material/core";
import {
MAT_DIALOG_DATA,
MatDialogModule,
MatDialogRef,
} from "@angular/material/dialog";
import { MatFormFieldModule } from "@angular/material/form-field";
import { MatTooltipModule } from "@angular/material/tooltip";
import { ConfigurableEnumService } from "#src/app/core/basic-datatypes/configurable-enum/configurable-enum.service";
import { ConfigurableEnumValue } from "#src/app/core/basic-datatypes/configurable-enum/configurable-enum.types";
import { Entity, EntityConstructor } from "#src/app/core/entity/model/entity";
import { EntitySchemaService } from "#src/app/core/entity/schema/entity-schema.service";
import { DialogCloseComponent } from "../../../../core/common-components/dialog-close/dialog-close.component";
import { DefaultValueConfigInheritedField } from "../../inherited-field-config";
import { FormFieldConfig } from "#src/app/core/common-components/entity-form/FormConfig";
import { EntityFieldSelectComponent } from "#src/app/core/entity/entity-field-select/entity-field-select.component";
import { EntitySchemaField } from "#src/app/core/entity/schema/entity-schema-field";
import { MatSlideToggle } from "@angular/material/slide-toggle";
import { ConfigurableEnumDatatype } from "#src/app/core/basic-datatypes/configurable-enum/configurable-enum-datatype/configurable-enum.datatype";
/**
* Dialog to configure additional details for the "inherited-field"
* default value strategy, working in combination with the admin components.
*/
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: "app-automated-field-mapping",
imports: [
MatOptionModule,
MatFormFieldModule,
MatDialogModule,
MatButtonModule,
MatTooltipModule,
EntityFieldEditComponent,
EntityFieldLabelComponent,
DialogCloseComponent,
EntityFieldSelectComponent,
MatSlideToggle,
],
templateUrl: "./automated-field-mapping.component.html",
styleUrl: "./automated-field-mapping.component.scss",
})
export class AutomatedFieldMappingComponent {
private readonly dialogRef = inject<MatDialogRef<any>>(MatDialogRef);
private readonly configurableEnumService = inject(ConfigurableEnumService);
private readonly schemaService = inject(EntitySchemaService);
/** The full schema of the field for which this default value is configured */
targetFieldConfig: FormFieldConfig;
/** The entity type of the related entity that triggers the updates */
sourceValueEntityType: EntityConstructor;
value: DefaultValueConfigInheritedField;
/** The currently selected "sourceValueField" on the related entity */
selectedSourceValueField: WritableSignal<string | null>;
sourceValueFieldSchema: Signal<EntitySchemaField | undefined> = computed(
() => {
const fieldId = this.selectedSourceValueField();
if (!fieldId) return;
return this.sourceValueEntityType.schema.get(fieldId);
},
);
/**
* The possible values of the selected sourceValueField that can be mapped to custom target values.
* Currently mapping only supported for ConfigurableEnum fields.
*/
valueMappingOptions: Signal<
{
sourceValue: ConfigurableEnumValue;
sourceValueRaw: string;
form: EntityForm<Entity>;
}[]
> = computed(() => {
if (
this.sourceValueFieldSchema()?.dataType !==
ConfigurableEnumDatatype.dataType
) {
// only configurable-enum fields supported currently
return [];
}
const enumEntity = this.configurableEnumService.getEnum(
this.sourceValueFieldSchema().additional,
);
const values = enumEntity?.values ?? [];
return values.map((sourceValue) => {
const sourceValueRaw = sourceValue.id; // database format of the source value
// select existing mapping value if available
let selectedMappedValue: any = this.value.valueMapping?.[sourceValueRaw];
if (selectedMappedValue) {
selectedMappedValue = this.schemaService.valueToEntityFormat(
selectedMappedValue,
this.targetFieldConfig,
);
}
const formControl = new FormControl(selectedMappedValue);
const formGroup = new FormGroup({
[this.targetFieldConfig.id]: formControl,
});
return {
sourceValue,
sourceValueRaw,
form: {
formGroup,
} as unknown as EntityForm<Entity>,
};
});
});
/**
* If the user explicitly enabled the optional value mapping functionality
*/
mappingEnabled = signal(false);
isInvalidMapping: boolean = false;
constructor() {
const data = inject<AutomatedFieldMappingDialogData>(MAT_DIALOG_DATA);
this.targetFieldConfig = {
...data.currentField,
id: "targetValue", // simulate a faked full entity-form to use as basis for value mapping edit components
} as FormFieldConfig;
this.value = data.value;
this.sourceValueEntityType = data.sourceValueEntityType;
this.selectedSourceValueField = signal(
this.value?.sourceValueField ?? null,
);
if (this.value?.valueMapping) {
this.mappingEnabled.set(true);
}
}
save() {
const selectedMappings = {};
if (this.mappingEnabled()) {
this.isInvalidMapping = this.valueMappingOptions().some((v) => {
v.form.formGroup.markAllAsTouched();
return v.form.formGroup.invalid;
});
if (this.isInvalidMapping) return;
this.valueMappingOptions().forEach(({ sourceValueRaw, form }) => {
const value = form.formGroup.get(this.targetFieldConfig.id)?.value;
selectedMappings[sourceValueRaw] =
this.schemaService.valueToDatabaseFormat(
value,
this.targetFieldConfig,
);
});
}
const newValue: DefaultValueConfigInheritedField = {
...this.value,
sourceValueField: this.selectedSourceValueField(),
valueMapping: selectedMappings,
};
if (Object.keys(selectedMappings).length === 0) {
// do not store empty mappings and delete any potentially existing mappings
delete newValue.valueMapping;
}
this.dialogRef.close(newValue);
}
}
/**
* The DialogData for the `AutomatedFieldMappingComponent`
*/
export interface AutomatedFieldMappingDialogData {
currentEntityType: EntityConstructor;
currentField: EntitySchemaField;
sourceValueEntityType: EntityConstructor;
value: DefaultValueConfigInheritedField;
}
<h2 mat-dialog-title i18n>Configure Automation Rule</h2>
<app-dialog-close mat-dialog-close></app-dialog-close>
<mat-dialog-content>
<p i18n>
You are linking this field to the value of a related
{{ sourceValueEntityType.label }} record. The system will show a dialog to
automatically update this field whenever the related record's field changes.
You can still manually adjust the value if needed. This helps you ensure
data consistency and reduces manual work.
</p>
<p>
(<span i18n>Field defining the related source record:</span>
<app-entity-field-label
[field]="value?.sourceReferenceField"
[entityType]="sourceValueEntityType"
></app-entity-field-label
>)
</p>
<mat-form-field
appearance="fill"
matTooltip="Choose the field in the related record that will trigger updates to this field when it is changed."
i18n-matTooltip
>
<mat-label i18n
>Source value field (of {{ sourceValueEntityType.label }})
</mat-label>
<app-entity-field-select
[entityType]="sourceValueEntityType"
[value]="selectedSourceValueField()"
(valueChange)="
selectedSourceValueField.set(
typeof $event === 'object' ? $event[0] : $event
)
"
></app-entity-field-select>
</mat-form-field>
<!--
Detailed trigger value -> target value mapping
-->
<mat-slide-toggle
[disabled]="valueMappingOptions().length === 0"
matTooltip="If the source is a dropdown field, you can optionally also define a custom mapping for specific values."
i18n-matTooltip
[checked]="mappingEnabled()"
(change)="mappingEnabled.set($event.checked)"
i18n
>
Transform value with custom mapping
</mat-slide-toggle>
@if (valueMappingOptions().length > 0 && mappingEnabled()) {
<div class="mapping-grid">
<div class="header-row">
<h3 class="mapping-header" i18n>When "trigger field" changes to</h3>
<h3 class="mapping-header" i18n>then this field is updated to</h3>
</div>
@for (v of valueMappingOptions(); track v.sourceValue.id) {
<div class="option-label">{{ v.sourceValue.label }}</div>
<div class="dropdown-select">
@if (v.form) {
<app-entity-field-edit
[field]="targetFieldConfig"
[form]="v.form"
></app-entity-field-edit>
}
</div>
}
</div>
}
</mat-dialog-content>
<mat-dialog-actions class="flex-row flex-wrap">
<button
mat-button
(click)="save()"
[disabled]="!selectedSourceValueField()"
i18n
>
Save
</button>
<button mat-button mat-dialog-close i18n>Cancel</button>
@if (isInvalidMapping) {
<mat-error i18n>
The data is invalid, please check the fields in the "Configure mapping"
</mat-error>
}
</mat-dialog-actions>
.mapping-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
align-items: center;
margin-top: 20px;
.header-row {
grid-column: 1 / -1;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-bottom: 12px;
}
.option-label {
font-weight: 500;
padding: 8px 0;
}
}
:host ::ng-deep .mat-mdc-form-field {
width: 100%;
}
.mapping-header {
font-weight: bold;
}
.no-options-message {
font-style: italic;
}
Legend
Html element with directive