Summary

This article investigates a method of providing client-side form validation through the DOM, and ensures that it works as expected with screen readers. Modern screen readers work relatively well with scripting, but it's the extra steps required to inform screen reader users that the content has changed that needs addressing.

Author: Gez Lemon

Contents

Generic Form Validation

Before I start, I would like to extend my gratitude to Jim Thatcher, who was kind enough and patient enough to provide invaluable feedback about how different screen readers worked with the scripts I tried. Without Jim's help, I would never have been able to come up with this technique.

I've been investigating techniques to provide client-side form validation through the DOM. Ideally, this should be a generic technique that could be applied to any page with any number of forms. The simplest way of handling this is to use the class attribute, and assign a value indicating the type of validation required; for example, string, number, email, etc. As the class attribute can accept multiple class names with a space-separated string, it wouldn't impact on any other classes used. To include an input form control in the validation process just requires adding a class indicating the type of validation:

<input type="text" ... class="string">

Optional form fields that require no validation wouldn't require a validation class, but it's not unusual to have optional form fields that would require validating if something is entered. As the class attribute can accept multiple class names, this can be achieved by just adding another class to the form control:

<input type="text" ... class="email optional">

In this case, the validation for email would only result in a submission error if the user has added an ill-formed email address. There is obviously no guarantee that just because the email address is a valid format that it actually exists, but we're only interested in making it easy for the user to correct mistakes without having to jump through hoops.

If the script is included on a page that has form elements, each form is assigned a submit event handler. When the submit event is raised, the handler iterates through all form controls looking for the validation classes. If the validation fails, a nice error message is produced at the top of the form, with a list of links to the form controls that were not correct.

Client-Side Scripting and Screen Readers

All is well and good, except when a screen reader user tries the form. For example, after changing the DOM, Window-Eyes announces:

Loading Page, Load Done, Looking for visible line.

The last visible line is where they left off; the submit button. Screen reader users have no idea that extra content has been added to the page, without re-reading the whole page following the cue that the page has been reloaded. It's unreasonable to expect screen reader users to read the whole page again, and couldn't be considered an equivalent experience to a sighted user who can immediately see what has changed. Fortunately, the solution is very straightforward.

The solution is to simply set the focus to the start of the error message. To set the focus, we require an element that is able to receive focus, like an anchor element. We already have links indicating the error messages, so we could just set the focus to the first link in the list. This still wouldn't be considered an equivalent experience, as the screen reader user wouldn't be informed there is a submission error, but asked to enter something again. A better solution is to make the heading text "Errors in Submission" a link that points to the first error in the list. That way, screen reader users are explicitly told that there is an error with the form submission, with the ability to navigate through the error links. The error links themselves point to the input field in error. The downside to the solution is that the headings for the submission errors are now ugly links, but that is easily solved with a bit of CSS.

Because the heading is now a link to the first error, and the error link points to the form control with the error, Jim pointed out that a user might not realise there were other errors if they just followed the links. Jim suggested adding an error count to the title, so that the user is aware how many errors there are in the submission.

This solution works with Window-Eyes and JAWS, but doesn't work with HPR. I'm not entirely sure why the solution doesn't work with HPR, but it's likely to be to do with the way that HPR doesn't fire the focus event for links.

Example Form

To help clarify how it works, I've set up a demonstration page that uses the technique, along with the ECMAScript to produce the error message through the DOM. The demonstration page has two forms. The second form has two optional fields; one with no validation at all, and another that is validated if an email address is entered.

Due to the usual suspects not behaving correctly, the solution here uses the traditional event registration model rather than the correct DOM Level 2 Events Specification registration model. A far better cross-browser event registration technique can be seen from Scott Andrew LePera; I've stuck with the traditional model so as not to distract from the technique itself.

Category: Accessibility.

