Difference between revisions of "ARIA Combobox"

From Level Access Web Labs
Jump to navigation Jump to search
(Example 2, <button> using aria-owns)
(Results for example 1)
 
(23 intermediate revisions by 3 users not shown)
Line 17: Line 17:
 
     <title> ARIA Combobox </title>
 
     <title> ARIA Combobox </title>
 
     <style type="text/css">
 
     <style type="text/css">
 +
      ul#li {
 +
        list-style-type:none !important;
 +
        text-indent:0px;
 +
        border: thin solid #552c9f;
 +
        width:10em;
 +
        left:4em;
 +
        top:-1em;
 +
        position: relative;
 +
        padding-left:0;
 +
      }
 
       ul.closed {
 
       ul.closed {
        list-style-type:none !important;
 
        clear:both;
 
 
         display:none;
 
         display:none;
 
       }
 
       }
 
 
       ul.open {
 
       ul.open {
    text-indent:0px;
 
        list-style-type:none;
 
        clear:both;
 
 
         display:block;
 
         display:block;
        border: thin solid black;
 
        width:10em;
 
        left:4em;
 
        position: relative;
 
        padding-left:0;
 
 
       }
 
       }
 
 
       ul [aria-selected="true"] {
 
       ul [aria-selected="true"] {
         background-color:blue;
+
         background-color: #552c9f;
 
         color:white;
 
         color:white;
 
       }
 
       }
 
 
       li {
 
       li {
 
         padding-left:0;
 
         padding-left:0;
      }
+
         list-style-type:none !important;
 
 
      #i1 {
 
         float:left;
 
 
       }
 
       }
 
       #cb1 {
 
       #cb1 {
         //border: thin solid black;
+
         border: thin solid #552c9f;
        float:left;
 
        height:1em;
 
 
       }
 
       }
 
       label {
 
       label {
        float:left;
 
 
       }
 
       }
 
       #container {
 
       #container {
        width:9em;
+
 
 
       }
 
       }
 
       #b1 {
 
       #b1 {
        float:right;
+
      }
      }
 
 
     </style>
 
     </style>
 
     <script type="text/javascript">
 
     <script type="text/javascript">
Line 100: Line 90:
 
       document.getElementById("l1").className = "closed";
 
       document.getElementById("l1").className = "closed";
 
       document.getElementById("cb1").setAttribute('aria-expanded','false');
 
       document.getElementById("cb1").setAttribute('aria-expanded','false');
       document.getElementById("cb1").setAttribute('aria-activedescendant','i1');
+
       document.getElementById("cb1").setAttribute('aria-activedescendant','');
 +
      document.getElementById("cb1").setAttribute('aria-controls','');
 
             }
 
             }
  
        function openList () {
+
    function openList() {
          if (document.getElementById("l1").className == "closed") {
+
      document.getElementById("l1").className = "open";
            document.getElementById("l1").className = "open";
+
  document.getElementById("cb1").setAttribute('aria-expanded','true');
            document.getElementById("cb1").setAttribute('aria-expanded','true');
+
          document.getElementById("cb1").setAttribute('aria-controls','l1');
           }
+
          setActiveDescendant();
          setActiveDescendant()
+
    }
          document.getElementById("cb1").focus();
+
 
 +
        function listOpened () {
 +
           if (document.getElementById("l1").className == "open")
 +
            return true;
 +
        }
 +
 
 +
        function toggleList () {
 +
          if (!listOpened())
 +
            openList();
 +
    if (document.activeElement.id != "cb1")
 +
              document.getElementById("cb1").focus();
 +
          else {
 +
            closeList();
 +
          }
 
         }
 
         }
  
Line 124: Line 128:
 
         var newActive = null;
 
         var newActive = null;
  
         // expand on any keystroke
+
         // expand on any keystroke up or down
 
         if (e.which == 38 || e.which == 40) {
 
         if (e.which == 38 || e.which == 40) {
          openList();
+
          if (!listOpened())
 +
            openList();
 
         }
 
         }
 +
 +
        // enter
 +
        if (e.which == 13)
 +
          toggleList();
 +
 +
        // escape
 +
        if (e.which == 27)
 +
          closeList();
 +
 +
        if (!listOpened())
 +
          return false;
  
 
         var active = document.getElementById("cb1").getAttribute("aria-activedescendant");
 
         var active = document.getElementById("cb1").getAttribute("aria-activedescendant");
