argos/dmp-frontend/src/app/ui/admin/description-template/editor/components/composite-field/description-template-editor...

699 lines
22 KiB
TypeScript

import { Component, Input, OnChanges, OnInit, SimpleChanges, ViewChild } from '@angular/core';
import { AbstractControl, UntypedFormArray, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { MatCheckboxChange } from '@angular/material/checkbox';
import { MatDialog } from '@angular/material/dialog';
import { DescriptionTemplateFieldType } from '@app/core/common/enum/description-template-field-type';
import { ValidationType } from '@app/core/common/enum/validation-type';
import {
DescriptionTemplateExternalDatasetData,
DescriptionTemplateField,
DescriptionTemplateFieldSet,
DescriptionTemplateLabelAndMultiplicityData,
DescriptionTemplateLabelData,
DescriptionTemplateRadioBoxData,
DescriptionTemplateReferenceTypeData,
DescriptionTemplateSelectData,
DescriptionTemplateSelectOption,
DescriptionTemplateUploadData
} from '@app/core/model/description-template/description-template';
import { ConfigurationService } from "@app/core/services/configuration/configuration.service";
import { DescriptionTemplateService } from '@app/core/services/description-template/description-template.service';
import { EnumUtils } from '@app/core/services/utilities/enum-utils.service';
import { TransitionGroupComponent } from "@app/ui/transition-group/transition-group.component";
import { BaseComponent } from '@common/base/base.component';
import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model';
import { ConfirmationDialogComponent } from '@common/modules/confirmation-dialog/confirmation-dialog.component';
import { Guid } from '@common/types/guid';
import { TranslateService } from '@ngx-translate/core';
import { Subject } from 'rxjs';
import { debounceTime, delay, map, takeUntil, tap } from 'rxjs/operators';
import { GENERAL_ANIMATIONS } from '../../animations/animations';
import { EditorCustomValidators } from '../../custom-validators/editor-custom-validators';
import { DescriptionTemplateFieldEditorModel, DescriptionTemplateRuleEditorModel, DescriptionTemplateSectionEditorModel } from '../../description-template-editor.model';
import { DescriptionTemplateFieldSetPersist } from '@app/core/model/description-template/description-template-persist';
import { DescriptionEditorModel, DescriptionPropertyDefinitionEditorModel } from '@app/ui/description/editor/description-editor.model';
import { Description } from '@app/core/model/description/description';
import { VisibilityRulesService } from '@app/ui/description/editor/description-form/visibility-rules/visibility-rules.service';
@Component({
selector: 'app-description-template-editor-composite-field-component',
templateUrl: './description-template-editor-composite-field.component.html',
styleUrls: ['./description-template-editor-composite-field.component.scss'],
animations: [GENERAL_ANIMATIONS]
})
export class DescriptionTemplateEditorCompositeFieldComponent extends BaseComponent implements OnInit, OnChanges {
@Input() form: UntypedFormGroup;
@Input() viewOnly: boolean;
@Input() datasetProfileId?: string;
@Input() numbering: string;
@Input() hasFocus: boolean = false;
@ViewChild("inputs") inputs: TransitionGroupComponent;
@Input() validationErrorModel: ValidationErrorModel;
@Input() validationRootPath: string;
showPreview: boolean = true;
previewDirty: boolean = false;
showDescription: boolean = true;
showAdditionalInfo: boolean = false;
showExtendedDescription: boolean = false;
//Preview
previewFieldSet: DescriptionTemplateFieldSet = null;
previewPropertiesFormGroup: UntypedFormGroup = null;
// isComposite = false;
// isMultiplicityEnabled = false;
descriptionTemplateFieldTypeEnum = DescriptionTemplateFieldType;
private myCustomValidators: EditorCustomValidators = new EditorCustomValidators();
isMultiplicityEnabled = false;
constructor(
private dialog: MatDialog,
private language: TranslateService,
public enumUtils: EnumUtils,
public datasetProfileService: DescriptionTemplateService,
private configurationService: ConfigurationService,
) {
super();
}
ngOnChanges(changes: SimpleChanges) {
// this.setTargetField(null);
// this.showExtendedDescription = !!this.form.get('extendedDescription').value;
// this.showAdditionalInfo = !!this.form.get('additionalInformation').value;
// console.log(this.form.get('fields')['controls'])
if (changes['form']) {
try {
const multiplicity = this.form.get('multiplicity').value;
this.isMultiplicityEnabled = multiplicity.min > 0 || multiplicity.max > 0;
} catch {
this.isMultiplicityEnabled = false;
}
}
}
get firstField() {
try {
return (this.form.get('fields') as UntypedFormArray).at(0);
} catch {
return null;
}
}
ngOnInit() {
//this.addNewField();
// if (this.form.get('multiplicity')) {
// if (this.form.get('multiplicity').value.min > 1 || this.form.get('multiplicity').value.max > 1) {
// this.isMultiplicityEnabled = true;
// }
// }
// this.isComposite = (this.form.get('fields') as FormArray).length > 1;
if (this.viewOnly) {
this.form.get('hasCommentField').disable();
}
//SET UP TARGET FIELD
// if((this.form.get('fields') as FormArray).length>0){
// //get the first field in list
// this.targetField = (this.form.get('fields') as FormArray).at(0) as FormGroup;
// }
this.showExtendedDescription = !!this.form.get('extendedDescription').value;
this.showAdditionalInfo = !!this.form.get('additionalInformation').value;
this.form.valueChanges.pipe(takeUntil(this._destroyed)).subscribe(changes => {
// this.previewForm = null;
this.previewDirty = true;
this.generatePreviewForm();
});
this.previewSubject$
.pipe(debounceTime(600))
.pipe(
takeUntil(this._destroyed),
map(model => model.buildForm()),
map(updatedForm => {
const previewContainer = document.getElementById('preview_container' + this.form.get('id').value);
// let clientHeight = -1;
if (previewContainer) {
// console.log(previewContainer);
const clientHeight = previewContainer.clientHeight;
// console.log(clientHeight);
if (clientHeight) {
previewContainer.style.height = clientHeight.toString() + 'px';
// console.log('height:' ,previewContainer.style.height);
}
}
this.showPreview = false;
this.previewDirty = true;
// this.previewForm = updatedForm;
return previewContainer;
}),
delay(100),
tap(previewContainer => {
this.showPreview = true;
this.previewDirty = false;
}),
delay(100)
)
.subscribe(previewContainer => {
if (previewContainer) {
previewContainer.style.height = 'auto';
}
// const updatedForm = model.buildForm();
// this.reloadPreview(updatedForm)
});
this.generatePreviewForm();
}
get updatedClass() {
if (this.previewDirty) return '';
else return 'updated';
}
// private reloadPreview(updatedForm: UntypedFormGroup) {
// setTimeout(() => {
// const previewContainer = document.getElementById('preview_container' + this.form.get('id').value);
// // let clientHeight = -1;
// if (previewContainer) {
// // console.log(previewContainer);
// const clientHeight = previewContainer.clientHeight;
// // console.log(clientHeight);
// if (clientHeight) {
// previewContainer.style.height = clientHeight.toString() + 'px';
// // console.log('height:' ,previewContainer.style.height);
// }
// }
// this.showPreview = false;
// this.previewDirty = true;
// this.previewForm = updatedForm;
// setTimeout(() => {
// this.showPreview = true;
// this.previewDirty = false;
// if (previewContainer) {
// setTimeout(() => {
// if (previewContainer) {
// previewContainer.style.height = 'auto';
// }
// });
// }
// });
// });
// }
previewSubject$: Subject<DescriptionTemplateSectionEditorModel> = new Subject<DescriptionTemplateSectionEditorModel>();
private generatePreviewForm() {
const formValue: DescriptionTemplateFieldSetPersist = this.form.getRawValue();
const fields: DescriptionTemplateField[] = formValue.fields.map(editorField => {
return {
id: editorField.id,
ordinal: editorField.ordinal,
numbering: '',
schematics: editorField.schematics,
defaultValue: editorField.defaultValue,
visibilityRules: editorField.visibilityRules,
validations: editorField.validations,
includeInExport: editorField.includeInExport,
data: editorField.data
}
});
const fieldSet: DescriptionTemplateFieldSet = {
id: formValue.id,
ordinal: formValue.ordinal,
numbering: '',
title: formValue.title,
description: formValue.description,
extendedDescription: formValue.extendedDescription,
additionalInformation: formValue.additionalInformation,
multiplicity: {
max: formValue.multiplicity.max, min: formValue.multiplicity.min,
placeholder: formValue.multiplicity.placeholder, tableView: formValue.multiplicity.tableView
},
hasCommentField: formValue.hasCommentField,
fields: fields
}
const mockDescription: Description = {
descriptionTemplate: {
definition: {
pages: [
{
sections: [{
fieldSets: [fieldSet]
}]
}
]
}
}
}
const descriptionEditorModel = new DescriptionEditorModel().fromModel(mockDescription, mockDescription.descriptionTemplate);
this.previewPropertiesFormGroup = descriptionEditorModel.properties.fieldSets.get(fieldSet.id).buildForm() as UntypedFormGroup;
this.previewFieldSet = fieldSet;
// this.previewSubject$.next(section);
}
// private _fieldToFieldDefinition(editorField: Field): FieldDefinition {
// const field = {
// id: editorField.id,
// title: '',
// page: editorField.page,
// numbering: '',
// multiplicity: null,
// multiplicityItems: null,
// viewStyle: editorField.viewStyle,
// defaultValue: editorField.defaultValue,
// value: null,
// validations: editorField.validations,
// } as FieldDefinition;
// field.data = editorField.data;
// // return new DatasetDescriptionFieldEditorModel().fromModel(field);
// return field;
// }
onIsCompositeChange(isComposite: boolean) {
if (!isComposite && (<UntypedFormArray>this.form.get('fields')).length > 1) {
for (let i = 0; i < (<UntypedFormArray>this.form.get('fields')).length - 1; i++) {
(<UntypedFormArray>this.form.get('fields')).removeAt(1);
}
(this.form.get('fields') as UntypedFormArray).controls.splice(1);
}
if ((<UntypedFormArray>this.form.get('fields')).length === 0) {
const field: DescriptionTemplateFieldEditorModel = new DescriptionTemplateFieldEditorModel();
(<UntypedFormArray>this.form.get('fields')).push(field.buildForm());
}
}
onIsMultiplicityEnabledChange(isMultiplicityEnabled: MatCheckboxChange) {
const multiplicity = this.form.get('multiplicity') as UntypedFormGroup;
const minControl = multiplicity.get('min');
const maxControl = multiplicity.get('max');
const placeholder = multiplicity.get('placeholder');
const tableView = multiplicity.get('tableView');
if (isMultiplicityEnabled.checked) {
minControl.setValue(0);
maxControl.setValue(1);
placeholder.setValue('');
tableView.setValue(false);
} else {
minControl.setValue(0);
maxControl.setValue(0);
placeholder.setValue(null);
tableView.setValue(null);
}
this.isMultiplicityEnabled = isMultiplicityEnabled.checked;
minControl.updateValueAndValidity();
maxControl.updateValueAndValidity();
}
// addNewField() {
// const field: DescriptionTemplateFieldEditorModel = new DescriptionTemplateFieldEditorModel();
// field.id = Guid.create().toString();
// field.ordinal = (this.form.get('fields') as UntypedFormArray).length;
// const fieldForm = field.buildForm();
// // fieldForm.setValidators(this.customFieldValidator());
// // fieldForm.get('viewStyle').get('renderStyle').setValidators(Validators.required);
// (<UntypedFormArray>this.form.get('fields')).push(fieldForm);
// this.setTargetField(fieldForm);
// fieldForm.updateValueAndValidity();
// }
DeleteField(index) {
const fieldsForm = <UntypedFormArray>this.form.get('fields');
fieldsForm.removeAt(index);
this.inputs.init();
// calculate ordinals
fieldsForm.controls.forEach((field, idx) => {
field.get('ordinal').setValue(idx);
field.updateValueAndValidity();
});
this.form.markAsDirty();//deactivate guard
}
getFieldTile(formGroup: UntypedFormGroup, index: number) {
if (formGroup.get('title') && formGroup.get('title').value && formGroup.get('title').value.length > 0) { return formGroup.get('title').value; }
return "Field " + (index + 1);
}
targetField: UntypedFormGroup;
validationTypeEnum = ValidationType;
addVisibilityRule(targetField: UntypedFormGroup) {
const rule: DescriptionTemplateRuleEditorModel = new DescriptionTemplateRuleEditorModel();
(<UntypedFormArray>targetField.get('visible').get('rules')).push(rule.buildForm());
}
toggleRequired(targetField: UntypedFormGroup, event: MatCheckboxChange) {
let validationsControl = targetField.get('validations') as UntypedFormControl;
let validations: Array<ValidationType> = validationsControl.value;
if (event.checked) {
if (!validations.includes(ValidationType.Required)) {//IS ALREADY REQUIRED
// validationsControl.setValue(validations.filter(validator=> validator != ValidationType.Required));
// validationsControl.updateValueAndValidity();
validations.push(ValidationType.Required);
// validationsControl.setValue(validations);
validationsControl.updateValueAndValidity();
}
} else {
validationsControl.setValue(validations.filter(validator => validator != ValidationType.Required));
validationsControl.updateValueAndValidity();
}
// if(validations.includes(ValidationType.Required)){//IS ALREADY REQUIRED
// validationsControl.setValue(validations.filter(validator=> validator != ValidationType.Required));
// validationsControl.updateValueAndValidity();
// }else{
// //SET REQUIRED VALIDATOR
// console.log('setting required validator');
// validations.push(ValidationType.Required);
// validationsControl.setValue(validations);
// validationsControl.updateValueAndValidity();
// }
}
setTargetField(field: AbstractControl) {
this.targetField = <UntypedFormGroup>field;
}
deleteTargetField() {
const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
restoreFocus: false,
data: {
message: this.language.instant('GENERAL.CONFIRMATION-DIALOG.DELETE-ITEM'),
confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'),
cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL'),
isDeleteConfirmation: true
}
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this._deleteTargetField();
}
});
}
private _deleteTargetField() {
if (!this.targetField) return;
let index = -1;
const fields = this.form.get('fields') as UntypedFormArray;
for (let i = 0; i < fields.length; i++) {
let field = fields.at(i);
if (field.get('id').value === this.targetField.get('id').value) {//index found
index = i;
break;
}
}
if (index >= 0) {//target found in fields
this.DeleteField(index);
this.targetField = null;
}
}
deleteField(index: number) {
const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
restoreFocus: false,
data: {
message: this.language.instant('GENERAL.CONFIRMATION-DIALOG.DELETE-ITEM'),
confirmButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CONFIRM'),
cancelButton: this.language.instant('GENERAL.CONFIRMATION-DIALOG.ACTIONS.CANCEL'),
isDeleteConfirmation: true
}
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.DeleteField(index);
}
});
}
addNewInput(type: DescriptionTemplateFieldType) {
const fieldsArray = this.form.get('fields') as UntypedFormArray;
let targetOrdinal = fieldsArray.length;
try {
targetOrdinal = fieldsArray.controls.map(control => control.get('ordinal').value).reduce((a, b) => Math.max(a, b)) + 1;
} catch {
}
const field = {
id: Guid.create().toString(),
ordinal: targetOrdinal,
validations: [],
includeInExport: true
} as DescriptionTemplateField;
switch (type) {
case DescriptionTemplateFieldType.REFERENCE_TYPES: {
const data: DescriptionTemplateReferenceTypeData = {
label: '',
multipleSelect: false,
fieldType: type
}
field.data = data;
break;
}
case DescriptionTemplateFieldType.RADIO_BOX: {
const data: DescriptionTemplateRadioBoxData = {
label: '',
options: [],
fieldType: type
}
field.data = data;
break;
}
case DescriptionTemplateFieldType.SELECT: {
const firstOption = { label: '', value: '' } as DescriptionTemplateSelectOption;
const data: DescriptionTemplateSelectData = {
label: '',
multipleSelect: false,
options: [firstOption],
fieldType: type
}
field.data = data;
break;
}
case DescriptionTemplateFieldType.BOOLEAN_DECISION:
case DescriptionTemplateFieldType.CHECK_BOX:
case DescriptionTemplateFieldType.FREE_TEXT:
case DescriptionTemplateFieldType.TEXT_AREA:
case DescriptionTemplateFieldType.RICH_TEXT_AREA:
case DescriptionTemplateFieldType.DATE_PICKER:
case DescriptionTemplateFieldType.TAGS:
case DescriptionTemplateFieldType.DATASET_IDENTIFIER:
case DescriptionTemplateFieldType.CURRENCY:
case DescriptionTemplateFieldType.VALIDATION: {
const data: DescriptionTemplateLabelData = {
label: '',
fieldType: type
}
field.data = data;
break;
}
case DescriptionTemplateFieldType.INTERNAL_ENTRIES_DMPS: {
//TODO: refactor
break;
}
case DescriptionTemplateFieldType.INTERNAL_ENTRIES_DMPS:
case DescriptionTemplateFieldType.INTERNAL_ENTRIES_DESCRIPTIONS: {
const data: DescriptionTemplateLabelAndMultiplicityData = {
label: '',
multipleSelect: false,
fieldType: type
}
field.data = data;
break;
}
case DescriptionTemplateFieldType.EXTERNAL_DATASETS: {
const data: DescriptionTemplateExternalDatasetData = {
label: '',
multipleSelect: false,
fieldType: type
}
field.data = data;
break;
}
case DescriptionTemplateFieldType.UPLOAD: {
const data: DescriptionTemplateUploadData = {
label: '',
types: [],
maxFileSizeInMB: this.configurationService.maxFileSizeInMB,
fieldType: type
}
field.data = data;
break;
}
}
(<UntypedFormArray>this.form.get('fields')).push(new DescriptionTemplateFieldEditorModel(this.validationErrorModel).fromModel(field).buildForm({ rootPath: this.validationRootPath + '.fields[' + this.fieldsArray.length + ']' }));
this.inputs.init();
// fieldForm.get('viewStyle').get('renderStyle').updateValueAndValidity();
// fieldForm.get('data').updateValueAndValidity();
}
// private customFieldValidator(): ValidatorFn{
// return (control):ValidationErrors | null=>{
// DescriptionTemplateFieldType
// switch(control.get('viewStyle').get('renderStyle').value){
// case DescriptionTemplateFieldType.TextArea:
// return null;
// case DescriptionTemplateFieldType.BooleanDecision:
// return null;
// case DescriptionTemplateFieldType.ComboBox:
// return null;
// case DescriptionTemplateFieldType.CheckBox:
// return null;
// case DescriptionTemplateFieldType.FreeText:
// return null;
// case DescriptionTemplateFieldType.RadioBox:
// return null;
// case DescriptionTemplateFieldType.DatePicker:
// return null;
// case DescriptionTemplateFieldType.InternalDmpEntities:
// return null;
// case DescriptionTemplateFieldType.ExternalDatasets:
// return null;
// case DescriptionTemplateFieldType.DATA_REPOSITORIES:
// return null;
// case DescriptionTemplateFieldType.Registries:
// return null;
// case DescriptionTemplateFieldType.Services:
// return null;
// case DescriptionTemplateFieldType.Tags:
// return null;
// case DescriptionTemplateFieldType.Researchers:
// return null;
// case DescriptionTemplateFieldType.Organizations:
// return null;
// case DescriptionTemplateFieldType.DatasetIdentifier:
// return null;
// case DescriptionTemplateFieldType.Currency:
// return null;
// case DescriptionTemplateFieldType.Validation:
// return null;
// default:
// return {inputTypeNotValid: true}
// }
// }
// }
// private _atLeastOneElementListValidator(arrayToCheck): ValidatorFn{
// return (control: AbstractControl): ValidationErrors | null=>{
// const fa = control.get(arrayToCheck) as FormArray;
// if(fa.length === 0){
// return {emptyArray: true};
// }
// return null;
// }
// }
calculateLabelWidth(numbering: string) {
const width = numbering.split('.').reduce((acc, item) => item + acc, '').length;
return { 'width': width + 'em' }
}
get fieldsArray(): UntypedFormArray {
if (this.form && this.form.get('fields')) {
return this.form.get('fields') as UntypedFormArray;
}
return null;
}
move(index, direction: "up" | "down" = "up") {
this.inputs.init();
if (direction === "up" && this.canGoUp(index)) {
let temp = this.fieldsArray.at(index);
this.fieldsArray.removeAt(index);
this.fieldsArray.insert(index - 1, temp);
} else if (direction === "down" && this.canGoDown(index)) {
let temp = this.fieldsArray.at(index + 1);
this.fieldsArray.removeAt(index + 1);
this.fieldsArray.insert(index, temp);
}
this.fieldsArray.controls.forEach((field, index) => {
field.get('ordinal').setValue(index);
});
}
canGoUp(index: number): boolean {
return index > 0 && !this.viewOnly;
}
canGoDown(index: number): boolean {
return index < (this.fieldsArray.length - 1) && !this.viewOnly;
}
}