From 3350ad42f2c05d233cc663700b53315cbe91c27d Mon Sep 17 00:00:00 2001 From: Tyrone Tse Date: Tue, 4 Jun 2024 15:02:18 -0500 Subject: [PATCH 1/7] Changed the default number items per page to 4 Added function debounce(func, wait) to delay the Previous and Next page to wait 0.5 seconds between each click, to stop the user clicking too fast and breaking pagination --- blocks/gmo-program-list/gmo-program-list.js | 30 +++++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/blocks/gmo-program-list/gmo-program-list.js b/blocks/gmo-program-list/gmo-program-list.js index 71806d5f..44e34120 100644 --- a/blocks/gmo-program-list/gmo-program-list.js +++ b/blocks/gmo-program-list/gmo-program-list.js @@ -33,7 +33,7 @@ const headerConfig = [ } ] -const DEFAULT_ITEMS_PER_PAGE = 8; +const DEFAULT_ITEMS_PER_PAGE = 4; //Global variables used by helper functions let currentPageInfo = {}; let cursorArray = []; @@ -313,9 +313,9 @@ function buildListFooter(rows, rowsPerPage) { const footerPrev = document.createElement('div'); footerPrev.classList.add('footer-pagination-button', 'prev'); footerPrev.textContent = 'Prev'; - footerPrev.addEventListener('click', (event) => { + footerPrev.addEventListener('click', debounce((event) => { prevPage(event.target); - }) + }, 500)); const footerPageBtnsWrapper = document.createElement('div'); footerPageBtnsWrapper.classList.add('footer-pages-wrapper'); @@ -324,9 +324,10 @@ function buildListFooter(rows, rowsPerPage) { //Show current page buildCurrentPageDivElement(currentPage, footerPageBtnsWrapper); - footerNext.addEventListener('click', (event) => { + footerNext.addEventListener('click', debounce((event) => { nextPage(event.target); - }) + }, 500)); + footerNext.textContent = 'Next'; footerPagination.appendChild(footerPrev); footerPagination.appendChild(footerPageBtnsWrapper); @@ -343,6 +344,7 @@ function buildListFooter(rows, rowsPerPage) { const footerPerPageDropdown = document.createElement('select'); footerPerPageDropdown.id = 'per-page'; footerPerPageDropdown.innerHTML = ` + @@ -394,6 +396,24 @@ function repaginate(dropdown) { decorate(block, currentNumberPerPage, '', false, false); } +/** + * Limits the rate at which a function can be executed by ensuring it is only called after a specified delay since the last invocation. + * @param {Function} func - The function to debounce. + * @param {number} wait - The delay in milliseconds. + * @returns {Function} - The debounced function. + */ +function debounce(func, wait) { + let timeout; + return function(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +} + function nextPage(nextBtn) { if (currentPageInfo.hasNextPage) { //Calculate Next Page From 017018fba096aa5475610a3367aaa6b67c1153e8 Mon Sep 17 00:00:00 2001 From: Tyrone Tse Date: Tue, 4 Jun 2024 15:07:06 -0500 Subject: [PATCH 2/7] Decrease debounce timeout to 200 milliseconds (0.2 secs) --- blocks/gmo-program-list/gmo-program-list.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blocks/gmo-program-list/gmo-program-list.js b/blocks/gmo-program-list/gmo-program-list.js index 44e34120..17778cce 100644 --- a/blocks/gmo-program-list/gmo-program-list.js +++ b/blocks/gmo-program-list/gmo-program-list.js @@ -315,7 +315,7 @@ function buildListFooter(rows, rowsPerPage) { footerPrev.textContent = 'Prev'; footerPrev.addEventListener('click', debounce((event) => { prevPage(event.target); - }, 500)); + }, 200)); const footerPageBtnsWrapper = document.createElement('div'); footerPageBtnsWrapper.classList.add('footer-pages-wrapper'); @@ -326,7 +326,7 @@ function buildListFooter(rows, rowsPerPage) { footerNext.addEventListener('click', debounce((event) => { nextPage(event.target); - }, 500)); + }, 200)); footerNext.textContent = 'Next'; footerPagination.appendChild(footerPrev); From c470ba4284f2e6cea1f46ebc606e21005075cd76 Mon Sep 17 00:00:00 2001 From: Tyrone Tse Date: Tue, 4 Jun 2024 15:20:26 -0500 Subject: [PATCH 3/7] Added check that next page cannot go past last page Added check that prev page cannot go back past page 1 --- blocks/gmo-program-list/gmo-program-list.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/blocks/gmo-program-list/gmo-program-list.js b/blocks/gmo-program-list/gmo-program-list.js index 17778cce..9a3cb0d7 100644 --- a/blocks/gmo-program-list/gmo-program-list.js +++ b/blocks/gmo-program-list/gmo-program-list.js @@ -325,7 +325,7 @@ function buildListFooter(rows, rowsPerPage) { buildCurrentPageDivElement(currentPage, footerPageBtnsWrapper); footerNext.addEventListener('click', debounce((event) => { - nextPage(event.target); + nextPage(event.target, pages); }, 200)); footerNext.textContent = 'Next'; @@ -414,8 +414,8 @@ function debounce(func, wait) { }; } -function nextPage(nextBtn) { - if (currentPageInfo.hasNextPage) { +function nextPage(nextBtn, lastPage) { + if (currentPageInfo.hasNextPage && currentPage<=lastPage) { //Calculate Next Page currentPage++; const block = document.querySelector('.gmo-program-list.block'); @@ -429,7 +429,7 @@ function nextPage(nextBtn) { } function prevPage(prevBtn) { - if (currentPageInfo.hasPreviousPage) { + if (currentPageInfo.hasPreviousPage && currentPage>1) { currentPage--; const block = document.querySelector('.gmo-program-list.block'); const currentCursor = currentPageInfo.currentCursor; From daca1651cb474598408a943a67eb32258d94f874 Mon Sep 17 00:00:00 2001 From: Tyrone Tse Date: Tue, 4 Jun 2024 18:55:22 -0500 Subject: [PATCH 4/7] fix(pagination): Ensure proper handling of next/prev buttons and page boundaries - Added totalPages calculation and checks to ensure "Next" button is disabled on the last page - Updated nextPage function to correctly enable/disable pagination buttons - Improved prevPage function to ensure it navigates back to the first page correctly - Applied debounce to prevent rapid clicking issues - Refactored logic for enabling/disabling pagination buttons based on current page state --- blocks/gmo-program-list/gmo-program-list.js | 79 ++++++++++++--------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/blocks/gmo-program-list/gmo-program-list.js b/blocks/gmo-program-list/gmo-program-list.js index 9a3cb0d7..6c19b952 100644 --- a/blocks/gmo-program-list/gmo-program-list.js +++ b/blocks/gmo-program-list/gmo-program-list.js @@ -39,8 +39,8 @@ let currentPageInfo = {}; let cursorArray = []; let currentPage = 1; let currentNumberPerPage = DEFAULT_ITEMS_PER_PAGE; - let currentGraphqlFilter = {}; +let totalPages = 0; //Get Campaign Count for pagination let campaignCount = await graphqlCampaignCount(); let blockConfig; @@ -95,6 +95,9 @@ export default async function decorate(block, numPerPage = currentNumberPerPage, } currentPageInfo.itemCount = campaigns.length; + // Calculate total number of pages + totalPages = Math.ceil(campaignCount / currentNumberPerPage); + const listHeaders = buildListHeaders(headerConfig); const listItems = await buildCampaignList(campaigns, numPerPage); const listFooter = buildListFooter(campaignCount, numPerPage); @@ -107,20 +110,21 @@ export default async function decorate(block, numPerPage = currentNumberPerPage, listContainer.appendChild(listHeaders); listContainer.appendChild(listItems); listContainer.appendChild(listFooter); - //Show Hide Previous and Next Page buttons + // Show Hide Previous and Next Page buttons const footerNext = document.querySelector('.footer-pagination-button.next'); const footerPrev = document.querySelector('.footer-pagination-button.prev'); - if (currentPageInfo.hasPreviousPage){ - footerPrev.classList.add('active'); + if (currentPage > 1) { + footerPrev.classList.add('active'); } else { - footerPrev.classList.remove('active'); + footerPrev.classList.remove('active'); } - if (currentPageInfo.hasNextPage){ - footerNext.classList.add('active'); + if (currentPage < totalPages) { + footerNext.classList.add('active'); } else { - footerNext.classList.remove('active'); + footerNext.classList.remove('active'); } + decorateIcons(block); } @@ -299,6 +303,7 @@ function buildListHeaders(headerConfig) { function buildListFooter(rows, rowsPerPage) { const pages = Math.ceil(rows / rowsPerPage); + totalPages = pages; const footerWrapper = document.createElement('div'); footerWrapper.classList.add('list-footer', 'footer-wrapper'); footerWrapper.dataset.pages = pages; @@ -325,7 +330,7 @@ function buildListFooter(rows, rowsPerPage) { buildCurrentPageDivElement(currentPage, footerPageBtnsWrapper); footerNext.addEventListener('click', debounce((event) => { - nextPage(event.target, pages); + nextPage(event.target); }, 200)); footerNext.textContent = 'Next'; @@ -414,36 +419,40 @@ function debounce(func, wait) { }; } -function nextPage(nextBtn, lastPage) { - if (currentPageInfo.hasNextPage && currentPage<=lastPage) { - //Calculate Next Page - currentPage++; - const block = document.querySelector('.gmo-program-list.block'); - decorate( block, currentNumberPerPage, currentPageInfo.nextCursor, false, true,currentGraphqlFilter); - if (!(nextBtn.classList.contains('active'))) { - return; - } - const prevBtn = document.querySelector('.footer-pagination-button.prev'); - prevBtn.classList.add('active'); +function nextPage(nextBtn) { + if (currentPage < totalPages) { + currentPage++; + const block = document.querySelector('.gmo-program-list.block'); + decorate(block, currentNumberPerPage, currentPageInfo.nextCursor, false, true, currentGraphqlFilter); + + const prevBtn = document.querySelector('.footer-pagination-button.prev'); + prevBtn.classList.add('active'); + + if (currentPage === totalPages) { + nextBtn.classList.remove('active'); + } else { + nextBtn.classList.add('active'); + } } } + function prevPage(prevBtn) { - if (currentPageInfo.hasPreviousPage && currentPage>1) { - currentPage--; - const block = document.querySelector('.gmo-program-list.block'); - const currentCursor = currentPageInfo.currentCursor; - //Calculate cursor for previous page - const indexCursor = cursorArray.indexOf(currentCursor) - currentNumberPerPage; - decorate(block, currentNumberPerPage, cursorArray[indexCursor], true, false,currentGraphqlFilter); - if (!(prevBtn.classList.contains('active'))) { - return; - } - const nextBtn = document.querySelector('.footer-pagination-button.next'); - const currentPageBtn = document.querySelector('#current-page'); - const currentPageValue = parseInt(currentPageBtn.dataset.pagenumber); - const targetPage = (currentPageValue - 1); - nextBtn.classList.add('active'); + if (currentPage > 1) { + currentPage--; + const block = document.querySelector('.gmo-program-list.block'); + const indexCursor = cursorArray.indexOf(currentPageInfo.currentCursor) - currentNumberPerPage; + const newCursor = indexCursor >= 0 ? cursorArray[indexCursor] : ''; + decorate(block, currentNumberPerPage, newCursor, true, false, currentGraphqlFilter); + + const nextBtn = document.querySelector('.footer-pagination-button.next'); + nextBtn.classList.add('active'); + + if (currentPage === 1) { + prevBtn.classList.remove('active'); + } else { + prevBtn.classList.add('active'); + } } } From 8b310ec7f0fc241e104279e1b7d20915ad03f547 Mon Sep 17 00:00:00 2001 From: Tyrone Tse Date: Wed, 5 Jun 2024 10:19:21 -0500 Subject: [PATCH 5/7] - Changed default items per page to 8 - Removed 4 items per page from filter - Removed cusorArray, and replaced it with currentPageInfo.previousCursor = currentPageInfo.currentCursor; - Set next cursor to data.programPaginated.pageInfo.endCursor; - Function prevPage now uses currentPage.previousCursor as the cursor parameter value, and calculating the cursor value to use from cursorArray is no longer needed. --- blocks/gmo-program-list/gmo-program-list.js | 28 ++++++--------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/blocks/gmo-program-list/gmo-program-list.js b/blocks/gmo-program-list/gmo-program-list.js index 6c19b952..f4a090d0 100644 --- a/blocks/gmo-program-list/gmo-program-list.js +++ b/blocks/gmo-program-list/gmo-program-list.js @@ -33,10 +33,9 @@ const headerConfig = [ } ] -const DEFAULT_ITEMS_PER_PAGE = 4; +const DEFAULT_ITEMS_PER_PAGE = 8; //Global variables used by helper functions let currentPageInfo = {}; -let cursorArray = []; let currentPage = 1; let currentNumberPerPage = DEFAULT_ITEMS_PER_PAGE; let currentGraphqlFilter = {}; @@ -62,7 +61,6 @@ document.addEventListener('gmoCampaignListBlock', async function() { //Trigger loading the gmo-campaign-block //Reset page variables currentPageInfo = {}; - cursorArray = []; currentPage = 1; currentNumberPerPage = DEFAULT_ITEMS_PER_PAGE; @@ -75,24 +73,18 @@ export default async function decorate(block, numPerPage = currentNumberPerPage, if (blockConfig == undefined) blockConfig = readBlockConfig(block); const campaignPaginatedResponse = await graphqlAllCampaignsFilter(numPerPage, cursor,graphQLFilter); const campaigns = campaignPaginatedResponse.data.programPaginated.edges; + + //Set previous cursor to currentCursor + currentPageInfo.previousCursor = currentPageInfo.currentCursor; + currentPageInfo = campaignPaginatedResponse.data.programPaginated.pageInfo; //Current cursor used in previous page logic currentPageInfo.currentCursor = cursor; //Next Page if (currentPageInfo.hasNextPage){ - currentPageInfo.nextCursor = campaigns[campaigns.length - 1].cursor; - } - - if (!previousPage && !nextPage) - { - cursorArray = campaigns.map(item => item.cursor); + currentPageInfo.nextCursor = currentPageInfo.endCursor === undefined ? campaigns[campaigns.length - 1].cursor : currentPageInfo.endCursor; } - else if (nextPage){ - campaigns.forEach(item => { - cursorArray.push(item.cursor); - }); - } currentPageInfo.itemCount = campaigns.length; // Calculate total number of pages @@ -349,7 +341,6 @@ function buildListFooter(rows, rowsPerPage) { const footerPerPageDropdown = document.createElement('select'); footerPerPageDropdown.id = 'per-page'; footerPerPageDropdown.innerHTML = ` - @@ -436,18 +427,13 @@ function nextPage(nextBtn) { } } - function prevPage(prevBtn) { if (currentPage > 1) { currentPage--; const block = document.querySelector('.gmo-program-list.block'); - const indexCursor = cursorArray.indexOf(currentPageInfo.currentCursor) - currentNumberPerPage; - const newCursor = indexCursor >= 0 ? cursorArray[indexCursor] : ''; - decorate(block, currentNumberPerPage, newCursor, true, false, currentGraphqlFilter); - + decorate(block, currentNumberPerPage, currentPage.previousCursor, true, false, currentGraphqlFilter); const nextBtn = document.querySelector('.footer-pagination-button.next'); nextBtn.classList.add('active'); - if (currentPage === 1) { prevBtn.classList.remove('active'); } else { From fee49f62a2af3446cbd9d6987fd3c3d5bfcadc4e Mon Sep 17 00:00:00 2001 From: Tyrone Tse Date: Wed, 5 Jun 2024 11:06:54 -0500 Subject: [PATCH 6/7] Increased the click delay to 500 milliseconds --- blocks/gmo-program-list/gmo-program-list.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blocks/gmo-program-list/gmo-program-list.js b/blocks/gmo-program-list/gmo-program-list.js index f4a090d0..6e330705 100644 --- a/blocks/gmo-program-list/gmo-program-list.js +++ b/blocks/gmo-program-list/gmo-program-list.js @@ -312,7 +312,7 @@ function buildListFooter(rows, rowsPerPage) { footerPrev.textContent = 'Prev'; footerPrev.addEventListener('click', debounce((event) => { prevPage(event.target); - }, 200)); + }, 500)); const footerPageBtnsWrapper = document.createElement('div'); footerPageBtnsWrapper.classList.add('footer-pages-wrapper'); @@ -323,7 +323,7 @@ function buildListFooter(rows, rowsPerPage) { footerNext.addEventListener('click', debounce((event) => { nextPage(event.target); - }, 200)); + }, 500)); footerNext.textContent = 'Next'; footerPagination.appendChild(footerPrev); From ca26d4f2ef8320783c89f452dc2f3df8d883028a Mon Sep 17 00:00:00 2001 From: Tyrone Tse Date: Wed, 5 Jun 2024 14:32:53 -0500 Subject: [PATCH 7/7] Disabled the Previous and Next Button as soon as they are clicked, to prevent the user multiple clicking the button. --- blocks/gmo-program-list/gmo-program-list.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/blocks/gmo-program-list/gmo-program-list.js b/blocks/gmo-program-list/gmo-program-list.js index aef4c428..0d2bada3 100644 --- a/blocks/gmo-program-list/gmo-program-list.js +++ b/blocks/gmo-program-list/gmo-program-list.js @@ -327,6 +327,9 @@ function buildListFooter(rows, rowsPerPage) { footerPrev.classList.add('footer-pagination-button', 'prev'); footerPrev.textContent = 'Prev'; footerPrev.addEventListener('click', debounce((event) => { + // Disable the button + footerPrev.classList.remove('active'); + footerPrev.classList.add('disabled'); prevPage(event.target); }, 500)); @@ -338,6 +341,9 @@ function buildListFooter(rows, rowsPerPage) { buildCurrentPageDivElement(currentPage, footerPageBtnsWrapper); footerNext.addEventListener('click', debounce((event) => { + // Disable the button + footerNext.classList.remove('active'); + footerNext.classList.add('disabled'); nextPage(event.target); }, 500)); @@ -505,4 +511,4 @@ function sortColumn(dir, property) { function dateFormat(dateString) { const formattedDate = dateString ? dateString.split('T')[0] : 'Not Available'; return formattedDate; -} \ No newline at end of file +}