From 5331b43a62b15a8e86dab4e0a4afae381179c411 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Wed, 7 Jun 2023 18:08:21 +0200 Subject: [PATCH 1/4] [DURACOM-152] Fixed read-only visibility for submission form fields --- .../models/ds-dynamic-input.model.ts | 1 + .../onebox/dynamic-onebox.component.html | 2 ++ .../models/onebox/dynamic-onebox.component.ts | 3 +++ ...dynamic-scrollable-dropdown.component.html | 7 ++++-- ...dynamic-scrollable-dropdown.component.scss | 4 +++ .../form/builder/models/form-field.model.ts | 4 +++ .../builder/parsers/concat-field-parser.ts | 6 +++++ .../form/builder/parsers/field-parser.ts | 17 +++++++++++-- .../builder/parsers/onebox-field-parser.ts | 11 ++++---- .../shared/form/builder/parsers/row-parser.ts | 25 ++++++++++++++++--- 10 files changed, 67 insertions(+), 13 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model.ts index edbd5710d2b..3c6abaa851a 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model.ts @@ -55,6 +55,7 @@ export class DsDynamicInputModel extends DynamicInputModel { this.metadataFields = config.metadataFields; this.hint = config.hint; this.readOnly = config.readOnly; + this.disabled = config.readOnly; this.value = config.value; this.relationship = config.relationship; this.submissionId = config.submissionId; diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component.html index e6b0cf508f5..3c19ecda13f 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component.html @@ -39,6 +39,7 @@ [ngbTypeahead]="search" [placeholder]="model.placeholder" [readonly]="model.readOnly" + [disabled]="model.readOnly" [resultTemplate]="rt" [type]="model.inputType" [(ngModel)]="currentValue" @@ -63,6 +64,7 @@ [name]="model.name" [placeholder]="model.placeholder" [readonly]="true" + [disabled]="model.readOnly" [type]="model.inputType" [value]="currentValue?.display" (focus)="onFocus($event)" diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component.ts index db278716e11..2ff4256404d 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component.ts @@ -216,6 +216,9 @@ export class DsDynamicOneboxComponent extends DsDynamicVocabularyComponent imple * @param event The click event fired */ openTree(event) { + if (this.model.readOnly) { + return; + } event.preventDefault(); event.stopImmediatePropagation(); this.subs.push(this.vocabulary$.pipe( diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.html index 8a4d502287f..3a75fe10371 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.html @@ -3,8 +3,10 @@ role="combobox" [attr.aria-label]="model.label" [attr.aria-owns]="'combobox_' + id + '_listbox'"> - + + = this.initModel(newId + QUALDROP_METADATA_SUFFIX, label, false, false); selectModelConfig.hint = null; this.setOptions(selectModelConfig); if (isNotEmpty(fieldValue)) { selectModelConfig.value = fieldValue.metadata; } - inputSelectGroup.group.push(new DynamicSelectModel(selectModelConfig, clsSelect)); - - const inputModelConfig: DsDynamicInputModelConfig = this.initModel(newId + QUALDROP_VALUE_SUFFIX, label, false, false); - inputModelConfig.hint = null; - this.setValues(inputModelConfig, fieldValue); + selectModelConfig.disabled = inputModelConfig.readOnly; inputSelectGroup.readOnly = selectModelConfig.disabled && inputModelConfig.readOnly; + inputSelectGroup.group.push(new DynamicSelectModel(selectModelConfig, clsSelect)); inputSelectGroup.group.push(new DsDynamicInputModel(inputModelConfig, clsInput)); return new DynamicQualdropModel(inputSelectGroup, clsGroup); diff --git a/src/app/shared/form/builder/parsers/row-parser.ts b/src/app/shared/form/builder/parsers/row-parser.ts index 2818e37b25f..6602e537147 100644 --- a/src/app/shared/form/builder/parsers/row-parser.ts +++ b/src/app/shared/form/builder/parsers/row-parser.ts @@ -1,9 +1,10 @@ +import { SectionVisibility } from './../../../../submission/objects/section-visibility.model'; import { Injectable, Injector } from '@angular/core'; import { DYNAMIC_FORM_CONTROL_TYPE_ARRAY, DynamicFormGroupModelConfig } from '@ng-dynamic-forms/core'; import uniqueId from 'lodash/uniqueId'; -import { isEmpty } from '../../../empty.util'; +import { isEmpty, isNotEmpty } from '../../../empty.util'; import { DynamicRowGroupModel } from '../ds-dynamic-form-ui/models/ds-dynamic-row-group-model'; import { FormFieldModel } from '../models/form-field.model'; import { CONFIG_DATA, FieldParser, INIT_FORM_VALUES, PARSER_OPTIONS, SUBMISSION_ID } from './field-parser'; @@ -12,6 +13,7 @@ import { ParserOptions } from './parser-options'; import { ParserType } from './parser-type'; import { setLayout } from './parser.utils'; import { DYNAMIC_FORM_CONTROL_TYPE_RELATION_GROUP } from '../ds-dynamic-form-ui/ds-dynamic-form-constants'; +import { VisibilityType } from '../../../../submission/sections/visibility-type'; export const ROW_ID_PREFIX = 'df-row-group-config-'; @@ -118,15 +120,30 @@ export class RowParser { return parsedResult; } - checksFieldScope(fieldScope, submissionScope) { - return (isEmpty(fieldScope) || isEmpty(submissionScope) || fieldScope === submissionScope); + checksFieldScope(fieldScope, submissionScope, visibility: SectionVisibility) { + return (isEmpty(fieldScope) || !this.isHidden(visibility, fieldScope, submissionScope)); + } + + /** + * Check if the field is hidden or not, based on the visibility and the submission scope + * @param visibility The visibility of the field + * @param scope the scope of the field + * @param submissionScope the scope of the submission + * @returns If the field is hidden or not + */ + private isHidden(visibility: SectionVisibility, scope: string, submissionScope: string): boolean { + return isEmpty(visibility) + || (isNotEmpty(visibility) && visibility.main !== VisibilityType.READONLY) + && isNotEmpty(submissionScope) + && (isNotEmpty(scope) + && scope !== submissionScope); } filterScopedFields(fields: FormFieldModel[], submissionScope): FormFieldModel[] { const filteredFields: FormFieldModel[] = []; fields.forEach((field: FormFieldModel) => { // Whether field scope doesn't match the submission scope, skip it - if (this.checksFieldScope(field.scope, submissionScope)) { + if (this.checksFieldScope(field.scope, submissionScope, field.visibility)) { filteredFields.push(field); } }); From fda4ef77e49b5eee2cafdbf8eec553c60b21a64b Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Thu, 15 Jun 2023 12:42:29 +0200 Subject: [PATCH 2/4] [DURACOM-152] Section visibility read-only --- .../sections/form/section-form.component.spec.ts | 2 ++ .../sections/form/section-form.component.ts | 16 ++++++++++++---- src/app/submission/sections/sections.service.ts | 5 +++-- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/app/submission/sections/form/section-form.component.spec.ts b/src/app/submission/sections/form/section-form.component.spec.ts index 889f17c3935..346a22fa842 100644 --- a/src/app/submission/sections/form/section-form.component.spec.ts +++ b/src/app/submission/sections/form/section-form.component.spec.ts @@ -249,6 +249,8 @@ describe('SubmissionSectionFormComponent test suite', () => { formConfigService.findByHref.and.returnValue(createSuccessfulRemoteDataObject$(testFormConfiguration)); sectionsServiceStub.getSectionData.and.returnValue(observableOf(sectionData)); sectionsServiceStub.getSectionServerErrors.and.returnValue(observableOf([])); + sectionsServiceStub.isSectionReadOnly.and.returnValue(observableOf(false)); + spyOn(comp, 'initForm'); spyOn(comp, 'subscriptions'); diff --git a/src/app/submission/sections/form/section-form.component.ts b/src/app/submission/sections/form/section-form.component.ts index 326d277ffb0..a89a9aa886d 100644 --- a/src/app/submission/sections/form/section-form.component.ts +++ b/src/app/submission/sections/form/section-form.component.ts @@ -118,6 +118,12 @@ export class SubmissionSectionFormComponent extends SectionModelComponent { protected subs: Subscription[] = []; protected submissionObject: SubmissionObject; + + /** + * A flag representing if this section is readonly + */ + protected isSectionReadonly = false; + /** * The FormComponent reference */ @@ -175,13 +181,15 @@ export class SubmissionSectionFormComponent extends SectionModelComponent { this.sectionService.getSectionData(this.submissionId, this.sectionData.id, this.sectionData.sectionType), this.submissionObjectService.findById(this.submissionId, true, false, followLink('item')).pipe( getFirstSucceededRemoteData(), - getRemoteDataPayload()) + getRemoteDataPayload()), + this.sectionService.isSectionReadOnly(this.submissionId, this.sectionData.id, this.submissionService.getSubmissionScope()) ])), take(1)) - .subscribe(([sectionData, submissionObject]: [WorkspaceitemSectionFormObject, SubmissionObject]) => { + .subscribe(([sectionData, submissionObject, isSectionReadOnly]: [WorkspaceitemSectionFormObject, SubmissionObject, boolean]) => { if (isUndefined(this.formModel)) { // this.sectionData.errorsToShow = []; this.submissionObject = submissionObject; + this.isSectionReadonly = isSectionReadOnly; // Is the first loading so init form this.initForm(sectionData); this.sectionData.data = sectionData; @@ -286,11 +294,11 @@ export class SubmissionSectionFormComponent extends SectionModelComponent { this.formConfig, this.collectionId, sectionData, - this.submissionService.getSubmissionScope() + this.submissionService.getSubmissionScope(), + this.isSectionReadonly ); const sectionMetadata = this.sectionService.computeSectionConfiguredMetadata(this.formConfig); this.sectionService.updateSectionData(this.submissionId, this.sectionData.id, sectionData, this.sectionData.errorsToShow, this.sectionData.serverValidationErrors, sectionMetadata); - } catch (e) { const msg: string = this.translate.instant('error.submission.sections.init-form-error') + e.toString(); const sectionError: SubmissionSectionError = { diff --git a/src/app/submission/sections/sections.service.ts b/src/app/submission/sections/sections.service.ts index cc2802dba7d..0ea62322370 100644 --- a/src/app/submission/sections/sections.service.ts +++ b/src/app/submission/sections/sections.service.ts @@ -334,8 +334,9 @@ export class SectionsService { filter((sectionObj) => hasValue(sectionObj)), map((sectionObj: SubmissionSectionObject) => { return isNotEmpty(sectionObj.visibility) - && sectionObj.visibility.other === 'READONLY' - && submissionScope !== SubmissionScopeType.WorkspaceItem; + && ((sectionObj.visibility.other === 'READONLY' && submissionScope !== SubmissionScopeType.WorkspaceItem) + || (sectionObj.visibility.main === 'READONLY' && submissionScope === SubmissionScopeType.WorkspaceItem) + ); }), distinctUntilChanged()); } From d00ad0cd0e33e8eb3590768e56e64f1e24e694ba Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Fri, 16 Jun 2023 13:27:51 +0200 Subject: [PATCH 3/4] [DURACOM-152] Field visibility --- .../submission/submission-field-scope-type.ts | 4 ++++ .../core/submission/submission-scope-type.ts | 2 +- .../form/builder/parsers/field-parser.ts | 22 ++++++++++++++----- .../shared/form/builder/parsers/row-parser.ts | 22 +++++++++++++------ 4 files changed, 37 insertions(+), 13 deletions(-) create mode 100644 src/app/core/submission/submission-field-scope-type.ts diff --git a/src/app/core/submission/submission-field-scope-type.ts b/src/app/core/submission/submission-field-scope-type.ts new file mode 100644 index 00000000000..4a9292ec3da --- /dev/null +++ b/src/app/core/submission/submission-field-scope-type.ts @@ -0,0 +1,4 @@ +export enum SubmissionFieldScopeType { + WorkspaceItem = 'SUBMISSION', + WorkflowItem = 'WORKFLOW', +} diff --git a/src/app/core/submission/submission-scope-type.ts b/src/app/core/submission/submission-scope-type.ts index 6ed32d3b4e5..f319e5c473e 100644 --- a/src/app/core/submission/submission-scope-type.ts +++ b/src/app/core/submission/submission-scope-type.ts @@ -1,4 +1,4 @@ export enum SubmissionScopeType { WorkspaceItem = 'WORKSPACE', - WorkflowItem = 'WORKFLOW' + WorkflowItem = 'WORKFLOW', } diff --git a/src/app/shared/form/builder/parsers/field-parser.ts b/src/app/shared/form/builder/parsers/field-parser.ts index e118ca7add2..7ea55d44549 100644 --- a/src/app/shared/form/builder/parsers/field-parser.ts +++ b/src/app/shared/form/builder/parsers/field-parser.ts @@ -24,6 +24,7 @@ import { RelationshipOptions } from '../models/relationship-options.model'; import { VocabularyOptions } from '../../../../core/submission/vocabularies/models/vocabulary-options.model'; import { ParserType } from './parser-type'; import { isNgbDateStruct } from '../../../date.util'; +import { SubmissionScopeType } from '../../../../core/submission/submission-scope-type'; export const SUBMISSION_ID: InjectionToken = new InjectionToken('submissionId'); export const CONFIG_DATA: InjectionToken = new InjectionToken('configData'); @@ -282,7 +283,7 @@ export abstract class FieldParser { controlModel.id = (this.fieldId).replace(/\./g, '_'); // Set read only option - controlModel.readOnly = this.parserOptions.readOnly || this.isFieldReadOnly(this.configData.visibility, this.parserOptions.submissionScope); + controlModel.readOnly = this.parserOptions.readOnly || this.isFieldReadOnly(this.configData.visibility, this.configData.scope, this.parserOptions.submissionScope); controlModel.disabled = controlModel.readOnly; if (hasValue(this.configData.selectableRelationship)) { controlModel.relationship = Object.assign(new RelationshipOptions(), this.configData.selectableRelationship); @@ -322,14 +323,25 @@ export abstract class FieldParser { } /** - * Check if a field is read-only with the given scope + * Checks if a field is read-only with the given scope. + * The field is readonly when submissionScope is WORKSPACE and the main visibility is READONLY + * or when submissionScope is WORKFLOW and the other visibility is READONLY * @param visibility * @param submissionScope */ - private isFieldReadOnly(visibility: SectionVisibility, submissionScope: string) { + private isFieldReadOnly(visibility: SectionVisibility, fieldScope: string, submissionScope: string) { return isNotEmpty(submissionScope) - && isNotEmpty(visibility) - && visibility.main === VisibilityType.READONLY; + && isNotEmpty(fieldScope) + && isNotEmpty(visibility) + && (( + submissionScope === SubmissionScopeType.WorkspaceItem + && visibility.main === VisibilityType.READONLY + ) + || + (visibility.other === VisibilityType.READONLY + && submissionScope === SubmissionScopeType.WorkflowItem + ) + ); } /** diff --git a/src/app/shared/form/builder/parsers/row-parser.ts b/src/app/shared/form/builder/parsers/row-parser.ts index 6602e537147..3f5b4a04c61 100644 --- a/src/app/shared/form/builder/parsers/row-parser.ts +++ b/src/app/shared/form/builder/parsers/row-parser.ts @@ -1,3 +1,4 @@ +import { SubmissionFieldScopeType } from './../../../../core/submission/submission-field-scope-type'; import { SectionVisibility } from './../../../../submission/objects/section-visibility.model'; import { Injectable, Injector } from '@angular/core'; @@ -13,7 +14,7 @@ import { ParserOptions } from './parser-options'; import { ParserType } from './parser-type'; import { setLayout } from './parser.utils'; import { DYNAMIC_FORM_CONTROL_TYPE_RELATION_GROUP } from '../ds-dynamic-form-ui/ds-dynamic-form-constants'; -import { VisibilityType } from '../../../../submission/sections/visibility-type'; +import { SubmissionScopeType } from '../../../../core/submission/submission-scope-type'; export const ROW_ID_PREFIX = 'df-row-group-config-'; @@ -125,18 +126,25 @@ export class RowParser { } /** - * Check if the field is hidden or not, based on the visibility and the submission scope + * Check if the field is hidden or not. + * It is hidden when we do have the scope, + * but we do not have the visibility, + * also the field scope should be different from the submissionScope. * @param visibility The visibility of the field * @param scope the scope of the field * @param submissionScope the scope of the submission * @returns If the field is hidden or not */ private isHidden(visibility: SectionVisibility, scope: string, submissionScope: string): boolean { - return isEmpty(visibility) - || (isNotEmpty(visibility) && visibility.main !== VisibilityType.READONLY) - && isNotEmpty(submissionScope) - && (isNotEmpty(scope) - && scope !== submissionScope); + return isNotEmpty(scope) + && ( + isEmpty(visibility) + && ( + submissionScope === SubmissionScopeType.WorkspaceItem && scope !== SubmissionFieldScopeType.WorkspaceItem + || + submissionScope === SubmissionScopeType.WorkflowItem && scope !== SubmissionFieldScopeType.WorkflowItem + ) + ); } filterScopedFields(fields: FormFieldModel[], submissionScope): FormFieldModel[] { From 42026b36d52b4468cc93b6e1f0ad8c108bad9211 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Fri, 16 Jun 2023 15:31:45 +0200 Subject: [PATCH 4/4] [DURACOM-152] refactored concatenated fields visibility --- .../ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts | 2 ++ src/app/shared/form/builder/parsers/concat-field-parser.ts | 6 +----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts index 1d6037a4097..dc7c7966485 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts @@ -46,6 +46,7 @@ export class DynamicConcatModel extends DynamicFormGroupModel { @serializable() submissionId: string; @serializable() hasSelectableMetadata: boolean; @serializable() metadataValue: MetadataValue; + @serializable() readOnly?: boolean; isCustomGroup = true; valueUpdates: Subject; @@ -65,6 +66,7 @@ export class DynamicConcatModel extends DynamicFormGroupModel { this.valueUpdates = new Subject(); this.valueUpdates.subscribe((value: string) => this.value = value); this.typeBindRelations = config.typeBindRelations ? config.typeBindRelations : []; + this.readOnly = config.disabled; } get value() { diff --git a/src/app/shared/form/builder/parsers/concat-field-parser.ts b/src/app/shared/form/builder/parsers/concat-field-parser.ts index b849698857e..e86de70c816 100644 --- a/src/app/shared/form/builder/parsers/concat-field-parser.ts +++ b/src/app/shared/form/builder/parsers/concat-field-parser.ts @@ -19,8 +19,6 @@ import { SUBMISSION_ID } from './field-parser'; import { DsDynamicInputModel, DsDynamicInputModelConfig } from '../ds-dynamic-form-ui/models/ds-dynamic-input.model'; -import { VisibilityType } from '../../../../submission/sections/visibility-type'; -import isEqual from 'lodash/isEqual'; export class ConcatFieldParser extends FieldParser { @@ -85,9 +83,7 @@ export class ConcatFieldParser extends FieldParser { input1ModelConfig.required = true; } - if (isNotEmpty(this.configData.visibility) && isEqual(this.configData.visibility.main, VisibilityType.READONLY)) { - concatGroup.disabled = true; - } + concatGroup.disabled = input1ModelConfig.readOnly; if (isNotEmpty(this.firstPlaceholder)) { input1ModelConfig.placeholder = this.firstPlaceholder;