From cdfb4e4c21725bf9bd645e4f3248750d2f88f455 Mon Sep 17 00:00:00 2001 From: asaki222 Date: Thu, 6 Feb 2025 17:03:09 -0500 Subject: [PATCH 001/173] changes --- src/registrar/assets/src/js/getgov/formset-forms.js | 12 ++++++++++-- src/registrar/assets/src/js/getgov/main.js | 2 +- src/registrar/templates/domain_nameservers.html | 1 + 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/registrar/assets/src/js/getgov/formset-forms.js b/src/registrar/assets/src/js/getgov/formset-forms.js index 27b85212e..87fcb5a66 100644 --- a/src/registrar/assets/src/js/getgov/formset-forms.js +++ b/src/registrar/assets/src/js/getgov/formset-forms.js @@ -214,13 +214,21 @@ export function initFormsetsForms() { let addButton = document.querySelector("#add-form"); let cloneIndex = 0; let formLabel = ''; + // remove nameservers + // clean up wherever nameservers are let isNameserversForm = document.querySelector(".nameservers-form"); let isOtherContactsForm = document.querySelector(".other-contacts-form"); let isDsDataForm = document.querySelector(".ds-data-form"); let isDotgovDomain = document.querySelector(".dotgov-domain-form"); // The Nameservers formset features 2 required and 11 optionals + // if not is other contactsform or dsdataform or isdotgovdomain + // remove nameservers form, start el + if( !(isOtherContactsForm || isDotgovDomain || isDsDataForm) ){ + console.log("we reached inside the if statement") + return + } if (isNameserversForm) { - // cloneIndex = 2; + // cloneInex = 2; formLabel = "Name server"; // DNSSEC: DS Data } else if (isDsDataForm) { @@ -240,7 +248,7 @@ export function initFormsetsForms() { addButton.setAttribute("disabled", "true"); } - // Hide forms which have previously been deleted + // Hide forms which have prevnniously been deleted hideDeletedForms() // Attach click event listener on the delete buttons of the existing forms diff --git a/src/registrar/assets/src/js/getgov/main.js b/src/registrar/assets/src/js/getgov/main.js index a077da929..ecb6de4a7 100644 --- a/src/registrar/assets/src/js/getgov/main.js +++ b/src/registrar/assets/src/js/getgov/main.js @@ -20,7 +20,7 @@ initDomainValidators(); initFormsetsForms(); triggerModalOnDsDataForm(); -nameserversFormListener(); +//nameserversFormListener(); hookupYesNoListener("other_contacts-has_other_contacts",'other-employees', 'no-other-employees'); hookupYesNoListener("additional_details-has_anything_else_text",'anything-else', null); diff --git a/src/registrar/templates/domain_nameservers.html b/src/registrar/templates/domain_nameservers.html index ad8d61592..fce8499ad 100644 --- a/src/registrar/templates/domain_nameservers.html +++ b/src/registrar/templates/domain_nameservers.html @@ -53,6 +53,7 @@ {% for form in formset %}
+
Domain Name
{{ form.domain }} From 1f109bd04046b2ed6f189e2ebe1e390dcd33b435 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Thu, 6 Feb 2025 17:18:44 -0500 Subject: [PATCH 002/173] pseudo code in template --- .../templates/domain_nameservers.html | 57 ++++++++++++++++++- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/src/registrar/templates/domain_nameservers.html b/src/registrar/templates/domain_nameservers.html index fce8499ad..ef2ff879a 100644 --- a/src/registrar/templates/domain_nameservers.html +++ b/src/registrar/templates/domain_nameservers.html @@ -51,9 +51,60 @@ {% csrf_token %} {{ formset.management_form }} - {% for form in formset %} + + + + + - {% comment %} Work around USWDS' button margins to add some spacing between the submit and the 'add more' This solution still works when we remove the 'add more' at 13 forms {% endcomment %}
diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 5ac6ecb20..f0b2a140d 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -758,8 +758,10 @@ class DomainNameserversView(DomainFormBaseView): # Ensure at least 3 fields, filled or empty if len(initial_data) == 0: - while len(initial_data) < 2: + while len(initial_data) < 1: initial_data.append({}) + else: + initial_data.append({}) return initial_data @@ -771,6 +773,7 @@ class DomainNameserversView(DomainFormBaseView): """Adjust context from FormMixin for formsets.""" context = super().get_context_data(**kwargs) # use "formset" instead of "form" for the key + print(context) context["formset"] = context.pop("form") return context From f03a61f93150fcf712824920f325d4a7699a3d6b Mon Sep 17 00:00:00 2001 From: asaki222 Date: Wed, 12 Feb 2025 17:15:53 -0500 Subject: [PATCH 005/173] stuff i have so far --- src/registrar/templates/domain_nameservers.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/templates/domain_nameservers.html b/src/registrar/templates/domain_nameservers.html index dcaef42c4..87041e282 100644 --- a/src/registrar/templates/domain_nameservers.html +++ b/src/registrar/templates/domain_nameservers.html @@ -44,7 +44,7 @@

Add a name server record by clicking "Add name servers". You must add at least two name servers (13 max).

{% include "includes/required_fields.html" %} - +
+ + + {% endif %} + {% endfor %} + + - - - {% comment %} Work around USWDS' button margins to add some spacing between the submit and the 'add more' - This solution still works when we remove the 'add more' at 13 forms {% endcomment %} -
- - - -
+ {% else %} -

No initial data

+

Add name servers

{% csrf_token %} {{ formset.management_form }} @@ -196,7 +189,6 @@
- {{ form.domain }} {% with sublabel_text="Example: ns"|concat:forloop.counter|concat:".example.com" %} {% if forloop.counter <= 2 %} {# span_for_text will wrap the copy in s , which we'll use in the JS for this component #} diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index f0b2a140d..e5e260a5d 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -756,11 +756,8 @@ class DomainNameserversView(DomainFormBaseView): # Add existing nameservers as initial data initial_data.extend({"server": name, "ip": ",".join(ip)} for name, ip in nameservers) - # Ensure at least 3 fields, filled or empty + # Ensure 2 fields in the case we have no data if len(initial_data) == 0: - while len(initial_data) < 1: - initial_data.append({}) - else: initial_data.append({}) return initial_data From 1b255366afd42dd4b35a132695096f93a6846dfa Mon Sep 17 00:00:00 2001 From: CocoByte Date: Mon, 17 Feb 2025 14:31:40 -0700 Subject: [PATCH 008/173] First draft --- .../js/getgov-admin/domain-request-form.js | 41 ++++++++++++++++++- .../src/js/getgov-admin/helpers-admin.js | 20 +++++++++ .../assets/src/js/getgov-admin/main.js | 4 +- .../templates/admin/change_list.html | 15 +++++++ 4 files changed, 78 insertions(+), 2 deletions(-) diff --git a/src/registrar/assets/src/js/getgov-admin/domain-request-form.js b/src/registrar/assets/src/js/getgov-admin/domain-request-form.js index b3d14839e..9352dd6b6 100644 --- a/src/registrar/assets/src/js/getgov-admin/domain-request-form.js +++ b/src/registrar/assets/src/js/getgov-admin/domain-request-form.js @@ -1,4 +1,4 @@ -import { hideElement, showElement, addOrRemoveSessionBoolean } from './helpers-admin.js'; +import { hideElement, showElement, addOrRemoveSessionBoolea, announceForScreenReaders } from './helpers-admin.js'; import { handlePortfolioSelection } from './helpers-portfolio-dynamic-fields.js'; function displayModalOnDropdownClick(linkClickedDisplaysModal, statusDropdown, actionButton, valueToCheck){ @@ -684,3 +684,42 @@ export function initDynamicDomainRequestFields(){ handleSuborgFieldsAndButtons(); } } + +export function initFilterFocusListeners() { + document.addEventListener("DOMContentLoaded", function() { + let filters = document.querySelectorAll("#changelist-filter li a"); // Get list of all filter links + let clickedFilter = false; // Used to determine if we are truly navigating away or not + + // Restore focus from localStorage if it exists + let lastClickedFilter = localStorage.getItem("admin_filter_focus"); + if (lastClickedFilter) { + let focusedElement = document.querySelector(`#changelist-filter li a[href='${lastClickedFilter}']`); + if (focusedElement) { + // Focus the element + focusedElement.setAttribute("tabindex", "-1"); + focusedElement.focus({ preventScroll: true }); + + // Announce focus change for screen readers + announceForScreenReaders("Filter refocused on " + focusedElement.textContent); + } + } + + // Setup listeners. When a filter is clicked, save it as the filter to focus after page refresh + filters.forEach(filter => { + filter.addEventListener("click", function() { + // Save this filter in local storage so we can focus it after page refresh + localStorage.setItem("admin_filter_focus", this.getAttribute("href")); + + // Mark that a filter was clicked so that our beforeunload listener doesn't clear the local storage + clickedFilter = true; + }); + }); + + // Clear focus selection in local storage if user is truly leaving the page + window.addEventListener("beforeunload", function(event) { + if (!clickedFilter) { + localStorage.removeItem("admin_filter_focus"); + } + }); + }); +} \ No newline at end of file diff --git a/src/registrar/assets/src/js/getgov-admin/helpers-admin.js b/src/registrar/assets/src/js/getgov-admin/helpers-admin.js index ff618a67d..de264f34f 100644 --- a/src/registrar/assets/src/js/getgov-admin/helpers-admin.js +++ b/src/registrar/assets/src/js/getgov-admin/helpers-admin.js @@ -22,3 +22,23 @@ export function addOrRemoveSessionBoolean(name, add){ sessionStorage.removeItem(name); } } + +/** + * Creates a temporary live region to announce messages for screen readers. + */ +export function announceForScreenReaders(message) { + let liveRegion = document.createElement("div"); + liveRegion.setAttribute("aria-live", "assertive"); + liveRegion.setAttribute("role", "alert"); + liveRegion.style.position = "absolute"; + liveRegion.style.left = "-9999px"; + document.body.appendChild(liveRegion); + + // Delay the update slightly to ensure it's recognized + setTimeout(() => { + liveRegion.textContent = message; + setTimeout(() => { + document.body.removeChild(liveRegion); + }, 1000); + }, 100); +} \ No newline at end of file diff --git a/src/registrar/assets/src/js/getgov-admin/main.js b/src/registrar/assets/src/js/getgov-admin/main.js index 64be572b2..22266a2c8 100644 --- a/src/registrar/assets/src/js/getgov-admin/main.js +++ b/src/registrar/assets/src/js/getgov-admin/main.js @@ -10,7 +10,8 @@ import { initRejectedEmail, initApprovedDomain, initCopyRequestSummary, - initDynamicDomainRequestFields } from './domain-request-form.js'; + initDynamicDomainRequestFields, + initFilterFocusListeners } from './domain-request-form.js'; import { initDomainFormTargetBlankButtons } from './domain-form.js'; import { initDynamicPortfolioFields } from './portfolio-form.js'; import { initDynamicDomainInformationFields } from './domain-information-form.js'; @@ -31,6 +32,7 @@ initRejectedEmail(); initApprovedDomain(); initCopyRequestSummary(); initDynamicDomainRequestFields(); +initFilterFocusListeners(); // Domain initDomainFormTargetBlankButtons(); diff --git a/src/registrar/templates/admin/change_list.html b/src/registrar/templates/admin/change_list.html index 43abf0861..806067025 100644 --- a/src/registrar/templates/admin/change_list.html +++ b/src/registrar/templates/admin/change_list.html @@ -39,6 +39,21 @@ {% endblock %} +{% for spec in cl.filter_specs %} +
+

{{ spec.title }}

+ +
+{% endfor %} + {% comment %} Replace the Django ul markup with a div. We'll replace the li with a p in change_list_object_tools {% endcomment %} {% block object-tools %}
From dac0841a0105cb8945f551c9fd13fc115e50f45e Mon Sep 17 00:00:00 2001 From: CocoByte Date: Mon, 17 Feb 2025 15:21:58 -0700 Subject: [PATCH 009/173] Add filter IDs and target those for focus --- .../js/getgov-admin/domain-request-form.js | 29 +++++++++---------- src/registrar/templates/admin/filter.html | 12 ++++++++ .../admin/multiple_choice_list_filter.html | 6 ++-- 3 files changed, 29 insertions(+), 18 deletions(-) create mode 100644 src/registrar/templates/admin/filter.html diff --git a/src/registrar/assets/src/js/getgov-admin/domain-request-form.js b/src/registrar/assets/src/js/getgov-admin/domain-request-form.js index 9352dd6b6..50fcbf0b1 100644 --- a/src/registrar/assets/src/js/getgov-admin/domain-request-form.js +++ b/src/registrar/assets/src/js/getgov-admin/domain-request-form.js @@ -690,35 +690,34 @@ export function initFilterFocusListeners() { let filters = document.querySelectorAll("#changelist-filter li a"); // Get list of all filter links let clickedFilter = false; // Used to determine if we are truly navigating away or not - // Restore focus from localStorage if it exists - let lastClickedFilter = localStorage.getItem("admin_filter_focus"); - if (lastClickedFilter) { - let focusedElement = document.querySelector(`#changelist-filter li a[href='${lastClickedFilter}']`); + // Restore focus from localStorage + let lastClickedFilterId = localStorage.getItem("admin_filter_focus_id"); + if (lastClickedFilterId) { + let focusedElement = document.getElementById(lastClickedFilterId); if (focusedElement) { - // Focus the element + //Focus the element focusedElement.setAttribute("tabindex", "-1"); focusedElement.focus({ preventScroll: true }); - + // Announce focus change for screen readers announceForScreenReaders("Filter refocused on " + focusedElement.textContent); } } - - // Setup listeners. When a filter is clicked, save it as the filter to focus after page refresh + + // Capture clicked filter and store its ID filters.forEach(filter => { filter.addEventListener("click", function() { - // Save this filter in local storage so we can focus it after page refresh - localStorage.setItem("admin_filter_focus", this.getAttribute("href")); - - // Mark that a filter was clicked so that our beforeunload listener doesn't clear the local storage - clickedFilter = true; + localStorage.setItem("admin_filter_focus_id", this.id); + clickedFilter = true; // Mark that a filter was clicked }); }); - + // Clear focus selection in local storage if user is truly leaving the page window.addEventListener("beforeunload", function(event) { if (!clickedFilter) { - localStorage.removeItem("admin_filter_focus"); + // If the user did not click a filter and the page is refreshing, + // clear the filter focus id from local storage + localStorage.removeItem("admin_filter_focus_id"); } }); }); diff --git a/src/registrar/templates/admin/filter.html b/src/registrar/templates/admin/filter.html new file mode 100644 index 000000000..035d4ad30 --- /dev/null +++ b/src/registrar/templates/admin/filter.html @@ -0,0 +1,12 @@ +{% load i18n %} +
+ + {% blocktranslate with filter_title=title %} By {{ filter_title }} {% endblocktranslate %} + + +
\ No newline at end of file diff --git a/src/registrar/templates/django/admin/multiple_choice_list_filter.html b/src/registrar/templates/django/admin/multiple_choice_list_filter.html index 167059594..7ae2f2bad 100644 --- a/src/registrar/templates/django/admin/multiple_choice_list_filter.html +++ b/src/registrar/templates/django/admin/multiple_choice_list_filter.html @@ -7,7 +7,7 @@ {% for choice in choices %} {% if choice.reset %} - {{ choice.display }} + {{ choice.display }} {% endif %} {% endfor %} @@ -15,7 +15,7 @@ {% if not choice.reset %} {% if choice.selected and choice.exclude_query_string %} - {{ choice.display }} + {{ choice.display }} @@ -25,7 +25,7 @@ {% endif %} {% if not choice.selected and choice.include_query_string %} - {{ choice.display }} + {{ choice.display }} From 98f4fe773269d37bd00bf7438dfc2472fa03e31f Mon Sep 17 00:00:00 2001 From: CocoByte Date: Mon, 17 Feb 2025 15:35:47 -0700 Subject: [PATCH 010/173] fix typo --- src/registrar/assets/src/js/getgov-admin/domain-request-form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/assets/src/js/getgov-admin/domain-request-form.js b/src/registrar/assets/src/js/getgov-admin/domain-request-form.js index 50fcbf0b1..34c6a42f3 100644 --- a/src/registrar/assets/src/js/getgov-admin/domain-request-form.js +++ b/src/registrar/assets/src/js/getgov-admin/domain-request-form.js @@ -1,4 +1,4 @@ -import { hideElement, showElement, addOrRemoveSessionBoolea, announceForScreenReaders } from './helpers-admin.js'; +import { hideElement, showElement, addOrRemoveSessionBoolean, announceForScreenReaders } from './helpers-admin.js'; import { handlePortfolioSelection } from './helpers-portfolio-dynamic-fields.js'; function displayModalOnDropdownClick(linkClickedDisplaysModal, statusDropdown, actionButton, valueToCheck){ From bbcabbe1b06cb528936c64e7d98cbf558c567677 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Mon, 17 Feb 2025 15:37:17 -0700 Subject: [PATCH 011/173] remove experiment --- src/registrar/templates/admin/change_list.html | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/registrar/templates/admin/change_list.html b/src/registrar/templates/admin/change_list.html index 806067025..43abf0861 100644 --- a/src/registrar/templates/admin/change_list.html +++ b/src/registrar/templates/admin/change_list.html @@ -39,21 +39,6 @@ {% endblock %} -{% for spec in cl.filter_specs %} - -{% endfor %} - {% comment %} Replace the Django ul markup with a div. We'll replace the li with a p in change_list_object_tools {% endcomment %} {% block object-tools %}
From ac826c9e053304cb1e6cfd76830ab554bda75691 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Wed, 19 Feb 2025 12:20:12 -0500 Subject: [PATCH 012/173] temp file to remove --- src/temp-remove | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/temp-remove diff --git a/src/temp-remove b/src/temp-remove new file mode 100644 index 000000000..e69de29bb From 0a0b9c04906253f0a494682963c9bade5448d68c Mon Sep 17 00:00:00 2001 From: CocoByte Date: Wed, 19 Feb 2025 12:50:12 -0800 Subject: [PATCH 013/173] Fixed multi-list bug --- .../templates/django/admin/multiple_choice_list_filter.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/registrar/templates/django/admin/multiple_choice_list_filter.html b/src/registrar/templates/django/admin/multiple_choice_list_filter.html index 7ae2f2bad..4fb70565e 100644 --- a/src/registrar/templates/django/admin/multiple_choice_list_filter.html +++ b/src/registrar/templates/django/admin/multiple_choice_list_filter.html @@ -13,7 +13,7 @@ {% endfor %} {% for choice in choices %} {% if not choice.reset %} - + {% if choice.selected and choice.exclude_query_string %} {{ choice.display }} - {% endif %} - {% if not choice.selected and choice.include_query_string %} + {% elif not choice.selected and choice.include_query_string %} {{ choice.display }}
Example: ns${num}.example.com
- - ` - ) - } - - - if(nameServerRows.length > 0){ - for(let i = 0; i < 1; i++){ - formspace.add(formTemplate(i + 1)) - } - } - else{ - formspace.add(formTemplate(nameServerRows.length + 1)) - } -} - -export function domainNameServers() { - let addButton = document.querySelector("#nameserver-add-form"); - let nameServerRows = document.querySelectorAll("nameserver-row"); - addButton.addEventListener('click', addForm(nameServerRows)); - -} - -// need a listener for save and cancel to hide the form when clicked -// add function to add rows to the table after submiting form \ No newline at end of file diff --git a/src/registrar/assets/src/js/getgov/form-nameservers.js b/src/registrar/assets/src/js/getgov/form-nameservers.js new file mode 100644 index 000000000..74906686d --- /dev/null +++ b/src/registrar/assets/src/js/getgov/form-nameservers.js @@ -0,0 +1,22 @@ +import { showElement, hideElement } from './helpers'; + +function handleAddForm(e) { + let nameserversForm = document.querySelector('.nameservers-form'); + if (!nameserversForm) { + console.warn('Expected DOM element but did not find it'); + return; + } + + showElement(nameserversForm); + + if (e?.target) { + hideElement(e.target); + } +} + +export function initFormNameservers() { + const addButton = document.getElementById('nameserver-add-form'); + if (!addButton) return; + + addButton.addEventListener('click', handleAddForm); +} diff --git a/src/registrar/assets/src/js/getgov/main.js b/src/registrar/assets/src/js/getgov/main.js index ecb6de4a7..637488187 100644 --- a/src/registrar/assets/src/js/getgov/main.js +++ b/src/registrar/assets/src/js/getgov/main.js @@ -1,6 +1,7 @@ -import { hookupYesNoListener, hookupRadioTogglerListener } from './radios.js'; +import { hookupYesNoListener } from './radios.js'; import { initDomainValidators } from './domain-validators.js'; -import { initFormsetsForms, triggerModalOnDsDataForm, nameserversFormListener } from './formset-forms.js'; +import { initFormsetsForms, triggerModalOnDsDataForm } from './formset-forms.js'; +import { initFormNameservers } from './form-nameservers' import { initializeUrbanizationToggle } from './urbanization.js'; import { userProfileListener, finishUserSetupListener } from './user-profile.js'; import { handleRequestingEntityFieldset } from './requesting-entity.js'; @@ -20,7 +21,7 @@ initDomainValidators(); initFormsetsForms(); triggerModalOnDsDataForm(); -//nameserversFormListener(); +initFormNameservers(); hookupYesNoListener("other_contacts-has_other_contacts",'other-employees', 'no-other-employees'); hookupYesNoListener("additional_details-has_anything_else_text",'anything-else', null); diff --git a/src/registrar/templates/domain_nameservers.html b/src/registrar/templates/domain_nameservers.html index a81cac242..11369ed59 100644 --- a/src/registrar/templates/domain_nameservers.html +++ b/src/registrar/templates/domain_nameservers.html @@ -47,35 +47,183 @@ {% if formset.initial and formset.forms.0.initial %} -
- {% csrf_token %} - {{ formset.management_form }} + + {% csrf_token %} + {{ formset.management_form }} -
+ - - - - - - - - - - - - {% for form in formset %} - {% if not forloop.last %} - - - - - - - - - - - - - {% endif %} - {% endfor %} - -
Your registered domains
Name ServersIP addressAction
{{ form.server.value }}{{ form.ip.value }} -
- - - - -
-
- -
- -
- -
-
- - - - {% else %} - -

Add name servers

-
- {% csrf_token %} - {{ formset.management_form }} - {% for form in formset %} -
-
-
- {% with sublabel_text="Example: ns"|concat:forloop.counter|concat:".example.com" %} - {% if forloop.counter <= 2 %} - {# span_for_text will wrap the copy in s , which we'll use in the JS for this component #} - {% with attr_required=True add_group_class="usa-form-group--unstyled-error" span_for_text=True %} - {% input_with_errors form.server %} - {% endwith %} - {% else %} - {% with span_for_text=True %} - {% input_with_errors form.server %} - {% endwith %} - {% endif %} - {% endwith %} -
-
- {% with label_text=form.ip.label sublabel_text="Example: 86.124.49.54 or 2001:db8::1234:5678" add_group_class="usa-form-group--unstyled-error" add_aria_label="Name server "|concat:forloop.counter|concat:" "|concat:form.ip.label %} - {% input_with_errors form.ip %} - {% endwith %} -
-
- -
-
-
- {% endfor %} - {% comment %} Work around USWDS' button margins to add some spacing between the submit and the 'add more' - This solution still works when we remove the 'add more' at 13 forms {% endcomment %} -
- - - -
-
{% endif %} {% endblock %} {# domain_content #} From 5c4d1a811cae35ed6ac704fbbd9c68014a7bbd2e Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Thu, 20 Feb 2025 21:16:10 -0500 Subject: [PATCH 016/173] wip --- .../templates/domain_nameservers.html | 30 ++++++++++++++----- src/registrar/views/domain.py | 3 -- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/registrar/templates/domain_nameservers.html b/src/registrar/templates/domain_nameservers.html index 11369ed59..e70336c14 100644 --- a/src/registrar/templates/domain_nameservers.html +++ b/src/registrar/templates/domain_nameservers.html @@ -35,6 +35,7 @@

Name Servers

+ @@ -114,14 +115,14 @@ {{ form.ip.value }}
- - +
@@ -166,9 +167,24 @@ {{ form.server }} {{ form.ip }} - - - + + + + + + Delete +
@@ -232,7 +248,7 @@ class="usa-button" >Save - + +
+ +

Before your domain can be used we’ll need information about your domain name servers. Name server records indicate which DNS server is authoritative for your domain.

Add a name server record by clicking "Add name servers". You must add at least two name servers (13 max).

- {% include "includes/required_fields.html" %} - - {% if formset.initial and formset.forms.0.initial %} + + {% include "includes/required_fields.html" %}
{% csrf_token %} {{ formset.management_form }} - + @@ -115,7 +113,7 @@ - - + + - + @@ -225,18 +225,18 @@ {% with sublabel_text="Example: ns"|concat:forloop.counter|concat:".example.com" %} {% if forloop.counter <= 2 %} {# span_for_text will wrap the copy in s , which we'll use in the JS for this component #} - {% with attr_required=True add_group_class="usa-form-group--unstyled-error" span_for_text=True %} + {% with attr_required=True add_initial_value_attr=True add_group_class="usa-form-group--unstyled-error" span_for_text=True %} {% input_with_errors form.server %} {% endwith %} {% else %} - {% with span_for_text=True %} + {% with span_for_text=True add_initial_value_attr=True %} {% input_with_errors form.server %} {% endwith %} {% endif %} {% endwith %}
- {% with attr_required=True label_text=form.ip.label sublabel_text="Example: 86.124.49.54 or 2001:db8::1234:5678" add_group_class="usa-form-group--unstyled-error" add_aria_label="Name server "|concat:forloop.counter|concat:" "|concat:form.ip.label %} + {% with attr_required=True add_initial_value_attr=True label_text=form.ip.label sublabel_text="Example: 86.124.49.54 or 2001:db8::1234:5678" add_group_class="usa-form-group--unstyled-error" add_aria_label="Name server "|concat:forloop.counter|concat:" "|concat:form.ip.label %} {% input_with_errors form.ip %} {% endwith %}
diff --git a/src/registrar/templatetags/field_helpers.py b/src/registrar/templatetags/field_helpers.py index 28a4d4556..c7389d3e3 100644 --- a/src/registrar/templatetags/field_helpers.py +++ b/src/registrar/templatetags/field_helpers.py @@ -173,6 +173,10 @@ def input_with_errors(context, field=None): # noqa: C901 if aria_labels: context["aria_label"] = " ".join(aria_labels) + # Conditionally add the data-initial-value attribute + if context.get("add_initial_value_attr", False): + attrs["data-initial-value"] = field.initial or "" + # ask Django to give us the widget dict # see Widget.get_context() on # https://docs.djangoproject.com/en/4.1/ref/forms/widgets From c8ccb45088828e74d778547395fa0f9718397f9f Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Tue, 25 Feb 2025 16:13:12 -0800 Subject: [PATCH 032/173] Add space to separate domains --- src/registrar/utility/csv_export.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/utility/csv_export.py b/src/registrar/utility/csv_export.py index 10c3ac379..e46f5f0c1 100644 --- a/src/registrar/utility/csv_export.py +++ b/src/registrar/utility/csv_export.py @@ -522,7 +522,7 @@ class MemberExport(BaseExport): "Domain management": f"{get_domains_display(roles, permissions)}: \ {get_domains_description_display(roles, permissions)}", "Number of domains": length_user_managed_domains, - "Domains": ",".join(user_managed_domains), + "Domains": ", ".join(user_managed_domains), } return [FIELDS.get(column, "") for column in columns] From a527bd736a353d2cf90cb507eae4deaa6230b366 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Tue, 25 Feb 2025 21:04:40 -0500 Subject: [PATCH 033/173] added modals for unsaved changes and deletes --- .../assets/src/js/getgov/form-nameservers.js | 115 ++++++++++++++++-- src/registrar/forms/domain.py | 2 +- .../templates/domain_nameservers.html | 54 ++++++-- src/registrar/templates/includes/modal.html | 2 +- 4 files changed, 147 insertions(+), 26 deletions(-) diff --git a/src/registrar/assets/src/js/getgov/form-nameservers.js b/src/registrar/assets/src/js/getgov/form-nameservers.js index fc67d3edc..94f8edf5f 100644 --- a/src/registrar/assets/src/js/getgov/form-nameservers.js +++ b/src/registrar/assets/src/js/getgov/form-nameservers.js @@ -5,10 +5,13 @@ export class NameserverForm { this.addNameserverButton = document.getElementById('nameserver-add-form'); this.nameserversForm = document.querySelector('.nameservers-form'); this.formChanged = false; + this.callback = null; // Bind event handlers to maintain 'this' context this.handleAddFormClick = this.handleAddFormClick.bind(this); this.handleEditClick = this.handleEditClick.bind(this); + this.handleDeleteClick = this.handleDeleteClick.bind(this); + this.handleDeleteKebabClick = this.handleDeleteKebabClick.bind(this); this.handleCancelClick = this.handleCancelClick.bind(this); } @@ -38,6 +41,7 @@ export class NameserverForm { editRows.forEach(editRow => { if (editRow.querySelector('.usa-input--error')) { const readOnlyRow = editRow.previousElementSibling; + this.formChanged = true; showElement(editRow); hideElement(readOnlyRow); } @@ -58,13 +62,52 @@ export class NameserverForm { cancelButton.addEventListener('click', this.handleCancelClick); }); + const deleteButtons = document.querySelectorAll('.nameserver-delete'); + deleteButtons.forEach(deleteButton => { + deleteButton.addEventListener('click', this.handleDeleteClick); + }); + + const deleteKebabButtons = document.querySelectorAll('.nameserver-delete-kebab'); + deleteKebabButtons.forEach(deleteKebabButton => { + deleteKebabButton.addEventListener('click', this.handleDeleteKebabClick); + }); + const textInputs = document.querySelectorAll("input[type='text']"); textInputs.forEach(input => { - input.addEventListener("input", function() { + input.addEventListener("input", () => { this.formChanged = true; - console.log("Form changed"); - }) - }) + }); + }); + + const unsaved_changes_modal = document.getElementById('unsaved-changes-modal'); + if (unsaved_changes_modal) { + const submitButton = document.getElementById('unsaved-changes-click-button'); + const closeButton = unsaved_changes_modal.querySelector('.usa-modal__close'); + submitButton.addEventListener('click', () => { + closeButton.click(); + this.executeCallback(); + }); + } + + const delete_modal = document.getElementById('delete-modal'); + if (delete_modal) { + const submitButton = document.getElementById('delete-click-button'); + const closeButton = delete_modal.querySelector('.usa-modal__close'); + submitButton.addEventListener('click', () => { + closeButton.click(); + this.executeCallback(); + }); + } + + } + + executeCallback() { + if (this.callback) { + this.callback(); + this.callback = null; + } else { + console.warn("No callback function set."); + } } handleAddFormClick(event) { @@ -80,17 +123,63 @@ export class NameserverForm { console.warn("Expected DOM element but did not find it"); return; } - // Check if any other edit row is currently visible and hide it - document.querySelectorAll('tr.edit-row:not(.display-none)').forEach(openEditRow => { - let correspondingReadOnlyRow = openEditRow.previousElementSibling; - hideElement(openEditRow); - showElement(correspondingReadOnlyRow); - }); - // hide and show rows as appropriate - hideElement(readOnlyRow); - showElement(editRow); + this.callback = () => { + // Check if any other edit row is currently visible and hide it + document.querySelectorAll('tr.edit-row:not(.display-none)').forEach(openEditRow => { + this.resetEditRowAndFormAndCollapseEditRow(openEditRow); + }); + // hide and show rows as appropriate + hideElement(readOnlyRow); + showElement(editRow); + }; + if (this.formChanged) { + //------- Show the confirmation modal + let modalTrigger = document.querySelector("#unsaved_changes_trigger"); + if (modalTrigger) { + modalTrigger.click(); + } + } else { + this.executeCallback(); + } } + handleDeleteClick(event) { + let deleteButton = event.target; + let editRow = deleteButton.closest('tr'); + if (!editRow) { + console.warn("Expected DOM element but did not find it"); + return; + } + this.deleteRow(editRow); + } + + handleDeleteKebabClick(event) { + let deleteKebabButton = event.target; + let readOnlyRow = deleteKebabButton.closest('tr'); // Find the closest row + let editRow = readOnlyRow.nextElementSibling; // Get the next row + if (!editRow) { + console.warn("Expected DOM element but did not find it"); + return; + } + this.deleteRow(editRow); + } + + deleteRow(editRow) { + this.callback = () => { + hideElement(editRow); + let textInputs = editRow.querySelectorAll("input[type='text']"); + textInputs.forEach(input => { + input.value = ""; + }); + document.querySelector("form").submit(); + }; + let modalTrigger = document.querySelector('#delete_trigger'); + if (modalTrigger) { + modalTrigger.click(); + } + } + + /** * Handles the click event for the cancel button within the table form. * diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py index 05eb90db3..d0e228e0b 100644 --- a/src/registrar/forms/domain.py +++ b/src/registrar/forms/domain.py @@ -212,7 +212,7 @@ class BaseNameserverFormset(forms.BaseFormSet): # We need to make sure not to trigger the duplicate error in case the first and second nameservers # are empty. If there are enough records in the formset, that error is an unecessary blocker. # If there aren't, the required error will block the submit. - if value in data and not (form.cleaned_data.get("server", "").strip() == "" and index == 1): + if value in data and not (form.cleaned_data.get("server", "").strip() == ""): form.add_error( "server", NameserverError(code=nsErrorCodes.DUPLICATE_HOST, nameserver=value), diff --git a/src/registrar/templates/domain_nameservers.html b/src/registrar/templates/domain_nameservers.html index d547378df..d2d4ecb00 100644 --- a/src/registrar/templates/domain_nameservers.html +++ b/src/registrar/templates/domain_nameservers.html @@ -148,9 +148,10 @@
@@ -265,4 +265,36 @@ {% endif %} + + +
+ {% include 'includes/modal.html' with modal_heading="Are you sure you want to continue?" modal_description="You have unsaved changes that will be lost." modal_button_id="unsaved-changes-click-button" modal_button_text="Continue without saving" cancel_button_text="Go back" %} +
+ + +
+ {% include 'includes/modal.html' with modal_heading="Are you sure you want to delete this name server?" modal_description="This will delete the name server from your DNS records. This action cannot be undone." modal_button_id="delete-click-button" modal_button_text="Yes, delete name server" modal_button_class="usa-button--secondary" %} +
{% endblock %} {# domain_content #} diff --git a/src/registrar/templates/includes/modal.html b/src/registrar/templates/includes/modal.html index 8e3f45c44..af49d2b6c 100644 --- a/src/registrar/templates/includes/modal.html +++ b/src/registrar/templates/includes/modal.html @@ -75,7 +75,7 @@ class="usa-button usa-button--unstyled padding-105 text-center" data-close-modal > - Cancel + {% if cancel_button_text %}{{ cancel_button_text }}{% else %}Cancel{% endif %} {% endif %} From 9d22f9f64a26b1a0e1ab8089d3ed3737407e47c9 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Tue, 25 Feb 2025 22:20:26 -0500 Subject: [PATCH 034/173] edit, cancel and delete interactions --- .../assets/src/js/getgov/form-nameservers.js | 85 +++++++++++++++---- .../templates/domain_nameservers.html | 23 +++-- 2 files changed, 80 insertions(+), 28 deletions(-) diff --git a/src/registrar/assets/src/js/getgov/form-nameservers.js b/src/registrar/assets/src/js/getgov/form-nameservers.js index 94f8edf5f..ac1332ae3 100644 --- a/src/registrar/assets/src/js/getgov/form-nameservers.js +++ b/src/registrar/assets/src/js/getgov/form-nameservers.js @@ -13,6 +13,7 @@ export class NameserverForm { this.handleDeleteClick = this.handleDeleteClick.bind(this); this.handleDeleteKebabClick = this.handleDeleteKebabClick.bind(this); this.handleCancelClick = this.handleCancelClick.bind(this); + this.handleCancelAddFormClick = this.handleCancelAddFormClick.bind(this); } init() { @@ -30,7 +31,12 @@ export class NameserverForm { if (secondNameserver && !thirdNameserver && errorMessages.length > 0) { showElement(this.nameserversForm); - hideElement(this.addNameserverButton); + this.formChanged = true; + } + + if (this.nameserversForm.querySelector('.usa-input--error')) { + showElement(this.nameserversForm); + this.formChanged = true; } // handle display of table view errors @@ -62,6 +68,11 @@ export class NameserverForm { cancelButton.addEventListener('click', this.handleCancelClick); }); + const cancelAddFormButtons = document.querySelectorAll('.nameserver-cancel-add-form'); + cancelAddFormButtons.forEach(cancelAddFormButton => { + cancelAddFormButton.addEventListener('click', this.handleCancelAddFormClick); + }); + const deleteButtons = document.querySelectorAll('.nameserver-delete'); deleteButtons.forEach(deleteButton => { deleteButton.addEventListener('click', this.handleDeleteClick); @@ -111,8 +122,27 @@ export class NameserverForm { } handleAddFormClick(event) { - showElement(this.nameserversForm); - hideElement(this.addNameserverButton); + this.callback = () => { + // Check if any other edit row is currently visible and hide it + document.querySelectorAll('tr.edit-row:not(.display-none)').forEach(openEditRow => { + this.resetEditRowAndFormAndCollapseEditRow(openEditRow); + }); + // Check if this.nameserversForm is visible (i.e., does not have 'display-none') + if (!this.nameserversForm.classList.contains('display-none')) { + this.resetNameserversForm(); + } + // show nameservers form + showElement(this.nameserversForm); + }; + if (this.formChanged) { + //------- Show the confirmation modal + let modalTrigger = document.querySelector("#unsaved_changes_trigger"); + if (modalTrigger) { + modalTrigger.click(); + } + } else { + this.executeCallback(); + } } handleEditClick(event) { @@ -128,6 +158,10 @@ export class NameserverForm { document.querySelectorAll('tr.edit-row:not(.display-none)').forEach(openEditRow => { this.resetEditRowAndFormAndCollapseEditRow(openEditRow); }); + // Check if this.nameserversForm is visible (i.e., does not have 'display-none') + if (!this.nameserversForm.classList.contains('display-none')) { + this.resetNameserversForm(); + } // hide and show rows as appropriate hideElement(readOnlyRow); showElement(editRow); @@ -180,6 +214,10 @@ export class NameserverForm { } + handleCancelAddFormClick(event) { + this.resetNameserversForm(); + } + /** * Handles the click event for the cancel button within the table form. * @@ -207,11 +245,11 @@ export class NameserverForm { return; } // reset the values set in editRow - this.resetInputValuesInRow(editRow); + this.resetInputValuesInElement(editRow); // copy values from editRow to readOnlyRow this.copyEditRowToReadonlyRow(editRow, readOnlyRow); // remove errors from the editRow - this.removeErrorsFromRow(editRow); + this.removeErrorsFromElement(editRow); // remove errors from the entire form this.removeFormErrors(); // reset formChanged @@ -221,8 +259,25 @@ export class NameserverForm { showElement(readOnlyRow); } - resetInputValuesInRow(editRow) { - let textInputs = editRow.querySelectorAll("input[type='text']"); + resetNameserversForm() { + if (!this.nameserversForm) { + console.warn("Expected DOM element but did not find it"); + return; + } + // reset the values set in nameserversForm + this.resetInputValuesInElement(this.nameserversForm); + // remove errors from the nameserversForm + this.removeErrorsFromElement(this.nameserversForm); + // remove errors from the entire form + this.removeFormErrors(); + // reset formChanged + this.resetFormChanged(); + // hide the nameserversForm + hideElement(this.nameserversForm); + } + + resetInputValuesInElement(domElement) { + let textInputs = domElement.querySelectorAll("input[type='text']"); textInputs.forEach(input => { input.value = input.dataset.initialValue; }) @@ -243,24 +298,24 @@ export class NameserverForm { } } - removeErrorsFromRow(editRow) { - // remove class 'usa-form-group--error' from divs in editRow - editRow.querySelectorAll("div.usa-form-group--error").forEach(div => { + removeErrorsFromElement(domElement) { + // remove class 'usa-form-group--error' from divs in domElement + domElement.querySelectorAll("div.usa-form-group--error").forEach(div => { div.classList.remove("usa-form-group--error"); }); - // remove class 'usa-label--error' from labels in editRow - editRow.querySelectorAll("label.usa-label--error").forEach(label => { + // remove class 'usa-label--error' from labels in domElement + domElement.querySelectorAll("label.usa-label--error").forEach(label => { label.classList.remove("usa-label--error"); }); // Remove divs whose id ends with '__error-message' (error message divs) - editRow.querySelectorAll("div[id$='__error-message']").forEach(errorDiv => { + domElement.querySelectorAll("div[id$='__error-message']").forEach(errorDiv => { errorDiv.remove(); }); - // remove class 'usa-input--error' from inputs in editRow - editRow.querySelectorAll("input.usa-input--error").forEach(input => { + // remove class 'usa-input--error' from inputs in domElement + domElement.querySelectorAll("input.usa-input--error").forEach(input => { input.classList.remove("usa-input--error"); }); } diff --git a/src/registrar/templates/domain_nameservers.html b/src/registrar/templates/domain_nameservers.html index d2d4ecb00..f8713fa12 100644 --- a/src/registrar/templates/domain_nameservers.html +++ b/src/registrar/templates/domain_nameservers.html @@ -80,11 +80,11 @@
- -
{% for form in formset %} {% if not forloop.last %} + {{ form.domain }} @@ -218,6 +220,7 @@ {% csrf_token %} {{ formset.management_form }} {% for form in formset %} + {{ form.domain }}
From e392ffac4e4a4b150797575b17f53bdd5576d32e Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Wed, 26 Feb 2025 07:01:04 -0500 Subject: [PATCH 036/173] initialize smart nameservers --- .../assets/src/js/getgov/form-nameservers.js | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/registrar/assets/src/js/getgov/form-nameservers.js b/src/registrar/assets/src/js/getgov/form-nameservers.js index ac1332ae3..57a24a30d 100644 --- a/src/registrar/assets/src/js/getgov/form-nameservers.js +++ b/src/registrar/assets/src/js/getgov/form-nameservers.js @@ -4,6 +4,7 @@ export class NameserverForm { constructor() { this.addNameserverButton = document.getElementById('nameserver-add-form'); this.nameserversForm = document.querySelector('.nameservers-form'); + this.domain = ''; this.formChanged = false; this.callback = null; @@ -22,6 +23,12 @@ export class NameserverForm { } initializeNameserverFormDisplay() { + + const domainName = document.getElementById('id_form-0-domain'); + if (domainName) { + this.domain = domainName.value; + } + // Check if exactly two nameserver forms exist: id_form-1-server is present but id_form-2-server is not const secondNameserver = document.getElementById('id_form-1-server'); const thirdNameserver = document.getElementById('id_form-2-server'); // This should not exist @@ -53,6 +60,23 @@ export class NameserverForm { } }) } + + // hide ip in forms unless nameserver ends with domain name + let formIndex = 0; + while (document.getElementById('id_form-' + formIndex + '-domain')) { + let serverInput = document.getElementById('id_form-' + formIndex + '-server'); + let ipInput = document.getElementById('id_form-' + formIndex + '-ip'); + if (serverInput && ipInput) { + let serverValue = serverInput.value.trim(); // Get the value and trim spaces + let ipParent = ipInput.parentElement; // Get the parent element of ipInput + + if (ipParent && !serverValue.endsWith('.' + this.domain)) { + hideElement(ipParent); // Hide the parent element of ipInput + } + } + formIndex++; + } + } initializeEventListeners() { From 4db7fe03e38ab6eb87d2ce2bed995ca85ba7e941 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Wed, 26 Feb 2025 07:24:58 -0500 Subject: [PATCH 037/173] nameserver forms are smart --- .../assets/src/js/getgov/form-nameservers.js | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/registrar/assets/src/js/getgov/form-nameservers.js b/src/registrar/assets/src/js/getgov/form-nameservers.js index 57a24a30d..e128b768a 100644 --- a/src/registrar/assets/src/js/getgov/form-nameservers.js +++ b/src/registrar/assets/src/js/getgov/form-nameservers.js @@ -114,6 +114,29 @@ export class NameserverForm { }); }); + // Add a specific listener for 'id_form-{number}-server' inputs to make + // nameserver forms 'smart' + let formIndex = 0; + while (document.getElementById(`id_form-${formIndex}-server`)) { + let serverInput = document.getElementById(`id_form-${formIndex}-server`); + let ipInput = document.getElementById(`id_form-${formIndex}-ip`); + if (serverInput && ipInput) { + let ipParent = ipInput.parentElement; // Get the parent element of ipInput + serverInput.addEventListener("input", () => { + let serverValue = serverInput.value.trim(); + if (ipParent) { + if (serverValue.endsWith('.' + this.domain)) { + showElement(ipParent); // Show IP field if the condition matches + } else { + hideElement(ipParent); // Hide IP field otherwise + ipInput.value = ""; // Set the IP value to blank + } + } + }); + } + formIndex++; // Move to the next index + } + const unsaved_changes_modal = document.getElementById('unsaved-changes-modal'); if (unsaved_changes_modal) { const submitButton = document.getElementById('unsaved-changes-click-button'); From 3f84587a16d097a77acf18225acdd84c66f8ec04 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Wed, 26 Feb 2025 13:41:02 -0500 Subject: [PATCH 038/173] enabling support for refactoring 13 nameservers handling --- .../assets/src/js/getgov/form-nameservers.js | 85 ++++++++++--------- .../templates/domain_nameservers.html | 48 +++++------ 2 files changed, 67 insertions(+), 66 deletions(-) diff --git a/src/registrar/assets/src/js/getgov/form-nameservers.js b/src/registrar/assets/src/js/getgov/form-nameservers.js index e128b768a..67eee34b7 100644 --- a/src/registrar/assets/src/js/getgov/form-nameservers.js +++ b/src/registrar/assets/src/js/getgov/form-nameservers.js @@ -3,7 +3,7 @@ import { showElement, hideElement } from './helpers'; export class NameserverForm { constructor() { this.addNameserverButton = document.getElementById('nameserver-add-form'); - this.nameserversForm = document.querySelector('.nameservers-form'); + this.addNameserversForm = document.querySelector('.add-nameservers-form'); this.domain = ''; this.formChanged = false; this.callback = null; @@ -36,13 +36,13 @@ export class NameserverForm { // Check if there are error messages in the form (indicated by elements with class 'usa-alert--error') const errorMessages = document.querySelectorAll('.usa-alert--error'); - if (secondNameserver && !thirdNameserver && errorMessages.length > 0) { - showElement(this.nameserversForm); + if (this.addNameserversForm && secondNameserver && !thirdNameserver && errorMessages.length > 0) { + showElement(this.addNameserversForm); this.formChanged = true; } - if (this.nameserversForm.querySelector('.usa-input--error')) { - showElement(this.nameserversForm); + if (this.addNameserversForm && this.addNameserversForm.querySelector('.usa-input--error')) { + showElement(this.addNameserversForm); this.formChanged = true; } @@ -169,26 +169,31 @@ export class NameserverForm { } handleAddFormClick(event) { - this.callback = () => { - // Check if any other edit row is currently visible and hide it - document.querySelectorAll('tr.edit-row:not(.display-none)').forEach(openEditRow => { - this.resetEditRowAndFormAndCollapseEditRow(openEditRow); - }); - // Check if this.nameserversForm is visible (i.e., does not have 'display-none') - if (!this.nameserversForm.classList.contains('display-none')) { - this.resetNameserversForm(); - } - // show nameservers form - showElement(this.nameserversForm); - }; - if (this.formChanged) { - //------- Show the confirmation modal - let modalTrigger = document.querySelector("#unsaved_changes_trigger"); - if (modalTrigger) { - modalTrigger.click(); + if (this.addNameserversForm) { + this.callback = () => { + // Check if any other edit row is currently visible and hide it + document.querySelectorAll('tr.edit-row:not(.display-none)').forEach(openEditRow => { + this.resetEditRowAndFormAndCollapseEditRow(openEditRow); + }); + // Check if this.addNameserversForm is visible (i.e., does not have 'display-none') + if (!this.addNameserversForm.classList.contains('display-none')) { + this.resetAddNameserversForm(); + } + // show nameservers form + showElement(this.addNameserversForm); + }; + if (this.formChanged) { + //------- Show the confirmation modal + let modalTrigger = document.querySelector("#unsaved_changes_trigger"); + if (modalTrigger) { + modalTrigger.click(); + } + } else { + this.executeCallback(); } } else { - this.executeCallback(); + // this indicates there are already 13 nameservers and we need to display an error message + console.warn("There are 13 nameservers"); } } @@ -205,9 +210,9 @@ export class NameserverForm { document.querySelectorAll('tr.edit-row:not(.display-none)').forEach(openEditRow => { this.resetEditRowAndFormAndCollapseEditRow(openEditRow); }); - // Check if this.nameserversForm is visible (i.e., does not have 'display-none') - if (!this.nameserversForm.classList.contains('display-none')) { - this.resetNameserversForm(); + // Check if this.addNameserversForm is visible (i.e., does not have 'display-none') + if (this.addNameserversForm && !this.addNameserversForm.classList.contains('display-none')) { + this.resetAddNameserversForm(); } // hide and show rows as appropriate hideElement(readOnlyRow); @@ -262,7 +267,7 @@ export class NameserverForm { handleCancelAddFormClick(event) { - this.resetNameserversForm(); + this.resetAddNameserversForm(); } /** @@ -306,21 +311,19 @@ export class NameserverForm { showElement(readOnlyRow); } - resetNameserversForm() { - if (!this.nameserversForm) { - console.warn("Expected DOM element but did not find it"); - return; + resetAddNameserversForm() { + if (this.addNameserversForm) { + // reset the values set in addNameserversForm + this.resetInputValuesInElement(this.addNameserversForm); + // remove errors from the addNameserversForm + this.removeErrorsFromElement(this.addNameserversForm); + // remove errors from the entire form + this.removeFormErrors(); + // reset formChanged + this.resetFormChanged(); + // hide the addNameserversForm + hideElement(this.addNameserversForm); } - // reset the values set in nameserversForm - this.resetInputValuesInElement(this.nameserversForm); - // remove errors from the nameserversForm - this.removeErrorsFromElement(this.nameserversForm); - // remove errors from the entire form - this.removeFormErrors(); - // reset formChanged - this.resetFormChanged(); - // hide the nameserversForm - hideElement(this.nameserversForm); } resetInputValuesInElement(domElement) { diff --git a/src/registrar/templates/domain_nameservers.html b/src/registrar/templates/domain_nameservers.html index cd89c647b..7499daa82 100644 --- a/src/registrar/templates/domain_nameservers.html +++ b/src/registrar/templates/domain_nameservers.html @@ -38,7 +38,7 @@
-
@@ -54,9 +54,9 @@ {% csrf_token %} {{ formset.management_form }} -
- - {% endif %} - {% endfor %} - -
- - -
- +
+ + +
+ + {% endif %} + {% endfor %}
{{ form.ip.value }}
-
{{ form.server.value }}
@@ -107,7 +105,7 @@ {% for form in formset %} - {% if not forloop.last %} + {% if forloop.counter == 13 or not forloop.last %} {{ form.domain }} @@ -213,7 +211,7 @@ {% endcomment %} - {% for form in formset %} - {% if forloop.counter == 13 or not forloop.last %} + {% if not forloop.last or form.initial %} {{ form.domain }} From a386fc5132f988c7aa35a4314ea09c2fcd5a8f58 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 26 Feb 2025 13:35:52 -0700 Subject: [PATCH 040/173] fix bug --- src/registrar/assets/js/uswds-edited.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/registrar/assets/js/uswds-edited.js b/src/registrar/assets/js/uswds-edited.js index ae246b05c..d8664d5bf 100644 --- a/src/registrar/assets/js/uswds-edited.js +++ b/src/registrar/assets/js/uswds-edited.js @@ -5284,7 +5284,10 @@ const setUpModal = baseComponent => { overlayDiv.classList.add(OVERLAY_CLASSNAME); // Set attributes - modalWrapper.setAttribute("role", "dialog"); + // DOTGOV + // Removes the dialog role as this causes a double readout bug with screenreaders + // modalWrapper.setAttribute("role", "dialog"); + // END DOTGOV modalWrapper.setAttribute("id", modalID); if (ariaLabelledBy) { modalWrapper.setAttribute("aria-labelledby", ariaLabelledBy); From 4bcb5bfd50ef97a55e792a2111b81be40bd19689 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Wed, 26 Feb 2025 13:45:40 -0700 Subject: [PATCH 041/173] initial draft (need to fix DOM targeting) --- src/registrar/admin.py | 9 +++-- .../assets/src/js/getgov-admin/andi.js | 36 +++++++++++++++++++ .../assets/src/js/getgov-admin/main.js | 2 ++ .../admin/includes/detail_table_fieldset.html | 9 +++-- src/registrar/utility/admin_helpers.py | 1 + 5 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 src/registrar/assets/src/js/getgov-admin/andi.js diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 2d2b90a5f..bb60ad6f4 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -283,6 +283,11 @@ class DomainRequestAdminForm(forms.ModelForm): admin.site, attrs={"data-placeholder": "---------", "ajax-url": "get-suborganization-list-json"}, ), + # 'investigator': forms.Select( + # attrs={ + # 'aria-describedby': 'id_investigator-arialabel'}), + # 'senior_official': forms.Select( + # attrs={'aria-describedby': 'id_senior_official-arialabel'}), } labels = { "action_needed_reason_email": "Email", @@ -2622,11 +2627,9 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): # Table ordering # NOTE: This impacts the select2 dropdowns (combobox) - # Currentl, there's only one for requests on DomainInfo + # Currently, there's only one for requests on DomainInfo ordering = ["-last_submitted_date", "requested_domain__name"] - change_form_template = "django/admin/domain_request_change_form.html" - def get_fieldsets(self, request, obj=None): fieldsets = super().get_fieldsets(request, obj) diff --git a/src/registrar/assets/src/js/getgov-admin/andi.js b/src/registrar/assets/src/js/getgov-admin/andi.js new file mode 100644 index 000000000..416c4f05f --- /dev/null +++ b/src/registrar/assets/src/js/getgov-admin/andi.js @@ -0,0 +1,36 @@ +export function initAriaInjections() { + console.log("FIRED") + document.addEventListener('DOMContentLoaded', function () { + // Find all spans with "--aria-description" in their id + const descriptionSpans = document.querySelectorAll('span[id*="--aria-description"]'); + + // Iterate through each span to add aria-describedby + descriptionSpans.forEach(function(span) { + // Extract the base ID from the span's id (remove "--aria-description" part) + const fieldId = span.id.replace('--aria-description', ''); + + // Find the field element with the corresponding ID + const field = document.getElementById(fieldId); + + // If the field exists, set the aria-describedby attribute + if (field) { + let select2ElementDetected = false + if (field.classList.contains('admin-autocomplete')) { + console.log("select2---> select2-"+${fieldId}+"-container") + // If it's a Select2 component, find the rendered span inside Select2 + const select2Span = field.querySelector('.select2-selection'); + if (select2Span) { + console.log("set select2 aria") + select2Span.setAttribute('aria-describedby', span.id); + select2ElementDetected=true + } + } + if (!select2ElementDetected) + { + // Otherwise, set aria-describedby directly on the field + field.setAttribute('aria-describedby', span.id); + } + } + }); + }); +} \ No newline at end of file diff --git a/src/registrar/assets/src/js/getgov-admin/main.js b/src/registrar/assets/src/js/getgov-admin/main.js index 64be572b2..7f9d7fc78 100644 --- a/src/registrar/assets/src/js/getgov-admin/main.js +++ b/src/registrar/assets/src/js/getgov-admin/main.js @@ -15,6 +15,7 @@ import { initDomainFormTargetBlankButtons } from './domain-form.js'; import { initDynamicPortfolioFields } from './portfolio-form.js'; import { initDynamicDomainInformationFields } from './domain-information-form.js'; import { initDynamicDomainFields } from './domain-form.js'; +import { initAriaInjections } from './andi.js' // General initModals(); @@ -22,6 +23,7 @@ initCopyToClipboard(); initFilterHorizontalWidget(); initDescriptions(); initSubmitBar(); +initAriaInjections(); // Domain request initIneligibleModal(); diff --git a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html index a074e8a7c..bf58853eb 100644 --- a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html +++ b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html @@ -160,6 +160,13 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html) {% endblock field_readonly %} {% block field_other %} + + {% if "related_widget_wrapper" in field.field.field.widget.template_name or field.field.field.widget.input_type == 'select' %} + + {{ field.field.label }}, combo-box, collapsed, edit, has autocomplete. To set the value, use the arrow keys or type the text. + + {% endif %} + {% if field.field.name == "action_needed_reason_email" %} {{ field.field }} @@ -251,7 +258,6 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html) {% elif field.field.name == "rejection_reason_email" %} {{ field.field }} - @@ -331,7 +337,6 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html) - {% if original_object.rejection_reason_email %} {% else %} diff --git a/src/registrar/utility/admin_helpers.py b/src/registrar/utility/admin_helpers.py index 93a0a16b5..5fe2f3b3b 100644 --- a/src/registrar/utility/admin_helpers.py +++ b/src/registrar/utility/admin_helpers.py @@ -105,6 +105,7 @@ class AutocompleteSelectWithPlaceholder(AutocompleteSelect): attrs = super().build_attrs(base_attrs, extra_attrs=extra_attrs) if "data-placeholder" in base_attrs: attrs["data-placeholder"] = base_attrs["data-placeholder"] + return attrs def __init__(self, field, admin_site, attrs=None, choices=(), using=None): From 8250a72f7357c1612d521f1a0ece200209822c83 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Wed, 26 Feb 2025 18:51:05 -0700 Subject: [PATCH 042/173] updates --- .../assets/src/js/getgov-admin/andi.js | 66 +++++++++++-------- .../admin/includes/detail_table_fieldset.html | 7 +- 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/src/registrar/assets/src/js/getgov-admin/andi.js b/src/registrar/assets/src/js/getgov-admin/andi.js index 416c4f05f..9be03fa89 100644 --- a/src/registrar/assets/src/js/getgov-admin/andi.js +++ b/src/registrar/assets/src/js/getgov-admin/andi.js @@ -1,36 +1,50 @@ +/* +This function intercepts all select2 dropdowns and adds aria content. +It relies on an override in detail_table_fieldset.html that provides +a span with a corresponding id for aria-describedby content. + +This allows us to avoid overriding aria-label, which is used by select2 +to send the current dropdown selection to ANDI +*/ export function initAriaInjections() { console.log("FIRED") document.addEventListener('DOMContentLoaded', function () { - // Find all spans with "--aria-description" in their id - const descriptionSpans = document.querySelectorAll('span[id*="--aria-description"]'); + // Set timeout so this fires after select2.js finishes adding to the DOM + setTimeout(function () { + // Find all spans with "--aria-description" in their id + const descriptionSpans = document.querySelectorAll('span[id*="--aria-description"]'); - // Iterate through each span to add aria-describedby - descriptionSpans.forEach(function(span) { - // Extract the base ID from the span's id (remove "--aria-description" part) - const fieldId = span.id.replace('--aria-description', ''); + // Iterate through each span to add aria-describedby + descriptionSpans.forEach(function(span) { + // Extract the base ID from the span's id (remove "--aria-description" part) + const fieldId = span.id.replace('--aria-description', ''); - // Find the field element with the corresponding ID - const field = document.getElementById(fieldId); + // Find the field element with the corresponding ID + const field = document.getElementById(fieldId); - // If the field exists, set the aria-describedby attribute - if (field) { - let select2ElementDetected = false - if (field.classList.contains('admin-autocomplete')) { - console.log("select2---> select2-"+${fieldId}+"-container") - // If it's a Select2 component, find the rendered span inside Select2 - const select2Span = field.querySelector('.select2-selection'); - if (select2Span) { - console.log("set select2 aria") - select2Span.setAttribute('aria-describedby', span.id); - select2ElementDetected=true + // If the field exists, set the aria-describedby attribute + if (field) { + let select2ElementDetected = false + if (field.classList.contains('admin-autocomplete')) { + const select2Id="select2-"+fieldId+"-container" + console.log("select2---> "+select2Id) + // If it's a Select2 component, find the rendered span inside Select2 + const select2SpanThatTriggersAria = document.querySelector("span[aria-labelledby='"+select2Id+"']"); + const select2SpanThatHoldsSelection = document.getElementById(select2Id) + if (select2SpanThatTriggersAria) { + console.log("set select2 aria") + select2SpanThatTriggersAria.setAttribute('aria-describedby', span.id); + // select2SpanThatTriggersAria.setAttribute('aria-labelledby', select2Id); + select2ElementDetected=true + } + } + if (!select2ElementDetected) + { + // Otherwise, set aria-describedby directly on the field + field.setAttribute('aria-describedby', span.id); } - } - if (!select2ElementDetected) - { - // Otherwise, set aria-describedby directly on the field - field.setAttribute('aria-describedby', span.id); } - } - }); + }); + }, 500); }); } \ No newline at end of file diff --git a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html index bf58853eb..3ca6a3b66 100644 --- a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html +++ b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html @@ -160,8 +160,11 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html) {% endblock field_readonly %} {% block field_other %} - - {% if "related_widget_wrapper" in field.field.field.widget.template_name or field.field.field.widget.input_type == 'select' %} + {% comment %} + .gov override - add Aria messages for select2 dropdowns. These messages are hooked-up to their respective DOM + elements via javascript (see andi.js) + {% endcomment %} + {% if "related_widget_wrapper" in field.field.field.widget.template_name %} {{ field.field.label }}, combo-box, collapsed, edit, has autocomplete. To set the value, use the arrow keys or type the text. From 1d8742237d2dddcc3e42ab87144c754bf6cf7f47 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 27 Feb 2025 08:07:22 -0700 Subject: [PATCH 043/173] fixes --- src/registrar/assets/src/js/getgov/formset-forms.js | 5 +++++ .../templates/domain_request_dotgov_domain.html | 10 +++++++--- .../templates/includes/domain_requests_table.html | 4 ++-- src/registrar/templates/includes/domains_table.html | 2 +- src/registrar/templates/includes/members_table.html | 2 +- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/registrar/assets/src/js/getgov/formset-forms.js b/src/registrar/assets/src/js/getgov/formset-forms.js index 27b85212e..f68650104 100644 --- a/src/registrar/assets/src/js/getgov/formset-forms.js +++ b/src/registrar/assets/src/js/getgov/formset-forms.js @@ -372,6 +372,11 @@ export function initFormsetsForms() { }); }); } + + if (newForm) { + // announce("new form added") + newForm.focus(); + } } } diff --git a/src/registrar/templates/domain_request_dotgov_domain.html b/src/registrar/templates/domain_request_dotgov_domain.html index 91373609d..9a037ab3b 100644 --- a/src/registrar/templates/domain_request_dotgov_domain.html +++ b/src/registrar/templates/domain_request_dotgov_domain.html @@ -61,7 +61,7 @@
-

Alternative domains (optional)

+

Alternative domains (optional)

Are there other domains you’d like if we can’t give @@ -79,19 +79,23 @@ {% endfor %} {% endwith %} {% endwith %} - -

+
Check domain availability
diff --git a/src/registrar/templates/includes/domain_requests_table.html b/src/registrar/templates/includes/domain_requests_table.html index 8adc0929a..f02f9f91c 100644 --- a/src/registrar/templates/includes/domain_requests_table.html +++ b/src/registrar/templates/includes/domain_requests_table.html @@ -25,9 +25,9 @@ Reset - + Reset - + Date: Thu, 27 Feb 2025 10:58:44 -0700 Subject: [PATCH 044/173] add focus on error --- src/registrar/assets/src/js/getgov/formset-forms.js | 5 ----- src/registrar/templates/includes/form_errors.html | 8 +++++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/registrar/assets/src/js/getgov/formset-forms.js b/src/registrar/assets/src/js/getgov/formset-forms.js index f68650104..27b85212e 100644 --- a/src/registrar/assets/src/js/getgov/formset-forms.js +++ b/src/registrar/assets/src/js/getgov/formset-forms.js @@ -372,11 +372,6 @@ export function initFormsetsForms() { }); }); } - - if (newForm) { - // announce("new form added") - newForm.focus(); - } } } diff --git a/src/registrar/templates/includes/form_errors.html b/src/registrar/templates/includes/form_errors.html index 52c82aaf0..816704ed5 100644 --- a/src/registrar/templates/includes/form_errors.html +++ b/src/registrar/templates/includes/form_errors.html @@ -1,16 +1,18 @@ {% if form.errors %}
{% for error in form.non_field_errors %} -
- + @@ -109,8 +109,7 @@ {{ form.domain }} - - + {% for form in formset %} {% if not forloop.last or form.initial %} + + {% comment %} + This section renders table rows for each existing nameserver. Two rows are rendered, a readonly row + and an edit row. Only one of which is displayed at a time. + {% endcomment %} + {{ form.domain }} @@ -205,10 +226,9 @@ {% else %} {% comment %} - No Nameservers. - We use the double-decker form + This section renders Add New Nameservers form which renders when there are no existing + nameservers defined on the domain. {% endcomment %} - - + From be11fafe83d0ec9923cfa55a1716a85d60da2c88 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Tue, 4 Mar 2025 10:22:49 -0500 Subject: [PATCH 090/173] make delete button red --- src/registrar/templates/domain_nameservers.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/templates/domain_nameservers.html b/src/registrar/templates/domain_nameservers.html index 94d8be323..09a337e20 100644 --- a/src/registrar/templates/domain_nameservers.html +++ b/src/registrar/templates/domain_nameservers.html @@ -207,7 +207,7 @@
Name ServersIP addressIP address Action
{{ form.server.value }}{{ form.ip.value }}{{ form.server.value }} {% if form.ip.value %}({{ form.ip.value }}){% endif %}
Name Servers IP addressActionAction
+
From a4e229279800f6b24fef8e31f90ea7f227177131 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Tue, 4 Mar 2025 14:05:32 -0500 Subject: [PATCH 101/173] refactored the js --- .../assets/src/js/getgov/domain-dnssec.js | 2 +- .../assets/src/js/getgov/domain-dsdata.js | 2 +- .../assets/src/js/getgov/domain-managers.js | 2 +- .../src/js/getgov/domain-request-form.js | 2 +- .../assets/src/js/getgov/form-helpers.js | 57 +++++++++++++++++ .../assets/src/js/getgov/form-nameservers.js | 62 ++++--------------- 6 files changed, 74 insertions(+), 53 deletions(-) create mode 100644 src/registrar/assets/src/js/getgov/form-helpers.js diff --git a/src/registrar/assets/src/js/getgov/domain-dnssec.js b/src/registrar/assets/src/js/getgov/domain-dnssec.js index 860359fe0..0d6ae4970 100644 --- a/src/registrar/assets/src/js/getgov/domain-dnssec.js +++ b/src/registrar/assets/src/js/getgov/domain-dnssec.js @@ -1,4 +1,4 @@ -import { submitForm } from './helpers.js'; +import { submitForm } from './form-helpers.js'; export function initDomainDNSSEC() { document.addEventListener('DOMContentLoaded', function() { diff --git a/src/registrar/assets/src/js/getgov/domain-dsdata.js b/src/registrar/assets/src/js/getgov/domain-dsdata.js index 7c0871bec..14132d812 100644 --- a/src/registrar/assets/src/js/getgov/domain-dsdata.js +++ b/src/registrar/assets/src/js/getgov/domain-dsdata.js @@ -1,4 +1,4 @@ -import { submitForm } from './helpers.js'; +import { submitForm } from './form-helpers.js'; export function initDomainDSData() { document.addEventListener('DOMContentLoaded', function() { diff --git a/src/registrar/assets/src/js/getgov/domain-managers.js b/src/registrar/assets/src/js/getgov/domain-managers.js index 26eccd8cd..3d77b7cca 100644 --- a/src/registrar/assets/src/js/getgov/domain-managers.js +++ b/src/registrar/assets/src/js/getgov/domain-managers.js @@ -1,4 +1,4 @@ -import { submitForm } from './helpers.js'; +import { submitForm } from './form-helpers.js'; export function initDomainManagersPage() { document.addEventListener('DOMContentLoaded', function() { diff --git a/src/registrar/assets/src/js/getgov/domain-request-form.js b/src/registrar/assets/src/js/getgov/domain-request-form.js index b49912fa4..73bb6accd 100644 --- a/src/registrar/assets/src/js/getgov/domain-request-form.js +++ b/src/registrar/assets/src/js/getgov/domain-request-form.js @@ -1,4 +1,4 @@ -import { submitForm } from './helpers.js'; +import { submitForm } from './form-helpers.js'; export function initDomainRequestForm() { document.addEventListener('DOMContentLoaded', function() { diff --git a/src/registrar/assets/src/js/getgov/form-helpers.js b/src/registrar/assets/src/js/getgov/form-helpers.js new file mode 100644 index 000000000..fabfab98a --- /dev/null +++ b/src/registrar/assets/src/js/getgov/form-helpers.js @@ -0,0 +1,57 @@ +/** + * Helper function to submit a form + * @param {} form_id - the id of the form to be submitted + */ +export function submitForm(form_id) { + let form = document.getElementById(form_id); + if (form) { + form.submit(); + } else { + console.error("Form '" + form_id + "' not found."); + } +} + + +/** + * Removes all error-related classes and messages from the specified DOM element. + * This method cleans up validation errors by removing error highlighting from input fields, + * labels, and form groups, as well as deleting error message elements. + * @param {HTMLElement} domElement - The parent element within which errors should be cleared. + */ +export function removeErrorsFromElement(domElement) { + // Remove the 'usa-form-group--error' class from all div elements + domElement.querySelectorAll("div.usa-form-group--error").forEach(div => { + div.classList.remove("usa-form-group--error"); + }); + + // Remove the 'usa-label--error' class from all label elements + domElement.querySelectorAll("label.usa-label--error").forEach(label => { + label.classList.remove("usa-label--error"); + }); + + // Remove all error message divs whose ID ends with '__error-message' + domElement.querySelectorAll("div[id$='__error-message']").forEach(errorDiv => { + errorDiv.remove(); + }); + + // Remove the 'usa-input--error' class from all input elements + domElement.querySelectorAll("input.usa-input--error").forEach(input => { + input.classList.remove("usa-input--error"); + }); +} + +/** + * Removes all form-level error messages displayed in the UI. + * The form error messages are contained within div elements with the ID 'form-errors'. + * Since multiple elements with the same ID may exist (even though not syntactically correct), + * this function removes them iteratively. + */ +export function removeFormErrors() { + let formErrorDiv = document.getElementById("form-errors"); + + // Recursively remove all instances of form error divs + while (formErrorDiv) { + formErrorDiv.remove(); + formErrorDiv = document.getElementById("form-errors"); + } +} \ No newline at end of file diff --git a/src/registrar/assets/src/js/getgov/form-nameservers.js b/src/registrar/assets/src/js/getgov/form-nameservers.js index e45d43f6a..cec8bcefb 100644 --- a/src/registrar/assets/src/js/getgov/form-nameservers.js +++ b/src/registrar/assets/src/js/getgov/form-nameservers.js @@ -1,4 +1,5 @@ import { showElement, hideElement, scrollToElement } from './helpers'; +import { removeErrorsFromElement, removeFormErrors } from './form-helpers'; export class NameserverForm { constructor() { @@ -46,11 +47,17 @@ export class NameserverForm { // Check if there are error messages in the form (indicated by elements with class 'usa-alert--error') const errorMessages = document.querySelectorAll('.usa-alert--error'); + // This check indicates that there are exactly two forms (which is the case for the Add New Nameservers form) + // and there is at least one error in the form. In this case, show the Add New Nameservers form, and + // indicate that the form has changed if (this.addNameserversForm && secondNameserver && !thirdNameserver && errorMessages.length > 0) { showElement(this.addNameserversForm); this.formChanged = true; } + // This check indicates that there is either an Add New Nameservers form or an Add New Nameserver form + // and that form has errors in it. In this case, show the form, and indicate that the form has + // changed. if (this.addNameserversForm && this.addNameserversForm.querySelector('.usa-input--error')) { showElement(this.addNameserversForm); this.formChanged = true; @@ -135,6 +142,8 @@ export class NameserverForm { let ipInput = document.getElementById(`id_form-${formIndex}-ip`); if (serverInput && ipInput) { let ipParent = ipInput.parentElement; // Get the parent element of ipInput + // add an event listener on the server input that adjusts visibility + // and value of the ip input (and its parent) serverInput.addEventListener("input", () => { let serverValue = serverInput.value.trim(); if (ipParent) { @@ -363,9 +372,9 @@ export class NameserverForm { // copy values from editRow to readOnlyRow this.copyEditRowToReadonlyRow(editRow, readOnlyRow); // remove errors from the editRow - this.removeErrorsFromElement(editRow); + removeErrorsFromElement(editRow); // remove errors from the entire form - this.removeFormErrors(); + removeFormErrors(); // reset formChanged this.resetFormChanged(); // hide and show rows as appropriate @@ -382,9 +391,9 @@ export class NameserverForm { // reset the values set in addNameserversForm this.resetInputValuesInElement(this.addNameserversForm); // remove errors from the addNameserversForm - this.removeErrorsFromElement(this.addNameserversForm); + removeErrorsFromElement(this.addNameserversForm); // remove errors from the entire form - this.removeFormErrors(); + removeFormErrors(); // reset formChanged this.resetFormChanged(); // hide the addNameserversForm @@ -435,51 +444,6 @@ export class NameserverForm { } } - /** - * Removes all error-related classes and messages from the specified DOM element. - * This method cleans up validation errors by removing error highlighting from input fields, - * labels, and form groups, as well as deleting error message elements. - * @param {HTMLElement} domElement - The parent element within which errors should be cleared. - */ - removeErrorsFromElement(domElement) { - // Remove the 'usa-form-group--error' class from all div elements - domElement.querySelectorAll("div.usa-form-group--error").forEach(div => { - div.classList.remove("usa-form-group--error"); - }); - - // Remove the 'usa-label--error' class from all label elements - domElement.querySelectorAll("label.usa-label--error").forEach(label => { - label.classList.remove("usa-label--error"); - }); - - // Remove all error message divs whose ID ends with '__error-message' - domElement.querySelectorAll("div[id$='__error-message']").forEach(errorDiv => { - errorDiv.remove(); - }); - - // Remove the 'usa-input--error' class from all input elements - domElement.querySelectorAll("input.usa-input--error").forEach(input => { - input.classList.remove("usa-input--error"); - }); - } - - /** - * Removes all form-level error messages displayed in the UI. - * The form error messages are contained within div elements with the ID 'form-errors'. - * Since multiple elements with the same ID may exist (even though not syntactically correct), - * this function removes them iteratively. - */ - removeFormErrors() { - let formErrorDiv = document.getElementById("form-errors"); - - // Recursively remove all instances of form error divs - while (formErrorDiv) { - formErrorDiv.remove(); - formErrorDiv = document.getElementById("form-errors"); - } - } - - /** * Resets the form change state. * This method marks the form as unchanged by setting `formChanged` to false. From d9cd4aeb4d1bcf0f4927cfaf9e574f6d9fe84230 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Tue, 4 Mar 2025 14:16:48 -0500 Subject: [PATCH 102/173] refactored the js --- .../assets/src/js/getgov/form-nameservers.js | 6 ++++-- src/registrar/assets/src/js/getgov/helpers.js | 13 ------------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/registrar/assets/src/js/getgov/form-nameservers.js b/src/registrar/assets/src/js/getgov/form-nameservers.js index cec8bcefb..2cf1fc106 100644 --- a/src/registrar/assets/src/js/getgov/form-nameservers.js +++ b/src/registrar/assets/src/js/getgov/form-nameservers.js @@ -458,10 +458,12 @@ export class NameserverForm { * This ensures that only the latest alert is displayed to the user. */ resetAlerts() { - const mainContent = document.querySelector("main#main-content"); + const mainContent = document.getElementById("main-content"); if (mainContent) { // Remove all alert elements within the main content area mainContent.querySelectorAll(".usa-alert").forEach(alert => alert.remove()); + } else { + console.warn("Expecting main-content DOM element"); } } @@ -474,7 +476,7 @@ export class NameserverForm { addAlert(level, message) { this.resetAlerts(); // Remove any existing alerts before adding a new one - const mainContent = document.querySelector("main#main-content"); + const mainContent = document.getElementById("main-content"); if (!mainContent) return; // Create a new alert div with appropriate classes based on alert level diff --git a/src/registrar/assets/src/js/getgov/helpers.js b/src/registrar/assets/src/js/getgov/helpers.js index 08be011c2..80a9fce1f 100644 --- a/src/registrar/assets/src/js/getgov/helpers.js +++ b/src/registrar/assets/src/js/getgov/helpers.js @@ -84,19 +84,6 @@ export function getCsrfToken() { return document.querySelector('input[name="csrfmiddlewaretoken"]').value; } -/** - * Helper function to submit a form - * @param {} form_id - the id of the form to be submitted - */ -export function submitForm(form_id) { - let form = document.getElementById(form_id); - if (form) { - form.submit(); - } else { - console.error("Form '" + form_id + "' not found."); - } -} - /** * Helper function to strip HTML tags * THIS IS NOT SUITABLE FOR SANITIZING DANGEROUS STRINGS From f537fa37aca2921db2c5055dcc38ffc24fc0ca0b Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Tue, 4 Mar 2025 14:29:25 -0500 Subject: [PATCH 103/173] fixed aria-label on kebab --- src/registrar/templates/domain_nameservers.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/templates/domain_nameservers.html b/src/registrar/templates/domain_nameservers.html index 23f5209b2..7647f5a70 100644 --- a/src/registrar/templates/domain_nameservers.html +++ b/src/registrar/templates/domain_nameservers.html @@ -158,7 +158,7 @@ class="usa-button usa-button--unstyled usa-button--with-icon usa-accordion__button usa-button--more-actions margin-top-0" aria-expanded="false" aria-controls="more-actions-{{ form.server.value }}" - aria-label="" + aria-label="More Actions for ({{ form.server.value }})" >

Manage your domains

- -

- -

+
+

Manage your domains

+
+ +
+
{% include "includes/domains_table.html" with user_domain_count=user_domain_count %} {% include "includes/domain_requests_table.html" %} diff --git a/src/registrar/templates/includes/domain_requests_table.html b/src/registrar/templates/includes/domain_requests_table.html index 7ddf88607..decbfcce6 100644 --- a/src/registrar/templates/includes/domain_requests_table.html +++ b/src/registrar/templates/includes/domain_requests_table.html @@ -4,17 +4,25 @@ {% url 'get_domain_requests_json' as url %} -
+
{% if not portfolio %} -

Domain requests

+
+

Domain requests

+
{% else %} {% endif %} - - {% with label_text=portfolio|yesno:"Search by domain name or creator,Search by domain name" item_id_prefix="domain-requests" aria_label_text="Domain requests search component" %} - {% include "includes/search_table.html" %} + + {% with label_text=portfolio|yesno:"Search by domain name or creator,Search by domain name" item_name="domain-requests" aria_label_text="Domain requests search component"%} + {% if portfolio %} + {% with use_search_icon="true" %} + {% include "includes/search.html" %} + {% endwith %} + {% else %} + {% include "includes/search.html" %} + {% endif %} {% endwith %}
diff --git a/src/registrar/templates/includes/domains_table.html b/src/registrar/templates/includes/domains_table.html index c47161d67..66076283a 100644 --- a/src/registrar/templates/includes/domains_table.html +++ b/src/registrar/templates/includes/domains_table.html @@ -28,19 +28,33 @@
{% if not portfolio %} -

Domains

+
+

Domains

+ + {% if user_domain_count and user_domain_count > 0 %} + {% with export_aria="Domains report component" export_url='export_data_type_user' %} + {% include "includes/export.html" %} + {% endwith %} + {% endif %} +
{% else %} {% endif %} - - {% if user_domain_count and user_domain_count > 0 %} - {% with label_text="Search by domain name" item_id_prefix="domains" aria_label_text="Domains search component" with_export="true" export_aria="Domains report component" export_url='export_data_type_user' %} - {% include "includes/search_table.html" %} - {% endwith %} - {% else %} - {% with label_text="Search by domain name" item_id_prefix="domains" aria_label_text="Domains search component"%} - {% include "includes/search_table.html" %} + + {% with label_text="Search by domain name" item_name="domains" aria_label_text="Domains search component"%} + {% if portfolio %} + {% with use_search_icon="true" %} + {% include "includes/search.html" %} + {% endwith %} + {% else %} + {% include "includes/search.html" %} + {% endif %} + {% endwith %} + + {% if user_domain_count and user_domain_count > 0 and portfolio%} + {% with export_aria="Domains report component" export_url='export_data_type_user' %} + {% include "includes/export.html" %} {% endwith %} {% endif %}
diff --git a/src/registrar/templates/includes/export.html b/src/registrar/templates/includes/export.html new file mode 100644 index 000000000..68c11a09a --- /dev/null +++ b/src/registrar/templates/includes/export.html @@ -0,0 +1,12 @@ +{% load static %} +
+ + \ No newline at end of file diff --git a/src/registrar/templates/includes/members_table.html b/src/registrar/templates/includes/members_table.html index d09e6de05..2af87036f 100644 --- a/src/registrar/templates/includes/members_table.html +++ b/src/registrar/templates/includes/members_table.html @@ -7,9 +7,12 @@
- - {% with label_text="Search by member name" item_id_prefix="members" aria_label_text="Members search component" with_export="true" export_aria="Members report component" export_url='export_members_portfolio'%} - {% include "includes/search_table.html" %} + + {% with label_text="Search by member name" item_name="members" aria_label_text="Members search component" use_search_icon="true" %} + {% include "includes/search.html" %} + {% endwith %} + {% with export_aria="Members report component" export_url='export_members_portfolio' %} + {% include "includes/export.html" %} {% endwith %}
diff --git a/src/registrar/templates/includes/search.html b/src/registrar/templates/includes/search.html index 8c9f60cdb..b83d8935e 100644 --- a/src/registrar/templates/includes/search.html +++ b/src/registrar/templates/includes/search.html @@ -1,26 +1,29 @@ {% load static %} -
- + From 5107fcb25211cfd622068f9ba4fd8f81830e66de Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 5 Mar 2025 15:22:42 -0700 Subject: [PATCH 125/173] refactor ids part 2 --- src/registrar/decorators.py | 6 +++--- .../includes/member_domains_edit_table.html | 4 ++-- .../templates/portfolio_member_domains.html | 8 ++++---- .../templates/portfolio_member_domains_edit.html | 8 ++++---- .../templates/portfolio_member_permissions.html | 4 ++-- src/registrar/views/portfolio_members_json.py | 5 ++++- src/registrar/views/portfolios.py | 15 ++++++++++++--- 7 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/registrar/decorators.py b/src/registrar/decorators.py index aa16e91f5..714e375c4 100644 --- a/src/registrar/decorators.py +++ b/src/registrar/decorators.py @@ -167,7 +167,7 @@ def _user_has_permission(user, request, rules, **kwargs): # TODO -- fix this on all related URLS :( and ( _member_exists_under_portfolio(portfolio, kwargs.get("member_pk")) - or _member_invitation_exists_under_portfolio(portfolio, kwargs.get("memberinvitation_pk")) + or _member_invitation_exists_under_portfolio(portfolio, kwargs.get("invitedmember_pk")) ), ), ] @@ -213,8 +213,8 @@ def _domain_request_exists_under_portfolio(portfolio, domain_request_pk): def _member_exists_under_portfolio(portfolio, member_pk): return UserPortfolioPermission.objects.filter(portfolio=portfolio, id=member_pk).exists() -def _member_invitation_exists_under_portfolio(portfolio, memberinvitation_pk): - return PortfolioInvitation.objects.filter(portfolio=portfolio, id=memberinvitation_pk).exists() +def _member_invitation_exists_under_portfolio(portfolio, invitedmember_pk): + return PortfolioInvitation.objects.filter(portfolio=portfolio, id=invitedmember_pk).exists() def _is_domain_request_creator(user, domain_request_pk): """Checks to see if the user is the creator of a domain request diff --git a/src/registrar/templates/includes/member_domains_edit_table.html b/src/registrar/templates/includes/member_domains_edit_table.html index a76f711cc..1d706f89a 100644 --- a/src/registrar/templates/includes/member_domains_edit_table.html +++ b/src/registrar/templates/includes/member_domains_edit_table.html @@ -91,9 +91,9 @@ aria-describedby="You have unsaved changes that will be lost." > {% if portfolio_permission %} - {% url 'member-domains' pk=portfolio_permission.id as url %} + {% url 'member-domains' member_pk=portfolio_permission.id as url %} {% else %} - {% url 'invitedmember-domains' pk=portfolio_invitation.id as url %} + {% url 'invitedmember-domains' invitedmember_pk=portfolio_invitation.id as url %} {% endif %} {% include 'includes/modal.html' with modal_heading="Are you sure you want to continue?" modal_description="You have unsaved changes that will be lost." modal_button_url=url modal_button_text="Continue without saving" %} diff --git a/src/registrar/templates/portfolio_member_domains.html b/src/registrar/templates/portfolio_member_domains.html index 76d48c20b..677539585 100644 --- a/src/registrar/templates/portfolio_member_domains.html +++ b/src/registrar/templates/portfolio_member_domains.html @@ -15,11 +15,11 @@ {% url 'members' as url %} {% if portfolio_permission %} - {% url 'member' pk=portfolio_permission.id as url2 %} - {% url 'member-domains-edit' pk=portfolio_permission.id as url3 %} + {% url 'member' member_pk=portfolio_permission.id as url2 %} + {% url 'member-domains-edit' member_pk=portfolio_permission.id as url3 %} {% else %} - {% url 'invitedmember' pk=portfolio_invitation.id as url2 %} - {% url 'invitedmember-domains-edit' pk=portfolio_invitation.id as url3 %} + {% url 'invitedmember' invitedmember_pk=portfolio_invitation.id as url2 %} + {% url 'invitedmember-domains-edit' invitedmember_pk=portfolio_invitation.id as url3 %} {% endif %} @@ -241,7 +241,7 @@
- {% with sublabel_text="Example: ns"|concat:forloop.counter|concat:".example.com" %} + {% with sublabel_text="Example: ns"|concat:forloop.counter|concat:".example.com" add_group_class="margin-top-2" %} {% if forloop.counter <= 2 %} {# span_for_text will wrap the copy in s , which we'll use in the JS for this component #} {% with attr_required=True add_initial_value_attr=True span_for_text=True %} @@ -255,7 +255,7 @@ {% endwith %}
- {% with attr_required=True add_initial_value_attr=True label_text=form.ip.label sublabel_text="Example: 86.124.49.54 or 2001:db8::1234:5678" add_group_class="usa-form-group--unstyled-error" add_aria_label="Name server "|concat:forloop.counter|concat:" "|concat:form.ip.label %} + {% with attr_required=True add_initial_value_attr=True label_text=form.ip.label sublabel_text="Example: 86.124.49.54 or 2001:db8::1234:5678" add_aria_label="Name server "|concat:forloop.counter|concat:" "|concat:form.ip.label add_group_class="margin-top-2" %} {% input_with_errors form.ip %} {% endwith %}
From fefefcadaf780679c403d721705fd434060673ad Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 5 Mar 2025 15:32:17 -0700 Subject: [PATCH 127/173] Fix bug in grant_access --- src/registrar/decorators.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/registrar/decorators.py b/src/registrar/decorators.py index 714e375c4..e6e45f0da 100644 --- a/src/registrar/decorators.py +++ b/src/registrar/decorators.py @@ -164,7 +164,6 @@ def _user_has_permission(user, request, rules, **kwargs): HAS_PORTFOLIO_MEMBERS_VIEW, lambda: user.is_org_user(request) and user.has_view_members_portfolio_permission(portfolio) - # TODO -- fix this on all related URLS :( and ( _member_exists_under_portfolio(portfolio, kwargs.get("member_pk")) or _member_invitation_exists_under_portfolio(portfolio, kwargs.get("invitedmember_pk")) @@ -173,7 +172,7 @@ def _user_has_permission(user, request, rules, **kwargs): ] # Check conditions iteratively - return any(check() for rule, check in permission_checks if rule in rules) + return all(check() for rule, check in permission_checks if rule in rules) def _has_portfolio_domain_requests_edit(user, request, domain_request_id): From 76a660a4c9fb7545516698957527f7d57f5200ce Mon Sep 17 00:00:00 2001 From: CocoByte Date: Wed, 5 Mar 2025 16:05:09 -0700 Subject: [PATCH 128/173] Removed timeout (thanks Zander!), cleanup --- .../assets/src/js/getgov-admin/andi.js | 83 +++++++++++-------- .../assets/src/js/getgov-admin/main.js | 4 +- src/registrar/fixtures/fixtures_portfolios.py | 1 + 3 files changed, 50 insertions(+), 38 deletions(-) diff --git a/src/registrar/assets/src/js/getgov-admin/andi.js b/src/registrar/assets/src/js/getgov-admin/andi.js index 8c17b2d07..a6b42b966 100644 --- a/src/registrar/assets/src/js/getgov-admin/andi.js +++ b/src/registrar/assets/src/js/getgov-admin/andi.js @@ -4,46 +4,57 @@ It relies on an override in detail_table_fieldset.html that provides a span with a corresponding id for aria-describedby content. This allows us to avoid overriding aria-label, which is used by select2 -to send the current dropdown selection to ANDI +to send the current dropdown selection to ANDI. */ -export function initAriaInjections() { +export function initAriaInjectionsForSelect2Dropdowns() { document.addEventListener('DOMContentLoaded', function () { - // Set timeout so this fires after select2.js finishes adding to the DOM - setTimeout(function () { - // Find all spans with "--aria-description" in their id - const descriptionSpans = document.querySelectorAll('span[id*="--aria-description"]'); + // Find all spans with "--aria-description" in their id + const descriptionSpans = document.querySelectorAll('span[id*="--aria-description"]'); - // Iterate through each span to add aria-describedby - descriptionSpans.forEach(function(span) { - // Extract the base ID from the span's id (remove "--aria-description" part) - const fieldId = span.id.replace('--aria-description', ''); + descriptionSpans.forEach(function (span) { + // Extract the base ID from the span's id (remove "--aria-description") + const fieldId = span.id.replace('--aria-description', ''); + const field = document.getElementById(fieldId); - // Find the field element with the corresponding ID - const field = document.getElementById(fieldId); - - // If the field exists, set the aria-describedby attribute - if (field) { - let select2ElementDetected = false - if (field.classList.contains('admin-autocomplete')) { - const select2Id="select2-"+fieldId+"-container" - console.log("select2---> "+select2Id) - // If it's a Select2 component, find the rendered span inside Select2 - const select2SpanThatTriggersAria = document.querySelector("span[aria-labelledby='"+select2Id+"']"); - const select2SpanThatHoldsSelection = document.getElementById(select2Id) - if (select2SpanThatTriggersAria) { - console.log("set select2 aria") - select2SpanThatTriggersAria.setAttribute('aria-describedby', span.id); - // select2SpanThatTriggersAria.setAttribute('aria-labelledby', select2Id); - select2ElementDetected=true - } - } - if (!select2ElementDetected) - { - // Otherwise, set aria-describedby directly on the field - field.setAttribute('aria-describedby', span.id); - } + if (field) { + // If Select2 is already initialized, apply aria-describedby immediately + if (field.classList.contains('select2-hidden-accessible')) { + applyAriaDescribedBy(field, span.id); + return; } - }); - }, 500); + + // Use MutationObserver to detect Select2 initialization + const observer = new MutationObserver(function (mutations) { + if (document.getElementById(fieldId)?.classList.contains("select2-hidden-accessible")) { + applyAriaDescribedBy(field, span.id); + observer.disconnect(); // Stop observing after applying attributes + } + }); + + observer.observe(document.body, { + childList: true, + subtree: true + }); + } + }); + + // Function to apply aria-describedby to Select2 UI + function applyAriaDescribedBy(field, descriptionId) { + let select2ElementDetected = false; + const select2Id = "select2-" + field.id + "-container"; + + // Find the Select2 selection box + const select2SpanThatTriggersAria = document.querySelector(`span[aria-labelledby='${select2Id}']`); + + if (select2SpanThatTriggersAria) { + select2SpanThatTriggersAria.setAttribute('aria-describedby', descriptionId); + select2ElementDetected = true; + } + + // If no Select2 component was detected, apply aria-describedby directly to the field + if (!select2ElementDetected) { + field.setAttribute('aria-describedby', descriptionId); + } + } }); } \ No newline at end of file diff --git a/src/registrar/assets/src/js/getgov-admin/main.js b/src/registrar/assets/src/js/getgov-admin/main.js index 456863437..6ea73b9f3 100644 --- a/src/registrar/assets/src/js/getgov-admin/main.js +++ b/src/registrar/assets/src/js/getgov-admin/main.js @@ -19,7 +19,7 @@ import { initDynamicDomainInformationFields } from './domain-information-form.js import { initDynamicDomainFields } from './domain-form.js'; import { initAnalyticsDashboard } from './analytics.js'; import { initButtonLinks } from './button-utils.js'; -import { initAriaInjections } from './andi.js' +import { initAriaInjectionsForSelect2Dropdowns } from './andi.js' // General initModals(); @@ -27,7 +27,7 @@ initCopyToClipboard(); initFilterHorizontalWidget(); initDescriptions(); initSubmitBar(); -initAriaInjections(); +initAriaInjectionsForSelect2Dropdowns(); initButtonLinks(); // Domain request diff --git a/src/registrar/fixtures/fixtures_portfolios.py b/src/registrar/fixtures/fixtures_portfolios.py index b93b9efdd..4bf6f5da6 100644 --- a/src/registrar/fixtures/fixtures_portfolios.py +++ b/src/registrar/fixtures/fixtures_portfolios.py @@ -74,6 +74,7 @@ class PortfolioFixture: if not portfolio.federal_agency: if portfolio_dict.get("federal_agency") is not None: portfolio.federal_agency, _ = FederalAgency.objects.get_or_create(name=portfolio_dict["federal_agency"]) + portfolio.federal_agency.federal_type, _ = Federal_type.objects.get_or_create(name=portfolio_dict["federal_agency"]) else: federal_agencies = FederalAgency.objects.all() # Random choice of agency for selects, used as placeholders for testing. From 5c48277e7bddf1e4266b3c9b21c61524fd1790d8 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Wed, 5 Mar 2025 16:15:42 -0700 Subject: [PATCH 129/173] revert stray fixtures experiment --- src/registrar/fixtures/fixtures_portfolios.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/registrar/fixtures/fixtures_portfolios.py b/src/registrar/fixtures/fixtures_portfolios.py index 4bf6f5da6..b93b9efdd 100644 --- a/src/registrar/fixtures/fixtures_portfolios.py +++ b/src/registrar/fixtures/fixtures_portfolios.py @@ -74,7 +74,6 @@ class PortfolioFixture: if not portfolio.federal_agency: if portfolio_dict.get("federal_agency") is not None: portfolio.federal_agency, _ = FederalAgency.objects.get_or_create(name=portfolio_dict["federal_agency"]) - portfolio.federal_agency.federal_type, _ = Federal_type.objects.get_or_create(name=portfolio_dict["federal_agency"]) else: federal_agencies = FederalAgency.objects.all() # Random choice of agency for selects, used as placeholders for testing. From b96be921ea583400a99e0569a3d1f5945bb0cd3b Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Wed, 5 Mar 2025 18:56:29 -0500 Subject: [PATCH 130/173] unstyle errors in add forms --- src/registrar/templates/domain_nameservers.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/registrar/templates/domain_nameservers.html b/src/registrar/templates/domain_nameservers.html index 610477b3d..5863e9a6c 100644 --- a/src/registrar/templates/domain_nameservers.html +++ b/src/registrar/templates/domain_nameservers.html @@ -78,13 +78,13 @@
{% with sublabel_text="Example: ns"|concat:forloop.counter|concat:".example.com" %} - {% with attr_required=True span_for_text=True add_initial_value_attr=True add_group_class="margin-top-2" %} + {% with attr_required=True span_for_text=True add_initial_value_attr=True add_group_class="usa-form-group--unstyled-error margin-top-2" %} {% input_with_errors form.server %} {% endwith %} {% endwith %}
- {% with attr_required=True add_initial_value_attr=True label_text=form.ip.label sublabel_text="Example: 86.124.49.54 or 2001:db8::1234:5678" add_aria_label="Name server "|concat:forloop.counter|concat:" "|concat:form.ip.label add_group_class="margin-top-2" %} + {% with attr_required=True add_initial_value_attr=True label_text=form.ip.label sublabel_text="Example: 86.124.49.54 or 2001:db8::1234:5678" add_aria_label="Name server "|concat:forloop.counter|concat:" "|concat:form.ip.label add_group_class="usa-form-group--unstyled-error margin-top-2" %} {% input_with_errors form.ip %} {% endwith %}
@@ -241,7 +241,7 @@
- {% with sublabel_text="Example: ns"|concat:forloop.counter|concat:".example.com" add_group_class="margin-top-2" %} + {% with sublabel_text="Example: ns"|concat:forloop.counter|concat:".example.com" add_group_class="usa-form-group--unstyled-error margin-top-2" %} {% if forloop.counter <= 2 %} {# span_for_text will wrap the copy in s , which we'll use in the JS for this component #} {% with attr_required=True add_initial_value_attr=True span_for_text=True %} @@ -255,7 +255,7 @@ {% endwith %}
- {% with attr_required=True add_initial_value_attr=True label_text=form.ip.label sublabel_text="Example: 86.124.49.54 or 2001:db8::1234:5678" add_aria_label="Name server "|concat:forloop.counter|concat:" "|concat:form.ip.label add_group_class="margin-top-2" %} + {% with attr_required=True add_initial_value_attr=True label_text=form.ip.label sublabel_text="Example: 86.124.49.54 or 2001:db8::1234:5678" add_aria_label="Name server "|concat:forloop.counter|concat:" "|concat:form.ip.label add_group_class="usa-form-group--unstyled-error margin-top-2" %} {% input_with_errors form.ip %} {% endwith %}
From 3e100d28c3cf99a2aa9afb2064d115d875341c96 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Wed, 5 Mar 2025 19:23:34 -0500 Subject: [PATCH 131/173] console logs for dev --- src/registrar/assets/src/js/getgov/form-nameservers.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/registrar/assets/src/js/getgov/form-nameservers.js b/src/registrar/assets/src/js/getgov/form-nameservers.js index 49ad9d691..f72ba116b 100644 --- a/src/registrar/assets/src/js/getgov/form-nameservers.js +++ b/src/registrar/assets/src/js/getgov/form-nameservers.js @@ -188,6 +188,7 @@ export class NameserverForm { */ executeCallback() { if (this.callback) { + console.log("callback") this.callback(); this.callback = null; } else { @@ -205,6 +206,7 @@ export class NameserverForm { document.querySelectorAll('tr.edit-row:not(.display-none)').forEach(openEditRow => { this.resetEditRowAndFormAndCollapseEditRow(openEditRow); }); + console.log("this.addNameserversForm " + this.addNameserversForm); if (this.addNameserversForm) { // Check if this.addNameserversForm is visible (i.e., does not have 'display-none') if (!this.addNameserversForm.classList.contains('display-none')) { @@ -217,6 +219,7 @@ export class NameserverForm { } }; if (this.formChanged) { + console.log("this.formChanged") //------- Show the unsaved changes confirmation modal let modalTrigger = document.querySelector("#unsaved_changes_trigger"); if (modalTrigger) { From 636fea2d99afa61d572181ca7c4722d8f6c78da4 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Wed, 5 Mar 2025 19:43:12 -0500 Subject: [PATCH 132/173] reduce margin bottom on h1 --- src/registrar/assets/src/js/getgov/form-nameservers.js | 3 --- src/registrar/templates/domain_nameservers.html | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/registrar/assets/src/js/getgov/form-nameservers.js b/src/registrar/assets/src/js/getgov/form-nameservers.js index f72ba116b..49ad9d691 100644 --- a/src/registrar/assets/src/js/getgov/form-nameservers.js +++ b/src/registrar/assets/src/js/getgov/form-nameservers.js @@ -188,7 +188,6 @@ export class NameserverForm { */ executeCallback() { if (this.callback) { - console.log("callback") this.callback(); this.callback = null; } else { @@ -206,7 +205,6 @@ export class NameserverForm { document.querySelectorAll('tr.edit-row:not(.display-none)').forEach(openEditRow => { this.resetEditRowAndFormAndCollapseEditRow(openEditRow); }); - console.log("this.addNameserversForm " + this.addNameserversForm); if (this.addNameserversForm) { // Check if this.addNameserversForm is visible (i.e., does not have 'display-none') if (!this.addNameserversForm.classList.contains('display-none')) { @@ -219,7 +217,6 @@ export class NameserverForm { } }; if (this.formChanged) { - console.log("this.formChanged") //------- Show the unsaved changes confirmation modal let modalTrigger = document.querySelector("#unsaved_changes_trigger"); if (modalTrigger) { diff --git a/src/registrar/templates/domain_nameservers.html b/src/registrar/templates/domain_nameservers.html index 5863e9a6c..4b2571d6b 100644 --- a/src/registrar/templates/domain_nameservers.html +++ b/src/registrar/templates/domain_nameservers.html @@ -34,7 +34,7 @@
-

Name servers

+

Name servers

From c8925b733b6ca2db6fdd7967fe7ab7848d3d7bc0 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Wed, 5 Mar 2025 19:52:14 -0500 Subject: [PATCH 133/173] h1 and table last column margin, padding --- src/registrar/assets/src/sass/_theme/_tables.scss | 5 +++++ src/registrar/templates/domain_nameservers.html | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/registrar/assets/src/sass/_theme/_tables.scss b/src/registrar/assets/src/sass/_theme/_tables.scss index 222f44fcc..509bdc573 100644 --- a/src/registrar/assets/src/sass/_theme/_tables.scss +++ b/src/registrar/assets/src/sass/_theme/_tables.scss @@ -11,6 +11,11 @@ th { border: none; } + td.padding-right-0, + th.padding-right-0 { + padding-right: 0; + } + tr:first-child th:first-child { border-top: none; } diff --git a/src/registrar/templates/domain_nameservers.html b/src/registrar/templates/domain_nameservers.html index 4b2571d6b..3e0fdb2fa 100644 --- a/src/registrar/templates/domain_nameservers.html +++ b/src/registrar/templates/domain_nameservers.html @@ -115,7 +115,7 @@
- + @@ -131,7 +131,7 @@ - - - - - - From d6e665d3786e8795d9ad601bd40e563c4a387425 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Thu, 6 Mar 2025 20:47:05 -0500 Subject: [PATCH 153/173] minh for form rows --- src/registrar/assets/src/sass/_theme/_base.scss | 4 ++++ src/registrar/templates/domain_nameservers.html | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/registrar/assets/src/sass/_theme/_base.scss b/src/registrar/assets/src/sass/_theme/_base.scss index 1f23223a5..71894ce59 100644 --- a/src/registrar/assets/src/sass/_theme/_base.scss +++ b/src/registrar/assets/src/sass/_theme/_base.scss @@ -293,3 +293,7 @@ Fit-content itself does not work. .width-40p { width: 40%; } + +.minh-143px { + min-height: 143px; +} diff --git a/src/registrar/templates/domain_nameservers.html b/src/registrar/templates/domain_nameservers.html index 3f0236486..d3a9edab5 100644 --- a/src/registrar/templates/domain_nameservers.html +++ b/src/registrar/templates/domain_nameservers.html @@ -85,7 +85,7 @@
Your registered domains
Your registered domains
Name ServersName servers IP address Action
- {% with attr_required=True add_initial_value_attr=True label_text=form.ip.label sublabel_text="Example: 86.124.49.54 or 2001:db8::1234:5678" add_group_class="usa-form-group--unstyled-error" add_aria_label="Name server "|concat:forloop.counter|concat:" "|concat:form.ip.label add_group_class="margin-top-0" %} + {% with attr_required=True add_initial_value_attr=True label_text=form.ip.label sublabel_text="Example: 86.124.49.54 or 2001:db8::1234:5678" add_aria_label="Name server "|concat:forloop.counter|concat:" "|concat:form.ip.label add_group_class="margin-top-0" %} {% input_with_errors form.ip %} {% endwith %}
Name servers IP addressActionAction
{{ form.server.value }} {% if form.ip.value %}({{ form.ip.value }}){% endif %} +
+