Javascript – Browser and Events

Events, event handlers and propagation

Events are actions or occurrences that happen in the system you are programming — the system produces (or “fires”) a signal of some kind when an event occurs, and also provides a mechanism by which some kind of action can be automatically taken (that is, some code running) when the event occurs.

Some examples:

  • The user clicking the mouse over a certain element or hovering the cursor over a certain element.
  • The user pressing a key on the keyboard.
  • The user resizing or closing the browser window.
  • A web page finishing loading.

Each available event has an event handler, which is a block of code (usually a JavaScript function that you as a programmer create) that will be run when the event fires. When such a block of code is defined to be run in response to an event firing, we say we are registering an event handler. Note that event handlers are sometimes called event listeners — they are pretty much interchangeable for our purposes, although strictly speaking, they work together. The listener listens out for the event happening, and the handler is the code that is run in response to it happening.

Web events are not part of the core JavaScript language — they are defined as part of the APIs built into the browser.

Example:

<button>Change color</button>
const btn = document.querySelector('button');

function random(number) {
  return Math.floor(Math.random() * (number+1));
}

btn.onclick = function() {
  const rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  document.body.style.backgroundColor = rndCol;
}

Functions as Event Handlers

If you want a function to fire as the result of an event, it needs to be wired up to that event. Doing so makes that function an event handler. The function definition needs to include a single argument: a pointer to the event that fired it. 

var handleClick = function(event) {
  console.log(event.type);  // click
  console.log(event.currentTarget); // the thing you clicked
  console.log(event.screenX); // screen X coordinate
  console.log(event.screenY); // screen Y coordinate
}

Assigning Event Handlers via DOM APIs

In simple web pages you may occasionally see explicit assigned event handlers in the HTML. 

<button onclick="handleClick(event)">
  Click to Go
</button>

However modern web applications rarely use event binding in HTML. Instead, the DOM API is preferred, specifically the JavaScript Element.addEventListener() function. 

First you need a reference to the HTML element. Below we’ve added an id attribute to our button and removed the onclick attribute. 

<button id="clicker">

Now we reach into the DOM, get the reference to the button, and assign the event listener, handleClick, by passing it in as a value (note, no parentheses). 

let button = document.getElementById("clicker");
button.addEventListener("click", handleClick);

Using the DOM API gives the developer flexibility to make the UI highly interactive and responsive to what the user is doing. The developer can also remove an event listener if functionality needs to be turned off. 

button.removeEventListener("click", handleClick);

You will also see anonymous functions added as event listeners. 

button.addEventListener("click", function(event){
  //...anonymous function body...
});

Bear in mind, anonymous functions can’t be removed using removeEventListener, as there is no pointer to pass in to identify the function. 

In the increment example, assigning the increment function to the newIncrement variable moves the context where it is invoked: to the global object. This is easy to demonstrate.

console.log(this.aValue); // NaN
console.log(window.aValue); // NaN
console.log(typeof window.aValue); // number

Default Behaviour

Sometimes, you’ll come across a situation where you want to prevent an event from doing what it does by default. The most common example is that of a web form, for example, a custom registration form. When you fill in the details and press the submit button, the natural behavior is for the data to be submitted to a specified page on the server for processing, and the browser to be redirected to a “success message” page of some kind (or the same page, if another is not specified.). For this purpose we use the preventDefault() function.

Example: preventing a form from being submited if either First Name or Last Name are blank:

const form = document.querySelector('form');
const fname = document.getElementById('fname');
const lname = document.getElementById('lname');
const para = document.querySelector('p');

form.onsubmit = function(e) {
  if (fname.value === '' || lname.value === '') {
    e.preventDefault();
    para.textContent = 'You need to fill in both names!';
  }
}

Bubbling and Capturing

When an event is fired on an element that has parent elements (in this case, the <video> has the <div> as a parent), modern browsers run two different phases — the capturing phase and the bubbling phase.