Comments

  1. [dom-screen-readers.php#comment1]

    Very interesting information, thanks. I hadn't thought about the way people using screenreaders would have to deal with forms and error messages. Really makes me reconsider the design of form validation.

    Posted by Matthijs on

  2. [dom-screen-readers.php#comment2]

    Instead of using brute-force plain text to state the number of fields in error, why not just rely on the fact that you're using a list? You could even make it an ordered list. Semantics are not implied, they're stated outright.

    Posted by Joe Clark on

  3. [dom-screen-readers.php#comment3]

    Hi Joe,

    Instead of using brute-force plain text to state the number of fields in error, why not just rely on the fact that you're using a list?

    I didn't think of that, but you're right.

    You could even make it an ordered list. Semantics are not implied, they're stated outright.

    Thanks, Joe, that's a great suggestion. I've updated the script to produce an ordered list. I'll leave the number of errors in the title, as there's nothing wrong with summarising the result at the start.

    Posted by Gez on

  4. [dom-screen-readers.php#comment4]

    Interesting post, Gez. Especially to demonstrate that dynamically created content (validation error messages) are being picked up by screen readers (well JAWS and Window Eyes for starters).

    Brothercake's tests of dynamically generated content ( http://www.accessifyforum.com/viewtopic.php?p=33904&highlight=#33904 ), and his conclusions are somewhat different (the main difference is that his content updates are the result of an asynchonous update - AJAX) - the content is being written to the document, but screen readers are not noticing it in a reliable manner.

    Mike.

    Posted by Isofarro on

  5. [dom-screen-readers.php#comment5]

    Hi Mike,

    Thank you for the link to Brothercake's tests. I haven't read through in detail, yet, but I will take a look through tomorrow. I can't see that Ajax would make a difference to the results, but the version of the screen reader might.

    I'm collaborating with Steve Faulkner of Vision Australia for further screen reader tests, so will have some more results for scripting and screen readers soon. I'll add some Ajax tests for confirmation.

    Best regards,

    Posted by Gez on

  6. [dom-screen-readers.php#comment7]

    I have recently been working on a similar form validation which does the following:

    1. Page title changes to indicate errors
    2. Explanation text is written into the page
    3. The page jumps to the error text.
    3. A list of errors which link to each form element is written in
    (uses focus insted of anchors)
    4. Each area is flagged up with a coloured background.

    which can be seen here:

    http://www.lvsc.org.uk/templates/system/booking.asp?NodeID=90344&option=1

    Im using hidden fields for validation - so they can enter custom error messages.

    ben.

    Posted by ben morrison on

  7. [dom-screen-readers.php#comment10]

    Hi Ben,

    I like the bold (visual) error notification -including title change in your form. But it is a problem for JAWS whose focus stays on the submit button - of course a refresh puts one back at the begining.

    Posted by Jim Thatcher on

  8. [dom-screen-readers.php#comment11]

    Hi everyone,

    Thanks for the input. So I should be using focus() instead of this.location after the submit is used to help JAWS play nice. Patrick (http://www.splintered.co.uk), gave me some nice ideas too, like adding "Error: " to each label/legend.
    The idea behind hidden inputs for required fields and the related error message was that this could be re-used for the server side implementation as well.

    Thanks, Ben.

    Posted by ben morrison on

  9. [dom-screen-readers.php#comment12]

    Hi Gez

    you changed anything behind the scenes of the demo form? I'm getting an error when I submit:

    
    Method Not Allowed
    The requested method POST is not allowed 
    for the URL /experiments/form.html.
    

    Posted by bruce on

  10. [dom-screen-readers.php#comment13]

    Hi Bruce,

    I'm getting an error when I submit:

    The error is because the form is submitted to itself (an HTML page), which can't handle the method attribute. It was only meant to demonstrate error messages, but it makes sense for it to work, so I've updated it so that it will submit successfully if someone fills in all the details.

    Cheers,

    Posted by Gez on

  11. [dom-screen-readers.php#comment14]

    Soz, Gez, I was testing it out, forgetting that Firefox had JavaScript disabled from a previous test (doh!), so was seeing no validation (obviously) and then getting the submission error.

    I'm a fool and will buy you 10 pints of Old Scumfungus and a bag of twiglets at @media.

    Posted by bruce on

  12. [dom-screen-readers.php#comment15]

    It was a good suggestion to make it submit properly, so I'm glad you had JavaScript off *smile*

    Cheers,

    Posted by Gez on

  13. [dom-screen-readers.php#comment16]

    Silly question this - but how do you validate content of a textarea? I've tried just adding the string class but it doesn't seem to work.

    Thanks

    Posted by Helena on

  14. [dom-screen-readers.php#comment17]

    Hi Helena,

    Silly question this - but how do you validate content of a textarea? I've tried just adding the string class but it doesn't seem to work.

    The example here is just a skeleton, and only evaluates the input element. Changing:

    
    var objField = objForm.getElementsByTagName('input');
    

    to

    
    var objField = objForm.getElementsByTagName('textarea');
    

    Would validate textarea controls, but not input. To do both, you have to add extra code. The way I usually do it is to abstract the function, and pass the controls that needs validating.

    Posted by Gez on

  15. [dom-screen-readers.php#comment19]

    Following up on Helena's question about validating the content of a text area, would you mind demonstrating how to validate whether at least one radio button or check box is checked, and to make sure an <option> from a drop-down is selected? I have no experience with the DOM, etc, but wish to create a compliant form. Thank you so much.
    Audrey

    Posted by Audrey on

  16. [dom-screen-readers.php#comment20]

    Following up on Helena's question about validating the content of a text area, would you mind demonstrating how to validate whether at least one radio button or check box is checked, and to make sure an <option> from a drop-down is selected?

    If all options in a select are valid, then it wouldn't require validating. If the first option is not a valid option, but a call for action, such as 'Choose option', then select elements can be validated using the selectedIndex property. If the value is zero, then the first element is currently selected:

    
    if (objSelect.selectedIndex == 0)
    {
      // Please choose something
    }
    

    If a default option is chosen with a radio button, they wouldn't require validating. For checkboxes and radio buttons where no default option is given, the checked property can be used for validation. For example, if you have a group of checkbox controls with the name groupname:

    
    <input type="checkbox" name="groupname" id="a" value="A">
    <label for="a">One?</label><br>
    
    <input type="checkbox" name="groupname" id="b" value="B">
    <label for="b">Two?</label><br>
    
    <input type="checkbox" name="groupname" id="c" value="C">
    <label for="c">Three?</label><br>
    

    You could validate with the following:

    
    // Check if one option from a group
    // of checkboxes has been selected
    var bSuccess = validateChecked(objForm, 'groupname');
    
    // Validate checkbox or radio button
    function validateChecked(objForm, strName)
    {
      var objField = objForm.getElementsByTagName('input');
    
      for (var iCounter=0; iCounter<objField.length; iCounter++)
        if (objField[iCounter].name == strName && objField[iCounter].checked)
          return true;
    
      return false;
    }
    

    Posted by Gez on

  17. [dom-screen-readers.php#comment21]

    Thank you for the detail. I look forward to working on this today!
    Thanks again for your reply.
    Audrey

    Posted by Audrey on

  18. [dom-screen-readers.php#comment22]

    Gez:

    Should I create a new class, 'select', following your other classes, string, email, etc? I know this is asking a lot, but would you be willing to provide the code for how to integrate the select and radio buttons into your existing script? It would be, just as you said, to make sure at least one button is checked, or one option is selected.
    I apologize for not knowing how to code this myself.
    Thank you for your consideration.
    Audrey

    
    case 'number':
      if (!isNumber(objField[iFieldCounter].value, arClass))
      {
        if (iErrors == 0)
          logError(objField[iFieldCounter], objLabel, objList, strLinkID);
        else
          logError(objField[iFieldCounter], objLabel, objList, '');
        iErrors++;
      }
      break;
    

    Posted by Audrey on

  19. [dom-screen-readers.php#comment23]

    To merge the suggestions into the existing script requires a few changes. Because checkboxes and radio buttons are grouped, you'll only want to validate them once per name. One approach would be to store the names that have already been validated in an array to save validating the same group more than once:

    
      var arVisited = new Array();
    

    When you iterate through the input form controls, add a condition at the start just for checkboxes and radio buttons:

    
    // Checkboxes and radio buttons are grouped by the name attribute
    if (objField[iFieldCounter].type == 'checkbox' || objField[iFieldCounter].type == 'radio')
    {
      bCheckRequired = true;
    
      // Check if it's already been validated
      for (iCounter=0; iCounter<arVisited.length; iCounter++)
        if (arVisited[iCounter] == objField[iFieldCounter].name)
          bCheckRequired = false;
    
      if (bCheckRequired)
      {
        if (!validateChecked(objForm, objField[iFieldCounter].name))
        {
          // Search the label for the error prompt
          for (iCounter=0; iCounter<objLabel.length; iCounter++)
          if (objLabel[iCounter].htmlFor == objField[iFieldCounter].id)
            strError = objLabel[iCounter].firstChild.nodeValue;
    
          strError = 'Select an option from the group containing "' + strError + '"';
          if (iErrors == 0)
            addError(objList, strError, objField[iFieldCounter].id, strLinkID);
          else
            addError(objList, strError, objField[iFieldCounter].id, '');
    
          iErrors++;
        }
    
        arVisited.push(objField[iFieldCounter].name)
      }
    }
    

    And move the rest of the checks for input into an else section.

    The select elements will need to be validated outside of the loop that validates input form controls, but before where you check for errors and display them.

    
    // Validate select options
    var objField = objForm.getElementsByTagName('select');
    for (iFieldCounter=0; iFieldCounter<objField.length; iFieldCounter++)
    {
      if (objField[iFieldCounter].selectedIndex == 0)
      {
        // Search the label for the error prompt
        for (iCounter=0; iCounter<objLabel.length; iCounter++)
          if (objLabel[iCounter].htmlFor == objField[iFieldCounter].id)
            strError = objLabel[iCounter].firstChild.nodeValue;
    
        if (iErrors == 0)
          addError(objList, strError, objField[iFieldCounter].id, strLinkID);
        else
          addError(objList, strError, objField[iFieldCounter].id, '');
    
        iErrors++;
      }
    }
    

    It's much simpler than it sounds, but if you have a problem, email me and I'll send you the complete script.

    Posted by Gez on

Comments are closed for this entry.