diff --git a/config/config.example.yml b/config/config.example.yml
index 0692d29afc1..ebbc23bd3b8 100644
--- a/config/config.example.yml
+++ b/config/config.example.yml
@@ -234,6 +234,11 @@ submission:
- value: default
style: text-muted
icon: fa-circle-xmark
+ # If set to true avoid setting placeholder for simple fields, where the placeholder would be the same as the label.
+ # The default is set to true as placeholders that do not provide additional information to the field are to be avoided as they could cause accessibility issue.
+ # More info on the topic can be found at https://www.deque.com/blog/accessible-forms-the-problem-with-placeholders/
+ omitSimpleFieldPlaceholders:
+
# Default Language in which the UI will be rendered if the user's browser language is not an active language
defaultLanguage: en
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html
index c4c1d79c294..74e6238d24c 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html
@@ -1,14 +1,14 @@
- @if (!isCheckbox && hasLabel) {
+ [formGroup]="group"
+ [ngClass]="[getClass('element', 'container'), getClass('grid', 'container')]">
+ @if (!isCheckbox && hasLabel && !isDateField) {
+ [ngClass]="[getClass('element', 'label'), getClass('grid', 'label')]">
}
@@ -20,8 +20,7 @@
@if (hasHint && (formBuilderService.hasArrayGroupValue(model) || (!model.repeatable && (isRelationship === false || value?.value === null)) || (model.repeatable === true && context?.index === context?.context?.groups?.length - 1)) && (!showErrorMessages || errorMessages.length === 0)) {
-
+
}
@if (context?.parent?.groups?.length > 1 && (!showErrorMessages || errorMessages.length === 0)) {
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.spec.ts
index 60a61584d22..4b524053c38 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.spec.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.spec.ts
@@ -206,6 +206,8 @@ describe('DsDynamicFormControlContainerComponent test suite', () => {
const testItem: Item = new Item();
const testWSI: WorkspaceItem = new WorkspaceItem();
testWSI.item = of(createSuccessfulRemoteDataObject(testItem));
+ const renderer = jasmine.createSpyObj('Renderer2', ['setAttribute']);
+
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
@@ -268,6 +270,7 @@ describe('DsDynamicFormControlContainerComponent test suite', () => {
});
fixture.detectChanges();
+ renderer.setAttribute.calls.reset();
testElement = debugElement.query(By.css(`input[id='${testModel.id}']`));
}));
@@ -380,4 +383,43 @@ describe('DsDynamicFormControlContainerComponent test suite', () => {
expect(testFn(formModel[25])).toEqual(DsDynamicFormGroupComponent);
});
+ it('should not show a label if is a checkbox or a date field', () => {
+ const checkboxLabel = fixture.debugElement.query(By.css('#label_' + formModel[0].id));
+ const dsDatePickerLabel = fixture.debugElement.query(By.css('#label_' + formModel[22].id));
+
+ expect(checkboxLabel).toBeNull();
+ expect(dsDatePickerLabel).toBeNull();
+ });
+
+ it('should not call handleAriaLabelForLibraryComponents if is SSR', () => {
+ (component as any).platformId = 'server';
+ (component as any).componentRef = {
+ instance: new DynamicNGBootstrapInputComponent(null, null),
+ location: { nativeElement: document.createElement('div') },
+ } as any;
+ fixture.detectChanges();
+
+ (component as any).handleAriaLabelForLibraryComponents();
+
+ expect(renderer.setAttribute).not.toHaveBeenCalled();
+ });
+
+ it('should set aria-label when valid input and additional property ariaLabel exist and is on browser', () => {
+ (component as any).platformId = 'browser';
+ const inputEl = document.createElement('input');
+ const hostEl = {
+ querySelector: jasmine.createSpy('querySelector').and.returnValue(inputEl),
+ };
+
+ (component as any).componentRef = {
+ instance: new DynamicNGBootstrapInputComponent(null, null),
+ location: { nativeElement: hostEl },
+ } as any;
+ (component as any).renderer = renderer;
+ component.model = { additional: { ariaLabel: 'Accessible Label' } } as any;
+ fixture.detectChanges();
+ (component as any).handleAriaLabelForLibraryComponents();
+ expect(renderer.setAttribute).toHaveBeenCalledWith(inputEl, 'aria-label', 'Accessible Label');
+ });
+
});
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts
index 426560ca2e7..9b225ed17b7 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts
@@ -1,5 +1,6 @@
import {
AsyncPipe,
+ isPlatformBrowser,
NgClass,
NgTemplateOutlet,
} from '@angular/common';
@@ -18,7 +19,9 @@ import {
OnDestroy,
OnInit,
Output,
+ PLATFORM_ID,
QueryList,
+ Renderer2,
SimpleChanges,
Type,
ViewChild,
@@ -77,10 +80,12 @@ import {
import {
DYNAMIC_FORM_CONTROL_MAP_FN,
DYNAMIC_FORM_CONTROL_TYPE_CHECKBOX,
+ DynamicFormArrayComponent,
DynamicFormArrayGroupModel,
DynamicFormArrayModel,
DynamicFormComponentService,
DynamicFormControl,
+ DynamicFormControlComponent,
DynamicFormControlContainerComponent,
DynamicFormControlEvent,
DynamicFormControlEventType,
@@ -120,6 +125,7 @@ import { DsDynamicTypeBindRelationService } from './ds-dynamic-type-bind-relatio
import { ExistingMetadataListElementComponent } from './existing-metadata-list-element/existing-metadata-list-element.component';
import { ExistingRelationListElementComponent } from './existing-relation-list-element/existing-relation-list-element.component';
import { DYNAMIC_FORM_CONTROL_TYPE_CUSTOM_SWITCH } from './models/custom-switch/custom-switch.model';
+import { DYNAMIC_FORM_CONTROL_TYPE_DSDATEPICKER } from './models/date-picker/date-picker.model';
import { DsDynamicLookupRelationModalComponent } from './relation-lookup-modal/dynamic-lookup-relation-modal.component';
import { NameVariantService } from './relation-lookup-modal/name-variant.service';
@@ -211,6 +217,8 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
protected metadataService: MetadataService,
@Inject(APP_CONFIG) protected appConfig: AppConfig,
@Inject(DYNAMIC_FORM_CONTROL_MAP_FN) protected dynamicFormControlFn: DynamicFormControlMapFn,
+ protected renderer: Renderer2,
+ @Inject(PLATFORM_ID) protected platformId: string,
) {
super(ref, componentFactoryResolver, layoutService, validationService, dynamicFormComponentService, relationService);
this.fetchThumbnail = this.appConfig.browseBy.showThumbnails;
@@ -300,6 +308,11 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
return this.model.type === DYNAMIC_FORM_CONTROL_TYPE_CHECKBOX || this.model.type === DYNAMIC_FORM_CONTROL_TYPE_CUSTOM_SWITCH;
}
+
+ get isDateField(): boolean {
+ return this.model.type === DYNAMIC_FORM_CONTROL_TYPE_DSDATEPICKER;
+ }
+
ngOnChanges(changes: SimpleChanges) {
if (changes && !this.isRelationship && hasValue(this.group.get(this.model.id))) {
super.ngOnChanges(changes);
@@ -321,6 +334,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
ngAfterViewInit() {
this.showErrorMessagesPreviousStage = this.showErrorMessages;
+ this.handleAriaLabelForLibraryComponents();
}
protected createFormControlComponent(): void {
@@ -462,4 +476,22 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
this.subs.push(collection$.subscribe((collection) => this.collection = collection));
}
+
+ private handleAriaLabelForLibraryComponents(): void {
+ if (!isPlatformBrowser(this.platformId)) {
+ return;
+ }
+
+ if ((this.componentRef.instance instanceof DynamicFormControlComponent) &&
+ !(this.componentRef.instance instanceof DynamicFormArrayComponent) &&
+ this.componentRef.location.nativeElement) {
+ const inputEl: HTMLElement | null =
+ this.componentRef.location.nativeElement.querySelector('input,textarea,select,[role="textbox"]');
+
+
+ if (inputEl && this.model?.additional?.ariaLabel) {
+ this.renderer.setAttribute(inputEl, 'aria-label', this.model.additional.ariaLabel);
+ }
+ }
+ }
}
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.component.html
index 5faf188165d..3e22c1539f0 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.component.html
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.component.html
@@ -2,7 +2,7 @@
@if (!model.repeatable) {
- {{model.placeholder}} @if (model.required) {
+ {{model.label}} @if (model.required) {
*
}
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 da6de76594c..a9beff40dcd 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
@@ -41,7 +41,7 @@
class="form-control"
[attr.aria-labelledby]="'label_' + model.id"
[attr.autoComplete]="model.autoComplete"
- [attr.aria-label]="model.label | translate"
+ [attr.aria-label]="(model.label || model?.additional?.ariaLabel) | translate"
[class.is-invalid]="showErrorMessages"
[id]="model.id"
[inputFormatter]="formatter"
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 b60c93bb87f..c2a7fbf4dca 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
@@ -14,7 +14,7 @@
}
+ [attr.aria-label]="model.label | translate">