Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#3181 - dynamic labels for investigator and senior official [-Bob] #3577

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

CocoByte
Copy link
Contributor

Ticket 3181

Resolves #3181

Changes

  • Added overrides that target select2 dropdowns and add aria-describedby text in accordance with the ticket's ACs.

Context for reviewers

The two dropdowns listed in the original ACs really just highlight a more systemic issue with our ARIA labels for select2 dropdowns. The updates for this ticket addresses all select2 components throughout Django Admin so they will all follow the same pattern.

Setup

Code Review Verification Steps

As the original developer, I have

Satisfied acceptance criteria and met development standards

  • Met the acceptance criteria, or will meet them in a subsequent PR
  • Created/modified automated tests
  • Update documentation in READMEs and/or onboarding guide

Ensured code standards are met (Original Developer)

  • If any updated dependencies on Pipfile, also update dependencies in requirements.txt.
  • Interactions with external systems are wrapped in try/except
  • Error handling exists for unusual or missing values

Validated user-facing changes (if applicable)

  • Tag @dotgov-designers in this PR's Reviewers for design review. If code is not user-facing, delete design reviewer checklist
  • Verify new pages have been added to .pa11yci file so that they will be tested with our automated accessibility testing
  • Checked keyboard navigability
  • Tested general usability, landmarks, page header structure, and links with a screen reader (such as Voiceover or ANDI)

As a code reviewer, I have

Reviewed, tested, and left feedback about the changes

  • Pulled this branch locally and tested it
  • Verified code meets all checks above. Address any checks that are not satisfied
  • Reviewed this code and left comments. Indicate if comments must be addressed before code is merged
  • Checked that all code is adequately covered by tests
  • Verify migrations are valid and do not conflict with existing migrations

Validated user-facing changes as a developer

Note: Multiple code reviewers can share the checklists above, a second reviewer should not make a duplicate checklist. All checks should be checked before approving, even those labeled N/A.

  • New pages have been added to .pa11yci file so that they will be tested with our automated accessibility testing
  • Checked keyboard navigability
  • Meets all designs and user flows provided by design/product
  • Tested general usability, landmarks, page header structure, and links with a screen reader (such as Voiceover or ANDI)
  • (Rarely needed) Tested as both an analyst and applicant user

As a designer reviewer, I have

Verified that the changes match the design intention

  • Checked that the design translated visually
  • Checked behavior. Comment any found issues or broken flows.
  • Checked different states (empty, one, some, error)
  • Checked for landmarks, page heading structure, and links

Validated user-facing changes as a designer

  • Checked keyboard navigability
  • Tested general usability, landmarks, page header structure, and links with a screen reader (such as Voiceover or ANDI)
  • Tested with multiple browsers (check off which ones were used)
    • Chrome
    • Microsoft Edge
    • FireFox
    • Safari
  • (Rarely needed) Tested as both an analyst and applicant user

References

Screenshots

@CocoByte CocoByte changed the title [DRAFT] #3181 - dynamic labels for investigator and senior official #3181 - dynamic labels for investigator and senior official [-Bob] Feb 27, 2025
@zandercymatics zandercymatics self-assigned this Feb 28, 2025
Copy link
Contributor

@zandercymatics zandercymatics left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just needs some cleanup, but everything is here! I like how you are addressing this holistically, which is a very nice touch

This allows us to avoid overriding aria-label, which is used by select2
to send the current dropdown selection to ANDI
*/
export function initAriaInjections() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you rename this to something similar to initAriaInjectionsForSelect2? Would be more descriptive for someone unfamiliar with this part of the codebase

Comment on lines +13 to +40
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', '');

// 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
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works but this is definitely a workaround for select2 weirdness! I think this on its own should be fine but I'd like to try another solution first

I haven't tested it but according to select2's docs you can check if its intialized as so: https://select2.org/programmatic-control/methods#checking-if-the-plugin-is-initialized. Then you might be able to use a mutationObserver. If this works, it would be this would be less flaky overall since the load time can vary per machine

One thing to note is that instead of the $ icon, django admin binds it to django.jQuery

    // check if it's already initialized
    if ($('#mySelect2').hasClass("select2-hidden-accessible")) {
        console.log("Select2 already initialized when we checked");
        // do some stuff
        return;
    }
    const observer = new MutationObserver(function(mutations) {
        if (django.jQuery(`#${select2Id}`).hasClass("select2-hidden-accessible")) {
            console.log("initialized");
            // Stop observing once initialized
            observer.disconnect();
        }
    });
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm so glad you brought this up. I hate relying on timeouts, so thanks for the suggestion. I'll give it a try

// 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"]');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't find this in the dom - which page are you finding this id on?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is nested in components on pages like admin --> change domain request

Comment on lines +167 to +171
{% if "related_widget_wrapper" in field.field.field.widget.template_name %}
<span id="{{ field.field.id_for_label }}--aria-description" class="visually-hidden admin-select--aria-description">
{{ field.field.label }}, combo-box, collapsed, edit, has autocomplete. To set the value, use the arrow keys or type the text.
</span>
{% endif %}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is magic to me, nice job 😄

{% endcomment %}
{% if "related_widget_wrapper" in field.field.field.widget.template_name %}
<span id="{{ field.field.id_for_label }}--aria-description" class="visually-hidden admin-select--aria-description">
{{ field.field.label }}, combo-box, collapsed, edit, has autocomplete. To set the value, use the arrow keys or type the text.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SamiyahKey / @CocoByte I think the andi content should be different here. With narrator this reads: Combobox, collapsed. Investigator, combo-box, collapsed, edit, has autocomplete. To set the value, use the arrow keys or type the text.

I would propose that this should be:
Edit, has autocomplete. To set the value, use the arrow keys or type the text. since screenreaders will append the combobox information for us
image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also - per the andi warning, this should have a aria-labelledby! It should be aria-labelledby=${id_of_title} so the screenreader will automatically read out that this is associated with investigator, rather than us having to add it manually

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes @zandercymatics I agree! Also the form field for "Senior official" still needs to be corrected, right now there still needs to be an accessible name added to the senior official field to tie with that aria-describedby
Screenshot 2025-02-28 at 12 56 31 PM
Screenshot 2025-02-28 at 12 56 19 PM

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No can do with select2. That attr is already used

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zandercymatics can you think of a possibility to handle this senior official field?

if (select2SpanThatTriggersAria) {
console.log("set select2 aria")
select2SpanThatTriggersAria.setAttribute('aria-describedby', span.id);
// select2SpanThatTriggersAria.setAttribute('aria-labelledby', select2Id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you clean up some of the old comments and logs here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice catch - thanks!

@SamiyahKey SamiyahKey requested review from kimmsie and removed request for kimmsie February 28, 2025 17:15
Copy link

🥳 Successfully deployed to developer sandbox nl.

Copy link

github-actions bot commented Mar 1, 2025

🥳 Successfully deployed to developer sandbox nl.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add Programmatic Labels to "Investigator" and "Senior Official" Form Fields
3 participants