import { BindingEventService, BindingHelper } from '@slickgrid-universal/binding'; import type { BasePaginationComponent, GridOption, Locale, PaginationMetadata, PaginationService, PubSubService, SlickGrid, Subscription, TranslaterService, } from '@slickgrid-universal/common'; import { Constants, createDomElement, emptyElement, getTranslationPrefix } from '@slickgrid-universal/common'; export class SlickPaginationComponent implements BasePaginationComponent { protected _bindingHelper: BindingHelper; protected _bindingEventService: BindingEventService; protected _paginationElement!: HTMLDivElement; protected _enableTranslate = false; protected _grid!: SlickGrid; protected _gridContainerElm?: HTMLElement; protected _itemPerPageElm!: HTMLSelectElement; protected _spanInfoFromToElm!: HTMLSpanElement; protected _seekFirstElm!: HTMLLIElement; protected _seekPrevElm!: HTMLLIElement; protected _seekNextElm!: HTMLLIElement; protected _seekEndElm!: HTMLLIElement; protected _subscriptions: Subscription[] = []; protected _paginationService!: PaginationService; protected _pubSubService!: PubSubService; protected _translaterService?: TranslaterService; currentPagination = {} as PaginationMetadata; firstButtonClasses = ''; lastButtonClasses = ''; prevButtonClasses = ''; nextButtonClasses = ''; // text translations (handled by i18n or by custom locale) textItemsPerPage = 'items per page'; textItems = 'items'; textOf = 'of'; textPage = 'Page'; constructor() { this._bindingHelper = new BindingHelper(); this._bindingEventService = new BindingEventService(); } get availablePageSizes(): number[] { return this._paginationService.availablePageSizes || []; } get dataFrom(): number { return this._paginationService.dataFrom; } get dataTo(): number { return this._paginationService.dataTo; } get itemsPerPage(): number { return this._paginationService.itemsPerPage; } set itemsPerPage(count: number) { this._paginationService.changeItemPerPage(count); } get pageCount(): number { return this._paginationService.pageCount; } get pageNumber(): number { return this._paginationService.pageNumber; } /** Getter for the Grid Options pulled through the Grid Object */ get gridOptions(): GridOption { return this._grid?.getOptions() ?? {}; } get gridUid(): string { return this._grid?.getUID() || ''; } get locales(): Locale { // get locales provided by user in main file or else use default English locales via the Constants return this.gridOptions?.locales ?? Constants.locales; } get totalItems(): number { return this._paginationService.totalItems; } get isLeftPaginationDisabled(): boolean { return this.pageNumber === 1 || this.totalItems === 0; } get isRightPaginationDisabled(): boolean { return this.pageNumber === this.pageCount || this.totalItems === 0; } init( grid: SlickGrid, paginationService: PaginationService, pubSubService: PubSubService, translaterService?: TranslaterService | undefined ): void { this._grid = grid; this._pubSubService = pubSubService; this._translaterService = translaterService; this._paginationService = paginationService; this.currentPagination = paginationService.getFullPagination(); this._bindingHelper.querySelectorPrefix = this.gridUid ? `.${this.gridUid} ` : ''; this._enableTranslate = this.gridOptions?.enableTranslate ?? false; if (this._enableTranslate && (!this._translaterService || !this._translaterService.translate)) { throw new Error( '[Slickgrid-Universal] requires a Translate Service to be installed and configured when the grid option "enableTranslate" is enabled.' ); } this.translatePaginationTexts(); if (this._enableTranslate && this._pubSubService?.subscribe) { const translateEventName = this._translaterService?.eventName ?? 'onLanguageChange'; this._subscriptions.push(this._pubSubService.subscribe(translateEventName, () => this.translatePaginationTexts())); } // Anytime the pagination is initialized or has changes, // we'll copy the data into a local object so that we can add binding to this local object this._subscriptions.push( this._pubSubService.subscribe('onPaginationRefreshed', (paginationChanges) => { Object.keys(paginationChanges).forEach( (key) => ((this.currentPagination as any)[key] = paginationChanges[key as keyof PaginationMetadata]) ); this.updatePageButtonsUsability(); if (this._spanInfoFromToElm?.style) { this._spanInfoFromToElm.style.display = this.currentPagination.totalItems === 0 ? 'none' : ''; } }), this._pubSubService.subscribe('onPaginationSetCursorBased', () => { this.disposeDom(); // recreate pagination component, probably only used for GraphQL E2E tests this.renderPagination(this._gridContainerElm!); }) ); } /** dispose of all Subscriptions, DOM element & bindings */ dispose(): void { this._pubSubService.unsubscribeAll(this._subscriptions); this.disposeDom(); } /** dispose of the DOM element & bindings only (regular PubSub subscription will be preserved) */ disposeDom(): void { this._bindingEventService.unbindAll(); this._bindingHelper.dispose(); emptyElement(this._paginationElement); this._paginationElement.remove(); } /** render the Pagination in the DOM with all events & bindings */ renderPagination(containerElm: HTMLElement): void { this._gridContainerElm = containerElm; const paginationElm = this.createPaginationContainer(); const divNavContainerElm = createDomElement('div', { className: 'slick-pagination-nav' }); // left nav const leftNavElm = createDomElement('nav', { ariaLabel: 'Page navigation' }); const leftUlElm = createDomElement('ul', { className: 'pagination' }, leftNavElm); this._seekFirstElm = createDomElement('li', { className: 'page-item seek-first' }, leftUlElm); this._seekFirstElm.appendChild( createDomElement('a', { className: 'page-link sgi icon-seek-first', ariaLabel: 'First Page', role: 'button' }) ); this._seekPrevElm = createDomElement('li', { className: 'page-item seek-prev' }, leftUlElm); this._seekPrevElm.appendChild( createDomElement('a', { className: 'page-link sgi icon-seek-prev', ariaLabel: 'Previous Page', role: 'button' }) ); const pageNumberSectionElm = this.createPageNumberSection(); // right nav const rightNavElm = createDomElement('nav', { ariaLabel: 'Page navigation' }); const rightUlElm = createDomElement('ul', { className: 'pagination' }, rightNavElm); this._seekNextElm = createDomElement('li', { className: 'page-item seek-next' }, rightUlElm); this._seekNextElm.appendChild( createDomElement('a', { className: 'page-link sgi icon-seek-next', ariaLabel: 'Next Page', role: 'button' }) ); this._seekEndElm = createDomElement('li', { className: 'page-item seek-end' }, rightUlElm); this._seekEndElm.appendChild( createDomElement('a', { className: 'page-link sgi icon-seek-end', ariaLabel: 'Last Page', role: 'button' }) ); // append both navs to container paginationElm.appendChild(divNavContainerElm); divNavContainerElm.appendChild(leftNavElm); divNavContainerElm.appendChild(pageNumberSectionElm); divNavContainerElm.appendChild(rightNavElm); const paginationSettingsElm = this.createPaginationSettingsSection(); paginationElm.appendChild(divNavContainerElm); paginationElm.appendChild(paginationSettingsElm); this._paginationElement.appendChild(paginationElm); if (containerElm?.appendChild && this._paginationElement) { containerElm.appendChild(this._paginationElement); } this.renderPageSizes(); this.addBindings(); this.addEventListeners(); this.updatePageButtonsUsability(); } /** Render and fill the Page Sizes