diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js index 4bc17ba81..27022a423 100644 --- a/src/registrar/assets/js/get-gov.js +++ b/src/registrar/assets/js/get-gov.js @@ -880,32 +880,118 @@ function unloadModals() { } /** - * Helper function that scrolls to an element - * @param {string} attributeName - The string "class" or "id" - * @param {string} attributeValue - The class or id name + * Generalized function to update pagination for a list. + * @param {string} itemName - The name displayed in the counter + * @param {string} paginationSelector - CSS selector for the pagination container. + * @param {string} counterSelector - CSS selector for the pagination counter. + * @param {string} headerAnchor - ID of the header element to anchor the links to. + * @param {Function} loadPageFunction - Function to call when a page link is clicked. + * @param {number} currentPage - The current page number (starting with 1). + * @param {number} numPages - The total number of pages. + * @param {boolean} hasPrevious - Whether there is a page before the current page. + * @param {boolean} hasNext - Whether there is a page after the current page. + * @param {number} totalItems - The total number of items. */ -function ScrollToElement(attributeName, attributeValue) { - let targetEl = null; - - if (attributeName === 'class') { - targetEl = document.getElementsByClassName(attributeValue)[0]; - } else if (attributeName === 'id') { - targetEl = document.getElementById(attributeValue); - } else { - console.log('Error: unknown attribute name provided.'); - return; // Exit the function if an invalid attributeName is provided +function updatePagination(itemName, paginationSelector, counterSelector, headerAnchor, loadPageFunction, currentPage, numPages, hasPrevious, hasNext, totalItems) { + const paginationContainer = document.querySelector(paginationSelector); + const paginationCounter = document.querySelector(counterSelector); + const paginationButtons = document.querySelector(`${paginationSelector} .usa-pagination__list`); + paginationCounter.innerHTML = ''; + paginationButtons.innerHTML = ''; + + // Buttons should only be displayed if there are more than one pages of results + paginationButtons.classList.toggle('display-none', numPages <= 1); + + // Counter should only be displayed if there is more than 1 item + paginationContainer.classList.toggle('display-none', totalItems < 1); + + paginationCounter.innerHTML = `${totalItems} ${itemName}${totalItems > 1 ? 's' : ''}`; + + if (hasPrevious) { + const prevPageItem = document.createElement('li'); + prevPageItem.className = 'usa-pagination__item usa-pagination__arrow'; + prevPageItem.innerHTML = ` + + + Previous + + `; + prevPageItem.querySelector('a').addEventListener('click', (event) => { + event.preventDefault(); + loadPageFunction(currentPage - 1); + }); + paginationButtons.appendChild(prevPageItem); } - if (targetEl) { - const rect = targetEl.getBoundingClientRect(); - const scrollTop = window.scrollY || document.documentElement.scrollTop; - window.scrollTo({ - top: rect.top + scrollTop, - behavior: 'smooth' // Optional: for smooth scrolling + // Helper function to create a page item + function createPageItem(page) { + const pageItem = document.createElement('li'); + pageItem.className = 'usa-pagination__item usa-pagination__page-no'; + pageItem.innerHTML = ` + ${page} + `; + if (page === currentPage) { + pageItem.querySelector('a').classList.add('usa-current'); + pageItem.querySelector('a').setAttribute('aria-current', 'page'); + } + pageItem.querySelector('a').addEventListener('click', (event) => { + event.preventDefault(); + loadPageFunction(page); }); + return pageItem; + } + + // Add first page and ellipsis if necessary + if (currentPage > 3) { + paginationButtons.appendChild(createPageItem(1)); + if (currentPage > 4) { + const ellipsis = document.createElement('li'); + ellipsis.className = 'usa-pagination__item usa-pagination__overflow'; + ellipsis.setAttribute('aria-label', 'ellipsis indicating non-visible pages'); + ellipsis.innerHTML = ''; + paginationButtons.appendChild(ellipsis); + } + } + + // Add pages around the current page + for (let i = Math.max(1, currentPage - 1); i <= Math.min(numPages, currentPage + 1); i++) { + paginationButtons.appendChild(createPageItem(i)); + } + + // Add last page and ellipsis if necessary + if (currentPage < numPages - 2) { + if (currentPage < numPages - 3) { + const ellipsis = document.createElement('li'); + ellipsis.className = 'usa-pagination__item usa-pagination__overflow'; + ellipsis.setAttribute('aria-label', 'ellipsis indicating non-visible pages'); + ellipsis.innerHTML = ''; + paginationButtons.appendChild(ellipsis); + } + paginationButtons.appendChild(createPageItem(numPages)); + } + + if (hasNext) { + const nextPageItem = document.createElement('li'); + nextPageItem.className = 'usa-pagination__item usa-pagination__arrow'; + nextPageItem.innerHTML = ` + + Next + + + `; + nextPageItem.querySelector('a').addEventListener('click', (event) => { + event.preventDefault(); + loadPageFunction(currentPage + 1); + }); + paginationButtons.appendChild(nextPageItem); } } + /** * An IIFE that listens for DOM Content to be loaded, then executes. This function * initializes the domains list and associated functionality on the home page of the app. @@ -918,7 +1004,6 @@ document.addEventListener('DOMContentLoaded', function() { let currentSortBy = 'id'; let currentOrder = 'asc'; let noDomainsWrapper = document.querySelector('.no-domains-wrapper'); - let hasLoaded = false; /** * Loads rows in the domains list, as well as updates pagination around the domains list @@ -926,9 +1011,8 @@ document.addEventListener('DOMContentLoaded', function() { * @param {*} page - the page number of the results (starts with 1) * @param {*} sortBy - the sort column option * @param {*} order - the sort order {asc, desc} - * @param {*} loaded - control for the scrollToElement functionality */ - function loadDomains(page, sortBy = currentSortBy, order = currentOrder, loaded = hasLoaded) { + function loadDomains(page, sortBy = currentSortBy, order = currentOrder) { //fetch json of page of domains, given page # and sort fetch(`/get-domains-json/?page=${page}&sort_by=${sortBy}&order=${order}`) .then(response => response.json()) @@ -990,86 +1074,29 @@ document.addEventListener('DOMContentLoaded', function() { }); // initialize tool tips immediately after the associated DOM elements are added initializeTooltips(); - if (loaded) - ScrollToElement('id', 'domains-header'); hasLoaded = true; // update pagination - updateDomainsPagination(data.page, data.num_pages, data.has_previous, data.has_next, data.total); + updatePagination( + 'domain', + '#domains-pagination', + '#domains-pagination .usa-pagination__counter', + 'domains-header', + loadDomains, + data.page, + data.num_pages, + data.has_previous, + data.has_next, + data.total + ); currentSortBy = sortBy; currentOrder = order; }) .catch(error => console.error('Error fetching domains:', error)); } - /** - * Update the pagination below the domains list. - * @param {*} currentPage - the current page number (starting with 1) - * @param {*} numPages - the number of pages indicated by the domains list response - * @param {*} hasPrevious - if there is a page of results prior to the current page - * @param {*} hasNext - if there is a page of results after the current page - */ - function updateDomainsPagination(currentPage, numPages, hasPrevious, hasNext, totalItems) { - // identify the DOM element where the pagination will be inserted - const paginationContainer = document.querySelector('#domains-pagination'); - const paginationCounter = document.querySelector('#domains-pagination .usa-pagination__counter'); - const paginationButtons = document.querySelector('#domains-pagination .usa-pagination__list'); - paginationCounter.innerHTML = ''; - paginationButtons.innerHTML = ''; - - // Buttons should only be displayed if there are more than one pages of results - paginationButtons.classList.toggle('display-none', numPages <= 1); - - // Counter should only be displayed if there is more than 1 item - paginationContainer.classList.toggle('display-none', totalItems < 1); - - paginationCounter.innerHTML = `${totalItems} domain${totalItems > 1 ? 's' : ''}`; - if (hasPrevious) { - const prevPageItem = document.createElement('li'); - prevPageItem.className = 'usa-pagination__item usa-pagination__arrow'; - prevPageItem.innerHTML = ` - - - Previous - - `; - prevPageItem.querySelector('a').addEventListener('click', () => loadDomains(currentPage - 1)); - paginationButtons.appendChild(prevPageItem); - } - - for (let i = 1; i <= numPages; i++) { - const pageItem = document.createElement('li'); - pageItem.className = 'usa-pagination__item usa-pagination__page-no'; - pageItem.innerHTML = ` - ${i} - `; - if (i === currentPage) { - pageItem.querySelector('a').classList.add('usa-current'); - pageItem.querySelector('a').setAttribute('aria-current', 'page'); - } - pageItem.querySelector('a').addEventListener('click', () => loadDomains(i)); - paginationButtons.appendChild(pageItem); - } - - if (hasNext) { - const nextPageItem = document.createElement('li'); - nextPageItem.className = 'usa-pagination__item usa-pagination__arrow'; - nextPageItem.innerHTML = ` - - Next - - - `; - nextPageItem.querySelector('a').addEventListener('click', () => loadDomains(currentPage + 1)); - paginationButtons.appendChild(nextPageItem); - } - } // Add event listeners to table headers for sorting document.querySelectorAll('.dotgov-table__registered-domains th[data-sortable]').forEach(header => { @@ -1103,7 +1130,6 @@ document.addEventListener('DOMContentLoaded', function() { let currentSortBy = 'id'; let currentOrder = 'asc'; let noDomainRequestsWrapper = document.querySelector('.no-domain-requests-wrapper'); - let hasLoaded = false; /** * Loads rows in the domain requests list, as well as updates pagination around the domain requests list @@ -1111,9 +1137,8 @@ document.addEventListener('DOMContentLoaded', function() { * @param {*} page - the page number of the results (starts with 1) * @param {*} sortBy - the sort column option * @param {*} order - the sort order {asc, desc} - * @param {*} loaded - control for the scrollToElement functionality */ - function loadDomainRequests(page, sortBy = currentSortBy, order = currentOrder, loaded = hasLoaded) { + function loadDomainRequests(page, sortBy = currentSortBy, order = currentOrder) { //fetch json of page of domain requests, given page # and sort fetch(`/get-domain-requests-json/?page=${page}&sort_by=${sortBy}&order=${order}`) .then(response => response.json()) @@ -1183,87 +1208,28 @@ document.addEventListener('DOMContentLoaded', function() { }); // initialize modals immediately after the DOM content is updated initializeModals(); - if (loaded) - ScrollToElement('id', 'domain-requests-header'); hasLoaded = true; // update the pagination after the domain requests list is updated - updateDomainRequestsPagination(data.page, data.num_pages, data.has_previous, data.has_next, data.total); + updatePagination( + 'domain request', + '#domain-requests-pagination', + '#domain-requests-pagination .usa-pagination__counter', + 'domain-requests-header', + loadDomainRequests, + data.page, + data.num_pages, + data.has_previous, + data.has_next, + data.total + ); currentSortBy = sortBy; currentOrder = order; }) .catch(error => console.error('Error fetching domain requests:', error)); } - /** - * Update the pagination below the domain requests list. - * @param {*} currentPage - the current page number (starting with 1) - * @param {*} numPages - the number of pages indicated by the domain request list response - * @param {*} hasPrevious - if there is a page of results prior to the current page - * @param {*} hasNext - if there is a page of results after the current page - */ - function updateDomainRequestsPagination(currentPage, numPages, hasPrevious, hasNext, totalItems) { - // identify the DOM element where pagination is contained - const paginationContainer = document.querySelector('#domain-requests-pagination'); - const paginationCounter = document.querySelector('#domain-requests-pagination .usa-pagination__counter'); - const paginationButtons = document.querySelector('#domain-requests-pagination .usa-pagination__list'); - paginationCounter.innerHTML = ''; - paginationButtons.innerHTML = ''; - - // Buttons should only be displayed if there are more than one pages of results - paginationButtons.classList.toggle('display-none', numPages <= 1); - - // Counter should only be displayed if there is more than 1 item - paginationContainer.classList.toggle('display-none', totalItems < 1); - - paginationCounter.innerHTML = `${totalItems} domain request${totalItems > 1 ? 's' : ''}`; - - if (hasPrevious) { - const prevPageItem = document.createElement('li'); - prevPageItem.className = 'usa-pagination__item usa-pagination__arrow'; - prevPageItem.innerHTML = ` - - - Previous - - `; - prevPageItem.querySelector('a').addEventListener('click', () => loadDomainRequests(currentPage - 1)); - paginationButtons.appendChild(prevPageItem); - } - - for (let i = 1; i <= numPages; i++) { - const pageItem = document.createElement('li'); - pageItem.className = 'usa-pagination__item usa-pagination__page-no'; - pageItem.innerHTML = ` - ${i} - `; - if (i === currentPage) { - pageItem.querySelector('a').classList.add('usa-current'); - pageItem.querySelector('a').setAttribute('aria-current', 'page'); - } - pageItem.querySelector('a').addEventListener('click', () => loadDomainRequests(i)); - paginationButtons.appendChild(pageItem); - } - - if (hasNext) { - const nextPageItem = document.createElement('li'); - nextPageItem.className = 'usa-pagination__item usa-pagination__arrow'; - nextPageItem.innerHTML = ` - - Next - - - `; - nextPageItem.querySelector('a').addEventListener('click', () => loadDomainRequests(currentPage + 1)); - paginationButtons.appendChild(nextPageItem); - } - } - // Add event listeners to table headers for sorting document.querySelectorAll('.dotgov-table__domain-requests th[data-sortable]').forEach(header => { header.addEventListener('click', function() { diff --git a/src/registrar/assets/sass/_theme/_pagination.scss b/src/registrar/assets/sass/_theme/_pagination.scss new file mode 100644 index 000000000..2ddd87d57 --- /dev/null +++ b/src/registrar/assets/sass/_theme/_pagination.scss @@ -0,0 +1,11 @@ +@use "uswds-core" as *; + +.usa-pagination { + flex-wrap: wrap; +} + +@include at-media(desktop) { + .usa-pagination { + flex-wrap: nowrap; + } +} diff --git a/src/registrar/assets/sass/_theme/styles.scss b/src/registrar/assets/sass/_theme/styles.scss index 64b113a29..921976b44 100644 --- a/src/registrar/assets/sass/_theme/styles.scss +++ b/src/registrar/assets/sass/_theme/styles.scss @@ -13,6 +13,7 @@ @forward "links"; @forward "lists"; @forward "buttons"; +@forward "pagination"; @forward "forms"; @forward "tooltips"; @forward "fieldsets"; diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 9a5082104..fd54769a8 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -63,7 +63,7 @@