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 = new Subject(); 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 && (this.form.get('fields')).length > 1) { for (let i = 0; i < (this.form.get('fields')).length - 1; i++) { (this.form.get('fields')).removeAt(1); } (this.form.get('fields') as UntypedFormArray).controls.splice(1); } if ((this.form.get('fields')).length === 0) { const field: DescriptionTemplateFieldEditorModel = new DescriptionTemplateFieldEditorModel(); (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); // (this.form.get('fields')).push(fieldForm); // this.setTargetField(fieldForm); // fieldForm.updateValueAndValidity(); // } DeleteField(index) { const fieldsForm = 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(); (targetField.get('visible').get('rules')).push(rule.buildForm()); } toggleRequired(targetField: UntypedFormGroup, event: MatCheckboxChange) { let validationsControl = targetField.get('validations') as UntypedFormControl; let validations: Array = 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 = 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; } } (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; } }