Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/app/init.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import {
TransferState,
Type,
} from '@angular/core';
import { DYNAMIC_FORM_CONTROL_MAP_FN } from '@ng-dynamic-forms/core';
import {
DYNAMIC_FORM_CONTROL_MAP_FN,
DYNAMIC_VALIDATORS,
} from '@ng-dynamic-forms/core';
import {
select,
Store,
Expand Down Expand Up @@ -41,6 +44,7 @@ import { LocaleService } from './core/locale/locale.service';
import { HeadTagService } from './core/metadata/head-tag.service';
import { CorrelationIdService } from './correlation-id/correlation-id.service';
import { dsDynamicFormControlMapFn } from './shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-map-fn';
import { CUSTOM_VALIDATORS } from './shared/form/builder/parsers/field-parser';
import { MenuService } from './shared/menu/menu.service';
import { MenuProviderService } from './shared/menu/menu-provider.service';
import { ThemeService } from './shared/theme-support/theme.service';
Expand Down Expand Up @@ -120,6 +124,10 @@ export abstract class InitService {
provide: DYNAMIC_FORM_CONTROL_MAP_FN,
useValue: dsDynamicFormControlMapFn,
},
{
provide: DYNAMIC_VALIDATORS,
useValue: CUSTOM_VALIDATORS,
},
];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
true,
true,
... itemLinksToFollow(this.fetchThumbnail, this.appConfig.item.showAccessStatuses)).pipe(
getAllSucceededRemoteData(),
getFirstSucceededRemoteData(),
getRemoteDataPayload());
this.relationshipValue$ = observableCombineLatest([this.item$.pipe(take(1)), relationship$]).pipe(
switchMap(([item, relationship]: [Item, Relationship]) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,6 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
*/
openDropdown(sdRef: NgbDropdown) {
if (!this.model.readOnly) {
this.group.markAsUntouched();
this.inputText = null;
this.updatePageInfo(this.model.maxOptions, 1);
this.loadOptions(false);
Expand Down Expand Up @@ -339,4 +338,9 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
this.currentValue = result;
}

onBlur(event: Event) {
super.onBlur(event);
this.group.markAsTouched();
}

}
43 changes: 42 additions & 1 deletion src/app/shared/form/builder/parsers/field-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
Inject,
InjectionToken,
} from '@angular/core';
import {
AbstractControl,
ValidatorFn,
} from '@angular/forms';
import {
DynamicFormControlLayout,
DynamicFormControlRelation,
Expand Down Expand Up @@ -48,6 +52,28 @@
*/
export const REGEX_FIELD_VALIDATOR = new RegExp('(\\/?)(.+)\\1([gimsuy]*)', 'i');

/**
* Define custom form validators here
*
* Register them by adding their key to a model's validator property, e.g:
* ```ts
* model.validators = Object.assign({}, model.validators, { notRepeatable: null });
* ```
*/
export const CUSTOM_VALIDATORS = new Map<string, ValidatorFn>([
['notRepeatable', notRepeatableValidator],
]);

export function notRepeatableValidator(control: AbstractControl) {
const value = control.value;

Check warning on line 68 in src/app/shared/form/builder/parsers/field-parser.ts

View check run for this annotation

Codecov / codecov/patch

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

Added line #L68 was not covered by tests
if (!Array.isArray(value) || value.length < 2) {
return null;

Check warning on line 70 in src/app/shared/form/builder/parsers/field-parser.ts

View check run for this annotation

Codecov / codecov/patch

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

Added line #L70 was not covered by tests
}
return {

Check warning on line 72 in src/app/shared/form/builder/parsers/field-parser.ts

View check run for this annotation

Codecov / codecov/patch

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

Added line #L72 was not covered by tests
notRepeatable: true,
};
}

export abstract class FieldParser {

protected fieldId: string;
Expand Down Expand Up @@ -131,7 +157,11 @@
},
};

return new DynamicRowArrayModel(config, layout);
const model = new DynamicRowArrayModel(config, layout);
if (config.notRepeatable) {
this.addNotRepeatableValidator(model);

Check warning on line 162 in src/app/shared/form/builder/parsers/field-parser.ts

View check run for this annotation

Codecov / codecov/patch

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

Added line #L162 was not covered by tests
}
return model;

} else {
const model = this.modelFactory(this.getInitFieldValue());
Expand Down Expand Up @@ -426,6 +456,17 @@
{ required: this.configData.mandatoryMessage });
}

protected addNotRepeatableValidator(controlModel) {
controlModel.validators = Object.assign({}, controlModel.validators, { notRepeatable: null });
controlModel.errorMessages = Object.assign(

Check warning on line 461 in src/app/shared/form/builder/parsers/field-parser.ts

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L460 - L461 were not covered by tests
{},
controlModel.errorMessages,
{
notRepeatable: 'error.validation.not.repeatable',
},
);
}

protected setLabel(controlModel, label = true, labelEmpty = false) {
if (label) {
controlModel.label = (labelEmpty) ? '&nbsp;' : this.configData.label;
Expand Down
2 changes: 1 addition & 1 deletion src/app/shared/form/form.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ export class FormService {
});
}

field.markAsUntouched();
field.markAsUntouched({ onlySelf: true });
}

public resetForm(formGroup: UntypedFormGroup, groupModel: DynamicFormControlModel[], formId: string) {
Expand Down
17 changes: 13 additions & 4 deletions src/app/submission/sections/form/section-form.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,17 +275,23 @@
}
});

const diffResult = [];

// compare current form data state with section data retrieved from store
const diffObj = difference(sectionDataToCheck, this.formData);
const diffFromObj = this.hasDifferences(sectionDataToCheck, this.formData);
const diffToObj = this.hasDifferences(this.formData, sectionDataToCheck);

return diffFromObj || diffToObj;
}

private hasDifferences(object1: object, object2: object) {
const diffResult = [];
const diffObj = difference(object1, object2);

// iterate over differences to check whether they are actually different
Object.keys(diffObj)
.forEach((key) => {
diffObj[key].forEach((value) => {
// the findIndex extra check excludes values already present in the form but in different positions
if (value.hasOwnProperty('value') && findIndex(this.formData[key], { value: value.value }) < 0) {
if (value.hasOwnProperty('value') && findIndex(object2[key], { value: value.value }) < 0) {
diffResult.push(value);
}
});
Expand All @@ -298,6 +304,9 @@
* @private
*/
private inCurrentSubmissionScope(field: string): boolean {
if (isNotEmpty(this.sectionMetadata) && !this.sectionMetadata.includes(field)) {
return false;

Check warning on line 308 in src/app/submission/sections/form/section-form.component.ts

View check run for this annotation

Codecov / codecov/patch

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

Added line #L308 was not covered by tests
Copy link
Member

@ybnd ybnd Jun 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Atmire-Kristof @alexandrevryghem apparently this change causes a regression for #1671 / #2252

For submission fields without a <linked-metadata-field> the Relationship list under the field is no longer updated on select/delete. You can see this in the Person form Publication field or by unlinking the Publication form author field from dc.contributor.author.

Can you find a way to reconcile both cases?

}
const scope = this.formConfig?.rows.find((row: FormRowModel) => {
if (row.fields?.[0]?.selectableMetadata) {
return row.fields?.[0]?.selectableMetadata?.[0]?.metadata === field;
Expand Down
3 changes: 3 additions & 0 deletions src/app/submission/sections/sections.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,9 @@
rawData.rows.forEach((currentRow) => {
if (currentRow.fields && !isEmpty(currentRow.fields)) {
currentRow.fields.forEach((field) => {
if (field.selectableRelationship) {
metadata.push(`relation.${field.selectableRelationship.relationshipType}`);

Check warning on line 526 in src/app/submission/sections/sections.service.ts

View check run for this annotation

Codecov / codecov/patch

src/app/submission/sections/sections.service.ts#L526

Added line #L526 was not covered by tests
}
if (field.selectableMetadata && !isEmpty(field.selectableMetadata)) {
field.selectableMetadata.forEach((selectableMetadata) => {
if (!metadata.includes(selectableMetadata.metadata)) {
Expand Down
2 changes: 2 additions & 0 deletions src/assets/i18n/en.json5
Original file line number Diff line number Diff line change
Expand Up @@ -1910,6 +1910,8 @@

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

"error.validation.not.repeatable": "This field only accepts one value. Please discard the additional ones.",

"error.validation.NotValidEmail": "This is not a valid email",

"error.validation.emailTaken": "This email is already taken",
Expand Down
Loading