Events with JS and HTML

Adding JS to HTML pages

There are two ways to add JS code into HTML pages. The first way is to make use of the <script> tag:

<script>
    // code goes here
</script>

The second way is to write the code in a separate file, save it as with the .js file extension, and import it using the <script> tag:

<script src="script.js"></script>

This is the recommended method which we will stick to for this guide, but note that for shorter programs (3-4 lines long) it is sometimes easier to use the first method.

Event Listeners

Event Listeners in JavaScript are functions that wait and "listen" for events (like clicks) on the page. Once the event happens, they can execute a listener function. These are nullary functions that have no return value.

An event listener can be added either to an element on the page (like a button, a paragraph or a form), or the page itself. The method to add an event listener is addEventListener as is used as follows:

document.addEventListener(event, listener); // add a listener to the page itself
element.addEventListner(event, listener); // add a listener to a particular element

Adding a click event listener to the button

Go back to the html file from before, and remove the button's onclick attribute. Then, open a new file and save it as script.js. Here, write the following code:

let button = document.getElementById("hello");
button.addEventListener("click", () => alert("Hello!"));

The first line will query for the button, and the second line adds an event listener. The event it listens for is "click", and the listener is a lambda expression for an anonymous nullary function.

Save the code, and import it into the html document by adding the following line into the head of the document:

<script src="script.js"></script>

Now save the file, reload the page, click the button and...

Hmmm. Nothing is happening. Go back, check the syntax and spelling. No issues there. Check the browser console, and aha. There's an error: Uncaught TypeError: button is null

Well, we're running the same query that we were running in the Browser console in the previous sections, and that worked fine. What's the issue here?

The problem

Let's take a look our html file:

<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" href="../styles.css">
        <script src="script.js"></script>
        <title>My web page</title>
    </head>
    <body>
        <h1>Hello world</h1>
        <button id="hello">Say hello</button>
    </body>
</html>

The browser reads the file from top to bottom, rendering elements and styles as it reads them line by line. This creates the issue that the JavaScript code is read and executed before the rest of the document is rendered. So the button we're querying for does not exist by the time the code is read, which means that querying the document for the button returns null, hence the TypeError we see.

Fix 1

The easiest way to fix it is to execute the code after the document is rendered. This involves moving it to the bottom of the file:

<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" href="../styles.css">
        <title>My web page</title>
    </head>
    <body>
        <h1>Hello world</h1>
        <button id="hello">Say hello</button>
        <script src="script.js"></script>
    </body>
</html>

Save the file, reload the page, and try clicking the button. It should display a popup with "Hello!" on it. If it doesn't, check for syntax errors or spelling mistakes in the code and in your HTML file, and make sure the file is saved and the page is reloaded.

This is an easy way to fix the issue, and is quite convenient. However, it means that the code is not executed until the entire page is loaded and rendered, including images, stylesheets and other large files. So if there is just one single file or image that is taking time to load, the JS code will not be executed until it is loaded, which could cause some inconvenience as event listeners won't be added in time.

The second option is to wait till the elements of the page are loaded into the DOM (which is done before rendering the elements on the page itself). This will happen much earlier and is faster than waiting for the elements to render. To do this, there is a special event only applicable to the document itself. This event is called "DOMContentLoaded" (case-sensitive). It can be used as below:

document.addEventListener("DOMContentLoaded", function() {
    // add event listeners to elements on the page
});

This will allow the JS code to be executed much before the page renders, so event listeners and other JS-dependent elements or features are ready as soon as the page is rendered and displayed to the user. Let's apply this to our code:

// In the script.js file:
document.addEventListener("DOMContentLoaded", function() {
    let button = document.getElementById("hello");
    button.addEventListener("click", () => alert("Hello!"));
});
/*
Note that function() {...} is an alternate way of
 creating anonymous functions spanning multiple lines,
 as explained much earlier in the guide
*/

Now move the <script> tag back into the head of the document, save the file, reload the page on the browser, and the button should work:

You can remove a listener from an element using the removeEventListener function:

element.removeEventListener(event, listener); // to remove a specific listener
element.removeEventListener(event); // to remove all listeners for an event

Next steps

Next, we'll make a simple example of querying and updating the DOM using a click counter.

Last updated