Skip to content
Snippets Groups Projects
Commit 10a784c7 authored by Martin's avatar Martin
Browse files

OZG-7591 add validation error id for aria-describedby

parent bb27c1a1
No related branches found
No related tags found
1 merge request!92Administration: Validierung der Pflichtfelder bei "Benutzer & Rollen"
Showing
with 43 additions and 34 deletions
<h2 class="heading-2 mt-4">Rollen für OZG-Cloud *</h2>
<ods-validation-error
[id]="validationErrorId"
[invalidParams]="invalidParams$ | async"
label="Rollen"
data-test-id="rollen-error"
......
import { InvalidParam } from '@alfa-client/tech-shared';
import { generateValidationErrorId, InvalidParam } from '@alfa-client/tech-shared';
import { AsyncPipe } from '@angular/common';
import { Component, Input, OnInit } from '@angular/core';
import { AbstractControl, FormControlStatus, ReactiveFormsModule, UntypedFormGroup } from '@angular/forms';
......@@ -26,6 +26,7 @@ export class UserFormRolesComponent implements OnInit {
public invalidParams$: Observable<InvalidParam[]> = of([]);
public readonly UserFormService = UserFormService;
public readonly validationErrorId: string = generateValidationErrorId();
ngOnInit(): void {
const control: AbstractControl = this.formGroupParent.controls[UserFormService.CLIENT_ROLES];
......
......@@ -29,9 +29,11 @@
[label]="label"
[disabled]="control.disabled"
[hasError]="hasError"
[ariaDescribedBy]="validationErrorId"
>
<ods-validation-error
error
[id]="validationErrorId"
[invalidParams]="invalidParams"
[label]="label"
[attr.data-test-id]="(label | convertForDataTest) + '-checkbox-editor-error'"
......
......@@ -21,7 +21,7 @@
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
import { ConvertForDataTestPipe } from '@alfa-client/tech-shared';
import { ConvertForDataTestPipe, generateValidationErrorId } from '@alfa-client/tech-shared';
import { Component, Input } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { CheckboxComponent } from '@ods/system';
......@@ -38,6 +38,8 @@ export class CheckboxEditorComponent extends FormControlEditorAbstractComponent
@Input() inputId: string;
@Input() label: string;
public readonly validationErrorId: string = generateValidationErrorId();
get hasError(): boolean {
return this.invalidParams.length > 0;
}
......
......@@ -34,9 +34,11 @@
[required]="isRequired"
[focus]="focus"
[showLabel]="showLabel"
[ariaDescribedBy]="validationErrorId"
>
<ods-validation-error
error
[id]="validationErrorId"
[invalidParams]="invalidParams"
[label]="label"
[attr.data-test-id]="dataTestId + '-text-editor-error'"
......
......@@ -21,7 +21,7 @@
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
import { ConvertForDataTestPipe } from '@alfa-client/tech-shared';
import { ConvertForDataTestPipe, generateValidationErrorId } from '@alfa-client/tech-shared';
import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
......@@ -44,6 +44,8 @@ export class TextEditorComponent extends FormControlEditorAbstractComponent {
@Input() showLabel: boolean = true;
@Input() dataTestId: string;
public readonly validationErrorId: string = generateValidationErrorId();
get variant(): string {
return this.invalidParams.length > 0 ? 'error' : 'default';
}
......
......@@ -34,9 +34,11 @@
[focus]="focus"
[isResizable]="isResizable"
[showLabel]="showLabel"
[ariaDescribedBy]="validationErrorId"
>
<ods-validation-error
error
[id]="validationErrorId"
[invalidParams]="invalidParams"
[label]="label"
[attr.data-test-id]="(label | convertForDataTest) + '-textarea-editor-error'"
......
......@@ -21,7 +21,7 @@
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
import { ConvertForDataTestPipe } from '@alfa-client/tech-shared';
import { ConvertForDataTestPipe, generateValidationErrorId } from '@alfa-client/tech-shared';
import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
......@@ -44,6 +44,8 @@ export class TextareaEditorComponent extends FormControlEditorAbstractComponent
@Input() isResizable: boolean = true;
@Input() showLabel: boolean = true;
public readonly validationErrorId: string = generateValidationErrorId();
get variant(): string {
return this.invalidParams.length > 0 ? 'error' : 'default';
}
......
......@@ -24,5 +24,5 @@
-->
<ng-container *ngFor="let invalidParam of invalidParams"
><ods-error-message [text]="message(invalidParam)"></ods-error-message
><ods-error-message [id]="id" [text]="message(invalidParam)"></ods-error-message
></ng-container>
......@@ -33,6 +33,7 @@ import { ErrorMessageComponent } from '@ods/system';
templateUrl: './validation-error.component.html',
})
export class ValidationErrorComponent {
@Input({ required: true }) id: string;
@Input() label: string;
@Input() invalidParams: InvalidParam[];
......
......@@ -21,7 +21,7 @@
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
import { ConvertForDataTestPipe } from '@alfa-client/tech-shared';
import { ConvertForDataTestPipe, EMPTY_STRING } from '@alfa-client/tech-shared';
import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
......@@ -45,6 +45,7 @@ import { FormControl, ReactiveFormsModule } from '@angular/forms';
[attr.id]="inputId"
[attr.disabled]="disabled ? true : null"
[attr.data-test-id]="(label | convertForDataTest) + '-checkbox-editor'"
[attr.aria-describedby]="ariaDescribedBy"
/>
<label class="leading-5 text-text" [attr.for]="inputId">{{ label }}</label>
<svg
......@@ -69,4 +70,5 @@ export class CheckboxComponent {
@Input() label: string;
@Input() disabled: boolean = false;
@Input() hasError: boolean = false;
@Input() ariaDescribedBy: string = EMPTY_STRING;
}
......@@ -21,6 +21,7 @@
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
import { EMPTY_STRING } from '@alfa-client/tech-shared';
import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import { ExclamationIconComponent } from '../../icons/exclamation-icon/exclamation-icon.component';
......@@ -31,13 +32,14 @@ import { ExclamationIconComponent } from '../../icons/exclamation-icon/exclamati
imports: [CommonModule, ExclamationIconComponent],
styles: [':host {@apply flex text-error my-2 text-sm items-center font-medium}'],
template: `<ods-exclamation-icon class="mr-1"></ods-exclamation-icon>
<div class="flex-grow break-all">
<div class="flex-grow break-all" [id]="id">
{{ text }}
<br *ngIf="subText" aria-hidden="true" />
{{ subText }}
</div> `,
})
export class ErrorMessageComponent {
@Input() id: string = EMPTY_STRING;
@Input({ required: true }) text!: string;
@Input() subText: string = '';
}
......@@ -62,6 +62,7 @@ type TextInputVariants = VariantProps<typeof textInputVariants>;
<ng-content select="[prefix]" />
</div>
<input
#inputElement
type="text"
[id]="id"
[formControl]="fieldControl"
......@@ -71,8 +72,8 @@ type TextInputVariants = VariantProps<typeof textInputVariants>;
[attr.aria-required]="required"
[attr.aria-invalid]="variant === 'error'"
[attr.data-test-id]="_dataTestId + '-text-input'"
[attr.aria-describedby]="ariaDescribedBy"
(click)="clickEmitter.emit()"
#inputElement
/>
<div *ngIf="withSuffix" class="absolute bottom-2 right-2 flex size-6 items-center justify-center">
<ng-content select="[suffix]" />
......@@ -102,6 +103,7 @@ export class TextInputComponent implements AfterViewInit {
@Input() set dataTestId(value: string) {
if (isNotUndefined(value)) this._dataTestId = value;
}
@Input() ariaDescribedBy: string = EMPTY_STRING;
@Output() clickEmitter: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
......
......@@ -57,6 +57,7 @@ type TextareaVariants = VariantProps<typeof textareaVariants>;
{{ inputLabel }}<ng-container *ngIf="required"><i aria-hidden="true">*</i></ng-container>
</label>
<textarea
#textAreaElement
[id]="id"
[formControl]="fieldControl"
[rows]="rows"
......@@ -66,7 +67,7 @@ type TextareaVariants = VariantProps<typeof textareaVariants>;
[attr.aria-required]="required"
[attr.aria-invalid]="variant === 'error'"
[attr.data-test-id]="(inputLabel | convertForDataTest) + '-textarea'"
#textAreaElement
[attr.aria-describedby]="ariaDescribedBy"
></textarea>
<ng-content select="[error]"></ng-content>
</div>
......@@ -94,6 +95,7 @@ export class TextareaComponent {
this.textAreaElement.nativeElement.focus();
}
}
@Input() ariaDescribedBy: string = EMPTY_STRING;
inputLabel: string;
id: string;
......
......@@ -22,7 +22,7 @@
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
import { AbstractControl, UntypedFormGroup } from '@angular/forms';
import { isEmpty, isNil } from 'lodash-es';
import { isEmpty, isNil, uniqueId } from 'lodash-es';
import { ApiError, InvalidParam, Issue, IssueParam, ProblemDetail } from '../tech.model';
import { replacePlaceholder } from '../tech.util';
import { VALIDATION_MESSAGES, ValidationMessageCode } from './tech.validation.messages';
......@@ -31,28 +31,18 @@ export function isValidationError(issue: Issue): boolean {
return issue.messageCode.includes('javax.validation.constraints');
}
export function setIssueValidationError(
form: UntypedFormGroup,
issue: Issue,
pathPrefix?: string,
): void {
export function setIssueValidationError(form: UntypedFormGroup, issue: Issue, pathPrefix?: string): void {
const control: AbstractControl = getControlForIssue(form, issue, pathPrefix);
control.setErrors({ [issue.messageCode]: issue });
control.markAsTouched();
}
export function getControlForIssue(
form: UntypedFormGroup,
issue: Issue,
pathPrefix?: string,
): AbstractControl {
export function getControlForIssue(form: UntypedFormGroup, issue: Issue, pathPrefix?: string): AbstractControl {
const fieldPath: string = getFieldPath(issue.field, pathPrefix);
let curControl: AbstractControl = form;
fieldPath
.split('.')
.forEach((field) => (curControl = (<UntypedFormGroup>curControl).controls[field]));
fieldPath.split('.').forEach((field) => (curControl = (<UntypedFormGroup>curControl).controls[field]));
return curControl;
}
......@@ -66,9 +56,7 @@ export function getMessageForIssue(label: string, issue: Issue): string {
}
msg = replacePlaceholder(msg, 'field', label);
issue.parameters.forEach(
(param: IssueParam) => (msg = replacePlaceholder(msg, param.name, param.value)),
);
issue.parameters.forEach((param: IssueParam) => (msg = replacePlaceholder(msg, param.name, param.value)));
return msg;
}
......@@ -84,11 +72,7 @@ export function getMessageCode(apiError: ApiError): string {
return apiError.issues[0].messageCode;
}
export function setInvalidParamValidationError(
form: UntypedFormGroup,
invalidParam: InvalidParam,
pathPrefix?: string,
): void {
export function setInvalidParamValidationError(form: UntypedFormGroup, invalidParam: InvalidParam, pathPrefix?: string): void {
const control: AbstractControl = getControlForInvalidParam(form, invalidParam, pathPrefix);
control.setErrors({ [invalidParam.reason]: invalidParam });
......@@ -112,9 +96,7 @@ export function getMessageForInvalidParam(label: string, invalidParam: InvalidPa
}
msg = replacePlaceholder(msg, 'field', label);
invalidParam.constraintParameters.forEach(
(param: IssueParam) => (msg = replacePlaceholder(msg, param.name, param.value)),
);
invalidParam.constraintParameters.forEach((param: IssueParam) => (msg = replacePlaceholder(msg, param.name, param.value)));
return msg;
}
......@@ -126,3 +108,7 @@ export function getFieldPath(name: string, pathPrefix: string): string {
const indexOfField = name.lastIndexOf(pathPrefix) + pathPrefix.length + 1;
return name.slice(indexOfField);
}
export function generateValidationErrorId(): string {
return `${uniqueId()}-validation-error`;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment