refactor domain requests

This commit is contained in:
zandercymatics 2024-09-12 14:09:33 -06:00
parent bfb942a43d
commit 0126d6e81f
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
2 changed files with 424 additions and 568 deletions

View file

@ -33,6 +33,14 @@ const showElement = (element) => {
element.classList.remove('display-none');
};
/**
* Helper function to get the CSRF token from the cookie
*
*/
function getCsrfToken() {
return document.querySelector('input[name="csrfmiddlewaretoken"]').value;
}
/**
* Helper function that scrolls to an element
* @param {string} attributeName - The string "class" or "id"
@ -995,28 +1003,28 @@ function unloadModals() {
}
class LoadTableBase {
constructor(tableSelector, tableWrapper, searchFieldId, searchSubmitId, resetSearchBtn, resetFiltersBtn, noDataDisplay, noSearchresultsDisplay) {
this.domainsWrapper = document.querySelector(tableWrapper);
constructor(tableSelector, tableWrapperSelector, searchFieldId, searchSubmitId, resetSearchBtn, resetFiltersBtn, noDataDisplay, noSearchresultsDisplay) {
this.tableWrapper = document.querySelector(tableWrapperSelector);
this.tableHeaders = document.querySelectorAll(`${tableSelector} th[data-sortable]`);
this.currentSortBy = 'id';
this.currentOrder = 'asc';
this.currentStatus = [];
this.currentSearchTerm = '';
this.scrollToTable = false;
this.domainsSearchInput = document.querySelector(searchFieldId);
this.domainsSearchSubmit = document.querySelector(searchSubmitId);
this.tableAnnouncementRegion = document.querySelector(`${tableWrapper} .usa-table__announcement-region`);
this.searchInput = document.querySelector(searchFieldId);
this.searchSubmit = document.querySelector(searchSubmitId);
this.tableAnnouncementRegion = document.querySelector(`${tableWrapperSelector} .usa-table__announcement-region`);
this.resetSearchButton = document.querySelector(resetSearchBtn);
this.resetFiltersButton = document.querySelector(resetFiltersBtn);
// NOTE: these 3 can't be used if filters are active on a page with more than 1 table
this.statusCheckboxes = document.querySelectorAll('input[name="filter-status"]');
this.statusIndicator = document.querySelector('.filter-indicator');
this.statusToggle = document.querySelector('.usa-button--filter');
this.noDomainsWrapper = document.querySelector(noDataDisplay);
this.noTableWrapper = document.querySelector(noDataDisplay);
this.noSearchResultsWrapper = document.querySelector(noSearchresultsDisplay);
this.portfolioElement = document.getElementById('portfolio-js-value');
this.portfolioValue = this.portfolioElement ? this.portfolioElement.getAttribute('data-portfolio') : null;
this.initializetableHeaders();
this.initializeTableHeaders();
this.initializeSearchHandler();
this.initializeStatusToggleHandler();
this.initializeFilterCheckboxes();
@ -1188,7 +1196,7 @@ class LoadTableBase {
}
// Add event listeners to table headers for sorting
initializetableHeaders() {
initializeTableHeaders() {
this.tableHeaders.forEach(header => {
header.addEventListener('click', () => {
const sortBy = header.getAttribute('data-sortable');
@ -1205,9 +1213,9 @@ class LoadTableBase {
}
initializeSearchHandler() {
this.domainsSearchSubmit.addEventListener('click', (e) => {
this.searchSubmit.addEventListener('click', (e) => {
e.preventDefault();
this.currentSearchTerm = this.domainsSearchInput.value;
this.currentSearchTerm = this.searchInput.value;
// If the search is blank, we match the resetSearch functionality
if (this.currentSearchTerm) {
showElement(this.resetSearchButton);
@ -1272,7 +1280,7 @@ class LoadTableBase {
}
resetSearch() {
this.domainsSearchInput.value = '';
this.searchInput.value = '';
this.currentSearchTerm = '';
hideElement(this.resetSearchButton);
this.loadTable(1, 'id', 'asc');
@ -1404,7 +1412,7 @@ class DomainsTable extends LoadTableBase {
}
// handle the display of proper messaging in the event that no domains exist in the list or search returns no results
this.updateDisplay(data, this.domainsWrapper, this.noDomainsWrapper, this.noSearchResultsWrapper, this.currentSearchTerm);
this.updateDisplay(data, this.tableWrapper, this.noTableWrapper, this.noSearchResultsWrapper, this.currentSearchTerm);
// identify the DOM element where the domain list will be inserted into the DOM
const domainList = document.querySelector('.domains__table tbody');
@ -1491,114 +1499,25 @@ class DomainsTable extends LoadTableBase {
}
/**
* 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.
*
*/
document.addEventListener('DOMContentLoaded', function() {
const domainsTable = new DomainsTable();
class DomainRequestsTable extends LoadTableBase {
constructor() {
super('.domain-requests__table', '.domain-requests__table-wrapper', '#domain-requests__search-field', '#domain-requests__search-field-submit', '.domain-requests__reset-search', '.domain-requests__reset-filters', '.domain-requests__no-data', '.domain-requests__no-search-results');
this.domainRequestPk = 1
if (domainsTable.domainsWrapper) {
// Initial load
domainsTable.loadTable(1);
}
});
const utcDateString = (dateString) => {
const date = new Date(dateString);
const utcYear = date.getUTCFullYear();
const utcMonth = date.toLocaleString('en-US', { month: 'short', timeZone: 'UTC' });
const utcDay = date.getUTCDate().toString().padStart(2, '0');
let utcHours = date.getUTCHours();
const utcMinutes = date.getUTCMinutes().toString().padStart(2, '0');
const ampm = utcHours >= 12 ? 'PM' : 'AM';
utcHours = utcHours % 12 || 12; // Convert to 12-hour format, '0' hours should be '12'
return `${utcMonth} ${utcDay}, ${utcYear}, ${utcHours}:${utcMinutes} ${ampm} UTC`;
};
/**
* An IIFE that listens for DOM Content to be loaded, then executes. This function
* initializes the domain requests list and associated functionality on the home page of the app.
*
*/
document.addEventListener('DOMContentLoaded', function() {
const domainRequestsSectionWrapper = document.querySelector('.domain-requests');
const domainRequestsWrapper = document.querySelector('.domain-requests__table-wrapper');
if (domainRequestsWrapper) {
let currentSortBy = 'id';
let currentOrder = 'asc';
const noDomainRequestsWrapper = document.querySelector('.domain-requests__no-data');
const noSearchResultsWrapper = document.querySelector('.domain-requests__no-search-results');
let scrollToTable = false;
let currentStatus = [];
let currentSearchTerm = '';
const domainRequestsSearchInput = document.getElementById('domain-requests__search-field');
const domainRequestsSearchSubmit = document.getElementById('domain-requests__search-field-submit');
const tableHeaders = document.querySelectorAll('.domain-requests__table th[data-sortable]');
const tableAnnouncementRegion = document.querySelector('.domain-requests__table-wrapper .usa-table__announcement-region');
const resetSearchButton = document.querySelector('.domain-requests__reset-search');
const resetFiltersButton = document.querySelector('.domains__reset-filters');
const statusCheckboxes = document.querySelectorAll('input[name="filter-status"]');
const statusIndicator = document.querySelector('.filter-indicator');
const statusToggle = document.querySelector('.usa-button--filter');
const portfolioElement = document.getElementById('portfolio-js-value');
const portfolioValue = portfolioElement ? portfolioElement.getAttribute('data-portfolio') : null;
/**
* Delete is actually a POST API that requires a csrf token. The token will be waiting for us in the template as a hidden input.
* @param {*} domainRequestPk - the identifier for the request that we're deleting
* @param {*} pageToDisplay - If we're deleting the last item on a page that is not page 1, we'll need to display the previous page
*/
function deleteDomainRequest(domainRequestPk,pageToDisplay) {
// Use to debug uswds modal issues
//console.log('deleteDomainRequest')
// Get csrf token
const csrfToken = getCsrfToken();
// Create FormData object and append the CSRF token
const formData = `csrfmiddlewaretoken=${encodeURIComponent(csrfToken)}&delete-domain-request=`;
fetch(`/domain-request/${domainRequestPk}/delete`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRFToken': csrfToken,
},
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// Update data and UI
loadDomainRequests(pageToDisplay, currentSortBy, currentOrder, scrollToTable, currentSearchTerm);
})
.catch(error => console.error('Error fetching domain requests:', error));
}
// Helper function to get the CSRF token from the cookie
function getCsrfToken() {
return document.querySelector('input[name="csrfmiddlewaretoken"]').value;
}
/**
* Loads rows in the domain requests list, as well as updates pagination around the domain requests list
* Loads rows in the domains list, as well as updates pagination around the domains list
* based on the supplied attributes.
* @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 {*} scroll - control for the scrollToElement functionality
* @param {*} status - control for the status filter
* @param {*} searchTerm - the search term
* @param {*} portfolio - the portfolio id
*/
function loadDomainRequests(page, sortBy = currentSortBy, order = currentOrder, scroll = scrollToTable, searchTerm = currentSearchTerm, status = currentStatus, portfolio = portfolioValue) {
// fetch json of page of domain requests, given params
loadTable(page, sortBy = this.currentSortBy, order = this.currentOrder, scroll = this.scrollToTable, status = this.currentStatus, searchTerm = this.currentSearchTerm, portfolio = this.portfolioValue) {
let baseUrl = document.getElementById("get_domain_requests_json_url");
if (!baseUrl) {
return;
@ -1632,7 +1551,7 @@ document.addEventListener('DOMContentLoaded', function() {
}
// handle the display of proper messaging in the event that no requests exist in the list or search returns no results
updateDisplay(data, domainRequestsWrapper, noDomainRequestsWrapper, noSearchResultsWrapper, currentSearchTerm);
this.updateDisplay(data, this.tableWrapper, this.noTableWrapper, this.noSearchResultsWrapper, this.currentSearchTerm);
// identify the DOM element where the domain request list will be inserted into the DOM
const tbody = document.querySelector('.domain-requests__table tbody');
@ -1685,7 +1604,7 @@ document.addEventListener('DOMContentLoaded', function() {
let markupCreatorRow = '';
if (portfolioValue) {
if (this.portfolioValue) {
markupCreatorRow = `
<td>
<span class="text-wrap break-word">${request.creator ? request.creator : ''}</span>
@ -1780,10 +1699,10 @@ document.addEventListener('DOMContentLoaded', function() {
</div>
`
domainRequestsSectionWrapper.appendChild(modal);
this.tableWrapper.appendChild(modal);
// Request is deletable, modal and modalTrigger are built. Now check if we are on the portfolio requests page (by seeing if there is a portfolio value) and enhance the modalTrigger accordingly
if (portfolioValue) {
if (this.portfolioValue) {
modalTrigger = `
<a
role="button"
@ -1866,8 +1785,8 @@ document.addEventListener('DOMContentLoaded', function() {
modals.forEach(modal => {
const submitButton = modal.querySelector('.usa-modal__submit');
const closeButton = modal.querySelector('.usa-modal__close');
submitButton.addEventListener('click', function() {
pk = submitButton.getAttribute('data-pk');
submitButton.addEventListener('click', () => {
let pk = submitButton.getAttribute('data-pk');
// Close the modal to remove the USWDS UI local classes
closeButton.click();
// If we're deleting the last item on a page that is not page 1, we'll need to refresh the display to the previous page
@ -1875,165 +1794,95 @@ document.addEventListener('DOMContentLoaded', function() {
if (data.total == 1 && data.unfiltered_total > 1) {
pageToDisplay--;
}
deleteDomainRequest(pk, pageToDisplay);
this.deleteDomainRequest(pk, pageToDisplay);
});
});
// Do not scroll on first page load
if (scroll)
ScrollToElement('class', 'domain-requests');
scrollToTable = true;
this.scrollToTable = true;
// update the pagination after the domain requests list is updated
updatePagination(
this.updatePagination(
'domain request',
'#domain-requests-pagination',
'#domain-requests-pagination .usa-pagination__counter',
'#domain-requests',
loadDomainRequests,
data.page,
data.num_pages,
data.has_previous,
data.has_next,
data.total,
currentSearchTerm
);
currentSortBy = sortBy;
currentOrder = order;
currentSearchTerm = searchTerm;
this.currentSortBy = sortBy;
this.currentOrder = order;
this.currentSearchTerm = searchTerm;
})
.catch(error => console.error('Error fetching domain requests:', error));
}
// Add event listeners to table headers for sorting
tableHeaders.forEach(header => {
header.addEventListener('click', function() {
const sortBy = this.getAttribute('data-sortable');
let order = 'asc';
// sort order will be ascending, unless the currently sorted column is ascending, and the user
// is selecting the same column to sort in descending order
if (sortBy === currentSortBy) {
order = currentOrder === 'asc' ? 'desc' : 'asc';
/**
* Delete is actually a POST API that requires a csrf token. The token will be waiting for us in the template as a hidden input.
* @param {*} domainRequestPk - the identifier for the request that we're deleting
* @param {*} pageToDisplay - If we're deleting the last item on a page that is not page 1, we'll need to display the previous page
*/
deleteDomainRequest(domainRequestPk, pageToDisplay) {
// Use to debug uswds modal issues
//console.log('deleteDomainRequest')
// Get csrf token
const csrfToken = getCsrfToken();
// Create FormData object and append the CSRF token
const formData = `csrfmiddlewaretoken=${encodeURIComponent(csrfToken)}&delete-domain-request=`;
fetch(`/domain-request/${domainRequestPk}/delete`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRFToken': csrfToken,
},
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
loadDomainRequests(1, sortBy, order);
});
});
domainRequestsSearchSubmit.addEventListener('click', function(e) {
e.preventDefault();
currentSearchTerm = domainRequestsSearchInput.value;
// If the search is blank, we match the resetSearch functionality
if (currentSearchTerm) {
showElement(resetSearchButton);
} else {
hideElement(resetSearchButton);
// Update data and UI
this.loadTable(pageToDisplay, this.currentSortBy, this.currentOrder, this.scrollToTable, this.currentSearchTerm);
})
.catch(error => console.error('Error fetching domain requests:', error));
}
loadDomainRequests(1, 'id', 'asc');
resetHeaders();
});
}
if (statusToggle) {
statusToggle.addEventListener('click', function() {
toggleCaret(statusToggle);
});
}
// Add event listeners to status filter checkboxes
statusCheckboxes.forEach(checkbox => {
checkbox.addEventListener('change', function() {
const checkboxValue = this.value;
// Update currentStatus array based on checkbox state
if (this.checked) {
currentStatus.push(checkboxValue);
} else {
const index = currentStatus.indexOf(checkboxValue);
if (index > -1) {
currentStatus.splice(index, 1);
/**
* 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.
*
*/
document.addEventListener('DOMContentLoaded', function() {
const isDomainsPage = document.querySelector("#domains")
if (isDomainsPage){
const domainsTable = new DomainsTable();
if (domainsTable.tableWrapper) {
// Initial load
domainsTable.loadTable(1);
}
}
});
// Manage visibility of reset filters button
if (currentStatus.length == 0) {
hideElement(resetFiltersButton);
} else {
showElement(resetFiltersButton);
}
// Disable the auto scroll
scrollToTable = false;
// Call loadTable with updated status
loadDomainRequests(1, 'id', 'asc');
resetHeaders();
updateStatusIndicator();
});
});
// Reset UI and accessibility
function resetHeaders() {
tableHeaders.forEach(header => {
// unset sort UI in headers
unsetHeader(header);
});
// Reset the announcement region
tableAnnouncementRegion.innerHTML = '';
}
function resetSearch() {
domainRequestsSearchInput.value = '';
currentSearchTerm = '';
hideElement(resetSearchButton);
loadDomainRequests(1, 'id', 'asc');
resetHeaders();
}
if (resetSearchButton) {
resetSearchButton.addEventListener('click', function() {
resetSearch();
});
}
function closeMoreActionMenu(accordionThatIsOpen) {
if (accordionThatIsOpen.getAttribute("aria-expanded") === "true") {
accordionThatIsOpen.click();
}
}
function resetFilters() {
currentStatus = [];
statusCheckboxes.forEach(checkbox => {
checkbox.checked = false;
});
hideElement(resetFiltersButton);
// Disable the auto scroll
scrollToTable = false;
loadDomainRequests(1, 'id', 'asc');
resetHeaders();
updateStatusIndicator();
// No need to toggle close the filters. The focus shift will trigger that for us.
}
if (resetFiltersButton) {
resetFiltersButton.addEventListener('click', function() {
resetFilters();
});
}
function updateStatusIndicator() {
statusIndicator.innerHTML = '';
// Even if the element is empty, it'll mess up the flex layout unless we set display none
hideElement(statusIndicator);
if (currentStatus.length)
statusIndicator.innerHTML = '(' + currentStatus.length + ')';
showElement(statusIndicator);
}
function closeFilters() {
if (statusToggle.getAttribute("aria-expanded") === "true") {
statusToggle.click();
/**
* An IIFE that listens for DOM Content to be loaded, then executes. This function
* initializes the domain requests list and associated functionality on the home page of the app.
*
*/
document.addEventListener('DOMContentLoaded', function() {
const domainRequestsSectionWrapper = document.querySelector('.domain-requests');
if (domainRequestsSectionWrapper) {
const domainRequestsTable = new DomainRequestsTable();
if (domainRequestsTable.tableWrapper) {
domainRequestsTable.loadTable(1);
}
}
@ -2045,6 +1894,12 @@ document.addEventListener('DOMContentLoaded', function() {
closeOpenAccordions(event);
});
function closeMoreActionMenu(accordionThatIsOpen) {
if (accordionThatIsOpen.getAttribute("aria-expanded") === "true") {
accordionThatIsOpen.click();
}
}
function closeOpenAccordions(event) {
const openAccordions = document.querySelectorAll('.usa-button--more-actions[aria-expanded="true"]');
openAccordions.forEach((openAccordionButton) => {
@ -2055,22 +1910,23 @@ document.addEventListener('DOMContentLoaded', function() {
closeMoreActionMenu(openAccordionButton);
}
});
// Close the filter accordion
const openFilterAccordion = document.querySelector('.usa-button--filter[aria-expanded="true"]');
const moreFilterAccordion = openFilterAccordion ? openFilterAccordion.closest('.usa-accordion--select') : undefined;
if (openFilterAccordion) {
if (!moreFilterAccordion.contains(event.target)) {
closeFilters();
}
}
}
// Initial load
loadDomainRequests(1);
}
});
const utcDateString = (dateString) => {
const date = new Date(dateString);
const utcYear = date.getUTCFullYear();
const utcMonth = date.toLocaleString('en-US', { month: 'short', timeZone: 'UTC' });
const utcDay = date.getUTCDate().toString().padStart(2, '0');
let utcHours = date.getUTCHours();
const utcMinutes = date.getUTCMinutes().toString().padStart(2, '0');
const ampm = utcHours >= 12 ? 'PM' : 'AM';
utcHours = utcHours % 12 || 12; // Convert to 12-hour format, '0' hours should be '12'
return `${utcMonth} ${utcDay}, ${utcYear}, ${utcHours}:${utcMinutes} ${ampm} UTC`;
};
/**
* An IIFE that displays confirmation modal on the user profile page

View file

@ -160,7 +160,7 @@
</div>
<button
type="button"
class="usa-button usa-button--small padding--8-12-9-12 usa-button--outline usa-button--filter domains__reset-filters display-none"
class="usa-button usa-button--small padding--8-12-9-12 usa-button--outline usa-button--filter domain-requests__reset-filters display-none"
>
Clear filters
<svg class="usa-icon top-1px" aria-hidden="true" focusable="false" role="img" width="24">