blob: 03e4917dd1277e8704603efcb303cd6e640e2d7a [file] [log] [blame]
<!DOCTYPE html>
<html lang="en">
<head>
<title>ARIA Combobox Example</title>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=480">
<style type="text/css">
html, body {
color: #000;
background-color: #fff;
font-size: 100%;
font-family: sans-serif;
}
[hidden] {
display: none !important;
}
.pc {
/* pc is positioning context */
position: relative;
width: 30em;
max-width: 400px;
}
.pc input[role="combobox"] {
font-size: 12px;
height: 1.5em;
width: 100%; /* of pc */
border: solid 1px #000;
min-height: 1em;
padding: 0.9em 0.5em;
box-sizing: border-box;
}
.pc [role="status"] {
position: absolute;
top: 0.3em;
right: 0.5em;
font-size: 0.8em;
color: #777;
}
.pc ul[role="listbox"] {
position: absolute;
margin: 0;
padding: 0;
list-style: none;
top: 1.5em;
left: 0;
background-color: #fff;
border: solid 1px #ccc;
width: 100%; /* of pc */
box-sizing: border-box;
}
.pc li[role="option"] {
display: block;
margin: 0;
padding: 0.3em 0.5em;
list-style: none;
font-size: 12px;
cursor: pointer;
}
.pc li[role="option"]:hover {
color: #000;
background-color: #ccc;
}
[aria-selected="true"] {
color: #fff;
background-color: #666;
}
</style>
<script type="text/javascript">
function $(id) {
return document.getElementById(id);
}
var initComboBoxes = (function() {
var field=null, status=null, list=null, options=null, selectedOption=null, hideAll=false, kRETURN=13, kESC=27, kUP=38, kDOWN=40;
var submitHandler = function (e) {
updateMenuDisplay();
e.preventDefault();
};
var keyUpHandler = function (e){
switch(e.keyCode) {
case kRETURN: return;
case kESC: handleEscape(e); break;
case kUP: return;
case kDOWN: return;
}
updateMenuDisplay();
};
var keyDownHandler = function (e){
switch(e.keyCode) {
case kRETURN: handleReturn(e); break; // Catch Return on keyDown, b/c keyUp fires after submit.
case kUP: handleUpArrow(e); break;
case kDOWN: handleDownArrow(e); break;
}
};
var handleEscape = function (e){
e.preventDefault();
clearField();
};
var handleUpArrow = function (e){
selectPreviousOption();
e.preventDefault();
};
var handleDownArrow = function (e){
selectNextOption();
e.preventDefault();
};
var handleReturn = function (e){
insertValue();
e.preventDefault();
};
var blurHandler = function (e){
hideMenu();
};
var focusHandler = function (e){
if (field.value) {
updateMenuDisplay();
} else {
hideMenu();
}
};
var mouseDownHandler = function (e) {
if (e.target.tagName.toLowerCase() === "li") {
updateSelectedOption(e.target);
field.focus();
insertValue();
}
};
var updateMenuDisplay = function () {
filter();
list.hidden = hideAll ? true : false;
field.setAttribute('aria-expanded', hideAll ? 'false': 'true');
updateStatus();
};
var hideMenu = function () {
list.hidden = true;
field.setAttribute('aria-expanded', 'false');
updateStatus();
};
var clearField = function (){
if (field.value) field.value = '';
else if (field.textContent) field.textContent = '';
clearSelectedOption();
};
var clearSelectedOption = function (){
if (selectedOption) {
field.removeAttribute('aria-activedescendant');
list.removeAttribute('data-selection');
selectedOption.removeAttribute('aria-selected');
}
selectedOption = null;
hideMenu();
};
var updateSelectedOption = function (el){
if (!el) {
return;
}
if (selectedOption) selectedOption.removeAttribute('aria-selected');
selectedOption = el;
list.setAttribute('data-selection', el.id);
el.setAttribute('aria-selected', 'true');
field.setAttribute('aria-activedescendant', el.id);
};
var selectNextOption = function () {
var so = shownOptions();
for (var i=0, c=so.length; i<c; i++) {
var o = so[i];
if (o == selectedOption) {
if (so[i+1]) updateSelectedOption(so[i+1]);
return;
}
}
// otherwise, select the first option
if (!selectedOption && so.length) {
updateSelectedOption(so[0]);
}
};
var selectPreviousOption = function () {
var so = shownOptions();
for (var i=so.length-1; i>0; i--) {
var o = so[i];
if (o == selectedOption) {
if (so[i-1]) updateSelectedOption(so[i-1]);
return;
}
}
// otherwise, select the last option
if (!selectedOption && so.length) {
updateSelectedOption(so[so.length-1]);
}
};
var shownOptions = function () {
// It's possible this selector will fail on some Windows browsers. May need to make it a simple classname.
return list.querySelectorAll('li:not([hidden])');
};
var updateStatus = function () {
var itemCount = shownOptions().length;
var statusContent = "";
if (!list.hidden && itemCount) {
if (itemCount === 1) {
statusContent = "%@ item".replace("%@", itemCount);
} else {
statusContent = "%@ items".replace("%@", itemCount);
}
}
status.textContent = statusContent;
};
var insertValue = function (){
if (selectedOption) {
if (field.value) {
field.value = selectedOption.textContent;
} else if (field.textContent) {
field.textContent = selectedOption.textContent;
}
clearSelectedOption();
}
};
var filter = function () {
var s = field.value || field.textContent;
s = s.toUpperCase();
hideAll = true;
if (s=='') return;
for (var i=0, c=options.length; i<c; i++) {
var o = options[i];
var t = o.textContent.toUpperCase();
// if typed string is not substring of state name or code (case insensitive), and is not exact match for state
if ((t.indexOf(s)==-1 && o.id.indexOf(s)==-1) || t==s) {
o.hidden = true;
} else {
o.hidden = false;
hideAll = false;
}
}
clearSelectedOption();
};
var initCombo = function (){
// TODO: If time allows, update example to account for multiple comboboxes.
form = document.querySelector('form');
field = document.querySelector('[role="combobox"]');
status = document.querySelector('[role="status"]');
list = $(field.getAttribute('aria-owns'));
options = list.getElementsByTagName('li');
form.addEventListener('submit', submitHandler); // Search on iOS "Go" button.
field.addEventListener('keyup', keyUpHandler);
field.addEventListener('keydown', keyDownHandler);
field.addEventListener('blur', blurHandler);
field.addEventListener('focus', focusHandler);
list.addEventListener('mousedown', mouseDownHandler);
};
return initCombo;
}());
window.addEventListener('load', function () {
initComboBoxes();
});
</script>
</head>
<body>
<p>Focus the accessible combobox, type a few letters of a US state or territory (e.g. 'New'), then arrow down on a desktop browser, or submit the form on mobile browsers. You'll get a filtered autocomplete list as well as status update listing the total number of filtered results.</p>
<p>Some <a href="#">focusable content</a> before the combobox.</p>
<form class="pc">
<!-- Note that this field is labelled by itself, which in this case, is an explicit pointer to use the placeholder attribute value. -->
<input type="text" tabindex="0" id="state" aria-labelledby="state" role="combobox" aria-autocomplete="list" aria-owns="statelist" aria-expanded="false" placeholder="US State or Territory" autocomplete="off" autocorrect="off" autocapitalize="off">
<div role="status">
<!-- This is the list status live region: e.g. "4 items." -->
</div>
<ul role="listbox" id="statelist" hidden>
<li id="AL" role="option">Alabama</li>
<li id="AK" role="option">Alaska</li>
<li id="AS" role="option">American Samoa</li>
<li id="AZ" role="option">Arizona</li>
<li id="AR" role="option">Arkansas</li>
<li id="CA" role="option">California</li>
<li id="CO" role="option">Colorado</li>
<li id="CT" role="option">Connecticut</li>
<li id="DE" role="option">Delaware</li>
<li id="DC" role="option">District of Columbia</li>
<li id="FL" role="option">Florida</li>
<li id="GA" role="option">Georgia</li>
<li id="GU" role="option">Guam</li>
<li id="HI" role="option">Hawaii</li>
<li id="ID" role="option">Idaho</li>
<li id="IL" role="option">Illinois</li>
<li id="IN" role="option">Indiana</li>
<li id="IA" role="option">Iowa</li>
<li id="KS" role="option">Kansas</li>
<li id="KY" role="option">Kentucky</li>
<li id="LA" role="option">Louisiana</li>
<li id="ME" role="option">Maine</li>
<li id="MD" role="option">Maryland</li>
<li id="MA" role="option">Massachusetts</li>
<li id="MI" role="option">Michigan</li>
<li id="MN" role="option">Minnesota</li>
<li id="MS" role="option">Mississippi</li>
<li id="MO" role="option">Missouri</li>
<li id="MT" role="option">Montana</li>
<li id="NE" role="option">Nebraska</li>
<li id="NV" role="option">Nevada</li>
<li id="NH" role="option">New Hampshire</li>
<li id="NJ" role="option">New Jersey</li>
<li id="NM" role="option">New Mexico</li>
<li id="NY" role="option">New York</li>
<li id="NC" role="option">North Carolina</li>
<li id="ND" role="option">North Dakota</li>
<li id="MP" role="option">Northern Marianas Islands</li>
<li id="OH" role="option">Ohio</li>
<li id="OK" role="option">Oklahoma</li>
<li id="OR" role="option">Oregon</li>
<li id="PA" role="option">Pennsylvania</li>
<li id="PR" role="option">Puerto Rico</li>
<li id="RI" role="option">Rhode Island</li>
<li id="SC" role="option">South Carolina</li>
<li id="SD" role="option">South Dakota</li>
<li id="TN" role="option">Tennessee</li>
<li id="TX" role="option">Texas</li>
<li id="UT" role="option">Utah</li>
<li id="VT" role="option">Vermont</li>
<li id="VA" role="option">Virginia</li>
<li id="VI" role="option">Virgin Islands</li>
<li id="WA" role="option">Washington</li>
<li id="WV" role="option">West Virginia</li>
<li id="WI" role="option">Wisconsin</li>
<li id="WY" role="option">Wyoming</li>
</ul>
</form>
<p>Some <a href="#">focusable content</a> after the combobox.</p>
</body>
</html>