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 = `
+
+ `;
+ 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 = `
+
+ `;
+ 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 = `
+
+ `;
+ 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 = `
-
- `;
- 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 = `
-
- `;
- 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 = `
-
- `;
- 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 = `
-
- `;
- 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 = `
-
- `;
- 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 = `
-
- `;
- 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 @@