One of the main issues with JavaScript is missing semantics – for that we can use ARIA, which is discussed in the relevant chapters. In this chapter however, we focus on coding issues, and discuses other JavaScript issues including device independent event handlers with JavaScript and JQuery.
(Sources WCAG 2.0 , http://www.w3.org/TR/WCAG20-TECHS, ARIA best practices , http://www.w3.org/TR/2009/WD-wai-aria-practices-20090224, http://www.openajax.org/member/wiki/Accessibility)
An event represents the precise moment when something happens. A JavaScript can be executed when an event occurs, like when a user clicks on an HTML element. However as not all users can use a mouse, the same function needs to be available for people who are using a j=keyboard via a keyboard driven event. For example, a script may perform the same action when a keypress is detected that is performed when a mouse button is clicked. (See WCAG 2.0 technique SCR20: Using both keyboard and other device-specific functions)
In JavaScript, commonly used event handlers include, onblur, onchange, onclick, ondblclick, onfocus, onkeydown, onkeypress, onkeyup, onload, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup, onreset, onselect, onsubmit, onunload. Some mouse-specific functions have a logical corresponding keyboard-specific function (such as 'onmouseover' and 'onfocus'). A keyboard event handler should be provided that executes the same function as the mouse event handler.
The following table suggests keyboard event handlers to pair mouse event handlers.
Device Handler Correspondences |
|
Use... |
...with |
mousedown |
keydown |
mouseup |
keyup |
click [1] |
keypress [2] |
mouseover |
focus |
mouseout |
blur |
1 Although click is in principle a mouse event handler, most HTML and XHTML user agents process this event when the control is activated, regardless of whether it was activated with the mouse or the keyboard. In practice, therefore, it is not necessary to duplicate this event. It is included here for completeness since non-HTML user agents do have this issue.
2 Since the keypress event handler reacts to any key, the event handler function should check first to ensure the Enter key was pressed before proceeding to handle the event. Otherwise, the event handler will run each time the user presses any key, even the tab key to leave the control, and this is usually not desirable.
Some mouse-specific functions (such as dblclick and mousemove) do not have a corresponding keyboard-specific function. This means that some functions may need to be implemented differently for each device (for example, including a series of buttons to execute, via keyboard, the equivalent mouse-specific functions implemented).
In this example the image is changed when the user positions the pointer over the image using onmouseover and onmouseout events. To provide keyboard users with a similar experience, the image is also changed when the user tabs to it using onfocus() and onblur().
(Note, many programmers consider using listeners and JQuery to be better programing practice. This will be addressed in the section on JQuery and device independents below.)
<a href="menu.php" onmouseover="swapImageOn('menu')" onfocus="swapImageOn('menu')"
onmouseout="swapImageOff('menu')" onblur="swapImageOff('menu')">
<img id="menu" src="menu_off.gif" alt="Menu" />
</a>
This example bellow uses tabindex on an img element. The tabindex attribute ensures that the keyboard will have a tab stop on the image. Without this the user would never be able to tab to the img and activate the onkeypress event.
Custom controls like this should also use WAI-ARIA to expose the role and state of the control
This example shows a custom image control. The mouse event onclick is duplicated by keyboard event onkeypress. However the tabindex attribute ensures that the keyboard will be able to tab to the image and will be able to activate the onkey event.
<img onclick="nextPage();" onkeypress="nextPage();" tabindex="0" src="arrow.gif" alt="Go to next page">
Note that in this example, the nextPage() function should check that the keyboard key pressed was Enter, otherwise it will respond to all keyboard actions while the image has focus, which is not the desired behavior.
In jQuery, most DOM events have an equivalent jQuery method. The same principles about DOM events apply in jQuery. Anything selected with a mouse event needs to be duplicated with a mouse independent event or keyboard event.
Here are some common events:
Mouse Event |
Keyboard Event |
---|---|
Click |
Keypress |
Dblclick |
Keydown |
Mouseenter |
Keyup |
Mouseleave |
Blur |
Hover |
Focus |
The code
$("#p1").hover(function(){
alert("You entered p1!");
},
Should have a redundant event method such as:
$("#p1").focus(function(){
alert("You entered p1!");
},
The following code will make all your buttons elements keyboard accessible by triggering the mouse click event when enter is pressed.
$("button"). onkeydown(function(ev) {
if (ev.which ==13) {
$(this).click();
}
});
Use onkeydown to trap key events, not onkeypress - Key press events do not fire for all keys and they vary across browsers.
Note this is only for the button selector. If you want it to be inputs of type button use input[type='button']
IE
$("input[type='button']"). onkeydown (function(ev) {
if (ev.which ==13) {
$(this).click()
}
});
(Note that mouse events on other types of controls still need to be made keyboard accessible)
You can bind the important char codes to variables to make your code more readable. Bellow are some useful keycodes bound to their names that you can use when building accessible jquery
This.keys = {
tab: 9,
enter: 13,
esc: 27,
space: 32,
pageup: 33,
pagedown: 34,
end: 35,
home: 36,
left: 37,
up: 38,
right: 39,
down: 40
};
Another good practice is to bind the CSS look of the elements with the aria state in the script. Then the script only has to control the state.
Example
CSS:
[aria-hidden="true"] { display: none;}
[aria-hidden="false"] { display: block;}
Script:
$("input#login").onkeydown (function(){
$("div#mydialog").attr("aria-hidden","false");
…..
});
.treeitem[role="treeitem"][aria-selected="true"] {color: white; background-color: #222222;}
.treeitem[role="treeitem"][aria-selected="false"] {color: white; background-color: beige;}
If the related CSS pseudo-classes are not appropriate or not supported in all browsers, authors can use JavaScript focus and blur events to toggle a classname on an element
As discussed in the section on ARIA, scripts need to manage the keyboard focus of accessible widgets. You can use the focus() event to manage the focus and create familiar design patterns.. For example, on closing a dialog you need to put the focus back where it was before the dialog was opened
Code:
// close the dialog and put the focus back to were it was
$("input#cancel"). onkeydown (function(){
$("div#mydialog").attr("aria-hidden","true");
$("input#login").focus() ;
ev.preventDefault();
});
The use of preventDefault()after setting the focus, to prevent an extra shift-tab from occurring. In general, you may need to prevent used key events from performing browser functions - If a key such as an arrow key is used, prevent the browser from using the key to do something (such as scrolling) by making sure the event will be consumed, preventing the browser from performing any action based on the keystroke.
As a side note: A preventDefault() provides that the default action is not executed. You can use a jQuery return false, but that will also ensures that a stopPropagation() is executed - prohibiting parent elements from receiving these events as well. You can use either but the fewer restrictions you place on your code but often the more flexible it will be to maintain.
Example: <span tabindex="-1" onkeydown="return handleKeyDown();">
If handleKeyDown() returns false, the event will be consumed, preventing the browser from performing any action based on the keystroke. Also note: There are user agent-specific considerations for key event handling.
Also, do not use createEvent(), initEvent() and dispatchEvent() to send focus to an element, they do not change the focus.
Often with accessible widgets you need to track the focus. Use focus and blur events (or event delegation) to monitor changes to the current focus - focus and blur events can now be used with every element.
Don't assume that all focus changes will come via key and mouse events, because assistive technologies such as screen readers can set the focus to any focusable element, and that needs to be handled elegantly by the JavaScript widget. Modern browsers also support an activeElement property on the document object to get the focused element.
It is worth noting that techniques such as "event delegation" (for example, intercepting events on a list rather than on every listitem) can greatly increase web application performance and code maintainability.
When moving or tracking focus, you need to also make sure that there is a persistent visual indication of focus. You can use CSS to make it clear when an element has focus. The use of :focus pseudo-class selectors to style the keyboard focus is not supported in many versions of Internet Explorer. Authors should use the :active pseudo-class (which older versions of IE treat like :focus) in conjunction with the :focus pseudo-class.
a:focus, a:active { text-decoration: underline; }
this.style.backgroundColor = "gray";
this.style.border = "1px dotted invert".
Note for a dotted border case, you will need to make sure those elements have an invisible 1px border to start with, so that the element doesn't grow when the border style is applied.
In the JQuery example below a CSS class is added whenever a button has focus and removed when the focus leaves the button. (Scripts should be inside the $(document).ready function.)
$("button").focus(function(){
$(" button ").removeClass("focuslook");
});
$("button").blur(function(){
$(" button ").addClass("focuslook");
});
Always draw the focus for tabindex="-1" items and elements that receive focus programmatically – For example change the background color add a dotted border
Also see the chapter on a "roving" tabindex for complex widgets or using the aria-activedescendant property.
The tabindex attribute is now applicable to all renderable HTML elements. Tab and Shift+Tab key move focus among widgets and standard HTML controls. Widgets with tabindex=0 will be added to the tab sequence based on document order. Setting a tabindex value of -1 to an element enables the element to receive focus via JavaScript using the focus() method. This method is used to enable arrow key navigation to elements. Each element that can be navigated to via arrow keys must have a tabindex of -1 to enable it to receive focus
You may want to update tabindex values if a custom control becomes disabled or enabled. Disabled controls should not be in the tab order. However, you can typically arrow to them if they're part of grouped navigation widget. When an element receives focus, it should change the tabindex value to 0 to make an element the default element of the widget. This is important if the user leaves the widget and returns to the widget again so focus is on the last element of the widget the user was on. If other elements of a widget can receive keyboard focus, only one element of the widget should have a tabindex value of 0.
Once a widget has keyboard focus, arrow keys, Space, Enter key, or other keyboard commands can be used to navigate the options of the widget, change its state, or trigger an application function associated with the widget. The author is responsible, in the scripts, for maintaining visual and programmatic focus and observing accessible behavior rules. The Common Widget Design Patterns shows best practices, for keyboard navigation, for creating common widgets found on the Web.
The following example opens a dialog by setting aria-hidden=false, but we also set the tab index to 0, so it can be tabbed to and move the focus to the dialog.
$(document).ready(function(){
$("input#login"). onkeydown (function(){
$("div#mydialog").attr("aria-hidden","false");
$("div#main1").attr("tabindex","-1");
$("#dialogheader").focus();
ev.preventDefault();
return false;
});
When we close the dialog close the dialog (aria-hidden=true), we put the focus back to were it was before the dialog was open AND we take the now hidden dialog out of the tabbing order by setting tabindex = -1
$("input#cancel"). onkeydown (function(){
$("div#mydialog").attr("aria-hidden","true");
$("div#main1").attr("tabindex","0");
$("input#login").focus() ;
ev.preventDefault();
return false;
});
Also see the section on a "roving" tabindex for complex widgets.