From 9186658ad245124cd771eaaaba909ba6d5c9aaa9 Mon Sep 17 00:00:00 2001 From: tnadkarni Date: Mon, 30 Sep 2024 13:56:30 -0700 Subject: [PATCH] feat(component): new builder paginag control component --- .../builderSearchPagingControl.html | 62 ++++++ .../builderSearchPagingControl.js | 184 ++++++++++++++++++ .../builderSearchPagingControl.js-meta.xml | 18 ++ .../builderSearchPagingControl/constants.js | 18 ++ .../lwc/builderSearchPagingControl/labels.js | 11 ++ .../pagingControlHelper.js | 99 ++++++++++ 6 files changed, 392 insertions(+) create mode 100644 force-app/main/default/lwc/builderSearchPagingControl/builderSearchPagingControl.html create mode 100644 force-app/main/default/lwc/builderSearchPagingControl/builderSearchPagingControl.js create mode 100644 force-app/main/default/lwc/builderSearchPagingControl/builderSearchPagingControl.js-meta.xml create mode 100644 force-app/main/default/lwc/builderSearchPagingControl/constants.js create mode 100644 force-app/main/default/lwc/builderSearchPagingControl/labels.js create mode 100644 force-app/main/default/lwc/builderSearchPagingControl/pagingControlHelper.js diff --git a/force-app/main/default/lwc/builderSearchPagingControl/builderSearchPagingControl.html b/force-app/main/default/lwc/builderSearchPagingControl/builderSearchPagingControl.html new file mode 100644 index 0000000..da79b01 --- /dev/null +++ b/force-app/main/default/lwc/builderSearchPagingControl/builderSearchPagingControl.html @@ -0,0 +1,62 @@ + diff --git a/force-app/main/default/lwc/builderSearchPagingControl/builderSearchPagingControl.js b/force-app/main/default/lwc/builderSearchPagingControl/builderSearchPagingControl.js new file mode 100644 index 0000000..cdae9dd --- /dev/null +++ b/force-app/main/default/lwc/builderSearchPagingControl/builderSearchPagingControl.js @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * For full license text, see the LICENSE file in the repo + * root or https://opensource.org/licenses/apache-2-0/ + */ +import { LightningElement, api } from 'lwc'; +import { generatePagesForRange } from './pagingControlHelper'; +import { PAGING_RANGE_SYMBOL, MAX_RESULTS_OFFSET } from './constants'; +import { previous, next, resultsLimitHitText } from './labels'; +import { createSearchFiltersUpdateAction, dispatchAction } from 'commerce/actionApi'; +/** + * @param {*} value The value to check. + * @param {number} min The minimum value that _`value`_ needs to have. + * @returns {boolean} Whether the given _`value`_ is a number greater than _`min`_. + */ +function isNumber(value, min) { + return typeof value === 'number' && !Number.isNaN(value) && value > min; +} + +/** + * A page object to render the page item. + * @typedef {object} PageItem + * @property {number} id + * Identifier used as the key for the rendering element. + * @property {?number} pageNumber + * Page number + * @property {boolean} isCurrentPage + * Whether this page is the current page. + * @property {boolean} isRange + * Whether this page is a range element. + */ + +/** + * A builder pagination UI control for any record visualization controls. + */ +export default class SearchPagingControl extends LightningElement { + static renderMode = 'light'; + + /** + * Current page number. + */ + @api + currentPageNumber; + + /** + * Number of items per page. + */ + @api + pageSize; + + /** + * Total number of items. + */ + @api + totalItemCount; + + /** + * The maximum quantity of numbered pages displayed to the user. + * This includes numbers and range symbol. + */ + @api + maximumPagesDisplayed; + + /** + * Gets the required i18n labels + * @readonly + * @private + */ + label = { + previous, + next, + resultsLimitHitText, + }; + get normalizedPageNumber() { + return isNumber(this.currentPageNumber, 1) ? this.currentPageNumber : 1; + } + get normalizedPageSize() { + return isNumber(this.pageSize, 1) ? this.pageSize : 1; + } + get normalizedItemCount() { + return isNumber(this.totalItemCount, 0) ? this.totalItemCount : 0; + } + + /** + * Disable previous page navigation? + * @type {boolean} + * @readonly + * @private + */ + get disablePaginationPrevious() { + return this.normalizedPageNumber === 1; + } + + /** + * Disable next page navigation? + * @type {boolean} + * @readonly + * @private + */ + get disablePaginationNext() { + return this.normalizedPageNumber >= this.totalPages; + } + + /** + * only show a message if this is the last page we could possibly show while there are more results due to API limitation: + * true if totalItemCount > 5000 + pageSize and this is the last page (aNumber to 5000+pageSize) + * @type {boolean} + * @readonly + * @private + */ + get showMessageForResultsLimit() { + const pageSize = this.normalizedPageSize; + return ( + this.normalizedItemCount > MAX_RESULTS_OFFSET + pageSize && + this.normalizedPageNumber >= Math.ceil((MAX_RESULTS_OFFSET + pageSize) / pageSize) + ); + } + + /** + * Gets total number of pages. + * @type {number} + * @readonly + * @private + */ + get totalPages() { + return Math.ceil(this.normalizedItemCount / this.normalizedPageSize); + } + + /** + * Gets page numbers as an array of objects. + * @type {PageItem[]} + * @readonly + * @private + */ + get pageNumbers() { + const max = isNumber(this.maximumPagesDisplayed, 0) ? this.maximumPagesDisplayed : 5; + return generatePagesForRange(this.normalizedPageNumber, this.totalPages, max); + } + + /** + * Gets the symbol for range symbol. + * @type {string} + * @readonly + * @private + */ + get rangeSymbol() { + return PAGING_RANGE_SYMBOL; + } + + /** + * Handler for the 'click' event from the previous button. + */ + handlePaginationPrevious() { + const previousPageNumber = Number(this.currentPageNumber) - 1; + this.dispatchUpdateCurrentPageEvent(previousPageNumber); + } + + /** + * Handler for the 'click' event from the next button. + */ + handlePaginationNext() { + const nextPageNumber = Number(this.currentPageNumber) + 1; + this.dispatchUpdateCurrentPageEvent(nextPageNumber); + } + + /** + * Handler for the 'click' event from the page number button. + * @param {Event} event The event object + */ + handlePaginationPage(event) { + let pageNumber = parseInt(event.target.value, 10); + this.dispatchUpdateCurrentPageEvent(pageNumber); + } + + /** + * Dispatch filterChange action on search data provider. + * @param {number} newPageNumber + */ + dispatchUpdateCurrentPageEvent(newPageNumber) { + dispatchAction(this, createSearchFiltersUpdateAction({ page: newPageNumber })); + } +} diff --git a/force-app/main/default/lwc/builderSearchPagingControl/builderSearchPagingControl.js-meta.xml b/force-app/main/default/lwc/builderSearchPagingControl/builderSearchPagingControl.js-meta.xml new file mode 100644 index 0000000..a1952d7 --- /dev/null +++ b/force-app/main/default/lwc/builderSearchPagingControl/builderSearchPagingControl.js-meta.xml @@ -0,0 +1,18 @@ + + + true + + lightningCommunity__Page + lightningCommunity__Default + + Custom Search Paging Control + Displays pagination control for search page. + + + + + + + + + diff --git a/force-app/main/default/lwc/builderSearchPagingControl/constants.js b/force-app/main/default/lwc/builderSearchPagingControl/constants.js new file mode 100644 index 0000000..c7c0424 --- /dev/null +++ b/force-app/main/default/lwc/builderSearchPagingControl/constants.js @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * For full license text, see the LICENSE file in the repo + * root or https://opensource.org/licenses/apache-2-0/ + */ +export const PAGING_RANGE_SYMBOL = '...'; + +export const EVENT = { + PAGE_CHANGE_PREVIOUS_EVT: 'pageprevious', + + PAGE_CHANGE_NEXT_EVT: 'pagenext', + + PAGE_CHANGE_GOTOPAGE_EVT: 'pagegoto', +}; + +export const MAX_RESULTS_OFFSET = 5000; diff --git a/force-app/main/default/lwc/builderSearchPagingControl/labels.js b/force-app/main/default/lwc/builderSearchPagingControl/labels.js new file mode 100644 index 0000000..3a7a2b9 --- /dev/null +++ b/force-app/main/default/lwc/builderSearchPagingControl/labels.js @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * For full license text, see the LICENSE file in the repo + * root or https://opensource.org/licenses/apache-2-0/ + */ +import previous from '@salesforce/label/c.Search_Results_previous'; +import next from '@salesforce/label/c.Search_Results_next'; +import resultsLimitHitText from '@salesforce/label/c.Search_Results_resultsLimitHitText'; +export { previous, next, resultsLimitHitText }; diff --git a/force-app/main/default/lwc/builderSearchPagingControl/pagingControlHelper.js b/force-app/main/default/lwc/builderSearchPagingControl/pagingControlHelper.js new file mode 100644 index 0000000..feddae3 --- /dev/null +++ b/force-app/main/default/lwc/builderSearchPagingControl/pagingControlHelper.js @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * For full license text, see the LICENSE file in the repo + * root or https://opensource.org/licenses/apache-2-0/ + */ +/** + * @typedef {import('./searchPagingControl').PageItem} PageItem + */ + +/** + * Construct a PageItem object with given parameters and add to the given array. + * @param {PageItem[]} pageObjArray reference array to add the PageItem object + * @param {boolean} isRange is this a range? + * @param {number} [pageNumber] page number + * @param {number} [currentPage] current page number + */ +function addPageObject(pageObjArray, isRange, pageNumber, currentPage) { + const id = pageObjArray.length; + pageObjArray.push({ + id, + pageNumber, + isRange, + isCurrentPage: pageNumber != null && pageNumber === currentPage, + }); +} + +/** + * @param {number} length length of the result array + * @param {number} start starting number of the range + * @returns {Array} An array filled with given range values + */ +function getRangeArray(length, start) { + return Array.from( + { + length, + }, + (v, k) => k + start + ); +} + +/** + * @param {number} currentPage current page number + * @param {number} totalPage total number of pages + * @param {number} maxPageButtons max no. of page buttons to show in this page range. + * @returns {PageItem[]} An array of {@link PageItem}s with relevant information + * such as `id`, `pageNumber`, `isCurrentPage`, `isRange` + */ +export function generatePagesForRange(currentPage, totalPage, maxPageButtons) { + let pages = []; + let balanceLength; + let lastPageNumber; + const pageRange = []; + + balanceLength = maxPageButtons - 2; + if (maxPageButtons > totalPage) { + pages = getRangeArray(totalPage, 1); + } else if (currentPage < balanceLength) { + pages = getRangeArray(balanceLength, 1); + + pages.push(totalPage); + } else if (currentPage > totalPage - balanceLength + 1) { + pages = getRangeArray(balanceLength, totalPage - balanceLength + 1); + + pages.unshift(1); + } else { + balanceLength = maxPageButtons - 4; + + let beginIndex = 0; + const halfRB = balanceLength >> 1; + if (halfRB) { + beginIndex = currentPage - halfRB; + + if (balanceLength === 2) { + beginIndex += 1; + } + } else { + beginIndex = currentPage; + } + pages = getRangeArray(balanceLength, beginIndex); + + pages.unshift(1); + pages.push(totalPage); + } + + pages.forEach((pageNumber) => { + if (lastPageNumber) { + if (pageNumber - lastPageNumber === 2) { + addPageObject(pageRange, false, lastPageNumber + 1, currentPage); + } else if (pageNumber - lastPageNumber !== 1) { + addPageObject(pageRange, true, undefined, undefined); + } + } + addPageObject(pageRange, false, pageNumber, currentPage); + lastPageNumber = pageNumber; + }); + return pageRange; +}