mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
441 lines
12 KiB
TypeScript
441 lines
12 KiB
TypeScript
import {
|
|
AsyncPipe,
|
|
NgClass,
|
|
NgFor,
|
|
NgIf,
|
|
} from '@angular/common';
|
|
import {
|
|
ChangeDetectionStrategy,
|
|
ChangeDetectorRef,
|
|
Component,
|
|
EventEmitter,
|
|
Input,
|
|
OnChanges,
|
|
OnDestroy,
|
|
OnInit,
|
|
Output,
|
|
SimpleChanges,
|
|
ViewEncapsulation,
|
|
} from '@angular/core';
|
|
import {
|
|
NgbDropdownModule,
|
|
NgbPaginationModule,
|
|
NgbTooltipModule,
|
|
} from '@ng-bootstrap/ng-bootstrap';
|
|
import { TranslateModule } from '@ngx-translate/core';
|
|
import {
|
|
Observable,
|
|
of as observableOf,
|
|
Subscription,
|
|
} from 'rxjs';
|
|
import {
|
|
map,
|
|
startWith,
|
|
switchMap,
|
|
take,
|
|
} from 'rxjs/operators';
|
|
|
|
import {
|
|
SortDirection,
|
|
SortOptions,
|
|
} from '../../core/cache/models/sort-options.model';
|
|
import { PaginatedList } from '../../core/data/paginated-list.model';
|
|
import { RemoteData } from '../../core/data/remote-data';
|
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
|
import { PaginationRouteParams } from '../../core/pagination/pagination-route-params.interface';
|
|
import { ViewMode } from '../../core/shared/view-mode.model';
|
|
import {
|
|
hasValue,
|
|
hasValueOperator,
|
|
} from '../empty.util';
|
|
import { HostWindowService } from '../host-window.service';
|
|
import { ListableObject } from '../object-collection/shared/listable-object.model';
|
|
import { RSSComponent } from '../rss-feed/rss.component';
|
|
import { EnumKeysPipe } from '../utils/enum-keys-pipe';
|
|
import { PaginationComponentOptions } from './pagination-component-options.model';
|
|
|
|
interface PaginationDetails {
|
|
range: string;
|
|
total: number;
|
|
}
|
|
|
|
/**
|
|
* The default pagination controls component.
|
|
*/
|
|
@Component({
|
|
exportAs: 'paginationComponent',
|
|
selector: 'ds-pagination',
|
|
styleUrls: ['pagination.component.scss'],
|
|
templateUrl: 'pagination.component.html',
|
|
changeDetection: ChangeDetectionStrategy.Default,
|
|
encapsulation: ViewEncapsulation.Emulated,
|
|
standalone: true,
|
|
imports: [NgIf, NgbDropdownModule, NgFor, NgClass, RSSComponent, NgbPaginationModule, NgbTooltipModule, AsyncPipe, TranslateModule, EnumKeysPipe],
|
|
})
|
|
export class PaginationComponent implements OnChanges, OnDestroy, OnInit {
|
|
/**
|
|
* ViewMode that should be passed to {@link ListableObjectComponentLoaderComponent}.
|
|
*/
|
|
viewMode: ViewMode = ViewMode.ListElement;
|
|
|
|
/**
|
|
* Number of items in collection.
|
|
*/
|
|
@Input() collectionSize: number;
|
|
|
|
/**
|
|
* Configuration for the NgbPagination component.
|
|
*/
|
|
@Input() paginationOptions: PaginationComponentOptions;
|
|
|
|
/**
|
|
* Sort configuration for this component.
|
|
*/
|
|
@Input() sortOptions: SortOptions;
|
|
|
|
/**
|
|
* Whether or not the pagination should be rendered as simple previous and next buttons instead of the normal pagination
|
|
*/
|
|
@Input() showPaginator = true;
|
|
|
|
/**
|
|
* The current pagination configuration
|
|
*/
|
|
@Input() config?: PaginationComponentOptions;
|
|
|
|
/**
|
|
* The list of listable objects to render in this component
|
|
*/
|
|
@Input() objects: RemoteData<PaginatedList<ListableObject>>;
|
|
|
|
/**
|
|
* The current sorting configuration
|
|
*/
|
|
@Input() sortConfig: SortOptions;
|
|
|
|
/**
|
|
* An event fired when the page is changed.
|
|
* Event's payload equals to the newly selected page.
|
|
*/
|
|
@Output() pageChange: EventEmitter<number> = new EventEmitter<number>();
|
|
|
|
/**
|
|
* An event fired when the page wsize is changed.
|
|
* Event's payload equals to the newly selected page size.
|
|
*/
|
|
@Output() pageSizeChange: EventEmitter<number> = new EventEmitter<number>();
|
|
|
|
/**
|
|
* An event fired when the sort direction is changed.
|
|
* Event's payload equals to the newly selected sort direction.
|
|
*/
|
|
@Output() sortDirectionChange: EventEmitter<SortDirection> = new EventEmitter<SortDirection>();
|
|
|
|
/**
|
|
* An event fired when the sort field is changed.
|
|
* Event's payload equals to the newly selected sort field.
|
|
*/
|
|
@Output() sortFieldChange: EventEmitter<string> = new EventEmitter<string>();
|
|
|
|
/**
|
|
* An event fired when the pagination is changed.
|
|
* Event's payload equals to the newly selected sort field.
|
|
*/
|
|
@Output() paginationChange: EventEmitter<any> = new EventEmitter<any>();
|
|
|
|
/**
|
|
* Option for hiding the pagination detail
|
|
*/
|
|
@Input() public hidePaginationDetail = false;
|
|
|
|
/**
|
|
* Option for hiding the gear
|
|
*/
|
|
@Input() public hideGear = false;
|
|
|
|
/**
|
|
* Option for hiding the gear
|
|
*/
|
|
@Input() public hideSortOptions = false;
|
|
|
|
/**
|
|
* Option for hiding the pager when there is less than 2 pages
|
|
*/
|
|
@Input() public hidePagerWhenSinglePage = true;
|
|
|
|
/**
|
|
* Option for retaining the scroll position upon navigating to an url with updated params.
|
|
* After the page update the page will scroll back to the current pagination component.
|
|
*/
|
|
@Input() public retainScrollPosition = false;
|
|
|
|
/**
|
|
* Current page.
|
|
*/
|
|
public currentPage$: Observable<number>;
|
|
|
|
/**
|
|
* Current page in the state of a Remote paginated objects.
|
|
*/
|
|
public currentPageState: number = undefined;
|
|
|
|
/**
|
|
* ID for the pagination instance. This ID is used in the routing to retrieve the pagination options.
|
|
* This ID needs to be unique between different pagination components when more than one will be displayed on the same page.
|
|
*/
|
|
public id: string;
|
|
|
|
/**
|
|
* A boolean that indicate if is an extra small devices viewport.
|
|
*/
|
|
public isXs: boolean;
|
|
|
|
/**
|
|
* Number of items per page.
|
|
*/
|
|
public pageSize$: Observable<number>;
|
|
|
|
/**
|
|
* Declare SortDirection enumeration to use it in the template
|
|
*/
|
|
public sortDirections = SortDirection;
|
|
|
|
/**
|
|
* A number array that represents options for a context pagination limit.
|
|
*/
|
|
public pageSizeOptions: number[];
|
|
|
|
/**
|
|
* Direction in which to sort: ascending or descending
|
|
*/
|
|
public sortDirection$: Observable<SortDirection>;
|
|
public defaultsortDirection: SortDirection = SortDirection.ASC;
|
|
|
|
/**
|
|
* Name of the field that's used to sort by
|
|
*/
|
|
public sortField$: Observable<string>;
|
|
public defaultSortField = 'name';
|
|
|
|
|
|
public showingDetails$: Observable<PaginationDetails>;
|
|
|
|
/**
|
|
* Array to track all subscriptions and unsubscribe them onDestroy
|
|
* @type {Array}
|
|
*/
|
|
private subs: Subscription[] = [];
|
|
|
|
/**
|
|
* If showPaginator is set to true, emit when the previous button is clicked
|
|
*/
|
|
@Output() prev = new EventEmitter<boolean>();
|
|
|
|
/**
|
|
* If showPaginator is set to true, emit when the next button is clicked
|
|
*/
|
|
@Output() next = new EventEmitter<boolean>();
|
|
/**
|
|
* Method provided by Angular. Invoked after the constructor.
|
|
*/
|
|
ngOnInit() {
|
|
this.subs.push(this.hostWindowService.isXs()
|
|
.subscribe((status: boolean) => {
|
|
this.isXs = status;
|
|
this.cdRef.markForCheck();
|
|
}));
|
|
this.checkConfig(this.paginationOptions);
|
|
this.initializeConfig();
|
|
}
|
|
|
|
ngOnChanges(changes: SimpleChanges): void {
|
|
if (changes.collectionSize.currentValue !== changes.collectionSize.previousValue) {
|
|
this.showingDetails$ = this.getShowingDetails(this.collectionSize);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Method provided by Angular. Invoked when the instance is destroyed.
|
|
*/
|
|
ngOnDestroy() {
|
|
this.subs
|
|
.filter((sub) => hasValue(sub))
|
|
.forEach((sub) => sub.unsubscribe());
|
|
}
|
|
|
|
/**
|
|
* Initializes all default variables
|
|
*/
|
|
private initializeConfig() {
|
|
// Set initial values
|
|
this.id = this.paginationOptions.id || null;
|
|
this.pageSizeOptions = this.paginationOptions.pageSizeOptions;
|
|
this.currentPage$ = this.paginationService.getCurrentPagination(this.id, this.paginationOptions).pipe(
|
|
map((currentPagination) => currentPagination.currentPage),
|
|
);
|
|
this.pageSize$ = this.paginationService.getCurrentPagination(this.id, this.paginationOptions).pipe(
|
|
map((currentPagination) => currentPagination.pageSize),
|
|
);
|
|
|
|
let sortOptions: SortOptions;
|
|
if (this.sortOptions) {
|
|
sortOptions = this.sortOptions;
|
|
} else {
|
|
sortOptions = new SortOptions(this.defaultSortField, this.defaultsortDirection);
|
|
}
|
|
this.sortDirection$ = this.paginationService.getCurrentSort(this.id, sortOptions).pipe(
|
|
map((currentSort) => currentSort.direction),
|
|
);
|
|
this.sortField$ = this.paginationService.getCurrentSort(this.id, sortOptions).pipe(
|
|
map((currentSort) => currentSort.field),
|
|
);
|
|
}
|
|
|
|
constructor(
|
|
protected cdRef: ChangeDetectorRef,
|
|
protected paginationService: PaginationService,
|
|
public hostWindowService: HostWindowService,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Method to change the route to the given page
|
|
*
|
|
* @param page
|
|
* The page being navigated to.
|
|
*/
|
|
public doPageChange(page: number) {
|
|
this.updateParams({ page: page });
|
|
this.emitPaginationChange();
|
|
}
|
|
|
|
/**
|
|
* Method to change the route to the given page size
|
|
*
|
|
* @param pageSize
|
|
* The page size being navigated to.
|
|
*/
|
|
public doPageSizeChange(pageSize: number) {
|
|
this.updateParams({ page: 1, pageSize: pageSize });
|
|
this.emitPaginationChange();
|
|
}
|
|
|
|
/**
|
|
* Method to change the route to the given sort direction
|
|
*
|
|
* @param sortDirection
|
|
* The sort direction being navigated to.
|
|
*/
|
|
public doSortDirectionChange(sortDirection: SortDirection) {
|
|
this.updateParams({ page: 1, sortDirection: sortDirection });
|
|
this.emitPaginationChange();
|
|
}
|
|
|
|
/**
|
|
* Method to emit a general pagination change event
|
|
*/
|
|
private emitPaginationChange() {
|
|
this.paginationChange.emit();
|
|
}
|
|
|
|
/**
|
|
* Update the current query params and optionally update the route
|
|
* @param params
|
|
*/
|
|
private updateParams(params: PaginationRouteParams) {
|
|
this.paginationService.updateRoute(this.id, params, {}, this.retainScrollPosition);
|
|
}
|
|
|
|
/**
|
|
* Method to get pagination details of the current viewed page.
|
|
*/
|
|
public getShowingDetails(collectionSize: number): Observable<PaginationDetails> {
|
|
return observableOf(collectionSize).pipe(
|
|
hasValueOperator(),
|
|
switchMap(() => this.paginationService.getCurrentPagination(this.id, this.paginationOptions)),
|
|
map((currentPaginationOptions) => {
|
|
let lastItem: number;
|
|
const pageMax = currentPaginationOptions.pageSize * currentPaginationOptions.currentPage;
|
|
|
|
const firstItem: number = currentPaginationOptions.pageSize * (currentPaginationOptions.currentPage - 1) + 1;
|
|
if (collectionSize > pageMax) {
|
|
lastItem = pageMax;
|
|
} else {
|
|
lastItem = collectionSize;
|
|
}
|
|
return {
|
|
range: `${firstItem} - ${lastItem}`,
|
|
total: collectionSize,
|
|
};
|
|
}),
|
|
startWith({
|
|
range: `${null} - ${null}`,
|
|
total: null,
|
|
}),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Method to ensure options passed contains the required properties.
|
|
*
|
|
* @param paginateOptions
|
|
* The paginate options object.
|
|
*/
|
|
private checkConfig(paginateOptions: any) {
|
|
const required = ['id', 'currentPage', 'pageSize', 'pageSizeOptions'];
|
|
const missing = required.filter((prop) => {
|
|
return !(prop in paginateOptions);
|
|
});
|
|
if (0 < missing.length) {
|
|
throw new Error('Paginate: Argument is missing the following required properties: ' + missing.join(', '));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Property to check whether the current pagination object has multiple pages
|
|
* @returns true if there are multiple pages, else returns false
|
|
*/
|
|
get hasMultiplePages(): Observable<boolean> {
|
|
return this.paginationService.getCurrentPagination(this.id, this.paginationOptions).pipe(
|
|
map((currentPaginationOptions) => this.collectionSize > currentPaginationOptions.pageSize),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Property to check whether the current pagination should show a bottom pages
|
|
* @returns true if a bottom pages should be shown, else returns false
|
|
*/
|
|
get shouldShowBottomPager(): Observable<boolean> {
|
|
return this.hasMultiplePages.pipe(
|
|
map((hasMultiplePages) => hasMultiplePages || !this.hidePagerWhenSinglePage),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Go to the previous page
|
|
*/
|
|
goPrev() {
|
|
this.prev.emit(true);
|
|
this.updatePagination(-1);
|
|
}
|
|
|
|
/**
|
|
* Go to the next page
|
|
*/
|
|
goNext() {
|
|
this.next.emit(true);
|
|
this.updatePagination(1);
|
|
}
|
|
|
|
/**
|
|
* Update page when next or prev button is clicked
|
|
* @param value
|
|
*/
|
|
updatePagination(value: number) {
|
|
this.paginationService.getCurrentPagination(this.id, this.paginationOptions).pipe(take(1)).subscribe((currentPaginationOptions) => {
|
|
this.updateParams({ page: (currentPaginationOptions.currentPage + value) });
|
|
});
|
|
}
|
|
|
|
}
|