From fab226912fc7c1008f7d75326772b4372502be41 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 5 Feb 2021 15:47:07 +0100 Subject: [PATCH] 76654: PaginationService --- .../epeople-registry.component.html | 3 +- .../epeople-registry.component.ts | 96 +++--- .../eperson-form/eperson-form.component.ts | 88 +++--- .../members-list/members-list.component.html | 6 +- .../members-list/members-list.component.ts | 62 ++-- .../subgroups-list.component.html | 6 +- .../subgroup-list/subgroups-list.component.ts | 55 ++-- .../groups-registry.component.html | 3 +- .../groups-registry.component.ts | 117 ++++---- .../bitstream-formats.component.html | 3 +- .../bitstream-formats.component.ts | 46 +-- .../metadata-registry.component.html | 3 +- .../metadata-registry.component.ts | 19 +- .../metadata-schema.component.html | 3 +- .../metadata-schema.component.ts | 26 +- .../browse-by-date-page.component.ts | 39 +-- .../browse-by-metadata-page.component.html | 4 +- .../browse-by-metadata-page.component.ts | 59 +--- .../browse-by-title-page.component.ts | 31 +- .../collection-page.component.html | 3 +- .../collection-page.component.ts | 36 +-- ...ty-page-sub-collection-list.component.html | 3 +- ...nity-page-sub-collection-list.component.ts | 45 ++- ...ity-page-sub-community-list.component.html | 3 +- ...unity-page-sub-community-list.component.ts | 42 +-- .../top-level-community-list.component.html | 3 +- .../top-level-community-list.component.ts | 42 ++- ...rag-and-drop-bitstream-list.component.html | 4 +- ...-drag-and-drop-bitstream-list.component.ts | 4 +- .../my-dspace-configuration.service.ts | 4 +- src/app/core/pagination/pagination.service.ts | 190 ++++++++++++ .../search/search-configuration.service.ts | 50 ++-- src/app/core/shared/search/search.service.ts | 32 +- .../overview/process-overview.component.html | 3 +- .../overview/process-overview.component.ts | 22 +- .../search-navbar/search-navbar.component.ts | 11 +- .../shared/browse-by/browse-by.component.ts | 16 +- .../page-size-selector.component.ts | 20 +- ...-paginated-drag-and-drop-list.component.ts | 27 +- .../pagination/pagination.component.html | 14 +- .../shared/pagination/pagination.component.ts | 273 +++++------------- .../eperson-group-list.component.html | 3 +- .../eperson-group-list.component.ts | 33 ++- .../search-form/search-form.component.ts | 15 +- .../search-settings.component.ts | 16 +- .../date/starts-with-date.component.ts | 5 +- .../starts-with-abstract.component.ts | 3 + .../submission-import-external.component.html | 3 +- .../submission-import-external.component.ts | 58 ++-- 49 files changed, 826 insertions(+), 826 deletions(-) create mode 100644 src/app/core/pagination/pagination.service.ts diff --git a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.html b/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.html index f478557e00..8466ff1817 100644 --- a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.html +++ b/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.html @@ -45,8 +45,7 @@ [pageInfoState]="pageInfoState$" [collectionSize]="(pageInfoState$ | async)?.totalElements" [hideGear]="true" - [hidePagerWhenSinglePage]="true" - (pageChange)="onPageChange($event)"> + [hidePagerWhenSinglePage]="true">
diff --git a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.ts b/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.ts index 11b146b294..cb8a76d627 100644 --- a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.ts +++ b/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.ts @@ -4,7 +4,7 @@ import { Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs'; import { map, switchMap, take } from 'rxjs/operators'; -import { PaginatedList, buildPaginatedList } from '../../../core/data/paginated-list.model'; +import { buildPaginatedList, PaginatedList } from '../../../core/data/paginated-list.model'; import { RemoteData } from '../../../core/data/remote-data'; import { EPersonDataService } from '../../../core/eperson/eperson-data.service'; import { EPerson } from '../../../core/eperson/models/eperson.model'; @@ -14,15 +14,13 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio import { EpersonDtoModel } from '../../../core/eperson/models/eperson-dto.model'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; -import { - getFirstCompletedRemoteData, - getAllSucceededRemoteData -} from '../../../core/shared/operators'; +import { getAllSucceededRemoteData, getFirstCompletedRemoteData } from '../../../core/shared/operators'; import { ConfirmationModalComponent } from '../../../shared/confirmation-modal/confirmation-modal.component'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { RequestService } from '../../../core/data/request.service'; import { PageInfo } from '../../../core/shared/page-info.model'; import { NoContent } from '../../../core/shared/NoContent.model'; +import { PaginationService } from '../../../core/pagination/pagination.service'; @Component({ selector: 'ds-epeople-registry', @@ -55,7 +53,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { * Pagination config used to display the list of epeople */ config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'epeople-list-pagination', + id: 'elp', pageSize: 5, currentPage: 1 }); @@ -77,6 +75,11 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { */ searchSub: Subscription; + /** + * FindListOptions + */ + findListOptionsSub: Subscription; + /** * List of subscriptions */ @@ -89,6 +92,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { private formBuilder: FormBuilder, private router: Router, private modalService: NgbModal, + private paginationService: PaginationService, public requestService: RequestService) { this.currentSearchQuery = ''; this.currentSearchScope = 'metadata'; @@ -107,7 +111,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { */ initialisePage() { this.isEPersonFormShown = false; - this.search({ scope: this.currentSearchScope, query: this.currentSearchQuery }); + this.search({scope: this.currentSearchScope, query: this.currentSearchQuery}); this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => { if (eperson != null && eperson.id) { this.isEPersonFormShown = true; @@ -133,54 +137,54 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { return [epeople]; } })).subscribe((value: PaginatedList) => { - this.ePeopleDto$.next(value); - this.pageInfoState$.next(value.pageInfo); + this.ePeopleDto$.next(value); + this.pageInfoState$.next(value.pageInfo); })); } - /** - * Event triggered when the user changes page - * @param event - */ - onPageChange(event) { - if (this.config.currentPage !== event) { - this.config.currentPage = event; - this.search({ scope: this.currentSearchScope, query: this.currentSearchQuery }); - } - } - /** * Search in the EPeople by metadata (default) or email * @param data Contains scope and query param */ search(data: any) { - const query: string = data.query; - const scope: string = data.scope; - if (query != null && this.currentSearchQuery !== query) { - this.router.navigateByUrl(this.epersonService.getEPeoplePageRouterLink()); - this.currentSearchQuery = query; - this.config.currentPage = 1; + if (hasValue(this.findListOptionsSub)) { + this.findListOptionsSub.unsubscribe(); } - if (scope != null && this.currentSearchScope !== scope) { - this.router.navigateByUrl(this.epersonService.getEPeoplePageRouterLink()); - this.currentSearchScope = scope; - this.config.currentPage = 1; - } - if (hasValue(this.searchSub)) { - this.searchSub.unsubscribe(); - this.subs = this.subs.filter((sub: Subscription) => sub !== this.searchSub); - } - this.searchSub = this.epersonService.searchByScope(this.currentSearchScope, this.currentSearchQuery, { - currentPage: this.config.currentPage, - elementsPerPage: this.config.pageSize - }).pipe( - getAllSucceededRemoteData(), - ).subscribe((peopleRD) => { - this.ePeople$.next(peopleRD.payload); - this.pageInfoState$.next(peopleRD.payload.pageInfo); + this.findListOptionsSub = this.paginationService.getCurrentPagination(this.config.id, this.config).subscribe((findListOptions) => { + const query: string = data.query; + const scope: string = data.scope; + if (query != null && this.currentSearchQuery !== query) { + this.router.navigate([], { + queryParamsHandling: 'merge' + }); + this.currentSearchQuery = query; + this.paginationService.resetPage(this.config.id); + } + if (scope != null && this.currentSearchScope !== scope) { + this.router.navigate([], { + queryParamsHandling: 'merge' + }); + this.currentSearchScope = scope; + this.paginationService.resetPage(this.config.id); + + } + if (hasValue(this.searchSub)) { + this.searchSub.unsubscribe(); + this.subs = this.subs.filter((sub: Subscription) => sub !== this.searchSub); + } + this.searchSub = this.epersonService.searchByScope(this.currentSearchScope, this.currentSearchQuery, { + currentPage: findListOptions.currentPage, + elementsPerPage: findListOptions.pageSize + }).pipe( + getAllSucceededRemoteData(), + ).subscribe((peopleRD) => { + this.ePeople$.next(peopleRD.payload); + this.pageInfoState$.next(peopleRD.payload.pageInfo); + } + ); + this.subs.push(this.searchSub); } ); - this.subs.push(this.searchSub); } /** @@ -233,7 +237,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { if (hasValue(ePerson.id)) { this.epersonService.deleteEPerson(ePerson).pipe(getFirstCompletedRemoteData()).subscribe((restResponse: RemoteData) => { if (restResponse.hasSucceeded) { - this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { name: ePerson.name })); + this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', {name: ePerson.name})); this.reset(); } else { this.notificationsService.error('Error occured when trying to delete EPerson with id: ' + ePerson.id + ' with code: ' + restResponse.statusCode + ' and message: ' + restResponse.errorMessage); @@ -273,7 +277,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { this.searchForm.patchValue({ query: '', }); - this.search({ query: '' }); + this.search({query: ''}); } /** diff --git a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts index 3c284735a9..6c66035f14 100644 --- a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts +++ b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts @@ -7,8 +7,8 @@ import { DynamicInputModel } from '@ng-dynamic-forms/core'; import { TranslateService } from '@ngx-translate/core'; -import { combineLatest, Observable, of, Subscription } from 'rxjs'; -import { switchMap, take } from 'rxjs/operators'; +import { combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs'; +import { switchMap, take, tap } from 'rxjs/operators'; import { PaginatedList } from '../../../../core/data/paginated-list.model'; import { RemoteData } from '../../../../core/data/remote-data'; import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; @@ -16,9 +16,9 @@ import { GroupDataService } from '../../../../core/eperson/group-data.service'; import { EPerson } from '../../../../core/eperson/models/eperson.model'; import { Group } from '../../../../core/eperson/models/group.model'; import { - getRemoteDataPayload, + getFirstCompletedRemoteData, getFirstSucceededRemoteData, - getFirstCompletedRemoteData + getRemoteDataPayload } from '../../../../core/shared/operators'; import { hasValue } from '../../../../shared/empty.util'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; @@ -31,6 +31,7 @@ import { ConfirmationModalComponent } from '../../../../shared/confirmation-moda import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { RequestService } from '../../../../core/data/request.service'; import { NoContent } from '../../../../core/shared/NoContent.model'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; @Component({ selector: 'ds-eperson-form', @@ -118,7 +119,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy { * Observable whether or not the admin is allowed to reset the EPerson's password * TODO: Initialize the observable once the REST API supports this (currently hardcoded to return false) */ - canReset$: Observable = of(false); + canReset$: Observable = observableOf(false); /** * Observable whether or not the admin is allowed to delete the EPerson @@ -144,7 +145,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy { * Pagination config used to display the list of groups */ config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'groups-ePersonMemberOf-list-pagination', + id: 'gem', pageSize: 5, currentPage: 1 }); @@ -167,6 +168,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy { private authService: AuthService, private authorizationService: AuthorizationDataService, private modalService: NgbModal, + private paginationService: PaginationService, public requestService: RequestService) { this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => { this.epersonInitial = eperson; @@ -184,13 +186,13 @@ export class EPersonFormComponent implements OnInit, OnDestroy { * This method will initialise the page */ initialisePage() { - combineLatest( - this.translateService.get(`${this.messagePrefix}.firstName`), - this.translateService.get(`${this.messagePrefix}.lastName`), - this.translateService.get(`${this.messagePrefix}.email`), - this.translateService.get(`${this.messagePrefix}.canLogIn`), - this.translateService.get(`${this.messagePrefix}.requireCertificate`), - this.translateService.get(`${this.messagePrefix}.emailHint`), + observableCombineLatest( + this.translateService.get(`${this.messagePrefix}.firstName`), + this.translateService.get(`${this.messagePrefix}.lastName`), + this.translateService.get(`${this.messagePrefix}.email`), + this.translateService.get(`${this.messagePrefix}.canLogIn`), + this.translateService.get(`${this.messagePrefix}.requireCertificate`), + this.translateService.get(`${this.messagePrefix}.emailHint`), ).subscribe(([firstName, lastName, email, canLogIn, requireCertificate, emailHint]) => { this.firstName = new DynamicInputModel({ id: 'firstName', @@ -222,19 +224,19 @@ export class EPersonFormComponent implements OnInit, OnDestroy { hint: emailHint }); this.canLogIn = new DynamicCheckboxModel( - { - id: 'canLogIn', - label: canLogIn, - name: 'canLogIn', - value: (this.epersonInitial != null ? this.epersonInitial.canLogIn : true) - }); + { + id: 'canLogIn', + label: canLogIn, + name: 'canLogIn', + value: (this.epersonInitial != null ? this.epersonInitial.canLogIn : true) + }); this.requireCertificate = new DynamicCheckboxModel( - { - id: 'requireCertificate', - label: requireCertificate, - name: 'requireCertificate', - value: (this.epersonInitial != null ? this.epersonInitial.requireCertificate : false) - }); + { + id: 'requireCertificate', + label: requireCertificate, + name: 'requireCertificate', + value: (this.epersonInitial != null ? this.epersonInitial.requireCertificate : false) + }); this.formModel = [ this.firstName, this.lastName, @@ -258,11 +260,31 @@ export class EPersonFormComponent implements OnInit, OnDestroy { requireCertificate: eperson != null ? eperson.requireCertificate : false }); })); - this.canImpersonate$ = this.epersonService.getActiveEPerson().pipe( - switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.LoginOnBehalfOf, hasValue(eperson) ? eperson.self : undefined)) + + const activeEPerson$ = this.epersonService.getActiveEPerson(); + + this.groups = activeEPerson$.pipe( + switchMap((eperson) => { + return observableCombineLatest([observableOf(eperson), this.paginationService.getFindListOptions(this.config.id, { + currentPage: 1, + elementsPerPage: this.config.pageSize + })]); + }), + switchMap(([eperson, findListOptions]) => { + return this.groupsDataService.findAllByHref(eperson._links.groups.href, findListOptions); + }) ); - this.canDelete$ = this.epersonService.getActiveEPerson().pipe( - switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined)) + // this.subs.push(combineLatest([activeEPerson$, paginationOption$]).subscribe(([eperson, options]) => { + // if (eperson != null) { + // this.groups = this.groupsDataService.findAllByHref(eperson._links.groups.href, options); + // } + // })); + + this.canImpersonate$ = activeEPerson$.pipe( + switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.LoginOnBehalfOf, hasValue(eperson) ? eperson.self : undefined)) + ); + this.canDelete$ = activeEPerson$.pipe( + switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined)) ); }); } @@ -322,10 +344,10 @@ export class EPersonFormComponent implements OnInit, OnDestroy { getFirstCompletedRemoteData() ).subscribe((rd: RemoteData) => { if (rd.hasSucceeded) { - this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.created.success', { name: ePersonToCreate.name })); + this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.created.success', {name: ePersonToCreate.name})); this.submitForm.emit(ePersonToCreate); } else { - this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.created.failure', { name: ePersonToCreate.name })); + this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.created.failure', {name: ePersonToCreate.name})); this.cancelForm.emit(); } }); @@ -361,10 +383,10 @@ export class EPersonFormComponent implements OnInit, OnDestroy { const response = this.epersonService.updateEPerson(editedEperson); response.pipe(getFirstCompletedRemoteData()).subscribe((rd: RemoteData) => { if (rd.hasSucceeded) { - this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.edited.success', { name: editedEperson.name })); + this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.edited.success', {name: editedEperson.name})); this.submitForm.emit(editedEperson); } else { - this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.edited.failure', { name: editedEperson.name })); + this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.edited.failure', {name: editedEperson.name})); this.cancelForm.emit(); } }); diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.html b/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.html index 8e2d23f8d5..54308a82ed 100644 --- a/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.html +++ b/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.html @@ -29,8 +29,7 @@ [pageInfoState]="(ePeopleSearchDtos | async)" [collectionSize]="(ePeopleSearchDtos | async)?.totalElements" [hideGear]="true" - [hidePagerWhenSinglePage]="true" - (pageChange)="onPageChangeSearch($event)"> + [hidePagerWhenSinglePage]="true">
@@ -83,8 +82,7 @@ [pageInfoState]="(ePeopleMembersOfGroupDtos | async)" [collectionSize]="(ePeopleMembersOfGroupDtos | async)?.totalElements" [hideGear]="true" - [hidePagerWhenSinglePage]="true" - (pageChange)="onPageChange($event)"> + [hidePagerWhenSinglePage]="true">
diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.ts b/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.ts index f1d25725d6..9f26474094 100644 --- a/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.ts +++ b/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.ts @@ -17,12 +17,14 @@ import { GroupDataService } from '../../../../../core/eperson/group-data.service import { EPerson } from '../../../../../core/eperson/models/eperson.model'; import { Group } from '../../../../../core/eperson/models/group.model'; import { - getRemoteDataPayload, + getFirstCompletedRemoteData, getFirstSucceededRemoteData, getFirstCompletedRemoteData, getAllCompletedRemoteData } from '../../../../../core/shared/operators'; import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model'; +import { PaginationService } from '../../../../../core/pagination/pagination.service'; +import { hasValue } from '../../../../../shared/empty.util'; import {EpersonDtoModel} from '../../../../../core/eperson/models/eperson-dto.model'; /** @@ -59,7 +61,7 @@ export class MembersListComponent implements OnInit, OnDestroy { * Pagination config used to display the list of EPeople that are result of EPeople search */ configSearch: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'search-members-list-pagination', + id: 'sml', pageSize: 5, currentPage: 1 }); @@ -67,7 +69,7 @@ export class MembersListComponent implements OnInit, OnDestroy { * Pagination config used to display the list of EPerson Membes of active group being edited */ config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'members-list-pagination', + id: 'ml', pageSize: 5, currentPage: 1 }); @@ -90,11 +92,15 @@ export class MembersListComponent implements OnInit, OnDestroy { // current active group being edited groupBeingEdited: Group; + paginationSub: Subscription; + + constructor(private groupDataService: GroupDataService, public ePersonDataService: EPersonDataService, private translateService: TranslateService, private notificationsService: NotificationsService, private formBuilder: FormBuilder, + private paginationService: PaginationService, private router: Router) { this.currentSearchQuery = ''; this.currentSearchScope = 'metadata'; @@ -113,23 +119,6 @@ export class MembersListComponent implements OnInit, OnDestroy { })); } - /** - * Event triggered when the user changes page on search result - * @param event - */ - onPageChangeSearch(event) { - this.configSearch.currentPage = event; - this.search({ scope: this.currentSearchScope, query: this.currentSearchQuery }); - } - - /** - * Event triggered when the user changes page on EPerson embers of active group - * @param event - */ - onPageChange(event) { - this.retrieveMembers(event); - } - /** * Retrieve the EPersons that are members of the group * @@ -138,10 +127,15 @@ export class MembersListComponent implements OnInit, OnDestroy { */ private retrieveMembers(page: number) { this.unsubFrom(SubKey.MembersDTO); - this.subs.set(SubKey.MembersDTO, this.ePersonDataService.findAllByHref(this.groupBeingEdited._links.epersons.href, { - currentPage: page, - elementsPerPage: this.config.pageSize - }).pipe( + this.subs.set(SubKey.MembersDTO, + this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( + switchMap((currentPagination) => { + return this.ePersonDataService.findAllByHref(this.groupBeingEdited._links.epersons.href, { + currentPage: currentPagination.currentPage, + elementsPerPage: currentPagination.pageSize + } + ); + }), getAllCompletedRemoteData(), map((rd: RemoteData) => { if (rd.hasFailed) { @@ -260,10 +254,13 @@ export class MembersListComponent implements OnInit, OnDestroy { this.unsubFrom(SubKey.SearchResultsDTO); this.subs.set(SubKey.SearchResultsDTO, - this.ePersonDataService.searchByScope(this.currentSearchScope, this.currentSearchQuery, { - currentPage: this.configSearch.currentPage, - elementsPerPage: this.configSearch.pageSize - }, false).pipe( + this.paginationService.getCurrentPagination(this.configSearch.id, this.configSearch).pipe( + switchMap((paginationOptions) => { + this.ePersonDataService.searchByScope(this.currentSearchScope, this.currentSearchQuery, { + currentPage: this.configSearch.currentPage, + elementsPerPage: this.configSearch.pageSize + }, false) + }), getAllCompletedRemoteData(), map((rd: RemoteData) => { if (rd.hasFailed) { @@ -292,15 +289,6 @@ export class MembersListComponent implements OnInit, OnDestroy { })); } - /** - * unsub all subscriptions - */ - ngOnDestroy(): void { - for (const key of this.subs.keys()) { - this.unsubFrom(key); - } - } - /** * Shows a notification based on the success/failure of the request * @param messageSuffix Suffix for message diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.html b/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.html index 87cbee0b60..93d968e126 100644 --- a/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.html +++ b/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.html @@ -23,8 +23,7 @@ [pageInfoState]="(searchResults$ | async)?.payload" [collectionSize]="(searchResults$ | async)?.payload?.totalElements" [hideGear]="true" - [hidePagerWhenSinglePage]="true" - (pageChange)="onPageChangeSearch($event)"> + [hidePagerWhenSinglePage]="true">
@@ -77,8 +76,7 @@ [pageInfoState]="(subGroups$ | async)?.payload" [collectionSize]="(subGroups$ | async)?.payload?.totalElements" [hideGear]="true" - [hidePagerWhenSinglePage]="true" - (pageChange)="onPageChange($event)"> + [hidePagerWhenSinglePage]="true">
diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts b/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts index d754e71e4f..3072c4538c 100644 --- a/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts +++ b/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts @@ -2,20 +2,21 @@ import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { FormBuilder } from '@angular/forms'; import { Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; -import { Observable, of as observableOf, Subscription, BehaviorSubject } from 'rxjs'; -import { map, mergeMap, take } from 'rxjs/operators'; +import { BehaviorSubject, Observable, of as observableOf, Subscription } from 'rxjs'; +import { map, mergeMap, switchMap, take } from 'rxjs/operators'; import { PaginatedList } from '../../../../../core/data/paginated-list.model'; import { RemoteData } from '../../../../../core/data/remote-data'; import { GroupDataService } from '../../../../../core/eperson/group-data.service'; import { Group } from '../../../../../core/eperson/models/group.model'; import { - getRemoteDataPayload, + getFirstCompletedRemoteData, getFirstSucceededRemoteData, - getFirstCompletedRemoteData + getRemoteDataPayload } from '../../../../../core/shared/operators'; import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model'; import { NoContent } from '../../../../../core/shared/NoContent.model'; +import { PaginationService } from '../../../../../core/pagination/pagination.service'; /** * Keys to keep track of specific subscriptions @@ -56,7 +57,7 @@ export class SubgroupsListComponent implements OnInit, OnDestroy { * Pagination config used to display the list of groups that are result of groups search */ configSearch: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'search-subgroups-list-pagination', + id: 'ssgl', pageSize: 5, currentPage: 1 }); @@ -64,7 +65,7 @@ export class SubgroupsListComponent implements OnInit, OnDestroy { * Pagination config used to display the list of subgroups of currently active group being edited */ config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'subgroups-list-pagination', + id: 'sgl', pageSize: 5, currentPage: 1 }); @@ -85,6 +86,7 @@ export class SubgroupsListComponent implements OnInit, OnDestroy { private translateService: TranslateService, private notificationsService: NotificationsService, private formBuilder: FormBuilder, + private paginationService: PaginationService, private router: Router) { this.currentSearchQuery = ''; } @@ -96,42 +98,27 @@ export class SubgroupsListComponent implements OnInit, OnDestroy { this.subs.set(SubKey.ActiveGroup, this.groupDataService.getActiveGroup().subscribe((activeGroup: Group) => { if (activeGroup != null) { this.groupBeingEdited = activeGroup; - this.retrieveSubGroups(this.config.currentPage); + this.retrieveSubGroups(); } })); } - /** - * Event triggered when the user changes page on search result - * @param event - */ - onPageChangeSearch(event) { - this.configSearch.currentPage = event; - this.search({ query: this.currentSearchQuery }); - } - - /** - * Event triggered when the user changes page on subgroups of active group - * @param event - */ - onPageChange(event) { - this.retrieveSubGroups(event); - } - /** * Retrieve the Subgroups that are members of the group * * @param page the number of the page to retrieve * @private */ - private retrieveSubGroups(page: number) { + private retrieveSubGroups() { this.unsubFrom(SubKey.Members); this.subs.set( SubKey.Members, - this.groupDataService.findAllByHref(this.groupBeingEdited._links.subgroups.href, { - currentPage: page, - elementsPerPage: this.config.pageSize - } + this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( + switchMap((config) => this.groupDataService.findAllByHref(this.groupBeingEdited._links.subgroups.href, { + currentPage: config.currentPage, + elementsPerPage: config.pageSize + } + )) ).subscribe((rd: RemoteData>) => { this.subGroups$.next(rd); })); @@ -226,10 +213,12 @@ export class SubgroupsListComponent implements OnInit, OnDestroy { this.searchDone = true; this.unsubFrom(SubKey.SearchResults); - this.subs.set(SubKey.SearchResults, this.groupDataService.searchGroups(this.currentSearchQuery, { - currentPage: this.configSearch.currentPage, - elementsPerPage: this.configSearch.pageSize - }).subscribe((rd: RemoteData>) => { + this.subs.set(SubKey.SearchResults, this.paginationService.getCurrentPagination(this.configSearch.id, this.configSearch).pipe( + switchMap((config) => this.groupDataService.searchGroups(this.currentSearchQuery, { + currentPage: config.currentPage, + elementsPerPage: config.pageSize + })) + ).subscribe((rd: RemoteData>) => { this.searchResults$.next(rd); })); } diff --git a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.html b/src/app/+admin/admin-access-control/group-registry/groups-registry.component.html index 7a557d79ac..a760423fc5 100644 --- a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.html +++ b/src/app/+admin/admin-access-control/group-registry/groups-registry.component.html @@ -35,8 +35,7 @@ [pageInfoState]="pageInfoState$" [collectionSize]="(pageInfoState$ | async)?.totalElements" [hideGear]="true" - [hidePagerWhenSinglePage]="true" - (pageChange)="onPageChange($event)"> + [hidePagerWhenSinglePage]="true">
diff --git a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts b/src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts index 305da75eeb..1a7ed6a599 100644 --- a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts +++ b/src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts @@ -5,15 +5,15 @@ import { TranslateService } from '@ngx-translate/core'; import { BehaviorSubject, combineLatest as observableCombineLatest, - Subscription, Observable, - of as observableOf + of as observableOf, + Subscription } from 'rxjs'; -import { catchError, map, switchMap, take } from 'rxjs/operators'; +import { catchError, map, switchMap, take, tap } from 'rxjs/operators'; import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; -import { PaginatedList, buildPaginatedList } from '../../../core/data/paginated-list.model'; +import { buildPaginatedList, PaginatedList } from '../../../core/data/paginated-list.model'; import { RemoteData } from '../../../core/data/remote-data'; import { RequestService } from '../../../core/data/request.service'; import { EPersonDataService } from '../../../core/eperson/eperson-data.service'; @@ -24,6 +24,7 @@ import { Group } from '../../../core/eperson/models/group.model'; import { RouteService } from '../../../core/services/route.service'; import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { + getAllSucceededRemoteData, getAllSucceededRemoteDataPayload, getFirstCompletedRemoteData, getFirstSucceededRemoteData @@ -33,6 +34,7 @@ import { hasValue } from '../../../shared/empty.util'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { NoContent } from '../../../core/shared/NoContent.model'; +import { PaginationService } from '../../../core/pagination/pagination.service'; @Component({ selector: 'ds-groups-registry', @@ -50,7 +52,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy { * Pagination config used to display the list of groups */ config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'groups-list-pagination', + id: 'gl', pageSize: 5, currentPage: 1 }); @@ -78,6 +80,8 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy { */ searchSub: Subscription; + paginationSub: Subscription; + /** * List of subscriptions */ @@ -92,6 +96,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy { protected routeService: RouteService, private router: Router, private authorizationService: AuthorizationDataService, + private paginationService: PaginationService, public requestService: RequestService) { this.currentSearchQuery = ''; this.searchForm = this.formBuilder.group(({ @@ -103,20 +108,12 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy { this.search({ query: this.currentSearchQuery }); } - /** - * Event triggered when the user changes page - * @param event - */ - onPageChange(event) { - this.config.currentPage = event; - this.search({ query: this.currentSearchQuery }); - } - /** * Search in the groups (searches by group name and by uuid exact match) * @param data Contains query param */ search(data: any) { + const query: string = data.query; if (query != null && this.currentSearchQuery !== query) { this.router.navigateByUrl(this.groupService.getGroupRegistryRouterLink()); @@ -128,44 +125,61 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy { this.subs = this.subs.filter((sub: Subscription) => sub !== this.searchSub); } - this.searchSub = this.groupService.searchGroups(this.currentSearchQuery.trim(), { - currentPage: this.config.currentPage, - elementsPerPage: this.config.pageSize - }).pipe( - getAllSucceededRemoteDataPayload(), - switchMap((groups: PaginatedList) => { - if (groups.page.length === 0) { - return observableOf(buildPaginatedList(groups.pageInfo, [])); + this.searchSub = this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( + switchMap((paginationOptions) => { + const query: string = data.query; + if (query != null && this.currentSearchQuery !== query) { + this.router.navigate([], { + queryParamsHandling: 'merge' + }); + this.currentSearchQuery = query; + this.paginationService.resetPage(this.config.id); } - return observableCombineLatest(groups.page.map((group: Group) => { - if (!this.deletedGroupsIds.includes(group.id)) { - return observableCombineLatest([ - this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(group) ? group.self : undefined), - this.hasLinkedDSO(group), - this.getSubgroups(group), - this.getMembers(group) - ]).pipe( - map(([isAuthorized, hasLinkedDSO, subgroups, members]: - [boolean, boolean, RemoteData>, RemoteData>]) => { - const groupDtoModel: GroupDtoModel = new GroupDtoModel(); - groupDtoModel.ableToDelete = isAuthorized && !hasLinkedDSO; - groupDtoModel.group = group; - groupDtoModel.subgroups = subgroups.payload; - groupDtoModel.epersons = members.payload; - return groupDtoModel; - } - ) - ); + if (hasValue(this.searchSub)) { + this.searchSub.unsubscribe(); + this.subs = this.subs.filter((sub: Subscription) => sub !== this.searchSub); + } + return this.groupService.searchGroups(this.currentSearchQuery.trim(), { + currentPage: paginationOptions.currentPage, + elementsPerPage: paginationOptions.pageSize + }).pipe( + getAllSucceededRemoteData() + switchMap((groups: PaginatedList) => { + if (groups.page.length === 0) { + return observableOf(buildPaginatedList(groups.pageInfo, [])); } - })).pipe(map((dtos: GroupDtoModel[]) => { - return buildPaginatedList(groups.pageInfo, dtos); - })); - })).subscribe((value: PaginatedList) => { - this.groupsDto$.next(value); - this.pageInfoState$.next(value.pageInfo); - }); - this.subs.push(this.searchSub); - } + return observableCombineLatest(groups.page.map((group: Group) => { + if (!this.deletedGroupsIds.includes(group.id)) { + return observableCombineLatest([ + this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(group) ? group.self : undefined), + this.hasLinkedDSO(group), + this.getSubgroups(group), + this.getMembers(group) + ]).pipe( + map(([isAuthorized, hasLinkedDSO, subgroups, members]: + [boolean, boolean, RemoteData>, RemoteData>]) => { + const groupDtoModel: GroupDtoModel = new GroupDtoModel(); + groupDtoModel.ableToDelete = isAuthorized && !hasLinkedDSO; + groupDtoModel.group = group; + groupDtoModel.subgroups = subgroups.payload; + groupDtoModel.epersons = members.payload; + return groupDtoModel; + } + ) + ); + } + })).pipe(map((dtos: GroupDtoModel[]) => { + return buildPaginatedList(groups.pageInfo, dtos); + })); + }) + ). + subscribe((value: PaginatedList) => { + this.groupsDto$.next(value); + this.pageInfoState$.next(value.pageInfo); + }); + this.subs.push(this.searchSub); + + } /** * Delete Group @@ -244,6 +258,9 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy { } cleanupSubscribes() { + if (hasValue(this.paginationSub)) { + this.paginationSub.unsubscribe(); + } this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); } } diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html index e5cf7cf5ec..6569b2d4c8 100644 --- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html +++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html @@ -14,8 +14,7 @@ [pageInfoState]="(bitstreamFormats | async)?.payload" [collectionSize]="(bitstreamFormats | async)?.payload?.totalElements" [hideGear]="true" - [hidePagerWhenSinglePage]="true" - (pageChange)="onPageChange($event)"> + [hidePagerWhenSinglePage]="true">
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts index 869e57ef89..7ae117b0a5 100644 --- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts +++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, zip } from 'rxjs'; +import { combineLatest as observableCombineLatest, Observable, zip } from 'rxjs'; import { RemoteData } from '../../../core/data/remote-data'; import { PaginatedList } from '../../../core/data/paginated-list.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; @@ -12,6 +12,7 @@ import { NotificationsService } from '../../../shared/notifications/notification import { Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { NoContent } from '../../../core/shared/NoContent.model'; +import { PaginationService } from '../../../core/pagination/pagination.service'; /** * This component renders a list of bitstream formats @@ -30,7 +31,7 @@ export class BitstreamFormatsComponent implements OnInit { /** * A BehaviourSubject that keeps track of the pageState used to update the currently displayed bitstreamFormats */ - pageState: BehaviorSubject; + // pageState: BehaviorSubject; /** * The current pagination configuration for the page used by the FindAll method @@ -45,14 +46,16 @@ export class BitstreamFormatsComponent implements OnInit { * Currently simply renders all bitstream formats */ pageConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'registry-bitstreamformats-pagination', + id: 'rbp', pageSize: 20 }); constructor(private notificationsService: NotificationsService, private router: Router, private translateService: TranslateService, - private bitstreamFormatService: BitstreamFormatDataService) { + private bitstreamFormatService: BitstreamFormatDataService, + private paginationService: PaginationService, + ) { } /** @@ -80,10 +83,8 @@ export class BitstreamFormatsComponent implements OnInit { this.deselectAll(); - this.router.navigate([], { - queryParams: Object.assign({}, { page: 1 }), - queryParamsHandling: 'merge' - }); }); + this.paginationService.resetPage(this.pageConfig.id); + }); } ); } @@ -141,31 +142,12 @@ export class BitstreamFormatsComponent implements OnInit { }); } - /** - * When the page is changed, make sure to update the list of bitstreams to match the new page - * @param event The page change event - */ - onPageChange(event) { - this.config = Object.assign(new FindListOptions(), this.config, { - currentPage: event, - }); - this.pageConfig.currentPage = event; - this.pageState.next('pageChange'); - } - ngOnInit(): void { - this.pageState = new BehaviorSubject('init'); - this.bitstreamFormats = this.pageState.pipe( - switchMap(() => { - return this.updateFormats() - ; - })); - } - /** - * Finds all formats based on the current config - */ - private updateFormats() { - return this.bitstreamFormatService.findAll(this.config); + this.bitstreamFormats = this.paginationService.getFindListOptions(this.pageConfig.id, this.config).pipe( + switchMap((findListOptions: FindListOptions) => { + return this.bitstreamFormatService.findAll(findListOptions); + }) + ); } } diff --git a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.html b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.html index 42b7558397..771f18ba4b 100644 --- a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.html +++ b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.html @@ -13,8 +13,7 @@ [paginationOptions]="config" [collectionSize]="(metadataSchemas | async)?.payload?.totalElements" [hideGear]="true" - [hidePagerWhenSinglePage]="true" - (pageChange)="onPageChange($event)"> + [hidePagerWhenSinglePage]="true">
diff --git a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.ts b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.ts index 41efb7a578..41b14ace88 100644 --- a/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.ts +++ b/src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.ts @@ -13,6 +13,7 @@ import { MetadataSchema } from '../../../core/metadata/metadata-schema.model'; import { toFindListOptions } from '../../../shared/pagination/pagination.utils'; import { NoContent } from '../../../core/shared/NoContent.model'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; +import { PaginationService } from '../../../core/pagination/pagination.service'; @Component({ selector: 'ds-metadata-registry', @@ -34,7 +35,7 @@ export class MetadataRegistryComponent { * Pagination config used to display the list of metadata schemas */ config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'registry-metadataschemas-pagination', + id: 'rm', pageSize: 25 }); @@ -46,26 +47,20 @@ export class MetadataRegistryComponent { constructor(private registryService: RegistryService, private notificationsService: NotificationsService, private router: Router, + private paginationService: PaginationService, private translateService: TranslateService) { this.updateSchemas(); } - /** - * Event triggered when the user changes page - * @param event - */ - onPageChange(event) { - this.config.currentPage = event; - this.forceUpdateSchemas(); - } - /** * Update the list of schemas by fetching it from the rest api or cache */ private updateSchemas() { + this.metadataSchemas = this.needsUpdate$.pipe( filter((update) => update === true), - switchMap(() => this.registryService.getMetadataSchemas(toFindListOptions(this.config))) + switchMap(() => this.paginationService.getCurrentPagination(this.config.id, this.config)), + switchMap((currentPagination) => this.registryService.getMetadataSchemas(toFindListOptions(currentPagination))) ); } @@ -169,7 +164,7 @@ export class MetadataRegistryComponent { const suffix = success ? 'success' : 'failure'; const messages = observableCombineLatest( this.translateService.get(success ? `${prefix}.${suffix}` : `${prefix}.${suffix}`), - this.translateService.get(`${prefix}.deleted.${suffix}`, { amount: amount }) + this.translateService.get(`${prefix}.deleted.${suffix}`, {amount: amount}) ); messages.subscribe(([head, content]) => { if (success) { diff --git a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.html b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.html index 49ef748349..49fed0c847 100644 --- a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.html +++ b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.html @@ -19,8 +19,7 @@ [pageInfoState]="fields" [collectionSize]="fields?.totalElements" [hideGear]="false" - [hidePagerWhenSinglePage]="true" - (pageChange)="onPageChange($event)"> + [hidePagerWhenSinglePage]="true">
diff --git a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts index 572bcc8a51..513f689f84 100644 --- a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts +++ b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts @@ -6,6 +6,7 @@ import { combineLatest as observableCombineLatest, combineLatest, Observable, + of as observableOf, zip } from 'rxjs'; import { RemoteData } from '../../../core/data/remote-data'; @@ -17,12 +18,10 @@ import { NotificationsService } from '../../../shared/notifications/notification import { TranslateService } from '@ngx-translate/core'; import { MetadataField } from '../../../core/metadata/metadata-field.model'; import { MetadataSchema } from '../../../core/metadata/metadata-schema.model'; -import { - getFirstCompletedRemoteData, - getFirstSucceededRemoteDataPayload -} from '../../../core/shared/operators'; +import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; import { toFindListOptions } from '../../../shared/pagination/pagination.utils'; import { NoContent } from '../../../core/shared/NoContent.model'; +import { PaginationService } from '../../../core/pagination/pagination.service'; @Component({ selector: 'ds-metadata-schema', @@ -48,7 +47,7 @@ export class MetadataSchemaComponent implements OnInit { * Pagination config used to display the list of metadata fields */ config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'registry-metadatafields-pagination', + id: 'rm', pageSize: 25, pageSizeOptions: [25, 50, 100, 200] }); @@ -62,6 +61,7 @@ export class MetadataSchemaComponent implements OnInit { private route: ActivatedRoute, private notificationsService: NotificationsService, private router: Router, + private paginationService: PaginationService, private translateService: TranslateService) { } @@ -81,25 +81,17 @@ export class MetadataSchemaComponent implements OnInit { this.updateFields(); } - /** - * Event triggered when the user changes page - * @param event - */ - onPageChange(event) { - this.config.currentPage = event; - this.forceUpdateFields(); - } - /** * Update the list of fields by fetching it from the rest api or cache */ private updateFields() { - this.metadataFields$ = combineLatest(this.metadataSchema$, this.needsUpdate$).pipe( - switchMap(([schema, update]: [MetadataSchema, boolean]) => { + this.metadataFields$ = this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( + switchMap((currentPagination) => combineLatest(this.metadataSchema$, this.needsUpdate$, observableOf(currentPagination))), + switchMap(([schema, update, currentPagination]: [MetadataSchema, boolean, PaginationComponentOptions]) => { if (update) { this.needsUpdate$.next(false); } - return this.registryService.getMetadataFieldsBySchema(schema, toFindListOptions(this.config), !update, true); + return this.registryService.getMetadataFieldsBySchema(schema, toFindListOptions(currentPagination), !update, true); }) ); } diff --git a/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts b/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts index 03c50dd051..7e3979e439 100644 --- a/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts +++ b/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts @@ -8,12 +8,16 @@ import { combineLatest as observableCombineLatest } from 'rxjs'; import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute, Params, Router } from '@angular/router'; import { BrowseService } from '../../core/browse/browse.service'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { StartsWithType } from '../../shared/starts-with/starts-with-decorator'; import { BrowseByType, rendersBrowseBy } from '../+browse-by-switcher/browse-by-decorator'; import { environment } from '../../../environments/environment'; +import { PaginationService } from '../../core/pagination/pagination.service'; +import { map } from 'rxjs/operators'; +import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; +import { SortOptions } from '../../core/cache/models/sort-options.model'; @Component({ selector: 'ds-browse-by-date-page', @@ -37,30 +41,31 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent { protected browseService: BrowseService, protected dsoService: DSpaceObjectDataService, protected router: Router, + protected paginationService: PaginationService, protected cdRef: ChangeDetectorRef) { - super(route, browseService, dsoService, router); + super(route, browseService, dsoService, paginationService, router); } ngOnInit(): void { this.startsWithType = StartsWithType.date; this.updatePage(new BrowseEntrySearchOptions(this.defaultBrowseId, this.paginationConfig, this.sortConfig)); + const currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); + const currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, this.sortConfig); this.subs.push( - observableCombineLatest( - this.route.params, - this.route.queryParams, - this.route.data, - (params, queryParams, data ) => { - return Object.assign({}, params, queryParams, data); + observableCombineLatest([this.route.params, this.route.queryParams, this.route.data, + currentPagination$, currentSort$]).pipe( + map(([routeParams, queryParams, data, currentPage, currentSort]) => { + return [Object.assign({}, routeParams, queryParams, data), currentPage, currentSort]; }) - .subscribe((params) => { - const metadataField = params.metadataField || this.defaultMetadataField; - this.browseId = params.id || this.defaultBrowseId; - this.startsWith = +params.startsWith || params.startsWith; - const searchOptions = browseParamsToOptions(params, Object.assign({}), this.sortConfig, this.browseId); - this.updatePageWithItems(searchOptions, this.value); - this.updateParent(params.scope); - this.updateStartsWithOptions(this.browseId, metadataField, params.scope); - })); + ).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => { + const metadataField = params.metadataField || this.defaultMetadataField; + this.browseId = params.id || this.defaultBrowseId; + this.startsWith = +params.startsWith || params.startsWith; + const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId); + this.updatePageWithItems(searchOptions, this.value); + this.updateParent(params.scope); + this.updateStartsWithOptions(this.browseId, metadataField, params.scope); + })); } /** diff --git a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html index 45f2ef3b2a..d770d8cb01 100644 --- a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html +++ b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html @@ -33,9 +33,7 @@ [startsWithOptions]="startsWithOptions" [enableArrows]="true" (prev)="goPrev()" - (next)="goNext()" - (pageSizeChange)="pageSizeChange($event)" - (sortDirectionChange)="sortDirectionChange($event)"> + (next)="goNext()"> diff --git a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.ts index 3b67d2e3d0..54359ff0c3 100644 --- a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -4,7 +4,7 @@ import { RemoteData } from '../../core/data/remote-data'; import { PaginatedList } from '../../core/data/paginated-list.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute, Params, Router } from '@angular/router'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { BrowseService } from '../../core/browse/browse.service'; import { BrowseEntry } from '../../core/shared/browse-entry.model'; @@ -15,6 +15,8 @@ import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.serv import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { StartsWithType } from '../../shared/starts-with/starts-with-decorator'; import { BrowseByType, rendersBrowseBy } from '../+browse-by-switcher/browse-by-decorator'; +import { PaginationService } from '../../core/pagination/pagination.service'; +import { map } from 'rxjs/operators'; @Component({ selector: 'ds-browse-by-metadata-page', @@ -48,7 +50,7 @@ export class BrowseByMetadataPageComponent implements OnInit { * The pagination config used to display the values */ paginationConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'browse-by-metadata-pagination', + id: 'bbm', currentPage: 1, pageSize: 20 }); @@ -100,23 +102,24 @@ export class BrowseByMetadataPageComponent implements OnInit { public constructor(protected route: ActivatedRoute, protected browseService: BrowseService, protected dsoService: DSpaceObjectDataService, + protected paginationService: PaginationService, protected router: Router) { } ngOnInit(): void { this.updatePage(new BrowseEntrySearchOptions(this.defaultBrowseId, this.paginationConfig, this.sortConfig)); + const currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); + const currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, this.sortConfig); this.subs.push( - observableCombineLatest( - this.route.params, - this.route.queryParams, - (params, queryParams, ) => { - return Object.assign({}, params, queryParams); + observableCombineLatest([this.route.params, this.route.queryParams, currentPagination$, currentSort$]).pipe( + map(([routeParams, queryParams, currentPage, currentSort]) => { + return [Object.assign({}, routeParams, queryParams),currentPage,currentSort]; }) - .subscribe((params) => { + ).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => { this.browseId = params.id || this.defaultBrowseId; this.value = +params.value || params.value || ''; this.startsWith = +params.startsWith || params.startsWith; - const searchOptions = browseParamsToOptions(params, this.paginationConfig, this.sortConfig, this.browseId); + const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId); if (isNotEmpty(this.value)) { this.updatePageWithItems(searchOptions, this.value); } else { @@ -203,28 +206,6 @@ export class BrowseByMetadataPageComponent implements OnInit { } } - /** - * Change the page size - * @param size - */ - pageSizeChange(size) { - this.router.navigate([], { - queryParams: Object.assign({ pageSize: size }), - queryParamsHandling: 'merge' - }); - } - - /** - * Change the sorting direction - * @param direction - */ - sortDirectionChange(direction) { - this.router.navigate([], { - queryParams: Object.assign({ sortDirection: direction }), - queryParamsHandling: 'merge' - }); - } - ngOnDestroy(): void { this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); } @@ -244,20 +225,8 @@ export function browseParamsToOptions(params: any, metadata?: string): BrowseEntrySearchOptions { return new BrowseEntrySearchOptions( metadata, - Object.assign({}, - paginationConfig, - { - currentPage: +params.page || paginationConfig.currentPage, - pageSize: +params.pageSize || paginationConfig.pageSize - } - ), - Object.assign({}, - sortConfig, - { - direction: params.sortDirection || sortConfig.direction, - field: params.sortField || sortConfig.field - } - ), + paginationConfig, + sortConfig, +params.startsWith || params.startsWith, params.scope ); diff --git a/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.ts b/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.ts index 3b3c29c3fd..d62c58d644 100644 --- a/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.ts +++ b/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.ts @@ -1,7 +1,7 @@ import { combineLatest as observableCombineLatest } from 'rxjs'; import { Component } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; -import { hasValue } from '../../shared/empty.util'; +import { ActivatedRoute, Params, Router } from '@angular/router'; +import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { BrowseByMetadataPageComponent, browseParamsToOptions @@ -11,6 +11,9 @@ import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.serv import { BrowseService } from '../../core/browse/browse.service'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { BrowseByType, rendersBrowseBy } from '../+browse-by-switcher/browse-by-decorator'; +import { PaginationService } from '../../core/pagination/pagination.service'; +import { map } from 'rxjs/operators'; +import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; @Component({ selector: 'ds-browse-by-title-page', @@ -26,26 +29,26 @@ export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent { public constructor(protected route: ActivatedRoute, protected browseService: BrowseService, protected dsoService: DSpaceObjectDataService, + protected paginationService: PaginationService, protected router: Router) { - super(route, browseService, dsoService, router); + super(route, browseService, dsoService, paginationService, router); } ngOnInit(): void { this.sortConfig = new SortOptions('dc.title', SortDirection.ASC); this.updatePage(new BrowseEntrySearchOptions(this.defaultBrowseId, this.paginationConfig, this.sortConfig)); + const currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); + const currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, this.sortConfig); this.subs.push( - observableCombineLatest( - this.route.params, - this.route.queryParams, - this.route.data, - (params, queryParams, data ) => { - return Object.assign({}, params, queryParams, data); + observableCombineLatest([this.route.params, this.route.queryParams, currentPagination$, currentSort$]).pipe( + map(([routeParams, queryParams, currentPage, currentSort]) => { + return [Object.assign({}, routeParams, queryParams),currentPage,currentSort]; }) - .subscribe((params) => { - this.browseId = params.id || this.defaultBrowseId; - this.updatePageWithItems(browseParamsToOptions(params, this.paginationConfig, this.sortConfig, this.browseId), undefined); - this.updateParent(params.scope); - })); + ).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => { + this.browseId = params.id || this.defaultBrowseId; + this.updatePageWithItems(browseParamsToOptions(params, currentPage, currentSort, this.browseId), undefined); + this.updateParent(params.scope); + })); this.updateStartsWithTextOptions(); } diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html index beb7413415..c2ad063d4a 100644 --- a/src/app/+collection-page/collection-page.component.html +++ b/src/app/+collection-page/collection-page.component.html @@ -52,8 +52,7 @@ [config]="paginationConfig" [sortConfig]="sortConfig" [objects]="itemRD" - [hideGear]="true" - (paginationChange)="onPaginationChange($event)"> + [hideGear]="true"> this.collectionRD$.pipe( + const currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); + const currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, this.sortConfig); + + this.itemRD$ = observableCombineLatest([currentPagination$, currentSort$]).pipe( + switchMap(([currentPagination, currentSort ]) => this.collectionRD$.pipe( getFirstSucceededRemoteData(), map((rd) => rd.payload.id), switchMap((id: string) => { return this.searchService.search( new PaginatedSearchOptions({ scope: id, - pagination: dto.paginationConfig, - sort: dto.sortConfig, + pagination: currentPagination, + sort: currentSort, dsoTypes: [DSpaceObjectType.ITEM] })).pipe(toDSpaceObjectListRD()) as Observable>>; }), @@ -103,19 +108,4 @@ export class CollectionPageComponent implements OnInit { return isNotEmpty(object); } - onPaginationChange(event: PaginationChangeEvent) { - this.paginationConfig = Object.assign(new PaginationComponentOptions(), { - currentPage: event.pagination.currentPage || this.paginationConfig.currentPage, - pageSize: event.pagination.pageSize || this.paginationConfig.pageSize, - id: 'collection-page-pagination' - }); - this.sortConfig = Object.assign(new SortOptions('dc.date.accessioned', SortDirection.DESC), { - direction: event.sort.direction || this.sortConfig.direction, - field: event.sort.field || this.sortConfig.field - }); - this.paginationChanges$.next({ - paginationConfig: this.paginationConfig, - sortConfig: this.sortConfig - }); - } } diff --git a/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.html b/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.html index bf6ce7fd57..9928ebd18a 100644 --- a/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.html +++ b/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.html @@ -5,8 +5,7 @@ [config]="config" [sortConfig]="sortConfig" [objects]="subCollectionsRD" - [hideGear]="false" - (paginationChange)="onPaginationChange($event)"> + [hideGear]="false"> diff --git a/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.ts b/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.ts index 261ae41aa2..4f37934575 100644 --- a/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.ts +++ b/src/app/+community-page/sub-collection-list/community-page-sub-collection-list.component.ts @@ -1,6 +1,6 @@ import { Component, Input, OnInit } from '@angular/core'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, combineLatest as observableCombineLatest } from 'rxjs'; import { RemoteData } from '../../core/data/remote-data'; import { Collection } from '../../core/shared/collection.model'; @@ -10,7 +10,8 @@ import { PaginatedList } from '../../core/data/paginated-list.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { CollectionDataService } from '../../core/data/collection-data.service'; -import { takeUntilCompletedRemoteData } from '../../core/shared/operators'; +import { PaginationService } from '../../core/pagination/pagination.service'; +import { switchMap } from 'rxjs/operators'; @Component({ selector: 'ds-community-page-sub-collection-list', @@ -29,7 +30,7 @@ export class CommunityPageSubCollectionListComponent implements OnInit { /** * The pagination id */ - pageId = 'community-collections-pagination'; + pageId = 'cmcl'; /** * The sorting configuration @@ -41,7 +42,10 @@ export class CommunityPageSubCollectionListComponent implements OnInit { */ subCollectionsRDObs: BehaviorSubject>> = new BehaviorSubject>>({} as any); - constructor(private cds: CollectionDataService) {} + constructor(private cds: CollectionDataService, + private paginationService: PaginationService, + + ) {} ngOnInit(): void { this.config = new PaginationComponentOptions(); @@ -49,30 +53,25 @@ export class CommunityPageSubCollectionListComponent implements OnInit { this.config.pageSize = 5; this.config.currentPage = 1; this.sortConfig = new SortOptions('dc.title', SortDirection.ASC); - this.updatePage(); + this.initPage(); } /** - * Called when one of the pagination settings is changed - * @param event The new pagination data + * Initialise the list of collections */ - onPaginationChange(event) { - this.config.currentPage = event.pagination.currentPage; - this.config.pageSize = event.pagination.pageSize; - this.sortConfig.field = event.sort.field; - this.sortConfig.direction = event.sort.direction; - this.updatePage(); - } + initPage() { + const pagination$ = this.paginationService.getCurrentPagination(this.config.id, this.config); + const sort$ = this.paginationService.getCurrentSort(this.config.id, this.sortConfig); - /** - * Update the list of collections - */ - updatePage() { - this.cds.findByParent(this.community.id,{ - currentPage: this.config.currentPage, - elementsPerPage: this.config.pageSize, - sort: { field: this.sortConfig.field, direction: this.sortConfig.direction } - }).pipe(takeUntilCompletedRemoteData()).subscribe((results) => { + observableCombineLatest([pagination$, sort$]).pipe( + switchMap(([currentPagination, currentSort]) => { + return this.cds.findByParent(this.community.id, { + currentPage: currentPagination.currentPage, + elementsPerPage: currentPagination.pageSize, + sort: {field: currentSort.field, direction: currentSort.direction} + }); + }) + ).subscribe((results) => { this.subCollectionsRDObs.next(results); }); } diff --git a/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.html b/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.html index 880ea9cc8e..2d14dce60a 100644 --- a/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.html +++ b/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.html @@ -5,8 +5,7 @@ [config]="config" [sortConfig]="sortConfig" [objects]="subCommunitiesRD" - [hideGear]="false" - (paginationChange)="onPaginationChange($event)"> + [hideGear]="false"> diff --git a/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.ts b/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.ts index c9f72fbc04..ffa2870a90 100644 --- a/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.ts +++ b/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.ts @@ -1,6 +1,6 @@ import { Component, Input, OnInit } from '@angular/core'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, combineLatest as observableCombineLatest } from 'rxjs'; import { RemoteData } from '../../core/data/remote-data'; import { Community } from '../../core/shared/community.model'; @@ -10,6 +10,8 @@ import { PaginationComponentOptions } from '../../shared/pagination/pagination-c import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { CommunityDataService } from '../../core/data/community-data.service'; import { takeUntilCompletedRemoteData } from '../../core/shared/operators'; +import { switchMap } from 'rxjs/operators'; +import { PaginationService } from '../../core/pagination/pagination.service'; @Component({ selector: 'ds-community-page-sub-community-list', @@ -31,7 +33,7 @@ export class CommunityPageSubCommunityListComponent implements OnInit { /** * The pagination id */ - pageId = 'community-subCommunities-pagination'; + pageId = 'cmscm'; /** * The sorting configuration @@ -43,7 +45,9 @@ export class CommunityPageSubCommunityListComponent implements OnInit { */ subCommunitiesRDObs: BehaviorSubject>> = new BehaviorSubject>>({} as any); - constructor(private cds: CommunityDataService) { + constructor(private cds: CommunityDataService, + private paginationService: PaginationService + ) { } ngOnInit(): void { @@ -52,25 +56,29 @@ export class CommunityPageSubCommunityListComponent implements OnInit { this.config.pageSize = 5; this.config.currentPage = 1; this.sortConfig = new SortOptions('dc.title', SortDirection.ASC); - this.updatePage(); - } - - /** - * Called when one of the pagination settings is changed - * @param event The new pagination data - */ - onPaginationChange(event) { - this.config.currentPage = event.pagination.currentPage; - this.config.pageSize = event.pagination.pageSize; - this.sortConfig.field = event.sort.field; - this.sortConfig.direction = event.sort.direction; - this.updatePage(); + this.initPage(); } /** * Update the list of sub-communities */ - updatePage() { + initPage() { + const pagination$ = this.paginationService.getCurrentPagination(this.config.id, this.config); + const sort$ = this.paginationService.getCurrentSort(this.config.id, this.sortConfig); + + observableCombineLatest([pagination$, sort$]).pipe( + switchMap(([currentPagination, currentSort]) => { + return this.cds.findByParent(this.community.id, { + currentPage: currentPagination.currentPage, + elementsPerPage: currentPagination.pageSize, + sort: { field: currentSort.field, direction: currentSort.direction } + }); + }) + ).subscribe((results) => { + this.subCommunitiesRDObs.next(results); + }); + + this.cds.findByParent(this.community.id, { currentPage: this.config.currentPage, elementsPerPage: this.config.pageSize, diff --git a/src/app/+home-page/top-level-community-list/top-level-community-list.component.html b/src/app/+home-page/top-level-community-list/top-level-community-list.component.html index f318a04f38..dbea87a175 100644 --- a/src/app/+home-page/top-level-community-list/top-level-community-list.component.html +++ b/src/app/+home-page/top-level-community-list/top-level-community-list.component.html @@ -6,8 +6,7 @@ [config]="config" [sortConfig]="sortConfig" [objects]="communitiesRD$ | async" - [hideGear]="true" - (paginationChange)="onPaginationChange($event)"> + [hideGear]="true"> diff --git a/src/app/+home-page/top-level-community-list/top-level-community-list.component.ts b/src/app/+home-page/top-level-community-list/top-level-community-list.component.ts index b089244008..137675e4fc 100644 --- a/src/app/+home-page/top-level-community-list/top-level-community-list.component.ts +++ b/src/app/+home-page/top-level-community-list/top-level-community-list.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, OnInit, OnDestroy } from '@angular/core'; -import { BehaviorSubject, Subscription } from 'rxjs'; +import { BehaviorSubject, combineLatest as observableCombineLatest, Subscription } from 'rxjs'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { CommunityDataService } from '../../core/data/community-data.service'; @@ -10,6 +10,8 @@ import { Community } from '../../core/shared/community.model'; import { fadeInOut } from '../../shared/animations/fade'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { hasValue } from '../../shared/empty.util'; +import { switchMap } from 'rxjs/operators'; +import { PaginationService } from '../../core/pagination/pagination.service'; /** * this component renders the Top-Level Community list @@ -36,7 +38,7 @@ export class TopLevelCommunityListComponent implements OnInit, OnDestroy { /** * The pagination id */ - pageId = 'top-level-pagination'; + pageId = 'tl'; /** * The sorting configuration @@ -48,7 +50,8 @@ export class TopLevelCommunityListComponent implements OnInit, OnDestroy { */ currentPageSubscription: Subscription; - constructor(private cds: CommunityDataService) { + constructor(private cds: CommunityDataService, + private paginationService: PaginationService) { this.config = new PaginationComponentOptions(); this.config.id = this.pageId; this.config.pageSize = 5; @@ -57,31 +60,26 @@ export class TopLevelCommunityListComponent implements OnInit, OnDestroy { } ngOnInit() { - this.updatePage(); + this.initPage(); } - /** - * Called when one of the pagination settings is changed - * @param event The new pagination data - */ - onPaginationChange(event) { - this.config.currentPage = event.pagination.currentPage; - this.config.pageSize = event.pagination.pageSize; - this.sortConfig.field = event.sort.field; - this.sortConfig.direction = event.sort.direction; - this.updatePage(); - } /** * Update the list of top communities */ - updatePage() { - this.unsubscribe(); - this.currentPageSubscription = this.cds.findTop({ - currentPage: this.config.currentPage, - elementsPerPage: this.config.pageSize, - sort: { field: this.sortConfig.field, direction: this.sortConfig.direction } - }).subscribe((results) => { + initPage() { + const pagination$ = this.paginationService.getCurrentPagination(this.config.id, this.config); + const sort$ = this.paginationService.getCurrentSort(this.config.id, this.sortConfig); + + this.currentPageSubscription = observableCombineLatest([pagination$, sort$]).pipe( + switchMap(([currentPagination, currentSort]) => { + return this.cds.findTop({ + currentPage: currentPagination.currentPage, + elementsPerPage: currentPagination.pageSize, + sort: {field: currentSort.field, direction: currentSort.direction} + }); + }) + ).subscribe((results) => { this.communitiesRD$.next(results); }); } diff --git a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.html b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.html index 9197b89796..8f0d83bd1f 100644 --- a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.html +++ b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.html @@ -4,9 +4,7 @@ [hidePaginationDetail]="true" [paginationOptions]="options" [pageInfoState]="(objectsRD$ | async)?.payload" - [collectionSize]="(objectsRD$ | async)?.payload?.totalElements" - [disableRouteParameterUpdate]="true" - (pageChange)="switchPage($event)"> + [collectionSize]="(objectsRD$ | async)?.payload?.totalElements">
} Emits the current pagination settings + */ + getCurrentPagination(paginationId: string, defaultPagination: PaginationComponentOptions): Observable { + const page$ = this.routeService.getQueryParameterValue(`p.${paginationId}`); + const size$ = this.routeService.getQueryParameterValue(`rpp.${paginationId}`); + return observableCombineLatest([page$, size$]).pipe(map(([page, size]) => { + return Object.assign(new PaginationComponentOptions(), defaultPagination, { + currentPage: this.convertToNumeric(page, defaultPagination.currentPage), + pageSize: this.getBestMatchPageSize(size, defaultPagination) + }); + }) + ); + } + + /** + * @returns {Observable} Emits the current sorting settings + */ + getCurrentSort(paginationId: string, defaultSort: SortOptions, ignoreDefault?: boolean): Observable { + if (!ignoreDefault && (isEmpty(defaultSort) || !hasValue(defaultSort))) { + defaultSort = this.defaultSortOptions; + } + const sortDirection$ = this.routeService.getQueryParameterValue(`sd.${paginationId}`); + const sortField$ = this.routeService.getQueryParameterValue(`sf.${paginationId}`); + return observableCombineLatest([sortDirection$, sortField$]).pipe(map(([sortDirection, sortField]) => { + const field = sortField || defaultSort?.field; + const direction = SortDirection[sortDirection] || defaultSort?.direction; + return new SortOptions(field, direction); + }) + ); + } + + getFindListOptions(paginationId: string, defaultFindList: FindListOptions, ignoreDefault?: boolean): Observable { + const paginationComponentOptions = new PaginationComponentOptions(); + paginationComponentOptions.currentPage = defaultFindList.currentPage; + paginationComponentOptions.pageSize = defaultFindList.elementsPerPage; + const currentPagination$ = this.getCurrentPagination(paginationId, paginationComponentOptions); + const currentSortOptions$ = this.getCurrentSort(paginationId, defaultFindList.sort, ignoreDefault); + + return observableCombineLatest([currentPagination$, currentSortOptions$]).pipe( + filter(([currentPagination, currentSortOptions]) => hasValue(currentPagination) && hasValue(currentSortOptions)), + map(([currentPagination, currentSortOptions]) => { + return Object.assign(new FindListOptions(), defaultFindList, { + sort: currentSortOptions, + currentPage: currentPagination.currentPage, + elementsPerPage: currentPagination.pageSize + }); + })); + } + + resetPage(paginationId: string) { + this.updateRoute(paginationId, {page: 1}); + } + + getCurrentRouting(paginationId: string) { + return this.getFindListOptions(paginationId, {}, true).pipe( + take(1), + map((findListoptions: FindListOptions) => { + return { + page: findListoptions.currentPage, + pageSize: findListoptions.elementsPerPage, + sortField: findListoptions.sort.field, + sortDir: findListoptions.sort.direction, + }; + }) + ); + } + + updateRoute(paginationId: string, params: { + page?: number + pageSize?: number + sortField?: string + sortDirection?: SortDirection + }, extraParams?, changeLocationNot?: boolean) { + this.getCurrentRouting(paginationId).subscribe((currentFindListOptions) => { + const currentParametersWithIdName = this.getParametersWithIdName(paginationId, currentFindListOptions); + const parametersWithIdName = this.getParametersWithIdName(paginationId, params); + if (isNotEmpty(difference(parametersWithIdName, currentParametersWithIdName)) || isNotEmpty(extraParams)) { + const queryParams = Object.assign({}, currentParametersWithIdName, + parametersWithIdName, extraParams); + if (changeLocationNot) { + this.location.go(this.router.createUrlTree([], { + relativeTo: this.route, queryParams: queryParams, queryParamsHandling: 'merge' + }).toString()); + } else { + this.router.navigate([], { + queryParams: queryParams, + queryParamsHandling: 'merge' + }); + } + } + }); + } + + updateRouteWithUrl(paginationId: string, url: string[], params: { + page?: number + pageSize?: number + sortField?: string + sortDirection?: SortDirection + }, extraParams?, changeLocationNot?: boolean) { + this.getCurrentRouting(paginationId).subscribe((currentFindListOptions) => { + const currentParametersWithIdName = this.getParametersWithIdName(paginationId, currentFindListOptions); + const parametersWithIdName = this.getParametersWithIdName(paginationId, params); + if (isNotEmpty(difference(parametersWithIdName, currentParametersWithIdName)) || isNotEmpty(extraParams)) { + const queryParams = Object.assign({}, currentParametersWithIdName, + parametersWithIdName, extraParams); + if (changeLocationNot) { + this.location.go(this.router.createUrlTree([], { + relativeTo: this.route, queryParams: queryParams, queryParamsHandling: 'merge' + }).toString()); + } else { + this.router.navigate(url, { + queryParams: queryParams, + queryParamsHandling: 'merge' + }); + } + } + }); + } + + + getParametersWithIdName(paginationId: string, params: { + page?: number + pageSize?: number + sortField?: string + sortDirection?: SortDirection + }) { + const paramsWithIdName = {}; + if (hasValue(params.page)) { + paramsWithIdName[`p.${paginationId}`] = `${params.page}`; + } + if (hasValue(params.pageSize)) { + paramsWithIdName[`rpp.${paginationId}`] = `${params.pageSize}`; + } + if (hasValue(params.sortField)) { + paramsWithIdName[`sf.${paginationId}`] = `${params.sortField}`; + } + if (hasValue(params.sortDirection)) { + paramsWithIdName[`sd.${paginationId}`] = `${params.sortDirection}`; + } + return paramsWithIdName; + } + + private convertToNumeric(param, defaultValue) { + let result = defaultValue; + if (isNumeric(param)) { + result = +param; + } + return result; + } + + + private getBestMatchPageSize(pageSize: any, defaultPagination: PaginationComponentOptions): number { + const numberPageSize = this.convertToNumeric(pageSize, defaultPagination.pageSize); + const differenceList = defaultPagination.pageSizeOptions.map((pageSizeOption) => { + return Math.abs(pageSizeOption - numberPageSize); + }); + const minDifference = Math.min.apply(Math, differenceList); + return defaultPagination.pageSizeOptions[differenceList.indexOf(minDifference)]; + } + +} diff --git a/src/app/core/shared/search/search-configuration.service.ts b/src/app/core/shared/search/search-configuration.service.ts index edd3982319..6639b53cb9 100644 --- a/src/app/core/shared/search/search-configuration.service.ts +++ b/src/app/core/shared/search/search-configuration.service.ts @@ -14,17 +14,21 @@ import { RouteService } from '../../services/route.service'; import { getFirstSucceededRemoteData } from '../operators'; import { hasNoValue, hasValue, isNotEmpty, isNotEmptyOperator } from '../../../shared/empty.util'; import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; +import { PaginationService } from '../../pagination/pagination.service'; +import { tap } from 'rxjs/internal/operators/tap'; /** * Service that performs all actions that have to do with the current search configuration */ @Injectable() export class SearchConfigurationService implements OnDestroy { + + public paginationID = 'spc'; /** * Default pagination settings */ protected defaultPagination = Object.assign(new PaginationComponentOptions(), { - id: 'search-page-configuration', + id: this.paginationID, pageSize: 10, currentPage: 1 }); @@ -75,6 +79,7 @@ export class SearchConfigurationService implements OnDestroy { * @param {ActivatedRoute} route */ constructor(protected routeService: RouteService, + protected paginationService: PaginationService, protected route: ActivatedRoute) { this.initDefaults(); @@ -91,7 +96,7 @@ export class SearchConfigurationService implements OnDestroy { this.paginatedSearchOptions = new BehaviorSubject(defs); this.searchOptions = new BehaviorSubject(defs); this.subs.push(this.subscribeToSearchOptions(defs)); - this.subs.push(this.subscribeToPaginatedSearchOptions(defs)); + this.subs.push(this.subscribeToPaginatedSearchOptions(defs.pagination.id, defs)); } ); } @@ -140,34 +145,15 @@ export class SearchConfigurationService implements OnDestroy { /** * @returns {Observable} Emits the current pagination settings */ - getCurrentPagination(defaultPagination: PaginationComponentOptions): Observable { - const page$ = this.routeService.getQueryParameterValue('page'); - const size$ = this.routeService.getQueryParameterValue('pageSize'); - return observableCombineLatest(page$, size$).pipe(map(([page, size]) => { - return Object.assign(new PaginationComponentOptions(), defaultPagination, { - currentPage: page || defaultPagination.currentPage, - pageSize: size || defaultPagination.pageSize - }); - }) - ); + getCurrentPagination(paginationId: string, defaultPagination: PaginationComponentOptions): Observable { + return this.paginationService.getCurrentPagination(paginationId, defaultPagination); } /** * @returns {Observable} Emits the current sorting settings */ - getCurrentSort(defaultSort: SortOptions): Observable { - const sortDirection$ = this.routeService.getQueryParameterValue('sortDirection'); - const sortField$ = this.routeService.getQueryParameterValue('sortField'); - return observableCombineLatest(sortDirection$, sortField$).pipe(map(([sortDirection, sortField]) => { - // Dirty fix because sometimes the observable value is null somehow - sortField = this.route.snapshot.queryParamMap.get('sortField'); - - const field = sortField || defaultSort.field; - const direction = SortDirection[sortDirection] || defaultSort.direction; - return new SortOptions(field, direction); - } - ) - ); + getCurrentSort(paginationId: string, defaultSort: SortOptions): Observable { + return this.paginationService.getCurrentSort(paginationId, defaultSort); } /** @@ -234,10 +220,10 @@ export class SearchConfigurationService implements OnDestroy { * @param {PaginatedSearchOptions} defaults Default values for when no parameters are available * @returns {Subscription} The subscription to unsubscribe from */ - private subscribeToPaginatedSearchOptions(defaults: PaginatedSearchOptions): Subscription { + private subscribeToPaginatedSearchOptions(paginationId: string, defaults: PaginatedSearchOptions): Subscription { return observableMerge( - this.getPaginationPart(defaults.pagination), - this.getSortPart(defaults.sort), + this.getPaginationPart(paginationId, defaults.pagination), + this.getSortPart(paginationId, defaults.sort), this.getConfigurationPart(defaults.configuration), this.getScopePart(defaults.scope), this.getQueryPart(defaults.query), @@ -317,8 +303,8 @@ export class SearchConfigurationService implements OnDestroy { /** * @returns {Observable} Emits the current pagination settings as a partial SearchOptions object */ - private getPaginationPart(defaultPagination: PaginationComponentOptions): Observable { - return this.getCurrentPagination(defaultPagination).pipe(map((pagination) => { + private getPaginationPart(paginationId: string, defaultPagination: PaginationComponentOptions): Observable { + return this.getCurrentPagination(paginationId, defaultPagination).pipe(map((pagination) => { return { pagination }; })); } @@ -326,8 +312,8 @@ export class SearchConfigurationService implements OnDestroy { /** * @returns {Observable} Emits the current sorting settings as a partial SearchOptions object */ - private getSortPart(defaultSort: SortOptions): Observable { - return this.getCurrentSort(defaultSort).pipe(map((sort) => { + private getSortPart(paginationId: string, defaultSort: SortOptions): Observable { + return this.getCurrentSort(paginationId, defaultSort).pipe(map((sort) => { return { sort }; })); } diff --git a/src/app/core/shared/search/search.service.ts b/src/app/core/shared/search/search.service.ts index b380a70d44..4dfd1964c3 100644 --- a/src/app/core/shared/search/search.service.ts +++ b/src/app/core/shared/search/search.service.ts @@ -1,7 +1,7 @@ import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs'; import { Injectable, OnDestroy } from '@angular/core'; -import { NavigationExtras, Router } from '@angular/router'; -import { first, map, switchMap, take } from 'rxjs/operators'; +import { Router } from '@angular/router'; +import { map, switchMap, take } from 'rxjs/operators'; import { followLink, FollowLinkConfig } from '../../../shared/utils/follow-link-config.model'; import { LinkService } from '../../cache/builders/link.service'; import { PaginatedList } from '../../data/paginated-list.model'; @@ -37,6 +37,10 @@ import { ListableObject } from '../../../shared/object-collection/shared/listabl import { getSearchResultFor } from '../../../shared/search/search-result-element-decorator'; import { FacetConfigResponse } from '../../../shared/search/facet-config-response.model'; import { FacetValues } from '../../../shared/search/facet-values.model'; +import { PaginationService } from '../../pagination/pagination.service'; +import { SearchConfigurationService } from './search-configuration.service'; +import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { tap } from 'rxjs/internal/operators/tap'; /** * Service that performs all general actions that have to do with the search page @@ -75,7 +79,9 @@ export class SearchService implements OnDestroy { private linkService: LinkService, private halService: HALEndpointService, private communityService: CommunityDataService, - private dspaceObjectService: DSpaceObjectDataService + private dspaceObjectService: DSpaceObjectDataService, + private paginationService: PaginationService, + private searchConfigurationService: SearchConfigurationService ) { } @@ -380,20 +386,16 @@ export class SearchService implements OnDestroy { * @param {ViewMode} viewMode Mode to switch to */ setViewMode(viewMode: ViewMode, searchLinkParts?: string[]) { - this.routeService.getQueryParameterValue('pageSize').pipe(first()) - .subscribe((pageSize) => { - let queryParams = { view: viewMode, page: 1 }; + this.paginationService.getCurrentPagination(this.searchConfigurationService.paginationID, new PaginationComponentOptions()).pipe(take(1)) + .subscribe((config) => { + let pageParams = { page: 1 }; + const queryParams = { view: viewMode }; if (viewMode === ViewMode.DetailedListElement) { - queryParams = Object.assign(queryParams, {pageSize: '1'}); - } else if (pageSize === '1') { - queryParams = Object.assign(queryParams, {pageSize: '10'}); + pageParams = Object.assign(pageParams, {pageSize: 1}); + } else if (config.pageSize === 1) { + pageParams = Object.assign(pageParams, {pageSize: 10}); } - const navigationExtras: NavigationExtras = { - queryParams: queryParams, - queryParamsHandling: 'merge' - }; - - this.router.navigate(hasValue(searchLinkParts) ? searchLinkParts : [this.getSearchLink()], navigationExtras); + this.paginationService.updateRouteWithUrl(this.searchConfigurationService.paginationID, hasValue(searchLinkParts) ? searchLinkParts : [this.getSearchLink()], pageParams, queryParams); }); } diff --git a/src/app/process-page/overview/process-overview.component.html b/src/app/process-page/overview/process-overview.component.html index 30eb44430e..62b1433b2c 100644 --- a/src/app/process-page/overview/process-overview.component.html +++ b/src/app/process-page/overview/process-overview.component.html @@ -8,8 +8,7 @@ [pageInfoState]="(processesRD$ | async)?.payload" [collectionSize]="(processesRD$ | async)?.payload?.totalElements" [hideGear]="true" - [hidePagerWhenSinglePage]="true" - (pageChange)="onPageChange($event)"> + [hidePagerWhenSinglePage]="true">
diff --git a/src/app/process-page/overview/process-overview.component.ts b/src/app/process-page/overview/process-overview.component.ts index 541d6c212e..1ea8c5b9c6 100644 --- a/src/app/process-page/overview/process-overview.component.ts +++ b/src/app/process-page/overview/process-overview.component.ts @@ -8,8 +8,9 @@ import { FindListOptions } from '../../core/data/request.models'; import { EPersonDataService } from '../../core/eperson/eperson-data.service'; import { getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; import { EPerson } from '../../core/eperson/models/eperson.model'; -import { map } from 'rxjs/operators'; +import { map, switchMap } from 'rxjs/operators'; import { ProcessDataService } from '../../core/data/processes/process-data.service'; +import { PaginationService } from '../../core/pagination/pagination.service'; @Component({ selector: 'ds-process-overview', @@ -36,7 +37,7 @@ export class ProcessOverviewComponent implements OnInit { * The current pagination configuration for the page */ pageConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'process-overview-pagination', + id: 'po', pageSize: 20 }); @@ -46,6 +47,7 @@ export class ProcessOverviewComponent implements OnInit { dateFormat = 'yyyy-MM-dd HH:mm:ss'; constructor(protected processService: ProcessDataService, + protected paginationService: PaginationService, protected ePersonService: EPersonDataService) { } @@ -53,23 +55,13 @@ export class ProcessOverviewComponent implements OnInit { this.setProcesses(); } - /** - * When the page is changed, make sure to update the list of processes to match the new page - * @param event The page change event - */ - onPageChange(event) { - this.config = Object.assign(new FindListOptions(), this.config, { - currentPage: event, - }); - this.pageConfig.currentPage = event; - this.setProcesses(); - } - /** * Send a request to fetch all processes for the current page */ setProcesses() { - this.processesRD$ = this.processService.findAll(this.config); + this.processesRD$ = this.paginationService.getFindListOptions(this.pageConfig.id, this.config).pipe( + switchMap((config) => this.processService.findAll(config)) + ); } /** diff --git a/src/app/search-navbar/search-navbar.component.ts b/src/app/search-navbar/search-navbar.component.ts index 1bedfb73ef..1e509a180b 100644 --- a/src/app/search-navbar/search-navbar.component.ts +++ b/src/app/search-navbar/search-navbar.component.ts @@ -3,6 +3,8 @@ import { FormBuilder } from '@angular/forms'; import { Router } from '@angular/router'; import { SearchService } from '../core/shared/search/search.service'; import { expandSearchInput } from '../shared/animations/slide'; +import { PaginationService } from '../core/pagination/pagination.service'; +import { SearchConfigurationService } from '../core/shared/search/search-configuration.service'; /** * The search box in the header that expands on focus and collapses on focus out @@ -24,7 +26,9 @@ export class SearchNavbarComponent { // Search input field @ViewChild('searchInput') searchField: ElementRef; - constructor(private formBuilder: FormBuilder, private router: Router, private searchService: SearchService) { + constructor(private formBuilder: FormBuilder, private router: Router, private searchService: SearchService, + private paginationService: PaginationService, + private searchConfig: SearchConfigurationService) { this.searchForm = this.formBuilder.group(({ query: '', })); @@ -63,9 +67,6 @@ export class SearchNavbarComponent { this.collapse(); const linkToNavigateTo = this.searchService.getSearchLink().split('/'); this.searchForm.reset(); - this.router.navigate(linkToNavigateTo, { - queryParams: Object.assign({}, { page: 1 }, data), - queryParamsHandling: 'merge' - }); + this.paginationService.updateRouteWithUrl(this.searchConfig.paginationID, linkToNavigateTo, {page: 1}, data); } } diff --git a/src/app/shared/browse-by/browse-by.component.ts b/src/app/shared/browse-by/browse-by.component.ts index 8706c39e54..8920726c8b 100644 --- a/src/app/shared/browse-by/browse-by.component.ts +++ b/src/app/shared/browse-by/browse-by.component.ts @@ -7,6 +7,7 @@ import { fadeIn, fadeInOut } from '../animations/fade'; import { Observable } from 'rxjs'; import { ListableObject } from '../object-collection/shared/listable-object.model'; import { getStartsWithComponent, StartsWithType } from '../starts-with/starts-with-decorator'; +import { PaginationService } from '../../core/pagination/pagination.service'; @Component({ selector: 'ds-browse-by', @@ -96,7 +97,9 @@ export class BrowseByComponent implements OnInit { */ public sortDirections = SortDirection; - public constructor(private injector: Injector) { + public constructor(private injector: Injector, + protected paginationService: PaginationService, + ) { } @@ -119,8 +122,7 @@ export class BrowseByComponent implements OnInit { * @param size */ doPageSizeChange(size) { - this.paginationConfig.pageSize = size; - this.pageSizeChange.emit(size); + this.paginationService.updateRoute(this.paginationConfig.id,{pageSize: size}); } /** @@ -128,8 +130,7 @@ export class BrowseByComponent implements OnInit { * @param direction */ doSortDirectionChange(direction) { - this.sortConfig.direction = direction; - this.sortDirectionChange.emit(direction); + this.paginationService.updateRoute(this.paginationConfig.id,{sortDirection: direction}); } /** @@ -141,7 +142,10 @@ export class BrowseByComponent implements OnInit { ngOnInit(): void { this.objectInjector = Injector.create({ - providers: [{ provide: 'startsWithOptions', useFactory: () => (this.startsWithOptions), deps:[] }], + providers: [ + { provide: 'startsWithOptions', useFactory: () => (this.startsWithOptions), deps:[] }, + { provide: 'paginationId', useFactory: () => (this.paginationConfig.id), deps:[] } + ], parent: this.injector }); } diff --git a/src/app/shared/page-size-selector/page-size-selector.component.ts b/src/app/shared/page-size-selector/page-size-selector.component.ts index dfea7d423f..764a8063db 100644 --- a/src/app/shared/page-size-selector/page-size-selector.component.ts +++ b/src/app/shared/page-size-selector/page-size-selector.component.ts @@ -1,11 +1,12 @@ import { Component, Inject, OnInit } from '@angular/core'; import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; import { Observable } from 'rxjs'; -import { ActivatedRoute, NavigationExtras, Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { SEARCH_CONFIG_SERVICE } from '../../+my-dspace-page/my-dspace-page.component'; import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; import { PaginatedSearchOptions } from '../search/paginated-search-options.model'; -import { map } from 'rxjs/operators'; +import { map, take } from 'rxjs/operators'; +import { PaginationService } from '../../core/pagination/pagination.service'; @Component({ selector: 'ds-page-size-selector', @@ -22,8 +23,10 @@ export class PageSizeSelectorComponent implements OnInit { */ paginationOptions$: Observable; + constructor(private route: ActivatedRoute, private router: Router, + private paginationService: PaginationService, @Inject(SEARCH_CONFIG_SERVICE) public searchConfigurationService: SearchConfigurationService) { } @@ -40,13 +43,10 @@ export class PageSizeSelectorComponent implements OnInit { */ reloadRPP(event: Event) { const value = (event.target as HTMLInputElement).value; - const navigationExtras: NavigationExtras = { - queryParams: { - pageSize: value, - page: 1 - }, - queryParamsHandling: 'merge' - }; - this.router.navigate([], navigationExtras); + this.paginationOptions$.pipe( + take(1) + ).subscribe((pagination: PaginationComponentOptions) => { + this.paginationService.updateRoute(pagination.id, {page: 1, pageSize: +value}); + }) ; } } diff --git a/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts b/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts index 4cc647d091..3b5d8bc3de 100644 --- a/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts +++ b/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts @@ -17,6 +17,7 @@ import { Component, ElementRef, EventEmitter, OnDestroy, Output, ViewChild } fro import { PaginationComponent } from '../pagination/pagination.component'; import { ObjectValuesPipe } from '../utils/object-values-pipe'; import { compareArraysUsing } from '../../+item-page/simple/item-types/shared/item-relationships-utils'; +import { PaginationService } from '../../core/pagination/pagination.service'; /** * Operator used for comparing {@link FieldUpdate}s by their field's UUID @@ -81,14 +82,14 @@ export abstract class AbstractPaginatedDragAndDropListComponent { + this.currentPage$.next(currentPagination.currentPage); + }); + } + /** * Initialize the field-updates in the store */ @@ -164,14 +177,6 @@ export abstract class AbstractPaginatedDragAndDropListComponent +
{{ 'pagination.showing.label' | translate }} - {{ 'pagination.showing.detail' | translate:getShowingDetails(collectionSize)}} + {{ 'pagination.showing.detail' | translate:(getShowingDetails(collectionSize)|async)}}
- + - +
@@ -20,15 +20,15 @@
-
diff --git a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts b/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts index dfd9ea65ab..26a7f47f52 100644 --- a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts +++ b/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts @@ -20,6 +20,7 @@ import { EPersonDataService } from '../../../../core/eperson/eperson-data.servic import { GroupDataService } from '../../../../core/eperson/group-data.service'; import { fadeInOut } from '../../../animations/fade'; import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; export interface SearchEvent { scope: string; @@ -93,13 +94,16 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy { */ private subs: Subscription[] = []; + private pageConfigSub: Subscription; + /** * Initialize instance variables and inject the properly DataService * * @param {DSONameService} dsoNameService * @param {Injector} parentInjector */ - constructor(public dsoNameService: DSONameService, private parentInjector: Injector) { + constructor(public dsoNameService: DSONameService, private parentInjector: Injector, + private paginationService: PaginationService) { } /** @@ -112,14 +116,14 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy { providers: [], parent: this.parentInjector }).get(provider); - this.paginationOptions.id = uniqueId('eperson-group-list-pagination'); + this.paginationOptions.id = uniqueId('egl'); this.paginationOptions.pageSize = 5; if (this.initSelected) { this.entrySelectedId.next(this.initSelected); } - this.updateList(this.paginationOptions, this.currentSearchScope, this.currentSearchQuery); + this.updateList(this.currentSearchScope, this.currentSearchQuery); } /** @@ -151,13 +155,6 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy { ); } - /** - * Method called on page change - */ - onPageChange(page: number): void { - this.paginationOptions.currentPage = page; - this.updateList(this.paginationOptions, this.currentSearchScope, this.currentSearchQuery); - } /** * Method called on search @@ -165,17 +162,22 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy { onSearch(searchEvent: SearchEvent) { this.currentSearchQuery = searchEvent.query; this.currentSearchScope = searchEvent.scope; - this.paginationOptions.currentPage = 1; - this.updateList(this.paginationOptions, this.currentSearchScope, this.currentSearchQuery); + this.paginationService.resetPage(this.paginationOptions.id); + this.updateList(this.currentSearchScope, this.currentSearchQuery); } /** * Retrieve a paginate list of eperson or group */ - updateList(config: PaginationComponentOptions, scope: string, query: string): void { + updateList(scope: string, query: string): void { + if (hasValue(this.pageConfigSub)) { + this.pageConfigSub.unsubscribe(); + } + this.pageConfigSub = this.paginationService.getCurrentPagination(this.paginationOptions.id, this.paginationOptions) + .subscribe((paginationOptions) => { const options: FindListOptions = Object.assign({}, new FindListOptions(), { - elementsPerPage: config.pageSize, - currentPage: config.currentPage + elementsPerPage: paginationOptions.pageSize, + currentPage: paginationOptions.currentPage }); const search$: Observable>> = this.isListOfEPerson ? @@ -187,6 +189,7 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy { this.list$.next(list); }) ); + }); } /** diff --git a/src/app/shared/search-form/search-form.component.ts b/src/app/shared/search-form/search-form.component.ts index 97541c4786..594f0c84e2 100644 --- a/src/app/shared/search-form/search-form.component.ts +++ b/src/app/shared/search-form/search-form.component.ts @@ -4,6 +4,8 @@ import { Router } from '@angular/router'; import { isNotEmpty } from '../empty.util'; import { SearchService } from '../../core/shared/search/search.service'; import { currentPath } from '../utils/route.utils'; +import { PaginationService } from '../../core/pagination/pagination.service'; +import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; /** * This component renders a simple item page. @@ -60,7 +62,10 @@ export class SearchFormComponent { */ @Output() submitSearch = new EventEmitter(); - constructor(private router: Router, private searchService: SearchService) { + constructor(private router: Router, private searchService: SearchService, + private paginationService: PaginationService, + private searchConfig: SearchConfigurationService + ) { } /** @@ -85,10 +90,14 @@ export class SearchFormComponent { * @param data Updated parameters */ updateSearch(data: any) { - this.router.navigate(this.getSearchLinkParts(), { - queryParams: Object.assign({}, { page: 1 }, data), + const queryParams = Object.assign({}, data); + queryParams[`p.${this.searchConfig.paginationID}`] = 1; + + this.router.navigate(this.getSearchLinkParts(), { + queryParams: queryParams, queryParamsHandling: 'merge' }); + this.paginationService.updateRouteWithUrl(this.searchConfig.paginationID, this.getSearchLinkParts(), { page: 1 }, data); } /** diff --git a/src/app/shared/search/search-settings/search-settings.component.ts b/src/app/shared/search/search-settings/search-settings.component.ts index 45d7c7b432..6f12c161ea 100644 --- a/src/app/shared/search/search-settings/search-settings.component.ts +++ b/src/app/shared/search/search-settings/search-settings.component.ts @@ -6,6 +6,7 @@ import { PaginatedSearchOptions } from '../paginated-search-options.model'; import { Observable } from 'rxjs'; import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; import { SEARCH_CONFIG_SERVICE } from '../../../+my-dspace-page/my-dspace-page.component'; +import { PaginationService } from '../../../core/pagination/pagination.service'; @Component({ selector: 'ds-search-settings', @@ -30,6 +31,7 @@ export class SearchSettingsComponent implements OnInit { constructor(private service: SearchService, private route: ActivatedRoute, private router: Router, + private paginationService: PaginationService, @Inject(SEARCH_CONFIG_SERVICE) public searchConfigurationService: SearchConfigurationService) { } @@ -46,14 +48,10 @@ export class SearchSettingsComponent implements OnInit { */ reloadOrder(event: Event) { const values = (event.target as HTMLInputElement).value.split(','); - const navigationExtras: NavigationExtras = { - queryParams: { - sortDirection: values[1], - sortField: values[0], - page: 1 - }, - queryParamsHandling: 'merge' - }; - this.router.navigate([], navigationExtras); + this.paginationService.updateRoute(this.searchConfigurationService.paginationID, { + sortField: values[0], + sortDirection: values[1] as SortDirection, + page: 1 + }); } } diff --git a/src/app/shared/starts-with/date/starts-with-date.component.ts b/src/app/shared/starts-with/date/starts-with-date.component.ts index 75173212f9..e947fb76a2 100644 --- a/src/app/shared/starts-with/date/starts-with-date.component.ts +++ b/src/app/shared/starts-with/date/starts-with-date.component.ts @@ -4,6 +4,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { renderStartsWithFor, StartsWithType } from '../starts-with-decorator'; import { StartsWithAbstractComponent } from '../starts-with-abstract.component'; import { hasValue } from '../../empty.util'; +import { PaginationService } from '../../../core/pagination/pagination.service'; /** * A switchable component rendering StartsWith options for the type "Date". @@ -33,9 +34,11 @@ export class StartsWithDateComponent extends StartsWithAbstractComponent { startsWithYear: number; public constructor(@Inject('startsWithOptions') public startsWithOptions: any[], + @Inject('paginationId') public paginationId: string, + protected paginationService: PaginationService, protected route: ActivatedRoute, protected router: Router) { - super(startsWithOptions, route, router); + super(startsWithOptions, paginationId, paginationService, route, router); } ngOnInit() { diff --git a/src/app/shared/starts-with/starts-with-abstract.component.ts b/src/app/shared/starts-with/starts-with-abstract.component.ts index 229777e96d..b0c7015a12 100644 --- a/src/app/shared/starts-with/starts-with-abstract.component.ts +++ b/src/app/shared/starts-with/starts-with-abstract.component.ts @@ -3,6 +3,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { Subscription } from 'rxjs'; import { FormControl, FormGroup } from '@angular/forms'; import { hasValue } from '../empty.util'; +import { PaginationService } from '../../core/pagination/pagination.service'; /** * An abstract component to render StartsWith options @@ -28,6 +29,8 @@ export abstract class StartsWithAbstractComponent implements OnInit, OnDestroy { subs: Subscription[] = []; public constructor(@Inject('startsWithOptions') public startsWithOptions: any[], + @Inject('paginationId') public paginationId: string, + protected paginationService: PaginationService, protected route: ActivatedRoute, protected router: Router) { } diff --git a/src/app/submission/import-external/submission-import-external.component.html b/src/app/submission/import-external/submission-import-external.component.html index bee5f5d872..3c7ed3cd64 100644 --- a/src/app/submission/import-external/submission-import-external.component.html +++ b/src/app/submission/import-external/submission-import-external.component.html @@ -20,8 +20,7 @@ [context]="context" [importable]="true" [importConfig]="importConfig" - (importObject)="import($event)" - (pageChange)="paginationChange();"> + (importObject)="import($event)"> diff --git a/src/app/submission/import-external/submission-import-external.component.ts b/src/app/submission/import-external/submission-import-external.component.ts index 7a7ec2a4a3..d8801880dc 100644 --- a/src/app/submission/import-external/submission-import-external.component.ts +++ b/src/app/submission/import-external/submission-import-external.component.ts @@ -2,7 +2,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { BehaviorSubject, combineLatest, Subscription } from 'rxjs'; -import { filter, mergeMap, take } from 'rxjs/operators'; +import { filter, mergeMap, take, tap } from 'rxjs/operators'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { ExternalSourceService } from '../../core/data/external-source.service'; @@ -20,6 +20,7 @@ import { fadeIn } from '../../shared/animations/fade'; import { PageInfo } from '../../core/shared/page-info.model'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { getFinishedRemoteData } from '../../core/shared/operators'; +import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model'; /** * This component allows to submit a new workspaceitem importing the data from an external source. @@ -44,6 +45,8 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { * TRUE if the REST service is called to retrieve the external source items */ public isLoading$: BehaviorSubject = new BehaviorSubject(false); + + public reload$: BehaviorSubject<{query: string, source: string}>; /** * Configuration to use for the import buttons */ @@ -64,7 +67,7 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { * The initial pagination options */ public initialPagination = Object.assign(new PaginationComponentOptions(), { - id: 'submission-external-source-relation-list', + id: 'spc', pageSize: 10 }); /** @@ -119,6 +122,7 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { ]).pipe( take(1) ).subscribe(([source, query]: [string, string]) => { + this.reload$ = new BehaviorSubject<{query: string; source: string}>({query: query, source: source}); this.retrieveExternalSources(source, query); })); } @@ -133,7 +137,7 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { queryParams: { source: event.sourceId, query: event.query }, replaceUrl: true } - ).then(() => this.retrieveExternalSources(event.sourceId, event.query)); + ).then(() => this.reload$.next({source: event.sourceId, query: event.query})); } /** @@ -148,13 +152,6 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { modalComp.externalSourceEntry = entry; } - /** - * Retrieve external sources on pagination change - */ - paginationChange() { - this.retrieveExternalSources(this.routeData.sourceId, this.routeData.query); - } - /** * Unsubscribe from all subscriptions */ @@ -170,26 +167,27 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy { * @param source The source tupe * @param query The query string to search */ - private retrieveExternalSources(source: string, query: string): void { - if (isNotEmpty(source) && isNotEmpty(query)) { - this.routeData.sourceId = source; - this.routeData.query = query; - this.isLoading$.next(true); - this.subs.push( - this.searchConfigService.paginatedSearchOptions.pipe( - filter((searchOptions) => searchOptions.query === query), - take(1), - mergeMap((searchOptions) => this.externalService.getExternalSourceEntries(this.routeData.sourceId, searchOptions).pipe( - getFinishedRemoteData(), - take(1) - )), - take(1) - ).subscribe((rdData) => { - this.entriesRD$.next(rdData); - this.isLoading$.next(false); - }) - ); - } + private retrieveExternalSources(sourcesss: string, querysss: string): void { + this.reload$.subscribe((sourceQueryObject: {source: string, query: string}) => { + const source = sourceQueryObject.source; + const query = sourceQueryObject.query; + if (isNotEmpty(source) && isNotEmpty(query)) { + this.routeData.sourceId = source; + this.routeData.query = query; + this.isLoading$.next(true); + this.subs.push( + this.searchConfigService.paginatedSearchOptions.pipe( + filter((searchOptions) => searchOptions.query === query), + mergeMap((searchOptions) => this.externalService.getExternalSourceEntries(this.routeData.sourceId, searchOptions).pipe( + getFinishedRemoteData(), + )), + ).subscribe((rdData) => { + this.entriesRD$.next(rdData); + this.isLoading$.next(false); + }) + ); + } + }); } }