简体   繁体   中英

Why isn't NVDA reading my custom error alert consistently?

I've set up a custom html page template for a client's azure b2c login page and the inline errors are getting read back as-expected (can provide additional details about those upon request if it'd be beneficial), but I'm a bit stumped as to why the page-level errors aren't getting read back as well.

The following are the relevant snippets of html from the template that get rendered during the initial page load:

<!-- B2C-Generated Form Element (all relevant html changes happen within this element) -->
<form id="localAccountForm" action="JavaScript:void(0);" class="localAccount" aria-label="Sign in to Your Account">

<!--B2C-Generated Error Container (prior to error / this html is generated/injected by Azure B2C when the page is loaded) -->
<div class="error pageLevel" aria-hidden="true" role="alert" style="display: none;">
  <p class="remove"></p>
</div>

<!-- Custom Error Container (prior to error / this html gets added to the template by jQuery once the window is ready) -->
<div role="alert" style="" class="errorContainer no-error" aria-hidden="false">
    <p id="pageError" role="" aria-hidden="true" style="" class="remove"></p>
</div>

After the initial content loads (both the content from Azure B2C, as well as the modifications from jQuery), the following logic gets run to ensure all of the error elements on the page are set up properly (or at least that's the intent) & eliminate some differences that may otherwise cause some problems:

initializeAlerts([
    '#pageError',
    'label[for="signInName"] + .error > p',
    '.password-label + .error > p'
]);

// Below functions are loaded & run from a separate .js file:

function initializeAlerts(errorSelectors) {
    errorSelectors.forEach((s) => {
        // Store the current error & whether or not inline styles were attempting to hide it
        var errorMsg = $(s).html();   
        var errorStyle = `${$(s).attr('style')} ${$(s).parent().attr('style')}`; 

        // Ensure the parent element has the role="alert" attribute (rather than the error itself)
        $(s).attr('role', '');
        $(s).parent().attr('role', 'alert');
        
        // Default both the error element & it's parent to be visible
        $(s).removeClass('remove').attr('aria-hidden', 'false').attr('style','');        
        $(s).parent().addClass('errorContainer').addClass('no-error').removeClass('remove').attr('aria-hidden', 'false').attr('style','');
        
        // If an error message is NOT present, add a class to the parent for styling purposes
        if (errorMsg) {
            $(s).parent().removeClass('no-error');
        }

        // If the error was supposed to be hidden dynamically via Azure B2C (i.e. it's a standard error that's prepopulated & simply gets shown/hidden), ensure the error itself is hidden (NOT the parent)
        if (errorStyle.indexOf('none') > -1) {
            $(s).addClass('remove').attr('aria-hidden', 'true');
            $(s).parent().addClass('no-error');
        }   

        // If/when the error gets updated, ensure it gets displayed/read
        callbackOnDOMUpdate([s], function() {
            var currentError = $(s).html();
            if (currentError) {
                $(s).removeClass('remove').attr('aria-hidden', 'false').attr('style','');
                $(s).parent().removeClass('no-error');
            } else {
                $(s).addClass('remove').attr('aria-hidden', 'true').attr('style','');
                $(s).parent().addClass('no-error');
            }
        });
    });    
}

function callbackOnDOMUpdate(selectors, callback) {
    selectors.forEach(selector => {
        $(function() {
            var target = document.querySelector(selector);
            var observer = new MutationObserver(function(mutations, observer) { 
                callback();
            });

            observer.observe(target, { 
                subtree: true, 
                childList : true, 
                characterData : true
            });
        });
    });
}

After all of that runs, if the user enters an incorrect user/pass combination, an error is returned to the page (via the "B2C-Generated Error Container") which looks something like this (depending on the specific error returned):

<!--B2C-Generated Error Container within form (after receiving error) -->
<div class="error pageLevel" aria-hidden="false" role="alert" style="display: block;">
    <p class="remove">Unable to validate the information provided.</p>
</div>

Though, the client wants some verbiage/styling changes made, so rather than showing that message as-is, a "remove" class is added to it (which is associated with a display: none !important css rule) and my custom error container is updated to show something similar to the message below (again, actual message may vary depending on the message returned from b2c):

<!-- Custom Error Container (after receiving error) -->
<div role="alert" style="display: block">
    <p id="pageError">
        <strong>We're sorry, the information entered does not match what we have on file.</strong> 
        Please try re-entering the information below or you can recover your login info.
    </p>
</div>

Unfortunately, while this DOES appear to get read as expected every once in a while, most of the time, the only message I hear read is "Sign in to Your Account form continue button" (which seems to be a combination of the aria label for the form element the changes are nested within, the form element name itself & the name of the last button the user clicks prior to seeing the page update).

I've tried to ensure that the error itself is nested as a child element within a parent element that:

  1. Is always visible (from both a css & aria perspective)
  2. Has role='alert'

(and simply show/hide the error itself via the addition/removal of the "remove" class)

... but I must be missing something somewhere, so any help anyone can offer would be appreciated.

So... as it turns out, all of the above code was fine as-is (with the exception of the fact that #1 above wasn't entirely true). The issue turned out to be related to the following...

When the user clicks the "sign in" button, I used $('body').removeClass('loaded'); which in turn would cause:

  1. A loading animation to be displayed
  2. The element containing all page content to be set to display: none;

When an error was detected, I would then similarly fire off the following commands (in this order):

  1. $('body').addClass('loaded'); (thus making everything visible again)
  2. the logic to read the system error that was returned & populate the custom error area of the code accordingly

... so, I tried removing all jquery related to the loading animation to see if perhaps the problem was related to that & sure enough, the error was read as expected.

That being said, I believe the real issue at play here was that the error/alert update was getting completed before all visibility-related changes had kicked through from the css side & therefore NVDA wasn't reading the alert because, from NVDA's perspective, the error was still hidden.

... hopefully the documentation of this experience helps someone else down the road!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM