File

src/lib/paginator/paginator.component.ts

Description

Component used for pagination of data ./../examples/index.html#/paginator

Implements

OnInit OnChanges OnDestroy IFilterPub

Metadata

changeDetection ChangeDetectionStrategy.OnPush
encapsulation ViewEncapsulation.None
host {
}
providers PopupContainerService
selector nui-paginator
styleUrls ./paginator.component.less
templateUrl ./paginator.component.html

Index

Properties
Methods
Inputs
Outputs
Accessors

Constructor

Public constructor(logger: LoggerService, popupContainer: PopupContainerService, cd: ChangeDetectorRef)
Parameters :
Name Type Optional
logger LoggerService No
popupContainer PopupContainerService No
cd ChangeDetectorRef No

Inputs

activeClass
Default value : "active"

Paginator active item class

adjacent
Default value : 1

Number of items displayed before separator

appendToBody
Type : boolean

inner nui-select appendToBody input

disabledClass
Default value : "disabled"

Paginator disabled item class

dots
Default value : "..."

Paginator separator symbol.

hide
Type : boolean

Make paginator hidden

hideIfEmpty
Default value : false

Hide paginator if all items of data can be displayed on one page

itemsList
Type : Array<IPaginatorItem>
Default value : []
maxElements
Default value : this.adjacent * 2 + 5

Maximum number of items in paginator

page
Default value : 1

Current page number

pageSize
Type : number

Size of the page. For example: 10, 25, 50, 100;

pageSizeSet
Type : number[]
Default value : []

Array of page numbers

popupBaseElementSelector
Type : string

Popup parent element css class used determining of popup direction

popupDirectionTop
Default value : false

Display popup above paginator if equals to 'true'

showPrevNext
Default value : true

Show previous and next buttons

total
Default value : 0

Total number of items

Outputs

pageChange
Type : EventEmitter<number>
pagerAction
Type : EventEmitter

Action occurs on page change

Methods

Public getFilters
getFilters()
Returns : IFilter<IRange<number>>
Public getFirstItemOnPage
getFirstItemOnPage()

Get sequence number of first item of currently displayed paginated list

Returns : number
Public getLastItemOnPage
getLastItemOnPage()

Get sequence number of last item of currently displayed paginated repeat

Returns : number | undefined
Public getPageCount
getPageCount()

Get number of pages

Returns : any
Public getRange
getRange(total: number)

Return range for info section

Parameters :
Name Type Optional
total number No
Returns : string
Public goToPage
goToPage(page: number)

Change page number

Parameters :
Name Type Optional Description
page number No

Page number

Returns : void
Public handleDotsClick
handleDotsClick()

Re-renders the virtual scroll viewport to properly display items within the virtual scroll viewport

Returns : void
Public initPageSizeSet
initPageSizeSet()

Initialize set of pages

Returns : void
Public onPageSizeChange
onPageSizeChange(value: InputValueTypes)
Parameters :
Name Type Optional
value InputValueTypes No
Returns : void
Public resetFilter
resetFilter()
Returns : void
Public setItemsPerPage
setItemsPerPage(changedEvent: ISelectChangedEvent)

Set items per page that should displayed

Parameters :
Name Type Optional Description
changedEvent ISelectChangedEvent<number> No

select change event

Returns : void
Public showPaginator
showPaginator()

Display paginator component

Returns : boolean

Properties

Public onDestroy$
Default value : new Subject<void>()

Accessors

dotsPagesPerRow
getdotsPagesPerRow()
import { CdkVirtualScrollViewport } from "@angular/cdk/scrolling";
import {
    ChangeDetectionStrategy,
    ChangeDetectorRef,
    Component,
    EventEmitter,
    Input,
    OnChanges,
    OnDestroy,
    OnInit,
    Output,
    SimpleChanges,
    ViewChild,
    ViewEncapsulation,
} from "@angular/core";
import _chunk from "lodash/chunk";
import _clone from "lodash/clone";
import _get from "lodash/get";
import _includes from "lodash/includes";
import _min from "lodash/min";
import _range from "lodash/range";
import {Subject} from "rxjs";

import {IFilter, IFilterPub, IRange} from "../../services/data-source/public-api";
import {LoggerService} from "../../services/log-service";
import { PopupContainerService } from "../popup/popup-container.service";
import { SelectComponent } from "../select";
import { InputValueTypes } from "../select-v2/types";
import { ISelectChangedEvent } from "../select/public-api";

import { IPaginatorItem } from "./public-api";

export const defaultPageSizeSet = [10, 25, 50, 100];
const singleSymbolWidth = 8;
const singleCellPaddings = 12;
const containerPaddingsWithScroll = 37;

/**
 * Component used for pagination of data
 * <example-url>./../examples/index.html#/paginator</example-url>
 */
@Component({
    selector: "nui-paginator",
    templateUrl: "./paginator.component.html",
    changeDetection: ChangeDetectionStrategy.OnPush,
    styleUrls: ["./paginator.component.less"],
    encapsulation: ViewEncapsulation.None,
    providers: [PopupContainerService],
    host: { "role": "navigation" },
})

export class PaginatorComponent implements OnInit, OnChanges, OnDestroy, IFilterPub {
    @Input() public itemsList: Array<IPaginatorItem> = [];
    /**
     * Current page number
     */
    @Input() public page = 1;
    /**
     * Size of the page. For example: 10, 25, 50, 100;
     */
    @Input() public pageSize: number;
    /**
     * Array of page numbers
     */
    @Input() public pageSizeSet: number[] = [];
    /**
     * Total number of items
     */
    @Input() public total = 0;
    /**
     * Paginator separator symbol.
     */
    @Input() public dots = "...";
    /**
     * Hide paginator if all items of data can be displayed on one page
     */
    @Input() public hideIfEmpty = false;
    /**
     * Make paginator hidden
     */
    @Input() public hide: boolean;
    /**
     * Paginator active item class
     */
    @Input() public activeClass = "active";
    /**
     * Paginator disabled item class
     */
    @Input() public disabledClass = "disabled";
    /**
     * Number of items displayed before separator
     */
    @Input() public adjacent = 1;
    /**
     * Maximum number of items in paginator
     */
    @Input() public maxElements = this.adjacent * 2 + 5;
    /**
     * Show previous and next buttons
     */
    @Input() public showPrevNext = true;
    /**
     * Display popup above paginator if equals to 'true'
     */
    @Input() public popupDirectionTop = false;
    /**
     * Popup parent element css class used determining of popup direction
     */
    @Input() public popupBaseElementSelector: string;
    /**
     * inner nui-select appendToBody input
     */
    @Input() public appendToBody: boolean;
    /**
     * Action occurs on page change
     */
    @Output() public pagerAction = new EventEmitter<any>();

    @Output() pageChange: EventEmitter<number> = new EventEmitter<number>();

    public onDestroy$ = new Subject<void>();

    @ViewChild("select")
    private selectComponent: SelectComponent;

    @ViewChild(CdkVirtualScrollViewport) private virtualScrollViewport: CdkVirtualScrollViewport;

    private _dotsPagesPerRow = 5;

    public get dotsPagesPerRow(): number {
        return this._dotsPagesPerRow;
    }
    private mainRangeStart: number;
    private mainRangeEnd: number;

    public constructor(
        private logger: LoggerService,
        private popupContainer: PopupContainerService,
        private cd: ChangeDetectorRef
    ) {
        this.popupContainer.parent = this;
    }

    /**
    * Component initialization
    */
    ngOnInit(): void {
        this.initPageSizeSet();
        const pageCount = this.getPageCount();
        if (this.page > pageCount) {
            this.page = pageCount;
            this.pageChange.emit(this.page);
        }
        if (this.page <= 0) {
            this.page = 1;
            this.pageChange.emit(this.page);
        }
    }

    /**
     * Redraw component when 'total' or 'page' propery was changed
     * @param changes Changed properties
     */
    ngOnChanges(changes: SimpleChanges) {
        if (changes["total"]
            || changes["page"]
            || changes["adjacent"]) {
            this.assemble();
        }
    }

    /**
     * Initialize set of pages
     */
    public initPageSizeSet() {
        if (!_get(this.pageSizeSet, "length")) {
            this.pageSizeSet = _clone(defaultPageSizeSet);
        }
        if (!this.pageSize || this.pageSize <= 0) {
            this.pageSize = this.pageSizeSet[0];
        }
        if (!_includes(this.pageSizeSet, this.pageSize)) {
            this.logger.warn(`pageSize ${this.pageSize} not found in the current pageSizeSet ${this.pageSizeSet}. pageSize will be set to
${this.pageSizeSet[0]}. To set the desired initial page size, include it as part of the paginator's pageSizeSet input.`);
            this.pageSize = this.pageSizeSet[0];
        }
    }

    public getFilters(): IFilter<IRange<number>> {
        return {
            type: "range",
            value: {
                start: (this.page - 1) * this.pageSize,
                end: this.page * this.pageSize,
            },
        };
    }

    public resetFilter() {
        this.page = 1;
        this.pageChange.emit(this.page);
    }

    /**
     * Change page number
     * @param page Page number
     */
    public goToPage(page: number) {
        this.page = page;
        this.pageChange.emit(this.page);

        if (this.pagerAction) {
            const result = {
                page: this.page,
                pageSize: this.pageSize,
                total: this.total,
                pageSizeSet: this.pageSizeSet,
            };
            this.pagerAction.emit(result);
        }

        this.assemble();
    }

    // TODO: remove in vNext. Needs only for backward compatibility
    /**
     * @deprecated - remove in v12. Needs only for backward compatibility - Removal: NUI-5837
     */
    public onPageSizeChange(value: InputValueTypes): void {
        this.setItemsPerPage({
            oldValue: 0,
            newValue: value as number,
        });
    }

    // TODO: refactor in vNext. Replace ISelectChangedEvent to InputValueTypes - NUI-5837
    /**
     * Set items per page that should displayed
     * @param changedEvent select change event
     */
    public setItemsPerPage(changedEvent: ISelectChangedEvent<number>) {
        if (changedEvent?.newValue === changedEvent?.oldValue) {
            return;
        }

        const newValue = changedEvent?.newValue;

        if (newValue) {
            if (newValue < 1) {
                this.logger.warn("paginator-controller.setItemsPerPage - invalid newValue: " + newValue);
                return;
            }
            this.pageSize = newValue;
        }

        this.goToPage(1);
    }

    /**
     * Get number of pages
     */
    public getPageCount() {
        if (this.total <= 0) {
            return 1;
        }
        return Math.ceil(this.total / this.pageSize);
    }

    /**
     * Display paginator component
     */
    public showPaginator() {
        const count: number = this.getPageCount();
        return isFinite(count) && (this.hideIfEmpty === false || count > 1);
    }

    /**
     * Get sequence number of first item of currently displayed paginated list
    */
    public getFirstItemOnPage(): number {
        return this.pageSize * (this.page - 1) + 1;
    }

    /**
     * Get sequence number of last item of currently displayed paginated repeat
    */
    public getLastItemOnPage(): number | undefined {
        return _min([this.pageSize * this.page, this.total]);
    }

    /**
     * Return range for info section
     */
    public getRange(total: number): string {
        return total > 0 ? `${this.getFirstItemOnPage()}-${this.getLastItemOnPage()}` : "0";
    }

    /**
     * Re-renders the virtual scroll viewport to properly display items within the virtual scroll viewport
     */
    public handleDotsClick() {
        if (this.virtualScrollViewport) {
            this.virtualScrollViewport.checkViewportSize();
        }
    }

    /**
     * Add items to paginator component
     */
    private assemble() {
        this.itemsList = [];
        if (this.getPageCount() <= 0) {
            return;
        }

        const pageCount = this.getPageCount();

        this.prepareSeparators();

        this.addPrev();

        if (this.mainRangeStart !== 1) {
            // add starting separator and the first page
            this.add(1);
            this.addSeparator(2, this.mainRangeStart - 1);
        }

        this.addRange(this.mainRangeStart, this.mainRangeEnd);

        if (this.mainRangeEnd !== pageCount) {
            // add ending separator and the last page
            this.addSeparator(this.mainRangeEnd + 1, pageCount - 1);
            this.add(pageCount);
        }

        this.addNext();
        this.cd.detectChanges();
    }

    /**
     * Fills mainRangeStart and mainRangeEnd properties with proper values. Those properties
     * are used for displaying of page numbers between 'dots' separators.
     */
    private prepareSeparators() {
        // the only 2 variables that are needed to display everything are:
        //      mainRangeStart      mainRangeEnd
        //            |                   |
        //  < 1  ... 100  101  102  103  104 >
        const pageCount = this.getPageCount();
        const page = +this.page;
        const adjacent = +this.adjacent;

        // case when there are few items
        if (pageCount <= this.maxElements) {
            this.mainRangeStart = 1;
            this.mainRangeEnd = pageCount;
            return;
        }

        // set main range to
        this.mainRangeStart = (page - adjacent < 1) ? 1 : page - adjacent;
        this.mainRangeEnd = (page + adjacent > pageCount) ? pageCount : page + adjacent;

        // case where starting separator is not shown
        if ((this.mainRangeStart - 1) // end of possible starting separator
            - 2 // start of possible starting separator
            < 1) {
            this.mainRangeStart = 1;
        }

        // case where ending separator is not shown
        if ((pageCount - 1) // end of possible ending separator
            - (this.mainRangeEnd + 1) // start of possible ending separator
            < 1) {
            this.mainRangeEnd = pageCount;
        }

        // case where we have one of the first pages selected and the ending separator
        if (this.mainRangeEnd !== pageCount && page < this.maxElements - 2 - adjacent) {
            this.mainRangeStart = 1;
            this.mainRangeEnd = this.mainRangeStart + this.maxElements - 3;
        }

        // case where we have one of the last pages selected and the starting separator
        if (this.mainRangeStart !== 1 && page > pageCount - this.maxElements + 2 + adjacent) {
            this.mainRangeEnd = pageCount;
            this.mainRangeStart = this.mainRangeEnd - this.maxElements + 3;
        }
    }

    private addSeparator(from: number, to: number) {
        const pageRows = _chunk(_range(from, to + 1), this._dotsPagesPerRow);
        this.itemsList.push({
            title: $localize `Pages ${from} - ${to}`,
            value: this.dots,
            pageRows: pageRows,
            popupWidth: (to.toString().length * singleSymbolWidth + singleCellPaddings)
                * this._dotsPagesPerRow + containerPaddingsWithScroll,  // each popup will have its own width
            useVirtualScroll: pageRows.length * this._dotsPagesPerRow >= 1000,
        });
    }

    private addPrev() {
        const pageCount = this.getPageCount();
        if (!this.showPrevNext || pageCount < 1) {
            return;
        }

        const prevBtn = {
            iconName: "caret-left",
            title: $localize `Previous Page`,
            page: this.page - 1 <= 0 ? 1 : this.page - 1,
        };
        const isDisabled = this.page - 1 <= 0;
        this.addItem(prevBtn, isDisabled);
    }

    private addNext() {
        const pageCount = this.getPageCount();
        if (!this.showPrevNext || pageCount < 1) {
            return;
        }

        const nextBtn = {
            iconName: "caret-right",
            title: $localize `Next Page`,
            page: this.page + 1 >= pageCount ? pageCount : this.page + 1,
        };
        const isDisabled = this.page + 1 > pageCount;
        this.addItem(nextBtn, isDisabled);
    }

    private addItem(item: IPaginatorItem, isDisabled: boolean) {
        this.itemsList.push({
            iconName: item.iconName,
            value: item.value,
            title: item.title,
            style: isDisabled ? this.disabledClass : "",
            action: () => {
                if (item.page) {
                    this.goToPage(item.page);
                }
                return false;
            },
        });
    }

    private add(page: number) {
        const inst = this;
        this.itemsList.push({
            value: page,
            title: $localize `Page ${page}`,
            style: this.page === page ? this.activeClass : "",
            action: function () {
                inst.goToPage(this.value);
                return false;
            },
        });
    }

    private addRange(start: number, finish: number) {
        for (let index = start; index <= finish; index++) {
            this.add(index);
        }
    }

    ngOnDestroy(): void {
        this.onDestroy$.next();
        this.onDestroy$.complete();
    }
}

result-matching ""

    No results matching ""