From e3a3e09cb23f0d02a98afc5839872cb56b8fdc11 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 10 Sep 2024 09:14:09 -0600 Subject: [PATCH 01/13] Use searchParams --- src/registrar/assets/js/get-gov.js | 38 ++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js index 7c523a12a..fda709585 100644 --- a/src/registrar/assets/js/get-gov.js +++ b/src/registrar/assets/js/get-gov.js @@ -33,6 +33,26 @@ const showElement = (element) => { element.classList.remove('display-none'); }; +/** + * Helper function that returns commonly used search params for tables + * @param {*} page - Used for the paginator to determine which page we reside on + * @param {*} sortBy - which value to sort on. Passed to sort_by in django + * @param {*} order - which value to order on. Passed to order_by in django + * @param {*} status - Represents the status filter button + * @param {*} searchTerm - Represents the value of the search bar + */ +function getGenericTableSearchParams(page, sortBy, order, status, searchTerm) { + return new URLSearchParams( + { + "page": page, + "sort_by": sortBy, + "order": order, + "status": status, + "search_term": searchTerm + } + ); +} + /** * Helper function that scrolls to an element * @param {string} attributeName - The string "class" or "id" @@ -1179,6 +1199,7 @@ document.addEventListener('DOMContentLoaded', function() { * @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 */ @@ -1195,10 +1216,11 @@ document.addEventListener('DOMContentLoaded', function() { } // fetch json of page of domains, given params - let url = `${baseUrlValue}?page=${page}&sort_by=${sortBy}&order=${order}&status=${status}&search_term=${searchTerm}` + let searchParams = getGenericTableSearchParams(page, sort_by, order, status, search_term); if (portfolio) - url += `&portfolio=${portfolio}` + searchParams.append("portfolio", portfolio) + let url = `${baseUrlValue}?${searchParams.toString()}` fetch(url) .then(response => response.json()) .then(data => { @@ -1485,6 +1507,8 @@ document.addEventListener('DOMContentLoaded', function() { 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 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. @@ -1524,6 +1548,7 @@ document.addEventListener('DOMContentLoaded', function() { return document.querySelector('input[name="csrfmiddlewaretoken"]').value; } + let currentStatus = [] /** * Loads rows in the domain requests list, as well as updates pagination around the domain requests list * based on the supplied attributes. @@ -1533,7 +1558,7 @@ document.addEventListener('DOMContentLoaded', function() { * @param {*} scroll - control for the scrollToElement functionality * @param {*} searchTerm - the search term */ - function loadDomainRequests(page, sortBy = currentSortBy, order = currentOrder, scroll = scrollToTable, searchTerm = currentSearchTerm) { + function loadDomainRequests(page, sortBy = currentSortBy, order = currentOrder, scroll = scrollToTable, status = currentStatus, searchTerm = currentSearchTerm, portfolio = portfolioValue) { // fetch json of page of domain requests, given params let baseUrl = document.getElementById("get_domain_requests_json_url"); if (!baseUrl) { @@ -1545,7 +1570,12 @@ document.addEventListener('DOMContentLoaded', function() { return; } - fetch(`${baseUrlValue}?page=${page}&sort_by=${sortBy}&order=${order}&search_term=${searchTerm}`) + let searchParams = getGenericTableSearchParams(page, sort_by, order, status, search_term); + if (portfolio) + searchParams.append("portfolio", portfolio) + + let url = `${baseUrlValue}?${searchParams.toString()}` + fetch(url) .then(response => response.json()) .then(data => { if (data.error) { From fb36ec1a54c7c7720e3821f8f0fd79014abc42df Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 10 Sep 2024 09:21:28 -0600 Subject: [PATCH 02/13] Remove unneeded function --- src/registrar/assets/js/get-gov.js | 40 ++++++++++++++---------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js index fda709585..2cb3eb23c 100644 --- a/src/registrar/assets/js/get-gov.js +++ b/src/registrar/assets/js/get-gov.js @@ -33,26 +33,6 @@ const showElement = (element) => { element.classList.remove('display-none'); }; -/** - * Helper function that returns commonly used search params for tables - * @param {*} page - Used for the paginator to determine which page we reside on - * @param {*} sortBy - which value to sort on. Passed to sort_by in django - * @param {*} order - which value to order on. Passed to order_by in django - * @param {*} status - Represents the status filter button - * @param {*} searchTerm - Represents the value of the search bar - */ -function getGenericTableSearchParams(page, sortBy, order, status, searchTerm) { - return new URLSearchParams( - { - "page": page, - "sort_by": sortBy, - "order": order, - "status": status, - "search_term": searchTerm - } - ); -} - /** * Helper function that scrolls to an element * @param {string} attributeName - The string "class" or "id" @@ -1216,7 +1196,15 @@ document.addEventListener('DOMContentLoaded', function() { } // fetch json of page of domains, given params - let searchParams = getGenericTableSearchParams(page, sort_by, order, status, search_term); + let searchParams = new URLSearchParams( + { + "page": page, + "sort_by": sortBy, + "order": order, + "status": status, + "search_term": searchTerm + } + ); if (portfolio) searchParams.append("portfolio", portfolio) @@ -1570,7 +1558,15 @@ document.addEventListener('DOMContentLoaded', function() { return; } - let searchParams = getGenericTableSearchParams(page, sort_by, order, status, search_term); + let searchParams = new URLSearchParams( + { + "page": page, + "sort_by": sortBy, + "order": order, + "status": status, + "search_term": searchTerm + } + ); if (portfolio) searchParams.append("portfolio", portfolio) From c11a282f270c80dfc926994b04fdb248feee0a15 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 10 Sep 2024 10:01:30 -0600 Subject: [PATCH 03/13] Update get-gov.js --- src/registrar/assets/js/get-gov.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js index b5f028bb5..b12c7d001 100644 --- a/src/registrar/assets/js/get-gov.js +++ b/src/registrar/assets/js/get-gov.js @@ -1559,6 +1559,7 @@ document.addEventListener('DOMContentLoaded', function() { return; } + // add searchParams let searchParams = new URLSearchParams( { "page": page, From b036b701122ebef9ea48d51c70585dc51e813913 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Tue, 10 Sep 2024 13:16:41 -0400 Subject: [PATCH 04/13] functionality done --- src/registrar/assets/js/get-gov.js | 141 ++++++++++++++---- .../includes/domain_requests_table.html | 121 ++++++++++++++- .../templates/includes/domains_table.html | 2 +- src/registrar/views/domain_requests_json.py | 22 ++- 4 files changed, 258 insertions(+), 28 deletions(-) diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js index b12c7d001..d1adece6e 100644 --- a/src/registrar/assets/js/get-gov.js +++ b/src/registrar/assets/js/get-gov.js @@ -1166,7 +1166,7 @@ document.addEventListener('DOMContentLoaded', function() { const resetSearchButton = document.querySelector('.domains__reset-search'); const resetFiltersButton = document.querySelector('.domains__reset-filters'); const statusCheckboxes = document.querySelectorAll('input[name="filter-status"]'); - const statusIndicator = document.querySelector('.domain__filter-indicator'); + 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; @@ -1419,10 +1419,10 @@ document.addEventListener('DOMContentLoaded', function() { function updateStatusIndicator() { statusIndicator.innerHTML = ''; // Even if the element is empty, it'll mess up the flex layout unless we set display none - statusIndicator.hideElement(); + hideElement(statusIndicator); if (currentStatus.length) statusIndicator.innerHTML = '(' + currentStatus.length + ')'; - statusIndicator.showElement(); + showElement(statusIndicator); } function closeFilters() { @@ -1436,9 +1436,9 @@ document.addEventListener('DOMContentLoaded', function() { // NOTE: We may need to evolve this as we add more filters. document.addEventListener('focusin', function(event) { const accordion = document.querySelector('.usa-accordion--select'); - const accordionIsOpen = document.querySelector('.usa-button--filter[aria-expanded="true"]'); + const openFilterAccordion = document.querySelector('.usa-button--filter[aria-expanded="true"]'); - if (accordionIsOpen && !accordion.contains(event.target)) { + if (openFilterAccordion && !accordion.contains(event.target)) { closeFilters(); } }); @@ -1447,9 +1447,9 @@ document.addEventListener('DOMContentLoaded', function() { // NOTE: We may need to evolve this as we add more filters. document.addEventListener('click', function(event) { const accordion = document.querySelector('.usa-accordion--select'); - const accordionIsOpen = document.querySelector('.usa-button--filter[aria-expanded="true"]'); + const openFilterAccordion = document.querySelector('.usa-button--filter[aria-expanded="true"]'); - if (accordionIsOpen && !accordion.contains(event.target)) { + if (openFilterAccordion && !accordion.contains(event.target)) { closeFilters(); } }); @@ -1488,14 +1488,17 @@ document.addEventListener('DOMContentLoaded', function() { 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 portfolioElement = document.getElementById('portfolio-js-value'); - const portfolioValue = portfolioElement ? portfolioElement.getAttribute('data-portfolio') : null; + 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; @@ -1537,7 +1540,6 @@ document.addEventListener('DOMContentLoaded', function() { return document.querySelector('input[name="csrfmiddlewaretoken"]').value; } - let currentStatus = [] /** * Loads rows in the domain requests list, as well as updates pagination around the domain requests list * based on the supplied attributes. @@ -1547,7 +1549,7 @@ document.addEventListener('DOMContentLoaded', function() { * @param {*} scroll - control for the scrollToElement functionality * @param {*} searchTerm - the search term */ - function loadDomainRequests(page, sortBy = currentSortBy, order = currentOrder, scroll = scrollToTable, searchTerm = currentSearchTerm, status = currentStatus, portfolio = portfolioValue, portfolio = portfolioValue) { + function loadDomainRequests(page, sortBy = currentSortBy, order = currentOrder, scroll = scrollToTable, searchTerm = currentSearchTerm, status = currentStatus, portfolio = portfolioValue) { // fetch json of page of domain requests, given params let baseUrl = document.getElementById("get_domain_requests_json_url"); if (!baseUrl) { @@ -1882,6 +1884,44 @@ document.addEventListener('DOMContentLoaded', function() { 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); + } + } + + // Manage visibility of reset filters button + if (currentStatus.length == 0) { + hideElement(resetFiltersButton); + } else { + showElement(resetFiltersButton); + } + + // Disable the auto scroll + scrollToTable = false; + + // Call loadDomains with updated status + loadDomainRequests(1, 'id', 'asc'); + resetHeaders(); + updateStatusIndicator(); + }); + }); + // Reset UI and accessibility function resetHeaders() { tableHeaders.forEach(header => { @@ -1906,34 +1946,85 @@ document.addEventListener('DOMContentLoaded', function() { }); } - function closeMoreActionMenu(accordionIsOpen) { - if (accordionIsOpen.getAttribute("aria-expanded") === "true") { - accordionIsOpen.click(); + function closeMoreActionMenu(openFilterAccordion) { + if (openFilterAccordion.getAttribute("aria-expanded") === "true") { + openFilterAccordion.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(); } } document.addEventListener('focusin', function(event) { - const accordions = document.querySelectorAll('.usa-accordion--more-actions'); - const openAccordions = document.querySelectorAll('.usa-button--more-actions[aria-expanded="true"]'); + const openMoreActionsAccordions = document.querySelectorAll('.usa-button--more-actions[aria-expanded="true"]'); - openAccordions.forEach((openAccordionButton) => { - const accordion = openAccordionButton.closest('.usa-accordion--more-actions'); // Find the corresponding accordion - if (accordion && !accordion.contains(event.target)) { - closeMoreActionMenu(openAccordionButton); // Close the accordion if the focus is outside + openMoreActionsAccordions.forEach((openMoreActionsAccordionButton) => { + const moreActionsAccordion = openMoreActionsAccordionButton.closest('.usa-accordion--more-actions'); // Find the corresponding accordion + if (moreActionsAccordion && !moreActionsAccordion.contains(event.target)) { + closeMoreActionMenu(openMoreActionsAccordionButton); // Close the accordion if the focus is outside } }); + + 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(); + } + } }); document.addEventListener('click', function(event) { - const accordions = document.querySelectorAll('.usa-accordion--more-actions'); - const openAccordions = document.querySelectorAll('.usa-button--more-actions[aria-expanded="true"]'); + const openMoreActionsAccordions = document.querySelectorAll('.usa-button--more-actions[aria-expanded="true"]'); - openAccordions.forEach((openAccordionButton) => { - const accordion = openAccordionButton.closest('.usa-accordion--more-actions'); // Find the corresponding accordion + openMoreActionsAccordions.forEach((openMoreActionsAccordionButton) => { + const accordion = openMoreActionsAccordionButton.closest('.usa-accordion--more-actions'); // Find the corresponding accordion if (accordion && !accordion.contains(event.target)) { - closeMoreActionMenu(openAccordionButton); // Close the accordion if the click is outside + closeMoreActionMenu(openMoreActionsAccordionButton); // Close the accordion if the click is outside } }); + + const openFilterAccordion = document.querySelector('.usa-button--filter[aria-expanded="true"]'); + const moreFilterAccordion = openFilterAccordion ? openFilterAccordion.closest('.usa-accordion--select') : undefined; + + if (openFilterAccordion && moreFilterAccordion && !moreFilterAccordion.contains(event.target)) { + closeFilters(); + } }); // Initial load diff --git a/src/registrar/templates/includes/domain_requests_table.html b/src/registrar/templates/includes/domain_requests_table.html index 0c123948e..e4a61a095 100644 --- a/src/registrar/templates/includes/domain_requests_table.html +++ b/src/registrar/templates/includes/domain_requests_table.html @@ -23,7 +23,7 @@ Reset - + + {% if portfolio %} +
+ Filter by +
+
+ +
+
+

Status

+
+ Select to apply status filter +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ +
+ {% endif %}