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 thevar
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 this1 == '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.