Difference between revisions of "ARIA Combobox"

From Level Access Web Labs
Jump to navigation Jump to search
(Results for example 1)
 
(30 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 +
== Example 1, Based on native <input> ==
 
Note about this example:  
 
Note about this example:  
 
* Bullet's are shown because wiki overrides list-style-type.
 
* Bullet's are shown because wiki overrides list-style-type.
Line 16: 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 99: 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 123: 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 144: 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 156: 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>
 +
      <ul role="listbox" id="l1" class="closed">
 +
        <li onclick="select(this);" aria-selected="true" id="li1" role="option" >iOS</li>
 +
        <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>
 +
</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.
  
    <div style="clear:both; padding-top:1em;">
+
== Example 2, &lt;button&gt; using aria-owns ==
      <button> Nothing </button>
+
Notes about this example:  
    </div>
+
* 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 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>
 +
<head>
 +
<!-- This example by Scott Huey, please direct any internal questions, comments or concerns to him directly. -->
 +
<style>
 +
ul.list.hidden {
 +
height:0px;
 +
width: 0px;
 +
overflow: hidden;
 +
border: none;
 +
}
 +
div.comboParent {
 +
background-color: white;
 +
border: 1px black solid;
 +
width: 10em;
 +
line-height: 1.6em;
 +
position: relative;
 +
}
 +
ul.list {
 +
position: absolute;
 +
top: 1em;
 +
left: 0em;
 +
background-color: white;
 +
border: 1px black solid;
 +
list-style: none;
 +
padding-left: 0em;
 +
height: auto;
 +
width: auto;
 +
}
 +
ul.list li {
 +
width: 10em;
 +
padding-left: 1em;
 +
}
 +
ul.list li.current {
 +
    background-color:blue;
 +
}
 +
ul.list li a {
 +
color: black;
 +
background-color: white;
 +
}
 +
ul.list li.current a {
 +
color: white;
 +
background-color:blue;
 +
}
 +
button.combobox {
 +
background-color: white;
 +
width: 9em;
 +
height: 1.5em;
 +
border: none;
 +
position: relative;
 +
left: 0em;
 +
}
 +
span.edit {
 +
position: absolute;
 +
left: -999em;
 +
}
 +
</style>
 +
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
 +
<script type="text/javascript">
 +
function clickCB (e) {
 +
if ($("#cb_List").hasClass("hidden")) {
 +
$("#cb_List").removeClass("hidden");
 +
$("#cb_Combobox").attr("aria-expanded", "true");
 +
cbUpdateFocus($("#cb_List a").get(0));
 +
}
 +
else {
 +
$("#cb_List").addClass("hidden");
 +
$("#cb_Combobox").attr("aria-expanded", "false");
 +
$("#cb_Combobox").get(0).focus();
 +
};
 +
};
 +
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();
 +
cbSelectValue(e.target);
 +
}
 +
function cbSelectValue(element) {
 +
console.log($(element));
 +
$("#cb_List").find("[aria-selected='true']").attr("aria-selected", "false")
 +
$(element).attr("aria-selected", "true");
 +
$("#cb_Content_Placeholder").text($(element).text());
 +
$("#cb_Edit").text($(element).text());
 +
if ($("#cb_List").is(":visible")) { cbClose() };
 +
};
 +
function cbClose() {
 +
$("#cb_List").addClass("hidden");
 +
$("#cb_List li.current").removeClass("current");
 +
$("#cb_Combobox").attr("aria-expanded", "false");
 +
$("#cb_Combobox").get(0).focus();
 +
};
 +
function cbUpdateFocus(element) {
 +
$("#cb_List li.current").removeClass("current");
 +
$(element).closest("li").addClass("current");
 +
element.focus();
 +
};
 +
function cbListKeyUp(e) {
 +
var jSet = $(e.target).closest("ul").find("a"); //Get root node for parent listbox, then drill back down to find option elements.
 +
switch (e.which) {
 +
case 13 :
 +
// Enter - Activate
 +
console.log("Enter");
 +
break;
 +
case 27 :
 +
// Escape - Close with no change
 +
console.log("Escape");
 +
cbClose();
 +
break;
 +
case 37 :
 +
// Left - Do Nothing
 +
console.log("Left Arrow");
 +
break;
 +
case 38:
 +
// Up - Move to Previous in Set
 +
console.log("Up Arrow");
 +
var indexThis = jSet.index($(e.target));
 +
var indexToBe;
 +
if (indexThis - 1 < 0){ indexToBe = 0 }
 +
else { indexToBe = indexThis - 1 };
 +
cbUpdateFocus(jSet.get(indexToBe));
 +
e.preventDefault();
 +
break;
 +
case 39:
 +
// Right - Do Nothing
 +
console.log("Right Arrow");
 +
break;
 +
case 40:
 +
// Down - Move to Next In Set
 +
console.log("Down Arrow");
 +
var indexThis = jSet.index($(e.target));
 +
var indexToBe;
 +
if (indexThis + 1 > jSet.length - 1){ indexToBe = indexThis }
 +
else { indexToBe = indexThis + 1 };
 +
cbUpdateFocus(jSet.get(indexToBe));
 +
e.preventDefault();
 +
break;
 +
case 32 :
 +
// Spacebar - Activate
 +
console.log("space");
 +
break;
 +
};
 +
}
 +
</script>
 +
</head>
 +
<body>
 +
<div>
 +
<span class="comboLabel">Select a Browser:</span>
 +
</div>
 +
<div class="comboParent">
 +
<button id="cb_Combobox" role="combobox" aria-label="Select a Browser" aria-expanded="false" aria-owns="cb_Edit cb_List" class="combobox">
 +
<span id="cb_Content_Placeholder" aria-hidden="true">...</span>
 +
</button>
 +
<span id="cb_Edit" role="textbox" aria-readonly="true" class="edit"></span>
 +
<ul id="cb_List" role="listbox" class="list hidden">
 +
<li role="presentation">
 +
<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 role="presentation">
 +
<a href="#" role="option" aria-selected="false" tabindex="-1">Firefox</a>
 +
</li>
 +
<li role="presentation">
 +
<a href="#" role="option" aria-selected="false" tabindex="-1">Internet Explorer 11</a>
 +
</li>
 +
<li role="presentation">
 +
<a href="#" role="option" aria-selected="false" tabindex="-1">Safari</a>
 +
</li>
 +
<li role="presentation">
 +
<a href="#" role="option" aria-selected="false" tabindex="-1">Chrome</a>
 +
</li>
 +
</ul>
 +
</div>
 +
<br />
 +
<script>
 +
(function(){
 +
$("#cb_Combobox").each(function(){
 +
this.addEventListener("click", clickCB);
 +
this.addEventListener("keyup", keyupCB);
 +
});
 +
$("#cb_List a").each(function(){
 +
this.addEventListener("click", cbClickValue);
 +
this.addEventListener("keyup", cbListKeyUp);
 +
});
 +
})();
 +
</script>
 
</body>
 
</body>
 
</html>
 
</html>
 +
 
[[Category:ARIA]]
 
[[Category:ARIA]]

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: