Skip to content

Commit 011df62

Browse files
committed
Merge branch 'w2p-133535_fix-preserve-scroll-position_contribute-7.6' into w2p-133535_fix-preserve-scroll-position_contribute-9.x-v2
2 parents dba2d7a + 1599633 commit 011df62

File tree

71 files changed

+385
-112
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+385
-112
lines changed

src/app/core/pagination/pagination.service.spec.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@ import {
88
} from '../cache/models/sort-options.model';
99
import { FindListOptions } from '../data/find-list-options.model';
1010
import { PaginationService } from './pagination.service';
11+
import { ScrollService } from '../scroll/scroll.service';
12+
import { ScrollServiceStub } from '../../shared/testing/scroll-service.stub';
1113

1214

1315
describe('PaginationService', () => {
1416
let service: PaginationService;
1517
let router;
1618
let routeService;
19+
let scrollService: ScrollService;
1720

1821
const defaultPagination = new PaginationComponentOptions();
1922
const defaultSort = new SortOptions('dc.title', SortDirection.ASC);
@@ -39,8 +42,9 @@ describe('PaginationService', () => {
3942
return of(value);
4043
},
4144
};
45+
scrollService = new ScrollServiceStub() as any;
4246

43-
service = new PaginationService(routeService, router);
47+
service = new PaginationService(routeService, router, scrollService);
4448
});
4549

4650
describe('getCurrentPagination', () => {
@@ -73,7 +77,7 @@ describe('PaginationService', () => {
7377
return of(value);
7478
},
7579
};
76-
service = new PaginationService(routeService, router);
80+
service = new PaginationService(routeService, router, scrollService);
7781

7882
service.getCurrentSort('test-id', defaultSort).subscribe((currentSort) => {
7983
expect(currentSort).toEqual(defaultSort);
@@ -97,7 +101,7 @@ describe('PaginationService', () => {
97101
spyOn(service, 'updateRoute');
98102
service.resetPage('test');
99103

100-
expect(service.updateRoute).toHaveBeenCalledWith('test', { page: 1 });
104+
expect(service.updateRoute).toHaveBeenCalledWith('test', { page: 1 }, undefined, undefined);
101105
});
102106
});
103107

src/app/core/pagination/pagination.service.ts

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { Injectable } from '@angular/core';
1+
import { Injectable, InjectionToken } from '@angular/core';
22
import {
33
NavigationExtras,
44
Router,
55
} from '@angular/router';
66
import {
77
combineLatest as observableCombineLatest,
8-
Observable,
8+
Observable, BehaviorSubject,
99
} from 'rxjs';
1010
import {
1111
filter,
@@ -28,6 +28,9 @@ import {
2828
import { FindListOptions } from '../data/find-list-options.model';
2929
import { RouteService } from '../services/route.service';
3030
import { PaginationRouteParams } from './pagination-route-params.interface';
31+
import { ScrollService } from '../scroll/scroll.service';
32+
33+
export const RETAIN_SCROLL_POSITION: InjectionToken<BehaviorSubject<any>> = new InjectionToken<boolean>('retainScrollPosition');
3134

3235
@Injectable({
3336
providedIn: 'root',
@@ -53,6 +56,7 @@ export class PaginationService {
5356

5457
constructor(protected routeService: RouteService,
5558
protected router: Router,
59+
protected scrollService: ScrollService,
5660
) {
5761
}
5862

@@ -124,9 +128,10 @@ export class PaginationService {
124128
/**
125129
* Reset the current page for the provided pagination ID to 1.
126130
* @param paginationId - The pagination id for which to reset the page
131+
* @param retainScrollPosition - Scroll to the pagination component after updating the route instead of the top of the page
127132
*/
128-
resetPage(paginationId: string) {
129-
this.updateRoute(paginationId, { page: 1 });
133+
resetPage(paginationId: string, retainScrollPosition?: boolean): void {
134+
this.updateRoute(paginationId, { page: 1 }, undefined, retainScrollPosition);
130135
}
131136

132137

@@ -155,7 +160,7 @@ export class PaginationService {
155160
* @param url - The url to navigate to
156161
* @param params - The page related params to update in the route
157162
* @param extraParams - Addition params unrelated to the pagination that need to be added to the route
158-
* @param retainScrollPosition - Scroll to the pagination component after updating the route instead of the top of the page
163+
* @param retainScrollPosition - Scroll to the active fragment after updating the route instead of the top of the page
159164
* @param navigationExtras - Extra parameters to pass on to `router.navigate`. Can be used to override values set by this service.
160165
*/
161166
updateRouteWithUrl(
@@ -170,16 +175,26 @@ export class PaginationService {
170175
const currentParametersWithIdName = this.getParametersWithIdName(paginationId, currentFindListOptions);
171176
const parametersWithIdName = this.getParametersWithIdName(paginationId, params);
172177
if (isNotEmpty(difference(parametersWithIdName, currentParametersWithIdName)) || isNotEmpty(extraParams) || isNotEmpty(this.clearParams)) {
173-
const queryParams = Object.assign({}, this.clearParams, currentParametersWithIdName,
174-
parametersWithIdName, extraParams);
178+
const queryParams = Object.assign({}, currentParametersWithIdName,
179+
parametersWithIdName, extraParams, this.clearParams);
175180
if (retainScrollPosition) {
181+
// By navigating to a non-existing ID, like "prevent-scroll", the browser won't perform any scroll operations
182+
const fragment: string = this.scrollService.activeFragment ?? 'prevent-scroll';
183+
this.scrollService.setFragment(fragment);
176184
this.router.navigate(url, {
177185
queryParams: queryParams,
178186
queryParamsHandling: 'merge',
179-
fragment: `p-${paginationId}`,
187+
fragment: fragment,
180188
...navigationExtras,
189+
}).then((success: boolean) => {
190+
setTimeout(() => {
191+
if (success) {
192+
this.scrollService.scrollToActiveFragment();
193+
}
194+
});
181195
});
182196
} else {
197+
this.scrollService.setFragment(null);
183198
this.router.navigate(url, {
184199
queryParams: queryParams,
185200
queryParamsHandling: 'merge',
@@ -230,16 +245,16 @@ export class PaginationService {
230245

231246
private getParametersWithIdName(paginationId: string, params: PaginationRouteParams) {
232247
const paramsWithIdName = {};
233-
if (hasValue(params.page)) {
248+
if (hasValue(params?.page)) {
234249
paramsWithIdName[`${paginationId}.page`] = `${params.page}`;
235250
}
236-
if (hasValue(params.pageSize)) {
251+
if (hasValue(params?.pageSize)) {
237252
paramsWithIdName[`${paginationId}.rpp`] = `${params.pageSize}`;
238253
}
239-
if (hasValue(params.sortField)) {
254+
if (hasValue(params?.sortField)) {
240255
paramsWithIdName[`${paginationId}.sf`] = `${params.sortField}`;
241256
}
242-
if (hasValue(params.sortDirection)) {
257+
if (hasValue(params?.sortDirection)) {
243258
paramsWithIdName[`${paginationId}.sd`] = `${params.sortDirection}`;
244259
}
245260
return paramsWithIdName;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Inject, Injectable } from '@angular/core';
2+
import { DOCUMENT } from '@angular/common';
3+
4+
/**
5+
* Service used to scroll to a specific fragment/ID on the page
6+
*/
7+
@Injectable({
8+
providedIn: 'root',
9+
})
10+
export class ScrollService {
11+
12+
activeFragment: string | null = null;
13+
14+
constructor(
15+
@Inject(DOCUMENT) protected document: Document,
16+
) {
17+
}
18+
19+
/**
20+
* Sets the fragment/ID that the user should jump to when the route is refreshed
21+
*
22+
* @param fragment The fragment/ID
23+
*/
24+
setFragment(fragment: string): void {
25+
this.activeFragment = fragment;
26+
}
27+
28+
/**
29+
* Scrolls to the active fragment/ID if it exists
30+
*/
31+
scrollToActiveFragment(): void {
32+
if (this.activeFragment) {
33+
this.document.getElementById(this.activeFragment)?.scrollIntoView({
34+
block: 'start',
35+
});
36+
}
37+
}
38+
}

src/app/core/shared/search/search.service.spec.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,21 @@ describe('SearchService', () => {
9898
it('should call the navigate method on the Router with view mode list parameter as a parameter when setViewMode is called', () => {
9999
service.setViewMode(ViewMode.ListElement);
100100

101-
expect(paginationService.updateRouteWithUrl).toHaveBeenCalledWith('test-id', ['/search'], { page: 1 }, { view: ViewMode.ListElement });
101+
expect(paginationService.updateRouteWithUrl).toHaveBeenCalledWith('test-id', ['/search'], { page: 1 }, { view: ViewMode.ListElement },
102+
undefined,
103+
);
102104
});
103105

104106
it('should call the navigate method on the Router with view mode grid parameter as a parameter when setViewMode is called', () => {
105107
service.setViewMode(ViewMode.GridElement);
106108

107-
expect(paginationService.updateRouteWithUrl).toHaveBeenCalledWith('test-id', ['/search'], { page: 1 }, { view: ViewMode.GridElement });
109+
expect(paginationService.updateRouteWithUrl).toHaveBeenCalledWith(
110+
'page-id',
111+
['/search'],
112+
{ page: 1 },
113+
{ view: ViewMode.GridElement },
114+
undefined,
115+
);
108116
});
109117
});
110118

src/app/core/shared/search/search.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ export class SearchService {
355355
* @param {ViewMode} viewMode Mode to switch to
356356
* @param {string[]} searchLinkParts
357357
*/
358-
setViewMode(viewMode: ViewMode, searchLinkParts?: string[]) {
358+
setViewMode(viewMode: ViewMode, searchLinkParts?: string[], retainScrollPosition?: boolean) {
359359
this.paginationService.getCurrentPagination(this.searchConfigurationService.paginationID, new PaginationComponentOptions()).pipe(take(1))
360360
.subscribe((config) => {
361361
let pageParams = { page: 1 };
@@ -365,7 +365,7 @@ export class SearchService {
365365
} else if (config.pageSize === 1) {
366366
pageParams = Object.assign(pageParams, { pageSize: 10 });
367367
}
368-
this.paginationService.updateRouteWithUrl(this.searchConfigurationService.paginationID, hasValue(searchLinkParts) ? searchLinkParts : [this.getSearchLink()], pageParams, queryParams);
368+
this.paginationService.updateRouteWithUrl(this.searchConfigurationService.paginationID, hasValue(searchLinkParts) ? searchLinkParts : [this.getSearchLink()], pageParams, queryParams, retainScrollPosition);
369369
});
370370
}
371371

src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ <h4 class="modal-title" id="modal-title">{{ ('submission.sections.describe.relat
2626
[item]="item"
2727
[isEditRelationship]="isEditRelationship"
2828
[toRemove]="toRemove"
29+
[retainScrollPosition]="true"
2930
(selectObject)="select($event)"
3031
(deselectObject)="deselect($event)"
3132
(resultFound)="setTotalInternals($event.pageInfo.totalElements)"
@@ -47,6 +48,7 @@ <h4 class="modal-title" id="modal-title">{{ ('submission.sections.describe.relat
4748
[context]="context"
4849
[query]="query"
4950
[externalSource]="source"
51+
[retainScrollPosition]="true"
5052
(importedObject)="imported($event)"
5153
class="d-block pt-3">
5254
</ds-dynamic-lookup-relation-external-source-tab>
@@ -62,6 +64,7 @@ <h4 class="modal-title" id="modal-title">{{ ('submission.sections.describe.relat
6264
[listId]="listId"
6365
[relationshipType]="relationshipOptions.relationshipType"
6466
[repeatable]="repeatable"
67+
[retainScrollPosition]="true"
6568
[context]="context"
6669
(selectObject)="select($event)"
6770
(deselectObject)="deselect($event)"

src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { AsyncPipe } from '@angular/common';
21
import {
32
Component,
43
EventEmitter,
@@ -40,10 +39,6 @@ import { DSpaceObject } from '../../../../../core/shared/dspace-object.model';
4039
import { ExternalSource } from '../../../../../core/shared/external-source.model';
4140
import { Item } from '../../../../../core/shared/item.model';
4241
import { RelationshipType } from '../../../../../core/shared/item-relationships/relationship-type.model';
43-
import {
44-
getAllSucceededRemoteDataPayload,
45-
getFirstSucceededRemoteDataPayload,
46-
} from '../../../../../core/shared/operators';
4742
import { SearchConfigurationService } from '../../../../../core/shared/search/search-configuration.service';
4843
import { SEARCH_CONFIG_SERVICE } from '../../../../../my-dspace-page/my-dspace-configuration.service';
4944
import { BtnDisabledDirective } from '../../../../btn-disabled.directive';
@@ -57,14 +52,16 @@ import { ListableObject } from '../../../../object-collection/shared/listable-ob
5752
import { SelectableListState } from '../../../../object-list/selectable-list/selectable-list.reducer';
5853
import { SelectableListService } from '../../../../object-list/selectable-list/selectable-list.service';
5954
import { SearchResult } from '../../../../search/models/search-result.model';
60-
import { followLink } from '../../../../utils/follow-link-config.model';
6155
import { RelationshipOptions } from '../../models/relationship-options.model';
6256
import { ThemedDynamicLookupRelationExternalSourceTabComponent } from './external-source-tab/themed-dynamic-lookup-relation-external-source-tab.component';
6357
import {
6458
AddRelationshipAction,
6559
RemoveRelationshipAction,
6660
UpdateRelationshipNameVariantAction,
6761
} from './relationship.actions';
62+
import { getAllSucceededRemoteDataPayload } from '../../../../../core/shared/operators';
63+
import { followLink } from '../../../../utils/follow-link-config.model';
64+
import { PaginationService } from '../../../../../core/pagination/pagination.service';
6865
import { ThemedDynamicLookupRelationSearchTabComponent } from './search-tab/themed-dynamic-lookup-relation-search-tab.component';
6966
import { DsDynamicLookupRelationSelectionTabComponent } from './selection-tab/dynamic-lookup-relation-selection-tab.component';
7067

@@ -209,15 +206,16 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
209206

210207
constructor(
211208
public modal: NgbActiveModal,
212-
private selectableListService: SelectableListService,
213-
private relationshipService: RelationshipDataService,
214-
private externalSourceService: ExternalSourceDataService,
215-
private lookupRelationService: LookupRelationService,
216-
private searchConfigService: SearchConfigurationService,
217-
private rdbService: RemoteDataBuildService,
218-
private zone: NgZone,
219-
private store: Store<AppState>,
220-
private router: Router,
209+
protected selectableListService: SelectableListService,
210+
protected relationshipService: RelationshipDataService,
211+
protected externalSourceService: ExternalSourceDataService,
212+
protected lookupRelationService: LookupRelationService,
213+
protected searchConfigService: SearchConfigurationService,
214+
protected rdbService: RemoteDataBuildService,
215+
protected zone: NgZone,
216+
protected store: Store<AppState>,
217+
protected router: Router,
218+
protected paginationService: PaginationService,
221219
) {
222220

223221
}
@@ -366,7 +364,10 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
366364
}
367365

368366
ngOnDestroy() {
369-
this.router.navigate([], {});
367+
this.paginationService.clearPagination(this.searchConfigService.paginationID);
368+
this.paginationService.updateRoute(this.searchConfigService.paginationID, undefined, undefined, true, {
369+
queryParamsHandling: '',
370+
});
370371
Object.values(this.subMap).forEach((subscription) => subscription.unsubscribe());
371372
}
372373

src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
<div class="row">
22
<div class="col-4">
33
<h3>{{ 'submission.sections.describe.relationship-lookup.selection-tab.settings' | translate}}</h3>
4-
<ds-page-size-selector></ds-page-size-selector>
4+
<ds-page-size-selector [retainScrollPosition]="retainScrollPosition"></ds-page-size-selector>
55
</div>
66
<div class="col-8">
77
<ds-search-form [query]="(searchConfigService.paginatedSearchOptions | async)?.query"
88
[inPlaceSearch]="true"
9+
[retainScrollPosition]="retainScrollPosition"
910
[searchPlaceholder]="'submission.sections.describe.relationship-lookup.selection-tab.search-form.placeholder' | translate">
1011
</ds-search-form>
1112
<div>
@@ -20,6 +21,7 @@ <h3>{{ 'submission.sections.describe.relationship-lookup.selection-tab.title.' +
2021
[context]="context"
2122
[importable]="true"
2223
[importConfig]="importConfig"
24+
[retainScrollPosition]="retainScrollPosition"
2325
(importObject)="import($event)">
2426
</ds-viewable-collection>
2527
}

src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,12 @@ export class DsDynamicLookupRelationExternalSourceTabComponent implements OnInit
126126
@Input() query: string;
127127

128128
@Input() repeatable: boolean;
129+
130+
/**
131+
* Should scroll to the pagination component after updating the route instead of the top of the page
132+
*/
133+
@Input() retainScrollPosition = false;
134+
129135
/**
130136
* Emit an event when an object has been imported (or selected from similar local entries)
131137
*/
@@ -234,6 +240,7 @@ export class DsDynamicLookupRelationExternalSourceTabComponent implements OnInit
234240
modalComp.relationship = this.relationship;
235241
modalComp.label = this.label;
236242
modalComp.relatedEntityType = this.relatedEntityType;
243+
modalComp.retainScrollPosition = this.retainScrollPosition;
237244
}));
238245

239246
this.subs.push(modalComp$.pipe(

src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/external-source-entry-import-modal/external-source-entry-import-modal.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ <h5 class="fw-bold">{{ (labelPrefix + 'entities' | translate) }}</h5>
2929
[selectionConfig]="{ repeatable: false, listId: entityListId }"
3030
[linkType]="linkTypes.ExternalLink"
3131
[context]="context"
32+
[retainScrollPosition]="retainScrollPosition"
3233
(deselectObject)="deselectEntity()"
3334
(selectObject)="selectEntity($event)">
3435
</ds-search-results>

0 commit comments

Comments
 (0)