In the capturing phase:

  • The browser checks to see if the element’s outer-most ancestor (<html>) has an onclick event handler registered on it for the capturing phase, and runs it if so.
  • Then it moves on to the next element inside <html> and does the same thing, then the next one, and so on until it reaches the element that was actually clicked on.

In the bubbling phase, the exact opposite occurs:

  • The browser checks to see if the element that was actually clicked on has an onclick event handler registered on it for the bubbling phase, and runs it if so.
  • Then it moves on to the next immediate ancestor element and does the same thing, then the next one, and so on until it reaches the <html> element.

In modern browsers, by default, all event handlers are registered for the bubbling phase. So in our current example, when you click the video, the click event bubbles from the <video> element outwards to the <html> element. Along the way:

  • It finds the video.onclick... handler and runs it, so the video first starts playing.
  • It then finds the videoBox.onclick... handler and runs it, so the video is hidden as well.

This is annoying behavior, but there is a way to fix it! The standard Event object has a function available on it called stopPropagation() which, when invoked on a handler’s event object, makes it so that first handler is run but the event doesn’t bubble any further up the chain, so no more handlers will be run.

video.onclick = function(e) {
  e.stopPropagation();
  video.play();
};

Document Object Model

Think of the DOM as a tree. It starts at the root of the browser’s display functionality: the window. From there, the page is encapsulated in window.document, with the page’s body in window.document.body. Then the tree fans out to every bit of content represented on the page.

As an API, the DOM is vast, and lets you touch every part of this tree. It also has a number of methods to optimize interaction with the DOM.

Example: https://jsbin.com/kejonuq/26/edit?html,js,output

JavaScript libraries (reactjs, jQuery) and frameworks (Angular, vuejs) have become the standard for interactive pages. Such frameworks abstract away and simplify DOM interactions, and often automatically apply polyfills for missing features.

Shadow DOM

The DOM API is rich and flexible. Using relatively simple JavaScript it is easy to make changes to the looks, behaviors, and actions invoked by the UI. 

But there is a pitfall. The DOM model makes it difficult to encapsulate pieces of the UI and protect them from accidental (or purposeful and malicious) changes. 

Shadow DOM creates a boundary around a particular part of UI functionality. The boundary prevents a parent from changing the elements or CSS of a child. It also forces any events propagated across the boundary to rescope their targets, preventing the parent from reaching across the shadow DOM boundary.

Web APIs

JavaScript runtime engine runs in many different places, but it’s most often hosted in a browser

browser APIs can: 

  • Interact with the structure of the current page rendered in the browser (Document Object Model or DOM API)
  • Perform asynchronous requests to the server without leaving the current page (Fetch API)
  • Interact with audio, video, and graphics
  • Interact with device features surfaced to the browser (geolocation, device orientation, client-side data storage)

Browser Dev Tools

Every modern web browser includes a powerful suite of developer tools. These tools do a range of things, from inspecting currently-loaded HTML, CSS and JavaScript to showing which assets the page has requested and how long they took to load.


Exploring the DOM inspector

Exploring the CSS editor

By default, the CSS editor displays the CSS rules applied to the currently selected element:

JavaScript debugger

The JavaScript debugger allows you to watch the value of variables and set breakpoints, places in your code that you want to pause execution and identify the problems that prevent your code from executing properly.

In the image, the first section, Watch expressions, shows that the listItems variable has been added. You can expand the list to view the values in the array.

The next section, Breakpoints, lists the breakpoints set on the page. In example.js, a breakpoint has been set on the statement listItems.push(inputNewItem.value);

The final two sections only appear when the code is running.

The Call stack section shows you what code was executed to get to the current line. You can see that the code is in the function that handles a mouse click, and that the code is currently paused on the breakpoint.

The final section, Scopes, shows what values are visible from various points within your code. For example, in the image below, you can see the objects available to the code in the addItemClick function.