extended pagination

This commit is contained in:
Rachid Mrad 2024-05-31 12:21:17 -04:00
parent 16a03d8f90
commit 411ff4220e
No known key found for this signature in database
6 changed files with 147 additions and 169 deletions

View file

@ -880,32 +880,118 @@ function unloadModals() {
} }
/** /**
* Helper function that scrolls to an element * Generalized function to update pagination for a list.
* @param {string} attributeName - The string "class" or "id" * @param {string} itemName - The name displayed in the counter
* @param {string} attributeValue - The class or id name * @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) { function updatePagination(itemName, paginationSelector, counterSelector, headerAnchor, loadPageFunction, currentPage, numPages, hasPrevious, hasNext, totalItems) {
let targetEl = null; const paginationContainer = document.querySelector(paginationSelector);
const paginationCounter = document.querySelector(counterSelector);
if (attributeName === 'class') { const paginationButtons = document.querySelector(`${paginationSelector} .usa-pagination__list`);
targetEl = document.getElementsByClassName(attributeValue)[0]; paginationCounter.innerHTML = '';
} else if (attributeName === 'id') { paginationButtons.innerHTML = '';
targetEl = document.getElementById(attributeValue);
} else { // Buttons should only be displayed if there are more than one pages of results
console.log('Error: unknown attribute name provided.'); paginationButtons.classList.toggle('display-none', numPages <= 1);
return; // Exit the function if an invalid attributeName is provided
// 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 = `
<a href="#${headerAnchor}" class="usa-pagination__link usa-pagination__previous-page" aria-label="Previous page">
<svg class="usa-icon" aria-hidden="true" role="img">
<use xlink:href="/public/img/sprite.svg#navigate_before"></use>
</svg>
<span class="usa-pagination__link-text">Previous</span>
</a>
`;
prevPageItem.querySelector('a').addEventListener('click', (event) => {
event.preventDefault();
loadPageFunction(currentPage - 1);
});
paginationButtons.appendChild(prevPageItem);
} }
if (targetEl) { // Helper function to create a page item
const rect = targetEl.getBoundingClientRect(); function createPageItem(page) {
const scrollTop = window.scrollY || document.documentElement.scrollTop; const pageItem = document.createElement('li');
window.scrollTo({ pageItem.className = 'usa-pagination__item usa-pagination__page-no';
top: rect.top + scrollTop, pageItem.innerHTML = `
behavior: 'smooth' // Optional: for smooth scrolling <a href="#${headerAnchor}" class="usa-pagination__button" aria-label="Page ${page}">${page}</a>
`;
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 = '<span>…</span>';
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 = '<span>…</span>';
paginationButtons.appendChild(ellipsis);
}
paginationButtons.appendChild(createPageItem(numPages));
}
if (hasNext) {
const nextPageItem = document.createElement('li');
nextPageItem.className = 'usa-pagination__item usa-pagination__arrow';
nextPageItem.innerHTML = `
<a href="#${headerAnchor}" class="usa-pagination__link usa-pagination__next-page" aria-label="Next page">
<span class="usa-pagination__link-text">Next</span>
<svg class="usa-icon" aria-hidden="true" role="img">
<use xlink:href="/public/img/sprite.svg#navigate_next"></use>
</svg>
</a>
`;
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 * 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. * 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 currentSortBy = 'id';
let currentOrder = 'asc'; let currentOrder = 'asc';
let noDomainsWrapper = document.querySelector('.no-domains-wrapper'); 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 * 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 {*} page - the page number of the results (starts with 1)
* @param {*} sortBy - the sort column option * @param {*} sortBy - the sort column option
* @param {*} order - the sort order {asc, desc} * @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 json of page of domains, given page # and sort
fetch(`/get-domains-json/?page=${page}&sort_by=${sortBy}&order=${order}`) fetch(`/get-domains-json/?page=${page}&sort_by=${sortBy}&order=${order}`)
.then(response => response.json()) .then(response => response.json())
@ -990,86 +1074,29 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
// initialize tool tips immediately after the associated DOM elements are added // initialize tool tips immediately after the associated DOM elements are added
initializeTooltips(); initializeTooltips();
if (loaded)
ScrollToElement('id', 'domains-header');
hasLoaded = true; hasLoaded = true;
// update pagination // 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; currentSortBy = sortBy;
currentOrder = order; currentOrder = order;
}) })
.catch(error => console.error('Error fetching domains:', error)); .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 = `
<a href="#" class="usa-pagination__link usa-pagination__previous-page" aria-label="Domains previous page">
<svg class="usa-icon" aria-hidden="true" role="img">
<use xlink:href="/public/img/sprite.svg#navigate_before"></use>
</svg>
<span class="usa-pagination__link-text">Previous</span>
</a>
`;
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 = `
<a href="#" class="usa-pagination__button" aria-label="Domains page ${i}">${i}</a>
`;
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 = `
<a href="#" class="usa-pagination__link usa-pagination__next-page" aria-label="Domains next page">
<span class="usa-pagination__link-text">Next</span>
<svg class="usa-icon" aria-hidden="true" role="img">
<use xlink:href="/public/img/sprite.svg#navigate_next"></use>
</svg>
</a>
`;
nextPageItem.querySelector('a').addEventListener('click', () => loadDomains(currentPage + 1));
paginationButtons.appendChild(nextPageItem);
}
}
// Add event listeners to table headers for sorting // Add event listeners to table headers for sorting
document.querySelectorAll('.dotgov-table__registered-domains th[data-sortable]').forEach(header => { document.querySelectorAll('.dotgov-table__registered-domains th[data-sortable]').forEach(header => {
@ -1103,7 +1130,6 @@ document.addEventListener('DOMContentLoaded', function() {
let currentSortBy = 'id'; let currentSortBy = 'id';
let currentOrder = 'asc'; let currentOrder = 'asc';
let noDomainRequestsWrapper = document.querySelector('.no-domain-requests-wrapper'); 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 * 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 {*} page - the page number of the results (starts with 1)
* @param {*} sortBy - the sort column option * @param {*} sortBy - the sort column option
* @param {*} order - the sort order {asc, desc} * @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 json of page of domain requests, given page # and sort
fetch(`/get-domain-requests-json/?page=${page}&sort_by=${sortBy}&order=${order}`) fetch(`/get-domain-requests-json/?page=${page}&sort_by=${sortBy}&order=${order}`)
.then(response => response.json()) .then(response => response.json())
@ -1183,87 +1208,28 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
// initialize modals immediately after the DOM content is updated // initialize modals immediately after the DOM content is updated
initializeModals(); initializeModals();
if (loaded)
ScrollToElement('id', 'domain-requests-header');
hasLoaded = true; hasLoaded = true;
// update the pagination after the domain requests list is updated // 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; currentSortBy = sortBy;
currentOrder = order; currentOrder = order;
}) })
.catch(error => console.error('Error fetching domain requests:', error)); .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 = `
<a href="#" class="usa-pagination__link usa-pagination__previous-page" aria-label="Domain requests previous page">
<svg class="usa-icon" aria-hidden="true" role="img">
<use xlink:href="/public/img/sprite.svg#navigate_before"></use>
</svg>
<span class="usa-pagination__link-text">Previous</span>
</a>
`;
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 = `
<a href="#" class="usa-pagination__button" aria-label="Domain requests page ${i}">${i}</a>
`;
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 = `
<a href="#" class="usa-pagination__link usa-pagination__next-page" aria-label="Domain requests next page">
<span class="usa-pagination__link-text">Next</span>
<svg class="usa-icon" aria-hidden="true" role="img">
<use xlink:href="/public/img/sprite.svg#navigate_next"></use>
</svg>
</a>
`;
nextPageItem.querySelector('a').addEventListener('click', () => loadDomainRequests(currentPage + 1));
paginationButtons.appendChild(nextPageItem);
}
}
// Add event listeners to table headers for sorting // Add event listeners to table headers for sorting
document.querySelectorAll('.dotgov-table__domain-requests th[data-sortable]').forEach(header => { document.querySelectorAll('.dotgov-table__domain-requests th[data-sortable]').forEach(header => {
header.addEventListener('click', function() { header.addEventListener('click', function() {

View file

@ -0,0 +1,11 @@
@use "uswds-core" as *;
.usa-pagination {
flex-wrap: wrap;
}
@include at-media(desktop) {
.usa-pagination {
flex-wrap: nowrap;
}
}

View file

@ -13,6 +13,7 @@
@forward "links"; @forward "links";
@forward "lists"; @forward "lists";
@forward "buttons"; @forward "buttons";
@forward "pagination";
@forward "forms"; @forward "forms";
@forward "tooltips"; @forward "tooltips";
@forward "fieldsets"; @forward "fieldsets";

View file

@ -63,7 +63,7 @@
</div> </div>
</section> </section>
<nav aria-label="Pagination" class="usa-pagination flex-justify" id="domains-pagination"> <nav aria-label="Pagination" class="usa-pagination flex-justify" id="domains-pagination">
<span class="usa-pagination__counter text-base-dark padding-left-2"> <span class="usa-pagination__counter text-base-dark padding-left-2 margin-bottom-1">
<!-- Count will be dynamically populated by JS --> <!-- Count will be dynamically populated by JS -->
</span> </span>
<ul class="usa-pagination__list"> <ul class="usa-pagination__list">
@ -134,7 +134,7 @@
</div> </div>
</section> </section>
<nav aria-label="Pagination" class="usa-pagination flex-justify" id="domain-requests-pagination"> <nav aria-label="Pagination" class="usa-pagination flex-justify" id="domain-requests-pagination">
<span class="usa-pagination__counter text-base-dark padding-left-2"> <span class="usa-pagination__counter text-base-dark padding-left-2 margin-bottom-1">
<!-- Count will be dynamically populated by JS --> <!-- Count will be dynamically populated by JS -->
</span> </span>
<ul class="usa-pagination__list"> <ul class="usa-pagination__list">

View file

@ -20,7 +20,7 @@ def get_domain_requests_json(request):
sort_by = f"-{sort_by}" sort_by = f"-{sort_by}"
domain_requests = domain_requests.order_by(sort_by) domain_requests = domain_requests.order_by(sort_by)
page_number = request.GET.get("page", 1) page_number = request.GET.get("page", 1)
paginator = Paginator(domain_requests, 10) paginator = Paginator(domain_requests, 1)
page_obj = paginator.get_page(page_number) page_obj = paginator.get_page(page_number)
domain_requests_data = [ domain_requests_data = [

View file

@ -27,7 +27,7 @@ def get_domains_json(request):
sort_by = f"-{sort_by}" sort_by = f"-{sort_by}"
objects = objects.order_by(sort_by) objects = objects.order_by(sort_by)
paginator = Paginator(objects, 10) paginator = Paginator(objects, 1)
page_number = request.GET.get("page") page_number = request.GET.get("page")
page_obj = paginator.get_page(page_number) page_obj = paginator.get_page(page_number)