diff --git a/src/registrar/assets/modules/combobox.js b/src/registrar/assets/modules/combobox.js
index 5f51bc5ee..139106c59 100644
--- a/src/registrar/assets/modules/combobox.js
+++ b/src/registrar/assets/modules/combobox.js
@@ -1,122 +1,115 @@
-import { hideElement, showElement, scrollToElement, toggleCaret } from './helpers.js';
-import { initializeTooltips, initializeModals, unloadModals } from './helpers-uswds.js';
+import { hideElement, showElement } from './helpers.js';
-/**
- * An IIFE that changes the default clear behavior on comboboxes to the input field.
- * We want the search bar to act soley as a search bar.
- */
export function loadInitialValuesForComboBoxes() {
- var overrideDefaultClearButton = true;
- var isTyping = false;
-
- document.addEventListener('DOMContentLoaded', (event) => {
- handleAllComboBoxElements();
- });
-
- function handleAllComboBoxElements() {
- const comboBoxElements = document.querySelectorAll(".usa-combo-box");
- comboBoxElements.forEach(comboBox => {
- const input = comboBox.querySelector("input");
- const select = comboBox.querySelector("select");
- if (!input || !select) {
- console.warn("No combobox element found");
- return;
- }
- // Set the initial value of the combobox
- let initialValue = select.getAttribute("data-default-value");
- let clearInputButton = comboBox.querySelector(".usa-combo-box__clear-input");
- if (!clearInputButton) {
- console.warn("No clear element found");
- return;
- }
-
- // Override the default clear button behavior such that it no longer clears the input,
- // it just resets to the data-initial-value.
-
- // Due to the nature of how uswds works, this is slightly hacky.
-
- // Use a MutationObserver to watch for changes in the dropdown list
- const dropdownList = comboBox.querySelector(`#${input.id}--list`);
- const observer = new MutationObserver(function(mutations) {
- mutations.forEach(function(mutation) {
- if (mutation.type === "childList") {
- addBlankOption(clearInputButton, dropdownList, initialValue);
+ var overrideDefaultClearButton = true;
+ var isTyping = false;
+
+ document.addEventListener('DOMContentLoaded', (event) => {
+ handleAllComboBoxElements();
+ });
+
+ function handleAllComboBoxElements() {
+ const comboBoxElements = document.querySelectorAll(".usa-combo-box");
+ comboBoxElements.forEach(comboBox => {
+ const input = comboBox.querySelector("input");
+ const select = comboBox.querySelector("select");
+ if (!input || !select) {
+ console.warn("No combobox element found");
+ return;
+ }
+ // Set the initial value of the combobox
+ let initialValue = select.getAttribute("data-default-value");
+ let clearInputButton = comboBox.querySelector(".usa-combo-box__clear-input");
+ if (!clearInputButton) {
+ console.warn("No clear element found");
+ return;
+ }
+
+ // Override the default clear button behavior such that it no longer clears the input,
+ // it just resets to the data-initial-value.
+
+ // Due to the nature of how uswds works, this is slightly hacky.
+
+ // Use a MutationObserver to watch for changes in the dropdown list
+ const dropdownList = comboBox.querySelector(`#${input.id}--list`);
+ const observer = new MutationObserver(function(mutations) {
+ mutations.forEach(function(mutation) {
+ if (mutation.type === "childList") {
+ addBlankOption(clearInputButton, dropdownList, initialValue);
+ }
+ });
+ });
+
+ // Configure the observer to watch for changes in the dropdown list
+ const config = { childList: true, subtree: true };
+ observer.observe(dropdownList, config);
+
+ // Input event listener to detect typing
+ input.addEventListener("input", () => {
+ isTyping = true;
+ });
+
+ // Blur event listener to reset typing state
+ input.addEventListener("blur", () => {
+ isTyping = false;
+ });
+
+ // Hide the reset button when there is nothing to reset.
+ // Do this once on init, then everytime a change occurs.
+ updateClearButtonVisibility(select, initialValue, clearInputButton)
+ select.addEventListener("change", () => {
+ updateClearButtonVisibility(select, initialValue, clearInputButton)
+ });
+
+ // Change the default input behaviour - have it reset to the data default instead
+ clearInputButton.addEventListener("click", (e) => {
+ if (overrideDefaultClearButton && initialValue) {
+ e.preventDefault();
+ e.stopPropagation();
+ input.click();
+ // Find the dropdown option with the desired value
+ const dropdownOptions = document.querySelectorAll(".usa-combo-box__list-option");
+ if (dropdownOptions) {
+ dropdownOptions.forEach(option => {
+ if (option.getAttribute("data-value") === initialValue) {
+ // Simulate a click event on the dropdown option
+ option.click();
}
});
- });
-
- // Configure the observer to watch for changes in the dropdown list
- const config = { childList: true, subtree: true };
- observer.observe(dropdownList, config);
-
- // Input event listener to detect typing
- input.addEventListener("input", () => {
- isTyping = true;
- });
-
- // Blur event listener to reset typing state
- input.addEventListener("blur", () => {
- isTyping = false;
- });
-
- // Hide the reset button when there is nothing to reset.
- // Do this once on init, then everytime a change occurs.
- updateClearButtonVisibility(select, initialValue, clearInputButton)
- select.addEventListener("change", () => {
- updateClearButtonVisibility(select, initialValue, clearInputButton)
- });
-
- // Change the default input behaviour - have it reset to the data default instead
- clearInputButton.addEventListener("click", (e) => {
- if (overrideDefaultClearButton && initialValue) {
- e.preventDefault();
- e.stopPropagation();
- input.click();
- // Find the dropdown option with the desired value
- const dropdownOptions = document.querySelectorAll(".usa-combo-box__list-option");
- if (dropdownOptions) {
- dropdownOptions.forEach(option => {
- if (option.getAttribute("data-value") === initialValue) {
- // Simulate a click event on the dropdown option
- option.click();
- }
- });
- }
}
- });
+ }
});
- }
-
- function updateClearButtonVisibility(select, initialValue, clearInputButton) {
- if (select.value === initialValue) {
- hideElement(clearInputButton);
- }else {
- showElement(clearInputButton)
- }
- }
-
- function addBlankOption(clearInputButton, dropdownList, initialValue) {
- if (dropdownList && !dropdownList.querySelector('[data-value=""]') && !isTyping) {
- const blankOption = document.createElement("li");
- blankOption.setAttribute("role", "option");
- blankOption.setAttribute("data-value", "");
- blankOption.classList.add("usa-combo-box__list-option");
- if (!initialValue){
- blankOption.classList.add("usa-combo-box__list-option--selected")
- }
- blankOption.textContent = "⎯";
-
- dropdownList.insertBefore(blankOption, dropdownList.firstChild);
- blankOption.addEventListener("click", (e) => {
- e.preventDefault();
- e.stopPropagation();
- overrideDefaultClearButton = false;
- // Trigger the default clear behavior
- clearInputButton.click();
- overrideDefaultClearButton = true;
- });
- }
+ });
+ }
+
+ function updateClearButtonVisibility(select, initialValue, clearInputButton) {
+ if (select.value === initialValue) {
+ hideElement(clearInputButton);
+ }else {
+ showElement(clearInputButton)
}
}
+ function addBlankOption(clearInputButton, dropdownList, initialValue) {
+ if (dropdownList && !dropdownList.querySelector('[data-value=""]') && !isTyping) {
+ const blankOption = document.createElement("li");
+ blankOption.setAttribute("role", "option");
+ blankOption.setAttribute("data-value", "");
+ blankOption.classList.add("usa-combo-box__list-option");
+ if (!initialValue){
+ blankOption.classList.add("usa-combo-box__list-option--selected")
+ }
+ blankOption.textContent = "⎯";
+ dropdownList.insertBefore(blankOption, dropdownList.firstChild);
+ blankOption.addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ overrideDefaultClearButton = false;
+ // Trigger the default clear behavior
+ clearInputButton.click();
+ overrideDefaultClearButton = true;
+ });
+ }
+ }
+}
diff --git a/src/registrar/assets/modules/domain-validators.js b/src/registrar/assets/modules/domain-validators.js
index 5a09a06a2..368a1504c 100644
--- a/src/registrar/assets/modules/domain-validators.js
+++ b/src/registrar/assets/modules/domain-validators.js
@@ -1,10 +1,4 @@
-// <<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>
-// Event handlers.
-
var DEFAULT_ERROR = "Please check this field for errors.";
-
-var INFORMATIVE = "info";
-var WARNING = "warning";
var ERROR = "error";
var SUCCESS = "success";
diff --git a/src/registrar/assets/modules/formset-forms.js b/src/registrar/assets/modules/formset-forms.js
index bbce0919a..6743df7f6 100644
--- a/src/registrar/assets/modules/formset-forms.js
+++ b/src/registrar/assets/modules/formset-forms.js
@@ -415,4 +415,4 @@ export function nameserversFormListener() {
});
}
}
-}
\ No newline at end of file
+}
diff --git a/src/registrar/assets/modules/get-csrf-token.js b/src/registrar/assets/modules/helpers-csrf-token.js
similarity index 100%
rename from src/registrar/assets/modules/get-csrf-token.js
rename to src/registrar/assets/modules/helpers-csrf-token.js
diff --git a/src/registrar/assets/modules/helpers-uswds.js b/src/registrar/assets/modules/helpers-uswds.js
index 33dd2b076..e182d65c0 100644
--- a/src/registrar/assets/modules/helpers-uswds.js
+++ b/src/registrar/assets/modules/helpers-uswds.js
@@ -40,4 +40,4 @@ export function initializeModals() {
*/
export function unloadModals() {
window.modal.off();
-}
\ No newline at end of file
+}
diff --git a/src/registrar/assets/modules/main.js b/src/registrar/assets/modules/main.js
index d4e171bf6..f99ef188c 100644
--- a/src/registrar/assets/modules/main.js
+++ b/src/registrar/assets/modules/main.js
@@ -1,7 +1,4 @@
-import { hideElement, showElement, scrollToElement, toggleCaret } from './helpers.js';
-import { initializeTooltips, initializeModals, unloadModals } from './helpers-uswds.js';
-import { hookupYesNoListener, hookupRadioTogglerListener } from './helpers-radios.js';
-import { getCsrfToken } from './get-csrf-token.js';
+import { hookupYesNoListener, hookupRadioTogglerListener } from './radios.js';
import { initDomainValidators } from './domain-validators.js';
import { initFormsetsForms, triggerModalOnDsDataForm, nameserversFormListener } from './formset-forms.js';
import { initializeUrbanizationToggle } from './urbanization.js';
@@ -43,6 +40,3 @@ initDomainsTable();
initDomainRequestsTable();
initMembersTable();
initMemberDomainsTable();
-
-
-
diff --git a/src/registrar/assets/modules/helpers-radios.js b/src/registrar/assets/modules/radios.js
similarity index 100%
rename from src/registrar/assets/modules/helpers-radios.js
rename to src/registrar/assets/modules/radios.js
diff --git a/src/registrar/assets/modules/requesting-entity.js b/src/registrar/assets/modules/requesting-entity.js
index e9be29ad4..1b54f2660 100644
--- a/src/registrar/assets/modules/requesting-entity.js
+++ b/src/registrar/assets/modules/requesting-entity.js
@@ -1,5 +1,5 @@
-import { hideElement, showElement, scrollToElement, toggleCaret } from './helpers.js';
-import { initializeTooltips, initializeModals, unloadModals } from './helpers-uswds.js';
+import { hideElement, showElement } from './helpers.js';
+
/** An IIFE that intializes the requesting entity page.
* This page has a radio button that dynamically toggles some fields
* Within that, the dropdown also toggles some additional form elements.
@@ -20,27 +20,27 @@ export function handleRequestingEntityFieldset() {
var requestingNewSuborganization = document.getElementById(`id_${formPrefix}-is_requesting_new_suborganization`);
function toggleSuborganization(radio=null) {
- if (radio != null) requestingSuborganization = radio?.checked && radio.value === "True";
- requestingSuborganization ? showElement(suborgContainer) : hideElement(suborgContainer);
- requestingNewSuborganization.value = requestingSuborganization && select.value === "other" ? "True" : "False";
- requestingNewSuborganization.value === "True" ? showElement(suborgDetailsContainer) : hideElement(suborgDetailsContainer);
+ if (radio != null) requestingSuborganization = radio?.checked && radio.value === "True";
+ requestingSuborganization ? showElement(suborgContainer) : hideElement(suborgContainer);
+ requestingNewSuborganization.value = requestingSuborganization && select.value === "other" ? "True" : "False";
+ requestingNewSuborganization.value === "True" ? showElement(suborgDetailsContainer) : hideElement(suborgDetailsContainer);
}
// Add fake "other" option to sub_organization select
if (select && !Array.from(select.options).some(option => option.value === "other")) {
- select.add(new Option("Other (enter your organization manually)", "other"));
+ select.add(new Option("Other (enter your organization manually)", "other"));
}
if (requestingNewSuborganization.value === "True") {
- select.value = "other";
+ select.value = "other";
}
// Add event listener to is_suborganization radio buttons, and run for initial display
toggleSuborganization();
radios.forEach(radio => {
- radio.addEventListener("click", () => toggleSuborganization(radio));
+ radio.addEventListener("click", () => toggleSuborganization(radio));
});
// Add event listener to the suborg dropdown to show/hide the suborg details section
select.addEventListener("change", () => toggleSuborganization());
- }
\ No newline at end of file
+}
diff --git a/src/registrar/assets/modules/table-base.js b/src/registrar/assets/modules/table-base.js
index 74649b944..ed9cdc655 100644
--- a/src/registrar/assets/modules/table-base.js
+++ b/src/registrar/assets/modules/table-base.js
@@ -1,359 +1,358 @@
import { hideElement, showElement, toggleCaret } from './helpers.js';
-import { initializeTooltips, initializeModals, unloadModals } from './helpers-uswds.js';
export class LoadTableBase {
- constructor(sectionSelector) {
- this.tableWrapper = document.getElementById(`${sectionSelector}__table-wrapper`);
- this.tableHeaders = document.querySelectorAll(`#${sectionSelector} th[data-sortable]`);
- this.currentSortBy = 'id';
- this.currentOrder = 'asc';
- this.currentStatus = [];
- this.currentSearchTerm = '';
- this.scrollToTable = false;
- this.searchInput = document.getElementById(`${sectionSelector}__search-field`);
- this.searchSubmit = document.getElementById(`${sectionSelector}__search-field-submit`);
- this.tableAnnouncementRegion = document.getElementById(`${sectionSelector}__usa-table__announcement-region`);
- this.resetSearchButton = document.getElementById(`${sectionSelector}__reset-search`);
- this.resetFiltersButton = document.getElementById(`${sectionSelector}__reset-filters`);
- this.statusCheckboxes = document.querySelectorAll(`.${sectionSelector} input[name="filter-status"]`);
- this.statusIndicator = document.getElementById(`${sectionSelector}__filter-indicator`);
- this.statusToggle = document.getElementById(`${sectionSelector}__usa-button--filter`);
- this.noTableWrapper = document.getElementById(`${sectionSelector}__no-data`);
- this.noSearchResultsWrapper = document.getElementById(`${sectionSelector}__no-search-results`);
- this.portfolioElement = document.getElementById('portfolio-js-value');
- this.portfolioValue = this.portfolioElement ? this.portfolioElement.getAttribute('data-portfolio') : null;
- this.initializeTableHeaders();
- this.initializeSearchHandler();
- this.initializeStatusToggleHandler();
- this.initializeFilterCheckboxes();
- this.initializeResetSearchButton();
- this.initializeResetFiltersButton();
- this.initializeAccordionAccessibilityListeners();
+ constructor(sectionSelector) {
+ this.tableWrapper = document.getElementById(`${sectionSelector}__table-wrapper`);
+ this.tableHeaders = document.querySelectorAll(`#${sectionSelector} th[data-sortable]`);
+ this.currentSortBy = 'id';
+ this.currentOrder = 'asc';
+ this.currentStatus = [];
+ this.currentSearchTerm = '';
+ this.scrollToTable = false;
+ this.searchInput = document.getElementById(`${sectionSelector}__search-field`);
+ this.searchSubmit = document.getElementById(`${sectionSelector}__search-field-submit`);
+ this.tableAnnouncementRegion = document.getElementById(`${sectionSelector}__usa-table__announcement-region`);
+ this.resetSearchButton = document.getElementById(`${sectionSelector}__reset-search`);
+ this.resetFiltersButton = document.getElementById(`${sectionSelector}__reset-filters`);
+ this.statusCheckboxes = document.querySelectorAll(`.${sectionSelector} input[name="filter-status"]`);
+ this.statusIndicator = document.getElementById(`${sectionSelector}__filter-indicator`);
+ this.statusToggle = document.getElementById(`${sectionSelector}__usa-button--filter`);
+ this.noTableWrapper = document.getElementById(`${sectionSelector}__no-data`);
+ this.noSearchResultsWrapper = document.getElementById(`${sectionSelector}__no-search-results`);
+ this.portfolioElement = document.getElementById('portfolio-js-value');
+ this.portfolioValue = this.portfolioElement ? this.portfolioElement.getAttribute('data-portfolio') : null;
+ this.initializeTableHeaders();
+ this.initializeSearchHandler();
+ this.initializeStatusToggleHandler();
+ this.initializeFilterCheckboxes();
+ this.initializeResetSearchButton();
+ this.initializeResetFiltersButton();
+ this.initializeAccordionAccessibilityListeners();
+ }
+
+ /**
+ * 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} tableSelector - CSS selector for the header element to anchor the links to.
+ * @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} total - The total number of items.
+ */
+ updatePagination(
+ itemName,
+ paginationSelector,
+ counterSelector,
+ parentTableSelector,
+ currentPage,
+ numPages,
+ hasPrevious,
+ hasNext,
+ totalItems,
+ ) {
+ const paginationButtons = document.querySelector(`${paginationSelector} .usa-pagination__list`);
+ const counterSelectorEl = document.querySelector(counterSelector);
+ const paginationSelectorEl = document.querySelector(paginationSelector);
+ counterSelectorEl.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
+ paginationSelectorEl.classList.toggle('display-none', totalItems < 1);
+
+ counterSelectorEl.innerHTML = `${totalItems} ${itemName}${totalItems > 1 ? 's' : ''}${this.currentSearchTerm ? ' for ' + '"' + this.currentSearchTerm + '"' : ''}`;
+
+ 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();
+ this.loadTable(currentPage - 1);
+ });
+ paginationButtons.appendChild(prevPageItem);
}
-
- /**
- * 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} tableSelector - CSS selector for the header element to anchor the links to.
- * @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} total - The total number of items.
- */
- updatePagination(
- itemName,
- paginationSelector,
- counterSelector,
- parentTableSelector,
- currentPage,
- numPages,
- hasPrevious,
- hasNext,
- totalItems,
- ) {
- const paginationButtons = document.querySelector(`${paginationSelector} .usa-pagination__list`);
- const counterSelectorEl = document.querySelector(counterSelector);
- const paginationSelectorEl = document.querySelector(paginationSelector);
- counterSelectorEl.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
- paginationSelectorEl.classList.toggle('display-none', totalItems < 1);
-
- counterSelectorEl.innerHTML = `${totalItems} ${itemName}${totalItems > 1 ? 's' : ''}${this.currentSearchTerm ? ' for ' + '"' + this.currentSearchTerm + '"' : ''}`;
-
- 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();
- this.loadTable(currentPage - 1);
- });
- paginationButtons.appendChild(prevPageItem);
- }
-
- // Add first page and ellipsis if necessary
- if (currentPage > 2) {
- paginationButtons.appendChild(this.createPageItem(1, parentTableSelector, currentPage));
- if (currentPage > 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);
- }
- }
-
- // Add pages around the current page
- for (let i = Math.max(1, currentPage - 1); i <= Math.min(numPages, currentPage + 1); i++) {
- paginationButtons.appendChild(this.createPageItem(i, parentTableSelector, currentPage));
- }
-
- // Add last page and ellipsis if necessary
- if (currentPage < numPages - 1) {
- if (currentPage < numPages - 2) {
- 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(this.createPageItem(numPages, parentTableSelector, currentPage));
- }
-
- 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();
- this.loadTable(currentPage + 1);
- });
- paginationButtons.appendChild(nextPageItem);
+
+ // Add first page and ellipsis if necessary
+ if (currentPage > 2) {
+ paginationButtons.appendChild(this.createPageItem(1, parentTableSelector, currentPage));
+ if (currentPage > 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);
}
}
-
- /**
- * A helper that toggles content/ no content/ no search results
- *
- */
- updateDisplay = (data, dataWrapper, noDataWrapper, noSearchResultsWrapper) => {
- const { unfiltered_total, total } = data;
- if (unfiltered_total) {
- if (total) {
- showElement(dataWrapper);
- hideElement(noSearchResultsWrapper);
- hideElement(noDataWrapper);
- } else {
- hideElement(dataWrapper);
- showElement(noSearchResultsWrapper);
- hideElement(noDataWrapper);
- }
+
+ // Add pages around the current page
+ for (let i = Math.max(1, currentPage - 1); i <= Math.min(numPages, currentPage + 1); i++) {
+ paginationButtons.appendChild(this.createPageItem(i, parentTableSelector, currentPage));
+ }
+
+ // Add last page and ellipsis if necessary
+ if (currentPage < numPages - 1) {
+ if (currentPage < numPages - 2) {
+ 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(this.createPageItem(numPages, parentTableSelector, currentPage));
+ }
+
+ 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();
+ this.loadTable(currentPage + 1);
+ });
+ paginationButtons.appendChild(nextPageItem);
+ }
+ }
+
+ /**
+ * A helper that toggles content/ no content/ no search results
+ *
+ */
+ updateDisplay = (data, dataWrapper, noDataWrapper, noSearchResultsWrapper) => {
+ const { unfiltered_total, total } = data;
+ if (unfiltered_total) {
+ if (total) {
+ showElement(dataWrapper);
+ hideElement(noSearchResultsWrapper);
+ hideElement(noDataWrapper);
} else {
hideElement(dataWrapper);
- hideElement(noSearchResultsWrapper);
- showElement(noDataWrapper);
+ showElement(noSearchResultsWrapper);
+ hideElement(noDataWrapper);
}
- };
-
- // Helper function to create a page item
- createPageItem(page, parentTableSelector, currentPage) {
- 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();
- this.loadTable(page);
- });
- return pageItem;
+ } else {
+ hideElement(dataWrapper);
+ hideElement(noSearchResultsWrapper);
+ showElement(noDataWrapper);
}
-
- /**
- * A helper that resets sortable table headers
- *
- */
- unsetHeader = (header) => {
- header.removeAttribute('aria-sort');
- let headerName = header.innerText;
- const headerLabel = `${headerName}, sortable column, currently unsorted"`;
- const headerButtonLabel = `Click to sort by ascending order.`;
- header.setAttribute("aria-label", headerLabel);
- header.querySelector('.usa-table__header__button').setAttribute("title", headerButtonLabel);
- };
-
- // Abstract method (to be implemented in the child class)
- loadTable(page, sortBy, order) {
- throw new Error('loadData() must be implemented in a subclass');
+ };
+
+ // Helper function to create a page item
+ createPageItem(page, parentTableSelector, currentPage) {
+ 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');
}
-
- // Add event listeners to table headers for sorting
- initializeTableHeaders() {
- this.tableHeaders.forEach(header => {
- header.addEventListener('click', () => {
- const sortBy = header.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 === this.currentSortBy) {
- order = this.currentOrder === 'asc' ? 'desc' : 'asc';
- }
- // load the results with the updated sort
- this.loadTable(1, sortBy, order);
- });
- });
- }
-
- initializeSearchHandler() {
- this.searchSubmit.addEventListener('click', (e) => {
- e.preventDefault();
- this.currentSearchTerm = this.searchInput.value;
- // If the search is blank, we match the resetSearch functionality
- if (this.currentSearchTerm) {
- showElement(this.resetSearchButton);
- } else {
- hideElement(this.resetSearchButton);
+ pageItem.querySelector('a').addEventListener('click', (event) => {
+ event.preventDefault();
+ this.loadTable(page);
+ });
+ return pageItem;
+ }
+
+ /**
+ * A helper that resets sortable table headers
+ *
+ */
+ unsetHeader = (header) => {
+ header.removeAttribute('aria-sort');
+ let headerName = header.innerText;
+ const headerLabel = `${headerName}, sortable column, currently unsorted"`;
+ const headerButtonLabel = `Click to sort by ascending order.`;
+ header.setAttribute("aria-label", headerLabel);
+ header.querySelector('.usa-table__header__button').setAttribute("title", headerButtonLabel);
+ };
+
+ // Abstract method (to be implemented in the child class)
+ loadTable(page, sortBy, order) {
+ throw new Error('loadData() must be implemented in a subclass');
+ }
+
+ // Add event listeners to table headers for sorting
+ initializeTableHeaders() {
+ this.tableHeaders.forEach(header => {
+ header.addEventListener('click', () => {
+ const sortBy = header.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 === this.currentSortBy) {
+ order = this.currentOrder === 'asc' ? 'desc' : 'asc';
}
- this.loadTable(1, 'id', 'asc');
- this.resetHeaders();
+ // load the results with the updated sort
+ this.loadTable(1, sortBy, order);
});
- }
-
- initializeStatusToggleHandler() {
- if (this.statusToggle) {
- this.statusToggle.addEventListener('click', () => {
- toggleCaret(this.statusToggle);
- });
+ });
+ }
+
+ initializeSearchHandler() {
+ this.searchSubmit.addEventListener('click', (e) => {
+ e.preventDefault();
+ this.currentSearchTerm = this.searchInput.value;
+ // If the search is blank, we match the resetSearch functionality
+ if (this.currentSearchTerm) {
+ showElement(this.resetSearchButton);
+ } else {
+ hideElement(this.resetSearchButton);
}
- }
-
- // Add event listeners to status filter checkboxes
- initializeFilterCheckboxes() {
- this.statusCheckboxes.forEach(checkbox => {
- checkbox.addEventListener('change', () => {
- const checkboxValue = checkbox.value;
-
- // Update currentStatus array based on checkbox state
- if (checkbox.checked) {
- this.currentStatus.push(checkboxValue);
- } else {
- const index = this.currentStatus.indexOf(checkboxValue);
- if (index > -1) {
- this.currentStatus.splice(index, 1);
- }
- }
-
- // Manage visibility of reset filters button
- if (this.currentStatus.length == 0) {
- hideElement(this.resetFiltersButton);
- } else {
- showElement(this.resetFiltersButton);
- }
-
- // Disable the auto scroll
- this.scrollToTable = false;
-
- // Call loadTable with updated status
- this.loadTable(1, 'id', 'asc');
- this.resetHeaders();
- this.updateStatusIndicator();
- });
- });
- }
-
- // Reset UI and accessibility
- resetHeaders() {
- this.tableHeaders.forEach(header => {
- // Unset sort UI in headers
- this.unsetHeader(header);
- });
- // Reset the announcement region
- this.tableAnnouncementRegion.innerHTML = '';
- }
-
- resetSearch() {
- this.searchInput.value = '';
- this.currentSearchTerm = '';
- hideElement(this.resetSearchButton);
this.loadTable(1, 'id', 'asc');
this.resetHeaders();
- }
-
- initializeResetSearchButton() {
- if (this.resetSearchButton) {
- this.resetSearchButton.addEventListener('click', () => {
- this.resetSearch();
- });
- }
- }
-
- resetFilters() {
- this.currentStatus = [];
- this.statusCheckboxes.forEach(checkbox => {
- checkbox.checked = false;
- });
- hideElement(this.resetFiltersButton);
-
- // Disable the auto scroll
- this.scrollToTable = false;
-
- this.loadTable(1, 'id', 'asc');
- this.resetHeaders();
- this.updateStatusIndicator();
- // No need to toggle close the filters. The focus shift will trigger that for us.
- }
-
- initializeResetFiltersButton() {
- if (this.resetFiltersButton) {
- this.resetFiltersButton.addEventListener('click', () => {
- this.resetFilters();
- });
- }
- }
-
- updateStatusIndicator() {
- this.statusIndicator.innerHTML = '';
- // Even if the element is empty, it'll mess up the flex layout unless we set display none
- hideElement(this.statusIndicator);
- if (this.currentStatus.length)
- this.statusIndicator.innerHTML = '(' + this.currentStatus.length + ')';
- showElement(this.statusIndicator);
- }
-
- closeFilters() {
- if (this.statusToggle.getAttribute("aria-expanded") === "true") {
- this.statusToggle.click();
- }
- }
-
- initializeAccordionAccessibilityListeners() {
- // Instead of managing the toggle/close on the filter buttons in all edge cases (user clicks on search, user clicks on ANOTHER filter,
- // user clicks on main nav...) we add a listener and close the filters whenever the focus shifts out of the dropdown menu/filter button.
- // NOTE: We may need to evolve this as we add more filters.
- document.addEventListener('focusin', (event) => {
- const accordion = document.querySelector('.usa-accordion--select');
- const accordionThatIsOpen = document.querySelector('.usa-button--filter[aria-expanded="true"]');
-
- if (accordionThatIsOpen && !accordion.contains(event.target)) {
- this.closeFilters();
- }
- });
-
- // Close when user clicks outside
- // NOTE: We may need to evolve this as we add more filters.
- document.addEventListener('click', (event) => {
- const accordion = document.querySelector('.usa-accordion--select');
- const accordionThatIsOpen = document.querySelector('.usa-button--filter[aria-expanded="true"]');
-
- if (accordionThatIsOpen && !accordion.contains(event.target)) {
- this.closeFilters();
- }
+ });
+ }
+
+ initializeStatusToggleHandler() {
+ if (this.statusToggle) {
+ this.statusToggle.addEventListener('click', () => {
+ toggleCaret(this.statusToggle);
});
}
}
+
+ // Add event listeners to status filter checkboxes
+ initializeFilterCheckboxes() {
+ this.statusCheckboxes.forEach(checkbox => {
+ checkbox.addEventListener('change', () => {
+ const checkboxValue = checkbox.value;
+
+ // Update currentStatus array based on checkbox state
+ if (checkbox.checked) {
+ this.currentStatus.push(checkboxValue);
+ } else {
+ const index = this.currentStatus.indexOf(checkboxValue);
+ if (index > -1) {
+ this.currentStatus.splice(index, 1);
+ }
+ }
+
+ // Manage visibility of reset filters button
+ if (this.currentStatus.length == 0) {
+ hideElement(this.resetFiltersButton);
+ } else {
+ showElement(this.resetFiltersButton);
+ }
+
+ // Disable the auto scroll
+ this.scrollToTable = false;
+
+ // Call loadTable with updated status
+ this.loadTable(1, 'id', 'asc');
+ this.resetHeaders();
+ this.updateStatusIndicator();
+ });
+ });
+ }
+
+ // Reset UI and accessibility
+ resetHeaders() {
+ this.tableHeaders.forEach(header => {
+ // Unset sort UI in headers
+ this.unsetHeader(header);
+ });
+ // Reset the announcement region
+ this.tableAnnouncementRegion.innerHTML = '';
+ }
+
+ resetSearch() {
+ this.searchInput.value = '';
+ this.currentSearchTerm = '';
+ hideElement(this.resetSearchButton);
+ this.loadTable(1, 'id', 'asc');
+ this.resetHeaders();
+ }
+
+ initializeResetSearchButton() {
+ if (this.resetSearchButton) {
+ this.resetSearchButton.addEventListener('click', () => {
+ this.resetSearch();
+ });
+ }
+ }
+
+ resetFilters() {
+ this.currentStatus = [];
+ this.statusCheckboxes.forEach(checkbox => {
+ checkbox.checked = false;
+ });
+ hideElement(this.resetFiltersButton);
+
+ // Disable the auto scroll
+ this.scrollToTable = false;
+
+ this.loadTable(1, 'id', 'asc');
+ this.resetHeaders();
+ this.updateStatusIndicator();
+ // No need to toggle close the filters. The focus shift will trigger that for us.
+ }
+
+ initializeResetFiltersButton() {
+ if (this.resetFiltersButton) {
+ this.resetFiltersButton.addEventListener('click', () => {
+ this.resetFilters();
+ });
+ }
+ }
+
+ updateStatusIndicator() {
+ this.statusIndicator.innerHTML = '';
+ // Even if the element is empty, it'll mess up the flex layout unless we set display none
+ hideElement(this.statusIndicator);
+ if (this.currentStatus.length)
+ this.statusIndicator.innerHTML = '(' + this.currentStatus.length + ')';
+ showElement(this.statusIndicator);
+ }
+
+ closeFilters() {
+ if (this.statusToggle.getAttribute("aria-expanded") === "true") {
+ this.statusToggle.click();
+ }
+ }
+
+ initializeAccordionAccessibilityListeners() {
+ // Instead of managing the toggle/close on the filter buttons in all edge cases (user clicks on search, user clicks on ANOTHER filter,
+ // user clicks on main nav...) we add a listener and close the filters whenever the focus shifts out of the dropdown menu/filter button.
+ // NOTE: We may need to evolve this as we add more filters.
+ document.addEventListener('focusin', (event) => {
+ const accordion = document.querySelector('.usa-accordion--select');
+ const accordionThatIsOpen = document.querySelector('.usa-button--filter[aria-expanded="true"]');
+
+ if (accordionThatIsOpen && !accordion.contains(event.target)) {
+ this.closeFilters();
+ }
+ });
+
+ // Close when user clicks outside
+ // NOTE: We may need to evolve this as we add more filters.
+ document.addEventListener('click', (event) => {
+ const accordion = document.querySelector('.usa-accordion--select');
+ const accordionThatIsOpen = document.querySelector('.usa-button--filter[aria-expanded="true"]');
+
+ if (accordionThatIsOpen && !accordion.contains(event.target)) {
+ this.closeFilters();
+ }
+ });
+ }
+}
diff --git a/src/registrar/assets/modules/table-domain-requests.js b/src/registrar/assets/modules/table-domain-requests.js
index 53af5d619..55b2c951c 100644
--- a/src/registrar/assets/modules/table-domain-requests.js
+++ b/src/registrar/assets/modules/table-domain-requests.js
@@ -1,6 +1,6 @@
-import { hideElement, showElement, scrollToElement, toggleCaret } from './helpers.js';
-import { initializeTooltips, initializeModals, unloadModals } from './helpers-uswds.js';
-import { getCsrfToken } from './get-csrf-token.js';
+import { hideElement, showElement, scrollToElement } from './helpers.js';
+import { initializeModals, unloadModals } from './helpers-uswds.js';
+import { getCsrfToken } from './helpers-csrf-token.js';
import { LoadTableBase } from './table-base.js';
@@ -22,381 +22,375 @@ const utcDateString = (dateString) => {
export class DomainRequestsTable extends LoadTableBase {
- constructor() {
- super('domain-requests');
- }
-
- toggleExportButton(requests) {
- const exportButton = document.getElementById('export-csv');
- if (exportButton) {
- if (requests.length > 0) {
- showElement(exportButton);
- } else {
- hideElement(exportButton);
- }
- }
+ constructor() {
+ super('domain-requests');
}
- /**
- * 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
- */
- 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;
- }
-
- let baseUrlValue = baseUrl.innerHTML;
- if (!baseUrlValue) {
- return;
- }
-
- // add searchParams
- let searchParams = new URLSearchParams(
- {
- "page": page,
- "sort_by": sortBy,
- "order": order,
- "status": status,
- "search_term": searchTerm
+ toggleExportButton(requests) {
+ const exportButton = document.getElementById('export-csv');
+ if (exportButton) {
+ if (requests.length > 0) {
+ showElement(exportButton);
+ } else {
+ hideElement(exportButton);
}
- );
- if (portfolio)
- searchParams.append("portfolio", portfolio)
-
- let url = `${baseUrlValue}?${searchParams.toString()}`
- fetch(url)
- .then(response => response.json())
- .then(data => {
- if (data.error) {
- console.error('Error in AJAX call: ' + data.error);
- return;
+ }
+}
+
+ /**
+ * 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
+ */
+ 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;
+ }
+
+ let baseUrlValue = baseUrl.innerHTML;
+ if (!baseUrlValue) {
+ return;
+ }
+
+ // add searchParams
+ let searchParams = new URLSearchParams(
+ {
+ "page": page,
+ "sort_by": sortBy,
+ "order": order,
+ "status": status,
+ "search_term": searchTerm
+ }
+ );
+ if (portfolio)
+ searchParams.append("portfolio", portfolio)
+
+ let url = `${baseUrlValue}?${searchParams.toString()}`
+ fetch(url)
+ .then(response => response.json())
+ .then(data => {
+ if (data.error) {
+ console.error('Error in AJAX call: ' + data.error);
+ return;
+ }
+
+ // Manage "export as CSV" visibility for domain requests
+ this.toggleExportButton(data.domain_requests);
+
+ // handle the display of proper messaging in the event that no requests exist in the list or search returns no results
+ 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 tbody');
+ tbody.innerHTML = '';
+
+ // Unload modals will re-inject the DOM with the initial placeholders to allow for .on() in regular use cases
+ // We do NOT want that as it will cause multiple placeholders and therefore multiple inits on delete,
+ // which will cause bad delete requests to be sent.
+ const preExistingModalPlaceholders = document.querySelectorAll('[data-placeholder-for^="toggle-delete-domain-alert"]');
+ preExistingModalPlaceholders.forEach(element => {
+ element.remove();
+ });
+
+ // remove any existing modal elements from the DOM so they can be properly re-initialized
+ // after the DOM content changes and there are new delete modal buttons added
+ unloadModals();
+
+ let needsDeleteColumn = false;
+
+ needsDeleteColumn = data.domain_requests.some(request => request.is_deletable);
+
+ // Remove existing delete th and td if they exist
+ let existingDeleteTh = document.querySelector('.delete-header');
+ if (!needsDeleteColumn) {
+ if (existingDeleteTh)
+ existingDeleteTh.remove();
+ } else {
+ if (!existingDeleteTh) {
+ const delheader = document.createElement('th');
+ delheader.setAttribute('scope', 'col');
+ delheader.setAttribute('role', 'columnheader');
+ delheader.setAttribute('class', 'delete-header');
+ delheader.innerHTML = `
+ Delete Action`;
+ let tableHeaderRow = document.querySelector('#domain-requests thead tr');
+ tableHeaderRow.appendChild(delheader);
}
-
- // Manage "export as CSV" visibility for domain requests
- this.toggleExportButton(data.domain_requests);
-
- // handle the display of proper messaging in the event that no requests exist in the list or search returns no results
- 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 tbody');
- tbody.innerHTML = '';
-
- // Unload modals will re-inject the DOM with the initial placeholders to allow for .on() in regular use cases
- // We do NOT want that as it will cause multiple placeholders and therefore multiple inits on delete,
- // which will cause bad delete requests to be sent.
- const preExistingModalPlaceholders = document.querySelectorAll('[data-placeholder-for^="toggle-delete-domain-alert"]');
- preExistingModalPlaceholders.forEach(element => {
- element.remove();
- });
-
- // remove any existing modal elements from the DOM so they can be properly re-initialized
- // after the DOM content changes and there are new delete modal buttons added
- unloadModals();
-
- let needsDeleteColumn = false;
-
- needsDeleteColumn = data.domain_requests.some(request => request.is_deletable);
-
- // Remove existing delete th and td if they exist
- let existingDeleteTh = document.querySelector('.delete-header');
- if (!needsDeleteColumn) {
- if (existingDeleteTh)
- existingDeleteTh.remove();
- } else {
- if (!existingDeleteTh) {
- const delheader = document.createElement('th');
- delheader.setAttribute('scope', 'col');
- delheader.setAttribute('role', 'columnheader');
- delheader.setAttribute('class', 'delete-header');
- delheader.innerHTML = `
- Delete Action`;
- let tableHeaderRow = document.querySelector('#domain-requests thead tr');
- tableHeaderRow.appendChild(delheader);
- }
+ }
+
+ data.domain_requests.forEach(request => {
+ const options = { year: 'numeric', month: 'short', day: 'numeric' };
+ const domainName = request.requested_domain ? request.requested_domain : `New domain request
(${utcDateString(request.created_at)})`;
+ const actionUrl = request.action_url;
+ const actionLabel = request.action_label;
+ const submissionDate = request.last_submitted_date ? new Date(request.last_submitted_date).toLocaleDateString('en-US', options) : `Not submitted`;
+
+ // The markup for the delete function either be a simple trigger or a 3 dots menu with a hidden trigger (in the case of portfolio requests page)
+ // If the request is not deletable, use the following (hidden) span for ANDI screenreaders to indicate this state to the end user
+ let modalTrigger = `
+ Domain request cannot be deleted now. Edit the request for more information.`;
+
+ let markupCreatorRow = '';
+
+ if (this.portfolioValue) {
+ markupCreatorRow = `
+
- ${modalDescription} -
-+ ${modalDescription} +