A Problem¶
Two common patterns in front-end JavaScript development conspire to cause a problem:
1) We attach event handlers to page elements in the DOM with code like:
function handleClick(event) { // handles a click event };
const elem = document.querySelector("#myelement"); elem.addEventListener('click', handleClick);
2) We often load JavaScript with <script>
tags in the <head>
element of a page:
<html> <head> ... <script src="app.js"></script> </head> <body> ... </body> </html>
The problem arises because the code in our script is parsed and executed when
the browser reaches the <script>
tag in the page. At that time, the browser
has not yet reached the <body>
element, and so the DOM hasn’t been populated
with page elements yet. Therefore, when the browser executes
document.querySelector(...)
, it returns nothing (technically, it returns
null
), because there is no element to find (yet)!
This test page on JSFiddle shows
this. The event handler is not successfully attached to the button, and if you
dig in the console you’ll see an error: TypeError: elem is null
A Solution¶
To solve this, the browser needs to wait before it executes any code that is
working with DOM elements. It needs to wait until after it has fully loaded
the DOM. Helpfully, there is an event we can use just for this:
DOMContentLoaded
. It will fire after the browser has loaded and parsed the
body of the page, populating the DOM.
If we put any code that attaches events to DOM elements inside a function, we
can attach that function to the DOMContentLoaded
event. It will look
something like this:
// A function for attaching events to DOM elements function setupEvents() { const elem = document.querySelector("#myelement"); elem.addEventListener('click', handleClick); }; // Attach our setup function to DOMContentLoaded document.addEventListener('DOMContentLoaded', setupEvents);
Now, we won’t be trying to access elements in the DOM (using querySelector
or similar functions) until after the DOM has finished loading.
This test page on JSFiddle shows this working, with the event handler successfully added to the button. Try it out yourself!