Line 145: Line 161:
 
         if (e.which == 38 || e.which == 40) {
 
         if (e.which == 38 || e.which == 40) {
 
           if (newActive) {
 
           if (newActive) {
             document.getElementById("i1").value = document.getElementById(newActive).textContent;
+
             document.getElementById("cb1").value = document.getElementById(newActive).textContent;
 
             document.getElementById("cb1").setAttribute("aria-activedescendant",newActive);
 
             document.getElementById("cb1").setAttribute("aria-activedescendant",newActive);
 
             resetSelection();
 
             resetSelection();
Line 157: Line 173:
 
   </head>
 
   </head>
 
<body>
 
<body>
    <div>
+
  <div>
      <label id="lbl1" for="i1">Platform:</label>
+
    <div id="container">
       <div id="container">
+
      <label for="cb1">Platform:</label>
      <div role="combobox" onfocus="" id="cb1" aria-readonly="true" onkeydown="change(event);" aria-owns="l1 i1" autocomplete="list" aria-activedescendant="i1" aria-labelledby="lbl1" aria-expanded="false" onblur="closeList();" tabindex="0" >
+
       <input aria-readonly="true" onkeydown="change(event);" role="combobox" aria-controls="" aria-expand="false" aria-activedescendant="" onblur="closeList();" id="cb1" autocomplete="list" value="iOS" aria-labelledby="lbl1" type="text" />
        <input id="i1" tabindex="-1" value="iOS" type="text" />
+
    <button id="b1" aria-hidden="true" tabindex="-1" onclick="toggleList();" >V</button>
      </div>
 
      <button id="b1" aria-hidden="true" tabindex="-1" onclick="openList();" >V</button>
 
      <ul role="listbox" id="l1" class="closed" aria-labelledby="lbl1" tabindex="-1">
 
        <li onclick="select(this);" aria-selected="true" id="li1" role="option" tabindex="-1" aria-hidden="false"    >iOS</li>
 
        <li onclick="select(this);" aria-selected="false" id="li2" role="option" tabindex="-1" aria-hidden="false">Android</li>
 
        <li onclick="select(this);" aria-selected="false" id="li3" role="option" tabindex="-1" aria-hidden="false">Windows Phone</li>
 
      </ul>
 
    </div>
 
 
     </div>
 
     </div>
 
+
      <ul role="listbox" id="l1" class="closed">
    <div style="clear:both; padding-top:1em;">
+
        <li onclick="select(this);" aria-selected="true" id="li1" role="option" >iOS</li>
      <button> Nothing </button>
+
        <li onclick="select(this);" aria-selected="false" id="li2" role="option">Android</li>
 +
        <li onclick="select(this);" aria-selected="false" id="li3" role="option">Windows Phone</li>
 +
      </ul>
 
     </div>
 
     </div>
 
+
  </div>
 
</body>
 
</body>
 
</html>
 
</html>
 +
 +
==Results for example 1==
 +
With JAWS and IE all the values in the combo box are announced when reading in virtual cursor mode if the combo box is expanded. The Roll of combo box is not announced.
 +
In Chrome the Currently selected value is announced twice when navigating with the ARROW keys in Virtual cursor mode. In Firefox the currently selected value is announced in virtual cursor mode followed by the role of list box.
 +
NVDA in Firefox and Chrome work as expected. The name Role and value of the combobox are announced in Browse Mode and Focus mode.
 +
In IE it is not possible to determine the currently selected value of the combo box in Brows mode.
 +
In iOS the name and Role are announced. When activating the combo box, the values are not announced by VoiceOver.
 +
With Talkback and Chrome the Name and role of the combo box are announced as expected. when the Combo box is activated the list of choices is not announced by Talkback. When navigating by control the name of the combo box is not announced by TalkBack.
  
 
== Example 2, &lt;button&gt; using aria-owns ==
 
== Example 2, &lt;button&gt; using aria-owns ==
Note about this example:  
+
Notes about this example:  
 
* Example is primarily designed to demonstrate a remediation of a typical &lt;button&gt; bound pop-out section control, e.g. the type of simulated "drop-down" control generated by a UI library such as legacy versions of jQueryUI.
 
* Example is primarily designed to demonstrate a remediation of a typical &lt;button&gt; bound pop-out section control, e.g. the type of simulated "drop-down" control generated by a UI library such as legacy versions of jQueryUI.
* Example utilizes jQuery as a matter of expediency (i.e. developer of the example was able to generate the code from previous design work). It is not a requirement of compliant design for this type of structure.
+
* Example utilizes jQuery as a matter of expediency (i.e. developer of the example was able to generate the code by integrating event handler code from previous examples). It is not a requirement of compliant design for this type of structure.
 +
* 3/27/17 note - example code not currently complete, widget lacks event handler code for arrow keys to increment the value when combobox is collapsed. Update pending
  
 
<html>
 
<html>
Line 221: Line 240:
 
ul.list li a {
 
ul.list li a {
 
color: black;
 
color: black;
 +
background-color: white;
 
}
 
}
 
ul.list li.current a {
 
ul.list li.current a {
 
color: white;
 
color: white;
 +
background-color:blue;
 
}
 
}
 
button.combobox {
 
button.combobox {
Line 240: Line 261:
 
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
 
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
 
<script type="text/javascript">
 
<script type="text/javascript">
function clickCB(e) {
+
function clickCB (e) {
 
if ($("#cb_List").hasClass("hidden")) {
 
if ($("#cb_List").hasClass("hidden")) {
 
$("#cb_List").removeClass("hidden");
 
$("#cb_List").removeClass("hidden");
$(e.target).attr("aria-expanded", "true");
+
$("#cb_Combobox").attr("aria-expanded", "true");
 
cbUpdateFocus($("#cb_List a").get(0));
 
cbUpdateFocus($("#cb_List a").get(0));
 
}
 
}
 
else {
 
else {
 
$("#cb_List").addClass("hidden");
 
$("#cb_List").addClass("hidden");
$(e.target).attr("aria-expanded", "false");
+
$("#cb_Combobox").attr("aria-expanded", "false");
$(e.target).get(0).focus();
+
$("#cb_Combobox").get(0).focus();
 
};
 
};
 
};
 
};
function cbSelectValue(e) {
+
function keyupCB (e){
 +
switch (e.which) {
 +
case 38:
 +
// Up Arrow
 +
console.log("Up");
 +
var jSet = $("#cb_List a");
 +
var indexThis = jSet.index($("#cb_List a[aria-selected='true']"));
 +
var indexToBe;
 +
if (indexThis - 1 < 0){ indexToBe = 0 }
 +
else { indexToBe = indexThis - 1 };
 +
cbSelectValue(jSet.get(indexToBe));
 +
e.preventDefault();
 +
break;
 +
case 40:
 +
// Down Arrow
 +
console.log("Down");
 +
if(e.altKey == true) { clickCB(e); }
 +
else {
 +
var jSet = $("#cb_List a");
 +
var indexThis = jSet.index($("#cb_List a[aria-selected='true']"));
 +
var indexToBe;
 +
if (indexThis + 1 > jSet.length - 1){ indexToBe = indexThis }
 +
else { indexToBe = indexThis + 1 };
 +
cbSelectValue(jSet.get(indexToBe));
 +
}
 +
e.preventDefault();
 +
break;
 +
};
 +
};
 +
function cbClickValue(e) {
 
e.preventDefault();
 
e.preventDefault();
 +
cbSelectValue(e.target);
 +
}
 +
function cbSelectValue(element) {
 +
console.log($(element));
 
$("#cb_List").find("[aria-selected='true']").attr("aria-selected", "false")
 
$("#cb_List").find("[aria-selected='true']").attr("aria-selected", "false")
$(e.target).attr("aria-selected", "true");
+
$(element).attr("aria-selected", "true");
$("#cb_Content_Placeholder").text($(e.target).text());
+
$("#cb_Content_Placeholder").text($(element).text());
$("#cb_Edit").text($(e.target).text());
+
$("#cb_Edit").text($(element).text());
cbClose();
+
if ($("#cb_List").is(":visible")) { cbClose() };
 
};
 
};
 
function cbClose() {
 
function cbClose() {
Line 276: Line 330:
 
case 13 :  
 
case 13 :  
 
// Enter - Activate
 
// Enter - Activate
 +
console.log("Enter");
 
break;
 
break;
 
case 27 :
 
case 27 :
 
// Escape - Close with no change
 
// Escape - Close with no change
 +
console.log("Escape");
 
cbClose();
 
cbClose();
 
break;
 
break;
Line 287: Line 343:
 
case 38:
 
case 38:
 
// Up - Move to Previous in Set
 
// Up - Move to Previous in Set
 +
console.log("Up Arrow");
 
var indexThis = jSet.index($(e.target));
 
var indexThis = jSet.index($(e.target));
 
var indexToBe;  
 
var indexToBe;  
Line 296: Line 353:
 
case 39:
 
case 39:
 
// Right - Do Nothing
 
// Right - Do Nothing
 +
console.log("Right Arrow");
 
break;
 
break;
 
case 40:  
 
case 40:  
 
// Down - Move to Next In Set
 
// Down - Move to Next In Set
 +
console.log("Down Arrow");
 
var indexThis = jSet.index($(e.target));
 
var indexThis = jSet.index($(e.target));
 
var indexToBe;  
 
var indexToBe;  
Line 308: Line 367:
 
case 32 :
 
case 32 :
 
// Spacebar - Activate
 
// Spacebar - Activate
 +
console.log("space");
 
break;
 
break;
 
};
 
};
Line 324: Line 384:
 
<ul id="cb_List" role="listbox" class="list hidden">
 
<ul id="cb_List" role="listbox" class="list hidden">
 
<li role="presentation">
 
<li role="presentation">
<a href="#" role="option" aria-selected="true" tabindex="-1">&nbsp;</a>
+
<a href="#" role="option" aria-selected="true" tabindex="0">&nbsp;</a>
 +
<!-- Null option here provided for the benefit of Android. TalkBack appears to not handle the design properly is NO option is currently selected, which causes it to read out the entire listbox as the current value. Adding a &nbsp; entry solves the issue. -->
 
</li>
 
</li>
 
<li role="presentation">
 
<li role="presentation">
Line 343: Line 404:
 
<script>
 
<script>
 
(function(){
 
(function(){
document.getElementById("cb_Combobox").addEventListener("click", clickCB);
+
$("#cb_Combobox").each(function(){
 +
this.addEventListener("click", clickCB);
 +
this.addEventListener("keyup", keyupCB);
 +
});
 
$("#cb_List a").each(function(){
 
$("#cb_List a").each(function(){
this.addEventListener("click", cbSelectValue);
+
this.addEventListener("click", cbClickValue);
 
this.addEventListener("keyup", cbListKeyUp);
 
this.addEventListener("keyup", cbListKeyUp);
 
});
 
});

Latest revision as of 15:35, 11 October 2018

Example 1, Based on native <input>

Note about this example:

  • Bullet's are shown because wiki overrides list-style-type.
  • This solution uses the autocomplete="list" approach with aria-activedescendant on a listbox.

Challenges this solution overcomes:

  • IE does not render display none referred to be aria-activedescendant. This example make the visible child input as the activedescendant when the combobox is closed.
  • IE appears to create a infinite combobox structure when role combobox applied to input. This solution uses a div with role of combobox.
  • JAWS auto forms mode doesn't work correctly when role of combobox is used on input field -- it sees the box as an edit and turns off forms mode when arrow keys are used. This solution uses a div element instead with a role of combo box.
  • activedescendant doesn't work right in IE unless tabindex="-1" is put on each element. That is done in this example.

Reference: WAI - description, properties, keyboard interaction.
http://www.w3.org/TR/wai-aria-practices-1.1/#combobox

ARIA Combobox

  • iOS
  • Android
  • Windows Phone

Results for example 1

With JAWS and IE all the values in the combo box are announced when reading in virtual cursor mode if the combo box is expanded. The Roll of combo box is not announced. In Chrome the Currently selected value is announced twice when navigating with the ARROW keys in Virtual cursor mode. In Firefox the currently selected value is announced in virtual cursor mode followed by the role of list box. NVDA in Firefox and Chrome work as expected. The name Role and value of the combobox are announced in Browse Mode and Focus mode. In IE it is not possible to determine the currently selected value of the combo box in Brows mode. In iOS the name and Role are announced. When activating the combo box, the values are not announced by VoiceOver. With Talkback and Chrome the Name and role of the combo box are announced as expected. when the Combo box is activated the list of choices is not announced by Talkback. When navigating by control the name of the combo box is not announced by TalkBack.

Example 2, <button> using aria-owns

Notes about this example:

  • Example is primarily designed to demonstrate a remediation of a typical <button> bound pop-out section control, e.g. the type of simulated "drop-down" control generated by a UI library such as legacy versions of jQueryUI.
  • Example utilizes jQuery as a matter of expediency (i.e. developer of the example was able to generate the code by integrating event handler code from previous examples). It is not a requirement of compliant design for this type of structure.
  • 3/27/17 note - example code not currently complete, widget lacks event handler code for arrow keys to increment the value when combobox is collapsed. Update pending

Select a Browser: