Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(component): new builder paging control component #21

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<template lwc:render-mode="light">
<section
class="limitHitTextSection"
if:true={showMessageForResultsLimit}>
<p>{label.resultsLimitHitText}</p>
</section>

<nav class="pageControlPart">
<button
class="slds-m-horizontal_small slds-button slds-button_neutral nav-direction"
disabled={disablePaginationPrevious}
aria-disabled={disablePaginationPrevious}
onclick={handlePaginationPrevious}>
<lightning-icon
icon-name="utility:chevronleft"
size="xx-small"
alternative-text={label.previous}></lightning-icon>
</button>

<template
for:each={pageNumbers}
for:item="pageNumObj">
<template if:true={pageNumObj.isRange}>
<div
key={pageNumObj.id}
class="slds-m-horizontal_small range-symbol-container">
{rangeSymbol}
</div>
</template>
<template if:false={pageNumObj.isRange}>
<template if:true={pageNumObj.isCurrentPage}>
<span
class="slds-m-horizontal_small slds-button slds-button_brand nav-button-current"
key={pageNumObj.id}
aria-current="page">
{pageNumObj.pageNumber}
</span>
</template>
<template if:false={pageNumObj.isCurrentPage}>
<button
class="slds-m-horizontal_small slds-button slds-button_neutral nav-button"
key={pageNumObj.id}
value={pageNumObj.pageNumber}
onclick={handlePaginationPage}>
{pageNumObj.pageNumber}
</button>
</template>
</template>
</template>

<button
class="slds-m-horizontal_small slds-button slds-button_neutral nav-direction"
disabled={disablePaginationNext}
aria-disabled={disablePaginationNext}
onclick={handlePaginationNext}>
<lightning-icon
icon-name="utility:chevronright"
size="xx-small"
alternative-text={label.next}></lightning-icon>
</button>
</nav>
</template>
Original file line number Diff line number Diff line change
@@ -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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be const

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a number of warnings flagged by the GH actions that should also be heeded.

this.dispatchUpdateCurrentPageEvent(pageNumber);
}

/**
* Dispatch filterChange action on search data provider.
* @param {number} newPageNumber

Check warning on line 179 in force-app/main/default/lwc/builderSearchPagingControl/builderSearchPagingControl.js

View workflow job for this annotation

GitHub Actions / build (16.x)

Missing JSDoc @param "newPageNumber" description

Check warning on line 179 in force-app/main/default/lwc/builderSearchPagingControl/builderSearchPagingControl.js

View workflow job for this annotation

GitHub Actions / build (18.x)

Missing JSDoc @param "newPageNumber" description
*/
dispatchUpdateCurrentPageEvent(newPageNumber) {
dispatchAction(this, createSearchFiltersUpdateAction({ page: newPageNumber }));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<isExposed>true</isExposed>
<targets>
<target>lightningCommunity__Page</target>
<target>lightningCommunity__Default</target>
</targets>
<masterLabel>Custom Search Paging Control</masterLabel>
<description>Displays pagination control for search page.</description>
<targetConfigs>
<targetConfig targets="lightningCommunity__Default">
<property name="currentPageNumber" type="String" default="{!Search.Pagination.currentPage}" />
<property name="pageSize" type="String" default="{!Search.Pagination.totalPages}" />
<property name="totalItemCount" type="String" default="{!Search.Results.total}" />
<property name="maximumPagesDisplayed" type="String" default="5" />
</targetConfig>
</targetConfigs>
</LightningComponentBundle>
Original file line number Diff line number Diff line change
@@ -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;
11 changes: 11 additions & 0 deletions force-app/main/default/lwc/builderSearchPagingControl/labels.js
Original file line number Diff line number Diff line change
@@ -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 };
Original file line number Diff line number Diff line change
@@ -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<number>} 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;
}
Loading