Skip to content

Commit 3b95c6e

Browse files
Merge branch 'w2p-129946_enforce-non-repeatable-fields-in-submission-forms-7.6' into w2p-129946_enforce-non-repeatable-fields-in-submission-forms-main
2 parents 28f865e + 7755dbd commit 3b95c6e

File tree

4 files changed

+50
-2
lines changed

4 files changed

+50
-2
lines changed

src/app/init.service.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
TransferState,
1414
Type,
1515
} from '@angular/core';
16-
import { DYNAMIC_FORM_CONTROL_MAP_FN } from '@ng-dynamic-forms/core';
16+
import { DYNAMIC_FORM_CONTROL_MAP_FN, DYNAMIC_VALIDATORS } from '@ng-dynamic-forms/core';
1717
import {
1818
select,
1919
Store,
@@ -45,6 +45,7 @@ import { MenuService } from './shared/menu/menu.service';
4545
import { MenuProviderService } from './shared/menu/menu-provider.service';
4646
import { ThemeService } from './shared/theme-support/theme.service';
4747
import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider';
48+
import { CUSTOM_VALIDATORS } from './shared/form/builder/parsers/field-parser';
4849

4950
/**
5051
* Performs the initialization of the app.
@@ -120,6 +121,10 @@ export abstract class InitService {
120121
provide: DYNAMIC_FORM_CONTROL_MAP_FN,
121122
useValue: dsDynamicFormControlMapFn,
122123
},
124+
{
125+
provide: DYNAMIC_VALIDATORS,
126+
useValue: CUSTOM_VALIDATORS,
127+
},
123128
];
124129
}
125130

src/app/shared/form/builder/parsers/field-parser.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { VisibilityType } from './../../../../submission/sections/visibility-typ
3636
import { setLayout } from './parser.utils';
3737
import { ParserOptions } from './parser-options';
3838
import { ParserType } from './parser-type';
39+
import { AbstractControl, ValidatorFn } from '@angular/forms';
3940

4041
export const SUBMISSION_ID: InjectionToken<string> = new InjectionToken<string>('submissionId');
4142
export const CONFIG_DATA: InjectionToken<FormFieldModel> = new InjectionToken<FormFieldModel>('configData');
@@ -48,6 +49,28 @@ export const PARSER_OPTIONS: InjectionToken<ParserOptions> = new InjectionToken<
4849
*/
4950
export const REGEX_FIELD_VALIDATOR = new RegExp('(\\/?)(.+)\\1([gimsuy]*)', 'i');
5051

52+
/**
53+
* Define custom form validators here
54+
*
55+
* Register them by adding their key to a model's validator property, e.g:
56+
* ```ts
57+
* model.validators = Object.assign({}, model.validators, { notRepeatable: null });
58+
* ```
59+
*/
60+
export const CUSTOM_VALIDATORS = new Map<string, ValidatorFn>([
61+
['notRepeatable', notRepeatableValidator],
62+
]);
63+
64+
export function notRepeatableValidator(control: AbstractControl) {
65+
const value = control.value;
66+
if (!Array.isArray(value) || value.length < 2) {
67+
return null;
68+
}
69+
return {
70+
notRepeatable: true,
71+
};
72+
}
73+
5174
export abstract class FieldParser {
5275

5376
protected fieldId: string;
@@ -131,7 +154,11 @@ export abstract class FieldParser {
131154
},
132155
};
133156

134-
return new DynamicRowArrayModel(config, layout);
157+
const model = new DynamicRowArrayModel(config, layout);
158+
if (config.notRepeatable) {
159+
this.addNotRepeatableValidator(model);
160+
}
161+
return model;
135162

136163
} else {
137164
const model = this.modelFactory(this.getInitFieldValue());
@@ -426,6 +453,17 @@ export abstract class FieldParser {
426453
{ required: this.configData.mandatoryMessage });
427454
}
428455

456+
protected addNotRepeatableValidator(controlModel) {
457+
controlModel.validators = Object.assign({}, controlModel.validators, { notRepeatable: null });
458+
controlModel.errorMessages = Object.assign(
459+
{},
460+
controlModel.errorMessages,
461+
{
462+
notRepeatable: 'error.validation.not.repeatable',
463+
},
464+
);
465+
}
466+
429467
protected setLabel(controlModel, label = true, labelEmpty = false) {
430468
if (label) {
431469
controlModel.label = (labelEmpty) ? '&nbsp;' : this.configData.label;

src/app/submission/sections/form/section-form.component.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,9 @@ export class SubmissionSectionFormComponent extends SectionModelComponent {
298298
* @private
299299
*/
300300
private inCurrentSubmissionScope(field: string): boolean {
301+
if (!this.sectionMetadata.includes(field)) {
302+
return false;
303+
}
301304
const scope = this.formConfig?.rows.find((row: FormRowModel) => {
302305
if (row.fields?.[0]?.selectableMetadata) {
303306
return row.fields?.[0]?.selectableMetadata?.[0]?.metadata === field;

src/assets/i18n/en.json5

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1910,6 +1910,8 @@
19101910

19111911
"error.validation.required": "This field is required",
19121912

1913+
"error.validation.not.repeatable": "This field only accepts one value. Please discard the additional ones.",
1914+
19131915
"error.validation.NotValidEmail": "This is not a valid email",
19141916

19151917
"error.validation.emailTaken": "This email is already taken",

0 commit comments

Comments
 (0)