import { Pagination as PaginationInterface } from "../public-api/interfaces"; import { Logger, isNaturalNumber } from '../utils/toolbox/src'; import { parseBoolean, parseNumber, PaginationState, DEFAULT_PAGINATION_ROW_HEIGHT } from '../globals/helpers/helpers'; import { PaginationException } from '../../texts/exception'; import GridDataTable from '../data-layer/grid-datatable'; interface ShowPagesConfig { enable?: boolean; showtotal?: boolean; userinput?: boolean; } interface PageSizeConfig { default?: number; options?: boolean | number[]; } interface ConfigurationObject{ enable: boolean; showRecordCount: boolean; showJumpToEndButtons: boolean; showJumpToLastPageButton: boolean; showJumpToFirstPageButton: boolean; showPagesConfig: ShowPagesConfig; pageSizeConfig?: PageSizeConfig; calculatedPageSize: number; appliedPageSize?: number; defaultPageOptions: number[]; calculatedPageOptions?: number[]; currentPage: number; totalPages?: number; } interface InputConfiguration{ pagination: PaginationInterface; currentScreenWidth: number; logger: Logger; dispatchEvent: Function; bodyHeight: number; rowHeight: number; gridDataTable: GridDataTable; paginationHeight: number; stores: any; } interface ObjectTraverser { [propName: string]: any; } // Function that returns new options array till the first greater value than the totalRows const optionsFilterOperation: Function = (totalRows: number, optionsArr: number[], insert: number | undefined): number[] =>{ let firstMax = true; return appendAndSort(optionsArr.filter((pageSize: number)=>{ if (isNaturalNumber(pageSize)){ if (pageSize < totalRows){ return true; } else if (pageSize === totalRows){ firstMax = false; return true; } else if (firstMax){ firstMax = false; return true; } } return false; }), insert); }, // Function tat takes a new number and appends it at the proper position in ascending order appendAndSort: Function = (optionsArr: number[], insert: number | undefined): number[] => { let arrayToMutate: number[] = optionsArr.filter((val, index, self)=>self.indexOf(val) === index); if (!insert){ // trim and return the array return arrayToMutate.filter((val, index, self)=>self.indexOf(val) === index); } // push only if not present arrayToMutate.indexOf(insert) === -1 && arrayToMutate.push(insert); return arrayToMutate.sort((a,b)=>a-b); }; class Pagination{ config: ConfigurationObject; private _extPaginationInput: PaginationInterface; private _dispatchEvent: Function; private _logger: Logger; private _screenWidth: number; private _bodyHeight: number; private _rowHeight: number; private _totalRows: number; private _gridDataTable: GridDataTable; private _stores: any; paginationHeight: number; shouldMutateStore: boolean; constructor(input: InputConfiguration) { this._extPaginationInput = input.pagination; this._dispatchEvent = input.dispatchEvent; this._logger = input.logger; this._screenWidth = input.currentScreenWidth; this._bodyHeight = input.bodyHeight; this._rowHeight = input.rowHeight; this._gridDataTable = input.gridDataTable; this._totalRows = this._gridDataTable.getTotalRowCount(); this.paginationHeight = input.paginationHeight; this.shouldMutateStore = false; this._stores = input.stores; // set all defaults this.config = { enable : false, showRecordCount : false, showJumpToEndButtons : false, showJumpToLastPageButton: false, showJumpToFirstPageButton: false, defaultPageOptions : [10, 20, 50, 100, 200, 500, 1000], showPagesConfig : { enable : false, showtotal : false, userinput : false }, calculatedPageSize : this._calculatePageSize(), currentPage: 1 }; // configure the conponent with external pagination inputs this._configure(); } /** * Method to calculate the default page size based on the grid body height and row height * @returns Number calculated page size */ private _calculatePageSize(): number{ let rowHeight: number = this._rowHeight, bodyHeight: number = this._bodyHeight, numberOfRows: number = bodyHeight / rowHeight, fractionOfRows: number = numberOfRows - Math.floor(numberOfRows), totalRows: number = this._totalRows, rounder: Function = fractionOfRows > 0.4 ? Math.ceil : Math.floor; return Math.min(rounder(numberOfRows), totalRows); } /** * Method to configure the component based on provided external pagination input */ private _configure(): void{ let config: ConfigurationObject = this.config, extPaginationInput: PaginationInterface = this._extPaginationInput, pageSizeConfig: PageSizeConfig, showPagesConfig: ShowPagesConfig = extPaginationInput.showpages || {}, totalRows:number = this._totalRows, options: boolean | number[]; config.enable = parseBoolean(extPaginationInput.enable, config.enable); config.showRecordCount = parseBoolean(extPaginationInput.showrecordcount, config.showRecordCount); config.showJumpToEndButtons = parseBoolean(extPaginationInput.showjumptoendbuttons, config.showJumpToEndButtons); config.showJumpToLastPageButton = parseBoolean(extPaginationInput.showjumptolastpagebutton, config.showJumpToEndButtons); config.showJumpToFirstPageButton = parseBoolean(extPaginationInput.showjumptofirstpagebutton, config.showJumpToEndButtons); config.showPagesConfig!.enable = parseBoolean(showPagesConfig.enable, config.showPagesConfig!.enable); config.showPagesConfig!.showtotal = parseBoolean(showPagesConfig.showtotal, config.showPagesConfig!.showtotal); config.showPagesConfig!.userinput = parseBoolean(showPagesConfig.userinput, config.showPagesConfig!.userinput); config.pageSizeConfig = pageSizeConfig = extPaginationInput.pagesize || {}; // if no page size config is provided then use calculatedPageSize if (!Object.keys(pageSizeConfig).length){ config.appliedPageSize = config.calculatedPageSize; } else if (options = pageSizeConfig.options!){ this.calculatePageOptions(options); } else { config.appliedPageSize = parseNumber(pageSizeConfig['default'], config.calculatedPageSize); } config.totalPages = Math.ceil(totalRows / config.appliedPageSize!); // set the pagination state at initialization this._stores.paginationState.set({ enable: this.config.enable, showPages: { enable: this.config.showPagesConfig.enable!, showTotal: this.config.showPagesConfig.showtotal!, userInput: this.config.showPagesConfig.userinput! }, showRecordCount: this.config.showRecordCount, pageSize: { options: this.config.calculatedPageOptions!, applied: this.config.appliedPageSize! }, showJumptoFirstPage: this.config.showJumpToFirstPageButton, showJumpToLastPage: this.config.showJumpToLastPageButton, currentPage: this.getCurrentPage(), paginationHeight: this.paginationHeight, numRows: this.paginationHeight / DEFAULT_PAGINATION_ROW_HEIGHT, totalPages: config.totalPages, totalRecords: this._totalRows }); this._updateVisibleRecords(); } private _updateVisibleRecords():void{ let currentPage: number = this.getCurrentPage(), pageSize: number = this.getPageSize(), startIndex: number = pageSize * (currentPage - 1), totalRows: number = this._totalRows, enable: boolean = this.config.enable; enable && this._stores.vizRecordDomain.set({ start: startIndex, end: Math.min(startIndex + (pageSize - 1), totalRows - 1) }); } /** * Setter for curret page size * @param size page size * @param recalculateOptions boolean to decide whether to recalculate page options */ setPageSize(size: number, recalculateOptions: boolean = true): void{ let config: ConfigurationObject = this.config, totalRows: number = this._totalRows, previousPageSize: number = config.appliedPageSize!, formattedPageSize: number = Math.floor(+size), options = config.pageSizeConfig!.options; if (config.calculatedPageOptions!.length){ if (options && Array.isArray(options) && options.length){ // if options is present config.appliedPageSize = formattedPageSize; recalculateOptions && (config.calculatedPageOptions = optionsFilterOperation(totalRows, options.sort((a,b)=>a-b), config.appliedPageSize)); if (previousPageSize !== config.appliedPageSize){ this._dispatchEvent.call(this, 'pagesizechanged', { previousPageSize, newPageSize: config.appliedPageSize }); config.currentPage = 1; } } else{ config.appliedPageSize = formattedPageSize; recalculateOptions && (config.calculatedPageOptions = optionsFilterOperation(totalRows, config.defaultPageOptions, config.appliedPageSize)); if (previousPageSize !== config.appliedPageSize){ this._dispatchEvent.call(this, 'pagesizechanged', { previousPageSize, newPageSize: config.appliedPageSize }); config.currentPage = 1; } } } else{ config.appliedPageSize = formattedPageSize; if (previousPageSize !== config.appliedPageSize){ this._dispatchEvent.call(this, 'pagesizechanged', { previousPageSize, newPageSize: config.appliedPageSize }); config.currentPage = 1; } } // calculate new page size config.totalPages = Math.ceil(totalRows / config.appliedPageSize!); this._stores.paginationState.update((stateObj: PaginationState)=> { stateObj.pageSize.applied = config.appliedPageSize!; stateObj.pageSize.options = config.calculatedPageOptions!; stateObj.totalPages = config.totalPages!; stateObj.currentPage = config.currentPage!; return stateObj; }); this._updateVisibleRecords(); } /** * Getter for current page size */ getPageSize(): number{ return this.config.appliedPageSize!; } /** * Getter for current page number */ getCurrentPage(): number{ return this.config.currentPage; } /** * Getter for total pages */ getTotalPages(): number{ return this.config.totalPages!; } /** * Getter for total rows */ getRowCount(): number{ return this._totalRows; } setRowCount(count: any){ let config = this.config; this._totalRows = count; config.totalPages = Math.ceil(count / config.appliedPageSize!); } /** * Method for jumping to a specific page * @param pageNumber Page number */ jumpToPage(pageNumber: number): void{ let config: ConfigurationObject = this.config, totalPages: number = config.totalPages!, previousPage: number = config.currentPage, formattedPageNumber = Math.floor(+pageNumber); if (formattedPageNumber >= 1 && formattedPageNumber <= totalPages){ config.currentPage = formattedPageNumber; } else{ this._logger.error(PaginationException.invalidPageNumber); } this._stores.paginationState.update((stateObj: PaginationState)=> { stateObj.currentPage = config.currentPage; return stateObj; }); if (previousPage !== config.currentPage){ this._dispatchEvent.call(this, 'pagechanged', { previousPageNumber: previousPage, newPageNumber: config.currentPage }); } this._updateVisibleRecords(); } /** * Method for jumping to the next page */ jumpToNextPage(): void{ let config: ConfigurationObject = this.config, totalPages: number = config.totalPages!, previousPage: number = config.currentPage; config.currentPage = Math.min(config.currentPage + 1, totalPages); this._stores.paginationState.update((stateObj: PaginationState)=> { stateObj.currentPage = config.currentPage; return stateObj; }); if (previousPage !== config.currentPage){ this._dispatchEvent.call(this, 'pagechanged', { previousPageNumber: previousPage, newPageNumber: config.currentPage }); } this._updateVisibleRecords(); } /** * Method for jumping to the previous page */ jumpToPreviousPage(): void{ let config: ConfigurationObject = this.config, previousPage: number = config.currentPage; config.currentPage = Math.max(config.currentPage - 1, 1); this._stores.paginationState.update((stateObj: PaginationState)=> { stateObj.currentPage = config.currentPage; return stateObj; }); if (previousPage !== config.currentPage){ this._dispatchEvent.call(this, 'pagechanged', { previousPageNumber: previousPage, newPageNumber: config.currentPage }); } this._updateVisibleRecords(); } /** * Method for jumping to the first page */ jumpToFirstPage(): void{ let config: ConfigurationObject = this.config, previousPage: number = config.currentPage; config.currentPage = 1; this._stores.paginationState.update((stateObj: PaginationState)=> { stateObj.currentPage = 1; return stateObj; }); if (previousPage !== config.currentPage){ this._dispatchEvent.call(this, 'pagechanged', { previousPageNumber: previousPage, newPageNumber: config.currentPage }); } this._updateVisibleRecords(); } /** * Method for jumping to the last page */ jumpToLastPage(): void{ let config: ConfigurationObject = this.config, previousPage: number = config.currentPage; config.currentPage = this.config.totalPages!; this._stores.paginationState.update((stateObj: PaginationState)=> { stateObj.currentPage = this.config.totalPages!; return stateObj; }); if (previousPage !== config.currentPage){ this._dispatchEvent.call(this, 'pagechanged', { previousPageNumber: previousPage, newPageNumber: config.currentPage }); } this._updateVisibleRecords(); } /** * Method for setting pagination properties * @param param property name * @param value property value */ setPagination(param: string, value: any){ let config = this.config; switch(param){ case 'enable': if (config.enable !== value){ config.enable = value; this.shouldMutateStore = true; } break; case 'showpages': this.setShowPagesProperties(value); break; case 'showrecordcount': if (config.showRecordCount !== value){ config.showRecordCount = value; this.shouldMutateStore = true; } break; case 'pagesize': this.setPageSizeProperties(value); break; case 'showjumptoendbutton': if (config.showJumpToEndButtons !== value){ config.showJumpToEndButtons = config.showJumpToFirstPageButton = config.showJumpToLastPageButton = value; this.shouldMutateStore = true; } break; case 'showjumptofirstpagebutton': if (config.showJumpToFirstPageButton !== value){ config.showJumpToFirstPageButton = value; this.shouldMutateStore = true; } break; case 'showjumptolastpagebutton': if (config.showJumpToLastPageButton !== value){ config.showJumpToLastPageButton = value; this.shouldMutateStore = true; } break; default: return; } } /** * Method for setting showpages properties * @param props show page properties */ setShowPagesProperties(props: ObjectTraverser){ let config = this.config; for (var key in props){ switch(key){ case 'enable': if (config.showPagesConfig.enable !== props[key]){ config.showPagesConfig.enable = props[key]; this.shouldMutateStore = true; } break; case 'showtotal': if (config.showPagesConfig.showtotal !== props[key]){ config.showPagesConfig.showtotal = props[key]; this.shouldMutateStore = true; } break; case 'userinput': if (config.showPagesConfig.userinput !== props[key]){ config.showPagesConfig.userinput = props[key]; this.shouldMutateStore = true; } break; default: return; } } } calculatePageOptions(options: boolean | number[]){ let config: ConfigurationObject = this.config, pageSizeConfig: PageSizeConfig = config.pageSizeConfig || {}, totalRows:number = this._totalRows, defaultPageOptions: number[] = config.defaultPageOptions; config.calculatedPageOptions = []; // if the value is true or an empty options array is provided then fallback to default if (typeof options === 'boolean' || (Array.isArray(options) && !options.length)){ config.appliedPageSize = parseNumber(pageSizeConfig['default'], defaultPageOptions[0]); config.calculatedPageOptions = optionsFilterOperation(totalRows, defaultPageOptions, config.appliedPageSize); } else { // if options array is provided config.appliedPageSize = parseNumber(pageSizeConfig['default'], options[0]); config.calculatedPageOptions = optionsFilterOperation(totalRows, options.sort((a,b)=>a-b)); } } /** * Method for setting page size properties * @param props page size properties */ setPageSizeProperties(props: ObjectTraverser){ let config = this.config; for (var key in props){ switch(key){ case 'default': if (config.appliedPageSize !== props[key]){ config.appliedPageSize = Math.floor(+props[key]); config.calculatedPageOptions = optionsFilterOperation(this._totalRows, config.calculatedPageOptions, config.appliedPageSize); config.currentPage = 1; // calculate new page size config.totalPages = Math.ceil(this._totalRows / config.appliedPageSize!); this.shouldMutateStore = true; } break; case 'options': if (config.pageSizeConfig!.options !== props[key]){ this.calculatePageOptions(props[key]); config.appliedPageSize = config.calculatedPageOptions![0]; config.currentPage = 1; // calculate new page size config.totalPages = Math.ceil(this._totalRows / config.appliedPageSize!); this.shouldMutateStore = true; } break; default: return; } } } /** * Method for reconfiguring pagination state */ reconfigureState(): void{ this._stores.paginationState.set({ enable: this.config.enable, showPages: { enable: this.config.showPagesConfig.enable!, showTotal: this.config.showPagesConfig.showtotal!, userInput: this.config.showPagesConfig.userinput! }, showRecordCount: this.config.showRecordCount, pageSize: { options: this.config.calculatedPageOptions!, applied: this.config.appliedPageSize! }, showJumptoFirstPage: this.config.showJumpToFirstPageButton, showJumpToLastPage: this.config.showJumpToLastPageButton, currentPage: this.getCurrentPage(), paginationHeight: this.paginationHeight, numRows: this.paginationHeight / DEFAULT_PAGINATION_ROW_HEIGHT, totalPages: this.config.totalPages!, totalRecords: this._totalRows }); this.shouldMutateStore = false; this._updateVisibleRecords(); } /** * @description method for getting pagination properties * @param param pagination properties * @return pagination config */ getPagination(param: undefined | string | string[]): ObjectTraverser{ let returnObj: ObjectTraverser = {}, config = this.config, getConfig = function(prop: string): any{ switch (prop){ case 'enable': return config.enable; case 'showpages': return config.showPagesConfig; case 'showrecordcount': return config.showRecordCount; case 'pagesize': return config.pageSizeConfig; case 'showjumptoendbutton': return config.showJumpToEndButtons; case 'showjumptofirstpagebutton': return config.showJumpToFirstPageButton; case 'showjumptolastpagebutton': return config.showJumpToLastPageButton; default: return; } }; if (typeof param === 'undefined'){ returnObj = { enable: config.enable, showpages: config.showPagesConfig, showrecordcount: config.showRecordCount, pagesize: config.pageSizeConfig, showjumptoendbuttons: config.showJumpToEndButtons, showjumptofirstpagebutton: config.showJumpToFirstPageButton, showjumptolastpagebutton: config.showJumpToLastPageButton }; } else if (typeof param === 'string'){ returnObj = { [param]: getConfig(param.toLowerCase()) }; } else if (Array.isArray(param)){ for (let i = 0; i< param.length; i++) { returnObj[param[i]] = getConfig(param[i].toLowerCase()); } } return returnObj; } } export default Pagination;