JavaScript

JavaScript is the standard programming language of the client-side web. While browsers have standardised their JavaScript engine implementations quite well, they tend to lag behind the language specification somewhat. When it comes to building complex client-side applications, we will thus see that frequently we use a compiler to transform cutting-edge JavaScript into the version that is understood by the browser.

Loading JavaScript

JavaScript is loaded into the HTML file via the <script> element with the src attribute set to the URL to load the JavaScript from. It is important to note that the browser starts running the JavaScript code as soon as it encounters the <script> element. That means that the JavaScript will not be able to initially access any of the HTML elements that are defined after the <script> tag. As a result it has become common to place the <script> tag as the last element(s) of the <body>, as at that point all other HTML content has been processed by the browser.

There is a lot more to JavaScript, but as in the rest of the module we will be interacting with the DOM via the frameworks we will look at, we will only look at pure JavaScript briefly.

Basic Syntax

JavaScript mostly looks like any other imperative, untyped C-style programming language:

function focusFirst() {
    var first = document.querySelector('*[tabindex="1"]');
    if(first === null) {
        first = document.querySelector('*[tabindex="0"]');
    }
    if(first !== null) {
        first.focus();
    }
}
focusFirst();

Two small aspects that need to be briefly mentioned:

  • The var keyword is used to define a variable. If you do not use the var keyword the first time you assign to a variable, then the variable is created as a global variable, rather than local to the current scope. As you should avoid global variables, this is important to keep in mind.
  • When comparing for equality or inequality, you should generally use the triple constructs === and !==. The difference to the normal comparators is that they compare both the value of the variable and the type of the variable. This is important, as with the normal comparators (== and !=) JavaScript will, if they do not share the same type, first convert both to string values and then compare strings. As a result of this 1 == '1' is true for JavaScript.

Variables

There are three ways to declare variables in JavaScript:

  • var
  • let
  • const

While var has been the originally way to declare variables in JavaScript, the other two keywords are used in the scope, introduced by ECMAScript2015. Nowadays, var is used to declare function-scoped variables, while let is used for declaring block-scoped variables. If you want to store information that remains unchanged, use the const keyword. Like let, const is block-scoped. Should you declare an array as a const, the content may still be changed.

Functions

There are two ways to declare functions in JavaScript, either by using the function keyword or by using arrow functions. The main difference between the two declarations is the way the functions treat this. If we use the function keyword, this is bound to the context in which the function is called. Arrow functions use the this of the code containing this function. Thus, we do not (and can not) use .bind(this).

Finding Elements

One frequent activity in JavaScript is to access a specific element in the DOM in order to make changes:

document.querySelector('*[tabindex="1"]');

The document represents the whole HTML document and we can then use the querySelector function to find the first element that matches the CSS selector provided as the parameter. This will return null if nothing matches and the first matching element if one or more elements match.

To retrieve all matching elements we use:

document.querySelectorAll('*[role="menu"]')

Which will return a list of matching elements or an empty list if nothing is found.

Attaching Events

Events are one of the core building blocks of JavaScript. This is because the language is single threaded and uses an event queue to handle multiple, parallel code in a serial structure. The following code demonstrates the basics of adding an event:

item.addEventListener('keydown', function(ev) {
});

The addEventListener function is called to add an event listener on a DOM element. It takes two parameters. The first is the name of the event to attach to and the second is the function to call when the event happens. Here we see an important aspect of JavaScript, namely that functions can be passed as parameters, just like any other object. This so-called “callback” function takes a single parameter, which is an event object that describes the concrete event.

Example

Now create a new file scripts.js and add the following code:

/**
 * Function that focuses the first element with either a tabindex of 1 or 0.
 */
function focusFirst() {
    var first = document.querySelector('*[tabindex="1"]');
    if(first === null) {
        first = document.querySelector('*[tabindex="0"]');
    }
    if(first !== null) {
        first.focus();
    }
}
// Make sure we focus the first focusable element
focusFirst();


/**
 * Function that implements a menu component
 */
function menuComponent(menu) {
    menu.querySelectorAll('*[role=menuitem]').forEach(function(item) {
        item.addEventListener('keydown', function(ev) {
            // Left-arrow key
            if(ev.keyCode === 37) {
                // Get the previous sibling li wrapper
                var sibling = ev.target.parentElement.previousElementSibling;
                if(sibling !== null) {
                    // If it is a separator, then jump over it
                    if(sibling.getAttribute('role') === 'separator') {
                        sibling = sibling.previousElementSibling;
                    }
                    // Get the menuitem in the sibling and focus it
                    var nextItem = sibling.querySelector('*[role=menuitem]');
                    if(nextItem !== null) {
                        nextItem.focus();
                    }
                }
                // Right-arrow key
            } else if(ev.keyCode === 39) {
                // Get the next sibling li wrapper
                var sibling = ev.target.parentElement.nextElementSibling;
                if(sibling !== null) {
                    // If it is a separator, then jump over it
                    if(sibling.getAttribute('role') === 'separator') {
                        sibling = sibling.nextElementSibling;
                    }
                    // Get the menuitem in the sibling and focus it
                    var nextItem = sibling.querySelector('*[role=menuitem]');
                    if(nextItem !== null) {
                        nextItem.focus();
                    }
                }
            }
        });
    });
}
document.querySelectorAll('*[role="menu"]').forEach(menuComponent);

Auto-focusing the First Element

The first function that is implemented is to automatically focus the first element that has a tabindex set:

function focusFirst() {
    var first = document.querySelector('*[tabindex="1"]');
    if(first === null) {
        first = document.querySelector('*[tabindex="0"]');
    }
    if(first !== null) {
        first.focus();
    }
}
// Make sure we focus the first focusable element
focusFirst();

The code is relatively straightforward. First we get the first element that has a tabindex of 1. If there is none (first === null), then we look for the first element that has a tabindex of 2. If either of these found something (first !== null), then we use the focus() method to place the focus on that element.

Implementing the Menu Navigation

The second function defines the functionality for handling the navigation within ARIA menus. The first step is to find all menus and run the initialisation code on each one:

document.querySelectorAll('*[role="menu"]').forEach(menuComponent);

querySelectorAll returns a list of menus. The forEach function then allows us to easily execute a function (in this case the menuComponent function) for each element in the list. The function gets a single parameter and that is the item in the list.

In the menuComponent function we now attach an event listener to all

menu.querySelectorAll('*[role=menuitem]').forEach(function(item) {
    item.addEventListener('keydown', function(ev) {
    });
});

The code first finds all menu items in the menu and then uses the addEventListener to attach an event listener to the “keydown” event. In the event listener we can then react to they keyboard input from the user:

if(ev.keyCode === 37) {
    // Get the previous sibling li wrapper
    var sibling = ev.target.parentElement.previousElementSibling;
    if(sibling !== null) {
        // If it is a separator, then jump over it
        if(sibling.getAttribute('role') === 'separator') {
            sibling = sibling.previousElementSibling;
        }
        // Get the menuitem in the sibling and focus it
        var nextItem = sibling.querySelector('*[role=menuitem]');
        if(nextItem !== null) {
            nextItem.focus();
        }
    }
}

First we check the keyCode property of the event object to see which key is pressed. keyCode 37 is the left arrow key, thus we want to move the focus to the left. To get the element that the user interacted with, we use the target property of the event object. Because this is the <a> link element, we first need to use the parentElement property to access its parent (the <li> element, check the HTML file if you are unsure). Then from the parent element we can access the previousElementSibling to get the sibling element that is directly ahead of the current <li>. If this is not null, then there is a previous menu item that we can jump to. First we need to check that the role attribute on the previous <li> is not set to “separator”. Obviously, we would not want to focus the separator, so if the previousElementSibling is a separator, then we just go another previousElementSibling further. Finally we use the querySelector to find the actual menu item <a> and then focus that.

You can now try this out in the browser. Use the “tab” key to move the focus to any of the menus. Then use the left and right arrow keys to navigate between them.