Week 03

Check out this week's quiz.

DOM API (Continued)

To interact with the DOM, JavaScript code must be executed after the DOM has fully loaded. There are several ways to ensure this happens.

Adding script Before Closing body (Bad)

Adding JavaScript before closing the body tag ensures that the script loads after the DOM. Here's an example:

<body> <!-- DOM --> <script src="script.js"></script> </body>

This is an outdated approach and is not recommended today because the browser waits until the DOM is loaded before starting to load the JavaScript code. This delay can make the UI unresponsive for some time. Additionally, some code may not require access to the DOM and could be loaded earlier instead of waiting for the DOM to fully load. While you could split the code into multiple files—loading one in the <head> and another before closing the <body>—there are more efficient ways to achieve the same result.

Having a load Event in JavaScript (Complex)

We can use the load event on the window object to ensure that DOM-related code executes only after the DOM has fully loaded. This allows us to place the script in the <head> while still interacting with the DOM.

window.addEventListener("load", () => { const element = document.querySelector("h1") console.log(element) })

This method isn't bad, but it adds extra work and makes the code slightly less readable. There are simpler ways to achieve the same result.

Using the defer Attribute (Good)

The <script> tag supports the defer attribute, which tells the browser to execute the script only after the DOM has fully loaded. The advantage of this approach is that the browser downloads the script without delaying page rendering but waits to execute it, improving overall page load speed.

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

Using Modules (Good)

JavaScript modules allow programmers to split code into multiple files and import them as needed. This feature is widely used in Node.js and JavaScript frameworks, but its benefits go beyond just code organization.

By default, JavaScript modules execute only after the document has fully loaded, which aligns perfectly with our needs. Another advantage is the ability to use await at the top level, as modules function like a large asynchronous script.

However, one drawback of modules is that they must be run on a server. If you simply open an HTML file in a browser, the attached JavaScript module won’t execute. The Live Server extension in VSCode can help with this.

To make a JavaScript file a module, set the type attribute to "module" in the <script> tag.

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

Interacting with the DOM

Now that we know how to properly load JavaScript so it has access to the DOM, we can start interacting with it. The first step in manipulating any element is to retrieve it in JavaScript and, optionally, store it in a variable.

There are several ways to select an element or multiple elements. Here are the main ones:

Getting an Element by its HTML ID

If the HTML element you want to select already has an id attribute—or if it makes sense to add one—you can use document.getElementById('element-id') to retrieve it.

Example:

<h1 id="some-id">Header</h1>
const header = document.getElementById("some-id")

Getting an Element Using CSS Query

If the element you want to select doesn’t have an id attribute, you can use document.querySelector('css-query') to retrieve it. This is a more modern approach and is intended to replace document.getElementById. Even if an element has an id, you can still use querySelector.

This method is also more powerful because it allows you to select elements using any CSS selector, not just id attributes.

Example:

<h1 id="some-id">Header</h1>
const header = document.querySelector("#some-id")

or

const header = document.querySelector("h1")

If the CSS query matches more than one element, querySelector() will return the first matched element. In other words, you’ll either get one element or null if there’s no match.

Getting Elements Using CSS Query

If you want to retrieve multiple elements in JavaScript, you can use document.querySelectorAll('css-query'). This method returns a NodeList (similar to an array) of all matched elements.

Example:

<div class="some-class">one</div> <div class="some-class">two</div> <div class="some-class">three</div>
const header = document.querySelectorAll(".some-class")

Changing the DOM

Once you retrieve an element or multiple elements, you can modify them using JavaScript. For example, you can access, change, or remove an element’s HTML attributes using the following methods:

element.getAttribute('attribute-name') element.setAttribute('attribute-name', 'new-value') element.removeAttribute('attribute-name')

You can use the methods mentioned above to modify an element’s class attribute, but they are somewhat limited. A better approach is to use the classList property, which provides more flexible methods for interacting with an element’s classes.

element.classList.add('new-class') element.classList.remove('current-class') element.classList.toggle('some-class') element.classList.replace('current-class', 'new-class')

Reacting to Events

With JavaScript, you can respond to events happening on a webpage to make it more interactive. Events such as clicks, mouse hover, mouse out, key presses, key down, and window resizing are just a few examples. Look at this page for more event types.

To handle an event, follow these three steps:

  1. Select the element (using one of the methods mentioned earlier).
  2. Declare a function that will execute when the event occurs.
  3. Attach the function to the element using the addEventListener method.

Let’s look at an example. Suppose we want to handle a click event on an HTML button. Here’s how we can do it:

  1. Getting the element:

    const myButton = document.querySelector("button") // assuming there's only one button on the page
  2. A function to be executed after the click event happens on the element:

    const onClick = function() { // some logic here // for example, sending a network request using fetch console.log("the button was clicked") }
  3. Connecting the function to the element using addEventListener method:

    myButton.addEventListener('click', onClick)

Note that we don’t use parentheses around the function name like onClick(). Adding parentheses would immediately call the function, which is not what we want. Instead, we simply pass the function name to register it with the event and the element.