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 1a14469a2e0..2511a00ef9a 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 b9335f20783..f4174827c46 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 @@ -223,6 +223,8 @@ describe('DsDynamicFormControlContainerComponent test suite', () => { testWSI.item = of(createSuccessfulRemoteDataObject(testItem)); const actions$: ReplaySubject = new ReplaySubject(1); + const renderer = jasmine.createSpyObj('Renderer2', ['setAttribute']); + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ @@ -286,6 +288,7 @@ describe('DsDynamicFormControlContainerComponent test suite', () => { }); fixture.detectChanges(); + renderer.setAttribute.calls.reset(); testElement = debugElement.query(By.css(`input[id='${testModel.id}']`)); })); @@ -434,4 +437,43 @@ describe('DsDynamicFormControlContainerComponent test suite', () => { }); }); + 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 534300b55df..3e239ce2844 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'; @@ -19,7 +20,9 @@ import { OnDestroy, OnInit, Output, + PLATFORM_ID, QueryList, + Renderer2, SimpleChanges, Type, ViewChild, @@ -40,10 +43,12 @@ import { import { DYNAMIC_FORM_CONTROL_MAP_FN, DYNAMIC_FORM_CONTROL_TYPE_CHECKBOX, + DynamicFormArrayComponent, DynamicFormArrayGroupModel, DynamicFormArrayModel, DynamicFormComponentService, DynamicFormControl, + DynamicFormControlComponent, DynamicFormControlContainerComponent, DynamicFormControlEvent, DynamicFormControlEventType, @@ -130,6 +135,7 @@ import { } 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'; @Component({ @@ -223,6 +229,8 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo @Inject(APP_CONFIG) protected appConfig: AppConfig, @Inject(DYNAMIC_FORM_CONTROL_MAP_FN) protected dynamicFormControlFn: DynamicFormControlMapFn, private actions$: Actions, + protected renderer: Renderer2, + @Inject(PLATFORM_ID) protected platformId: string, ) { super(ref, componentFactoryResolver, layoutService, validationService, dynamicFormComponentService, relationService); this.fetchThumbnail = this.appConfig.browseBy.showThumbnails; @@ -324,6 +332,10 @@ 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); @@ -345,6 +357,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo ngAfterViewInit() { this.showErrorMessagesPreviousStage = this.showErrorMessages; + this.handleAriaLabelForLibraryComponents(); } protected createFormControlComponent(): void { @@ -516,4 +529,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 5d38f9e9084..3e6e05c7f74 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,12 +2,12 @@
@if (!model.repeatable) { - {{ model.placeholder }} + {{model.label}} @if (model.required) { * } - - } + + } + [attr.aria-label]="model.label | translate">
{ expect(fieldModel.value).toEqual(expectedValue); }); + + it('should skip setting the placeholder', () => { + const parser = new DateFieldParser(submissionId, field, initFormValues, parserOptions, translateService); + const fieldModel = parser.parse(); + + expect(fieldModel.placeholder).toBeNull(); + }); }); diff --git a/src/app/shared/form/builder/parsers/date-field-parser.ts b/src/app/shared/form/builder/parsers/date-field-parser.ts index 8104568b1aa..d086bd11ac6 100644 --- a/src/app/shared/form/builder/parsers/date-field-parser.ts +++ b/src/app/shared/form/builder/parsers/date-field-parser.ts @@ -11,8 +11,8 @@ export class DateFieldParser extends FieldParser { public modelFactory(fieldValue?: FormFieldMetadataValueObject, label?: boolean): any { let malformedDate = false; - const inputDateModelConfig: DynamicDsDateControlModelConfig = this.initModel(null, false, true); - inputDateModelConfig.legend = this.configData.label; + const inputDateModelConfig: DynamicDsDateControlModelConfig = this.initModel(null, label, true); + inputDateModelConfig.legend = this.configData.repeatable ? null : this.configData.label; inputDateModelConfig.disabled = inputDateModelConfig.readOnly; inputDateModelConfig.toggleIcon = 'fas fa-calendar'; this.setValues(inputDateModelConfig as any, fieldValue); diff --git a/src/app/shared/form/builder/parsers/field-parser.ts b/src/app/shared/form/builder/parsers/field-parser.ts index e4543bcff8d..5825c6edfbb 100644 --- a/src/app/shared/form/builder/parsers/field-parser.ts +++ b/src/app/shared/form/builder/parsers/field-parser.ts @@ -306,7 +306,8 @@ export abstract class FieldParser { if (hint) { controlModel.hint = this.configData.hints || ' '; } - controlModel.placeholder = this.configData.label; + + controlModel.additional = { ...controlModel.additional, ariaLabel: this.configData.label }; if (this.configData.mandatory && setErrors) { this.markAsRequired(controlModel); diff --git a/src/app/shared/form/number-picker/number-picker.component.html b/src/app/shared/form/number-picker/number-picker.component.html index 807bb0acf13..e704e278c6a 100644 --- a/src/app/shared/form/number-picker/number-picker.component.html +++ b/src/app/shared/form/number-picker/number-picker.component.html @@ -26,7 +26,6 @@ [readonly]="disabled" [disabled]="disabled" [ngClass]="{'is-invalid': invalid}" - placeholder="{{'form.number-picker.label.' + placeholder | translate }}" title="{{'form.number-picker.label.' + placeholder | translate}}" [attr.aria-labelledby]="id + ' increment-' + id + ' decrement-' + id" >