Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
[class.disabled]="isOnlyValue || saving" [dsBtnDisabled]="isOnlyValue || saving"
[title]="dsoType + '.edit.metadata.edit.buttons.drag' | translate"
ngbTooltip="{{ dsoType + '.edit.metadata.edit.buttons.drag' | translate }}">
<i class="fas fa-grip-vertical fa-fw"></i>
<i class="drag-icon"></i>
</button>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,7 @@
scope="row" id="{{ entry.nameStripped }}" headers="{{ bundleName }} name">
<div class="drag-handle text-muted float-start p-1 me-2 d-inline" tabindex="0" cdkDragHandle
(keydown.enter)="select($event, entry)" (keydown.space)="select($event, entry)" (click)="select($event, entry)">
<i class="fas fa-grip-vertical fa-fw"
[title]="'item.edit.bitstreams.edit.buttons.drag' | translate"></i>
<i class="drag-icon" [title]="'item.edit.bitstreams.edit.buttons.drag' | translate"></i>
</div>
<span class="dont-break-out">{{ entry.name }}</span>
</th>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import {
import { ScrollToService } from '@nicky-lenaers/ngx-scroll-to';

import { HostWindowService } from '../../shared/host-window.service';
import { LiveRegionService } from '../../shared/live-region/live-region.service';
import { getLiveRegionServiceStub } from '../../shared/live-region/live-region.service.stub';
import { UploaderComponent } from '../../shared/upload/uploader/uploader.component';
import { MyDSpaceNewSubmissionComponent } from './my-dspace-new-submission.component';
import { getMockEntityTypeService } from './my-dspace-new-submission-dropdown/my-dspace-new-submission-dropdown.component.spec';
Expand Down Expand Up @@ -76,6 +78,7 @@ describe('MyDSpaceNewSubmissionComponent test', () => {
{ provide: CookieService, useValue: new CookieServiceMock() },
{ provide: HostWindowService, useValue: new HostWindowServiceStub(800) },
{ provide: EntityTypeDataService, useValue: getMockEntityTypeService() },
{ provide: LiveRegionService, useValue: getLiveRegionServiceStub() },
],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@
<div [id]="id + '_errors'"
[ngClass]="[getClass('element', 'errors'), getClass('grid', 'errors')]">
@for (message of errorMessages; track message) {
<small class="invalid-feedback d-block">{{ message | translate: model.validators }}</small>
<small class="invalid-feedback d-block"
aria-required="true"
aria-invalid="true"
[attr.aria-describedby]="'label_' + model.id"
aria-live="assertive"
>{{ message | translate: model.validators }}</small>
}
</div>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,27 @@ import {
DynamicNGBootstrapTextAreaComponent,
DynamicNGBootstrapTimePickerComponent,
} from '@ng-dynamic-forms/ui-ng-bootstrap';
import { Actions } from '@ngrx/effects';
import { Store } from '@ngrx/store';
import { TranslateModule } from '@ngx-translate/core';
import { NgxMaskModule } from 'ngx-mask';
import { of } from 'rxjs';
import {
of,
ReplaySubject,
} from 'rxjs';

import { environment } from '../../../../../environments/environment';
import {
SaveForLaterSubmissionFormErrorAction,
SaveSubmissionFormErrorAction,
SaveSubmissionFormSuccessAction,
SaveSubmissionSectionFormErrorAction,
SaveSubmissionSectionFormSuccessAction,
} from '../../../../submission/objects/submission-objects.actions';
import { SubmissionService } from '../../../../submission/submission.service';
import { SubmissionObjectService } from '../../../../submission/submission-object.service';
import { LiveRegionService } from '../../../live-region/live-region.service';
import { getLiveRegionServiceStub } from '../../../live-region/live-region.service.stub';
import { SelectableListService } from '../../../object-list/selectable-list/selectable-list.service';
import { FormBuilderService } from '../form-builder.service';
import { DsDynamicFormControlContainerComponent } from './ds-dynamic-form-control-container.component';
Expand Down Expand Up @@ -206,6 +219,8 @@ describe('DsDynamicFormControlContainerComponent test suite', () => {
const testItem: Item = new Item();
const testWSI: WorkspaceItem = new WorkspaceItem();
testWSI.item = of(createSuccessfulRemoteDataObject(testItem));
const actions$: ReplaySubject<any> = new ReplaySubject<any>(1);

beforeEach(waitForAsync(() => {

TestBed.configureTestingModule({
Expand Down Expand Up @@ -238,6 +253,8 @@ describe('DsDynamicFormControlContainerComponent test suite', () => {
{ provide: APP_CONFIG, useValue: environment },
{ provide: APP_DATA_SERVICES_MAP, useValue: {} },
{ provide: DYNAMIC_FORM_CONTROL_MAP_FN, useValue: dsDynamicFormControlMapFn },
{ provide: LiveRegionService, useValue: getLiveRegionServiceStub() },
{ provide: Actions, useValue: actions$ },
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents().then(() => {
Expand Down Expand Up @@ -379,4 +396,40 @@ describe('DsDynamicFormControlContainerComponent test suite', () => {
expect(testFn(formModel[25])).toEqual(DsDynamicFormGroupComponent);
});

describe('store action subscriptions', () => {
beforeEach(() => {
fixture.detectChanges();
});

it('should call announceErrorMessages on SAVE_SUBMISSION_FORM_SUCCESS', () => {
spyOn(component, 'announceErrorMessages');
actions$.next(new SaveSubmissionFormSuccessAction('1234', [] as any));
expect(component.announceErrorMessages).toHaveBeenCalled();
});

it('should call announceErrorMessages on SAVE_SUBMISSION_SECTION_FORM_SUCCESS', () => {
spyOn(component, 'announceErrorMessages');
actions$.next(new SaveSubmissionSectionFormSuccessAction('1234', [] as any));
expect(component.announceErrorMessages).toHaveBeenCalled();
});

it('should call announceErrorMessages on SAVE_SUBMISSION_FORM_ERROR', () => {
spyOn(component, 'announceErrorMessages');
actions$.next(new SaveSubmissionFormErrorAction('1234'));
expect(component.announceErrorMessages).toHaveBeenCalled();
});

it('should call announceErrorMessages on SAVE_FOR_LATER_SUBMISSION_FORM_ERROR', () => {
spyOn(component, 'announceErrorMessages');
actions$.next(new SaveForLaterSubmissionFormErrorAction('1234'));
expect(component.announceErrorMessages).toHaveBeenCalled();
});

it('should call announceErrorMessages on SAVE_SUBMISSION_SECTION_FORM_ERROR', () => {
spyOn(component, 'announceErrorMessages');
actions$.next(new SaveSubmissionSectionFormErrorAction('1234'));
expect(component.announceErrorMessages).toHaveBeenCalled();
});
});

});
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
DoCheck,
EventEmitter,
Inject,
inject,
Input,
OnChanges,
OnDestroy,
Expand All @@ -25,6 +26,7 @@ import {
ViewContainerRef,
} from '@angular/core';
import {
AbstractControl,
FormsModule,
ReactiveFormsModule,
UntypedFormArray,
Expand Down Expand Up @@ -92,6 +94,10 @@ import {
DynamicFormValidationService,
DynamicTemplateDirective,
} from '@ng-dynamic-forms/core';
import {
Actions,
ofType,
} from '@ngrx/effects';
import { Store } from '@ngrx/store';
import {
TranslateModule,
Expand All @@ -111,8 +117,10 @@ import {
} from 'rxjs/operators';

import { AppState } from '../../../../app.reducer';
import { SubmissionObjectActionTypes } from '../../../../submission/objects/submission-objects.actions';
import { SubmissionService } from '../../../../submission/submission.service';
import { SubmissionObjectService } from '../../../../submission/submission-object.service';
import { LiveRegionService } from '../../../live-region/live-region.service';
import { SelectableListState } from '../../../object-list/selectable-list/selectable-list.reducer';
import { SelectableListService } from '../../../object-list/selectable-list/selectable-list.service';
import { FormBuilderService } from '../form-builder.service';
Expand Down Expand Up @@ -171,6 +179,8 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
*/
private subs: Subscription[] = [];

private liveRegionErrorMessagesShownAlready = false;

/* eslint-disable @angular-eslint/no-output-rename */
@Output('dfBlur') blur: EventEmitter<DynamicFormControlEvent> = new EventEmitter<DynamicFormControlEvent>();
@Output('dfChange') change: EventEmitter<DynamicFormControlEvent> = new EventEmitter<DynamicFormControlEvent>();
Expand All @@ -190,6 +200,8 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
return this.dynamicFormControlFn(this.model);
}

private readonly liveRegionService = inject(LiveRegionService);

constructor(
protected componentFactoryResolver: ComponentFactoryResolver,
protected dynamicFormComponentService: DynamicFormComponentService,
Expand All @@ -210,6 +222,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
protected metadataService: MetadataService,
@Inject(APP_CONFIG) protected appConfig: AppConfig,
@Inject(DYNAMIC_FORM_CONTROL_MAP_FN) protected dynamicFormControlFn: DynamicFormControlMapFn,
private actions$: Actions,
) {
super(ref, componentFactoryResolver, layoutService, validationService, dynamicFormComponentService, relationService);
this.fetchThumbnail = this.appConfig.browseBy.showThumbnails;
Expand All @@ -222,6 +235,18 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
this.isRelationship = hasValue(this.model.relationship);
const isWrapperAroundRelationshipList = hasValue(this.model.relationshipConfig);

// Subscribe to specified submission actions to announce error messages
const errorAnnounceActionsSub = this.actions$.pipe(
ofType(
SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM_SUCCESS,
SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM_SUCCESS,
SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM_ERROR,
SubmissionObjectActionTypes.SAVE_FOR_LATER_SUBMISSION_FORM_ERROR,
SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM_ERROR,
),
).subscribe(() => this.announceErrorMessages());
this.subs.push(errorAnnounceActionsSub);

if (this.isRelationship || isWrapperAroundRelationshipList) {
const config = this.model.relationshipConfig || this.model.relationship;
const relationshipOptions = Object.assign(new RelationshipOptions(), config);
Expand Down Expand Up @@ -346,6 +371,36 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
if (this.showErrorMessages) {
this.destroyFormControlComponent();
this.createFormControlComponent();
this.announceErrorMessages();
}
}

/**
* Announce error messages to the user
*/
announceErrorMessages() {
if (!this.liveRegionErrorMessagesShownAlready) {
this.liveRegionErrorMessagesShownAlready = true;
const numberOfInvalidInputs = this.getNumberOfInvalidInputs() ?? 1;
const timeoutMs = numberOfInvalidInputs * 3500;
this.errorMessages.forEach((errorMsg) => {
// set timer based on the number of the invalid inputs
this.liveRegionService.setMessageTimeOutMs(timeoutMs);
const message = this.translateService.instant(errorMsg);
this.liveRegionService.addMessage(message);
});
setTimeout(() => {
this.liveRegionErrorMessagesShownAlready = false;
}, timeoutMs);
}
}

/**
* Get the number of invalid inputs in the formGroup
*/
private getNumberOfInvalidInputs(): number {
if (this.formGroup && this.formGroup.controls) {
return Object.values(this.formGroup.controls).filter((control: AbstractControl) => control.invalid).length;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
(keydown.escape)="cancelKeyboardDragAndDrop(sortableElement, idx, length)"
(keydown.arrowUp)="handleArrowPress($event, dropList, length, idx, 'up')"
(keydown.arrowDown)="handleArrowPress($event, dropList, length, idx, 'down')">
<i class="drag-icon fas fa-grip-vertical fa-fw" [class.drag-disable]="dragDisabled" ></i>
<i class="drag-icon" [class.drag-disable]="dragDisabled" aria-hidden="true"></i>
</div>
<ng-container *ngTemplateOutlet="startTemplate?.templateRef; context: groupModel"></ng-container>
@for (_model of groupModel.group; track _model) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,6 @@
width: calc(2 * var(--bs-spacer));
}

.drag-icon {
visibility: hidden;
width: calc(2 * var(--bs-spacer));
color: var(--bs-gray-600);
margin: var(--bs-btn-padding-y) 0;
line-height: var(--bs-btn-line-height);
text-indent: calc(0.5 * var(--bs-spacer))
}

&:hover, &:focus {
cursor: grab;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ import {
DynamicFormValidationService,
DynamicInputModel,
} from '@ng-dynamic-forms/core';
import { provideMockActions } from '@ngrx/effects/testing';
import { provideMockStore } from '@ngrx/store/testing';
import {
TranslateModule,
TranslateService,
} from '@ngx-translate/core';
import { NgxMaskModule } from 'ngx-mask';
import { of } from 'rxjs';
import {
Observable,
of,
} from 'rxjs';
import { LiveRegionService } from 'src/app/shared/live-region/live-region.service';

import { environment } from '../../../../../../../environments/environment.test';
Expand Down Expand Up @@ -61,6 +65,7 @@ describe('DsDynamicFormArrayComponent', () => {
{ provide: TranslateService, useValue: translateServiceStub },
{ provide: HttpClient, useValue: {} },
{ provide: SubmissionService, useValue: {} },
provideMockActions(() => new Observable<any>()),
{ provide: APP_CONFIG, useValue: environment },
{ provide: DYNAMIC_FORM_CONTROL_MAP_FN, useValue: dsDynamicFormControlMapFn },
{ provide: LiveRegionService, useValue: getLiveRegionServiceStub() },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<div>
<fieldset class="d-flex">
<fieldset class="d-flex justify-content-start flex-wrap gap-2">
@if (!model.repeatable) {
<legend [id]="'legend_' + model.id" [ngClass]="[getClass('element', 'label'), getClass('grid', 'label')]">
{{model.placeholder}} @if (model.required) {
<span>*</span>
}
</legend>
}
{{ model.placeholder }}
@if (model.required) {
<span>*</span>
}
</legend>
}
<ds-number-picker
tabindex="0"
[id]="model.id + '_year'"
Expand All @@ -19,28 +20,30 @@
[value]="year"
[invalid]="showErrorMessages"
[placeholder]="'form.date-picker.placeholder.year' | translate"
[widthClass]="'four-digits'"
(blur)="onBlur($event)"
(change)="onChange($event)"
(focus)="onFocus($event)"
></ds-number-picker>

<ds-number-picker
<ds-number-picker class="date-month"
tabindex="0"
[id]="model.id + '_month'"
[min]="minMonth"
[max]="maxMonth"
[name]="'month'"
[size]="6"
[size]="2"
[(ngModel)]="initialMonth"
[value]="month"
[placeholder]="'form.date-picker.placeholder.month' | translate"
[disabled]="!year || model.disabled"
[widthClass]="'two-digits'"
(blur)="onBlur($event)"
(change)="onChange($event)"
(focus)="onFocus($event)"
></ds-number-picker>

<ds-number-picker
<ds-number-picker class="date-day"
tabindex="0"
[id]="model.id + '_day'"
[min]="minDay"
Expand All @@ -51,6 +54,7 @@
[value]="day"
[placeholder]="'form.date-picker.placeholder.day' | translate"
[disabled]="!month || model.disabled"
[widthClass]="'two-digits'"
(blur)="onBlur($event)"
(change)="onChange($event)"
(focus)="onFocus($event)"
Expand Down
Loading
Loading