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 0000000000..4a9292ec3d --- /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 6ed32d3b4e..f319e5c473 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/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 1d6037a409..dc7c796648 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/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 edbd5710d2..3c6abaa851 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 e6b0cf508f..3c19ecda13 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 db278716e1..2ff4256404 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 8a4d502287..3a75fe1037 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'"> - + + = new InjectionToken('submissionId'); export const CONFIG_DATA: InjectionToken = new InjectionToken('configData'); @@ -280,8 +283,8 @@ export abstract class FieldParser { controlModel.id = (this.fieldId).replace(/\./g, '_'); // Set read only option - controlModel.readOnly = this.parserOptions.readOnly; - controlModel.disabled = this.parserOptions.readOnly; + 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); } @@ -319,6 +322,28 @@ export abstract class FieldParser { return controlModel; } + /** + * 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, fieldScope: string, submissionScope: string) { + return isNotEmpty(submissionScope) + && isNotEmpty(fieldScope) + && isNotEmpty(visibility) + && (( + submissionScope === SubmissionScopeType.WorkspaceItem + && visibility.main === VisibilityType.READONLY + ) + || + (visibility.other === VisibilityType.READONLY + && submissionScope === SubmissionScopeType.WorkflowItem + ) + ); + } + /** * Get the type bind values from the REST data for a specific field * The return value is any[] in the method signature but in reality it's diff --git a/src/app/shared/form/builder/parsers/onebox-field-parser.ts b/src/app/shared/form/builder/parsers/onebox-field-parser.ts index 3dafa4eebd..606c41ea74 100644 --- a/src/app/shared/form/builder/parsers/onebox-field-parser.ts +++ b/src/app/shared/form/builder/parsers/onebox-field-parser.ts @@ -59,19 +59,20 @@ export class OneboxFieldParser extends FieldParser { this.setLabel(inputSelectGroup, label); inputSelectGroup.required = isNotEmpty(this.configData.mandatory); + const inputModelConfig: DsDynamicInputModelConfig = this.initModel(newId + QUALDROP_VALUE_SUFFIX, label, false, false); + inputModelConfig.hint = null; + this.setValues(inputModelConfig, fieldValue); + const selectModelConfig: DynamicSelectModelConfig = 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 2818e37b25..3f5b4a04c6 100644 --- a/src/app/shared/form/builder/parsers/row-parser.ts +++ b/src/app/shared/form/builder/parsers/row-parser.ts @@ -1,9 +1,11 @@ +import { SubmissionFieldScopeType } from './../../../../core/submission/submission-field-scope-type'; +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 +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 { SubmissionScopeType } from '../../../../core/submission/submission-scope-type'; export const ROW_ID_PREFIX = 'df-row-group-config-'; @@ -118,15 +121,37 @@ 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. + * 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 isNotEmpty(scope) + && ( + isEmpty(visibility) + && ( + submissionScope === SubmissionScopeType.WorkspaceItem && scope !== SubmissionFieldScopeType.WorkspaceItem + || + submissionScope === SubmissionScopeType.WorkflowItem && scope !== SubmissionFieldScopeType.WorkflowItem + ) + ); } 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); } }); 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 4ea37b93e0..4fbf324edf 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 3fa9000039..2a07f7e3f1 100644 --- a/src/app/submission/sections/form/section-form.component.ts +++ b/src/app/submission/sections/form/section-form.component.ts @@ -119,6 +119,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 */ @@ -176,13 +182,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; @@ -295,11 +303,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 cc2802dba7..0ea6232237 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()); }