
Reacting to User Input Events and Updating the DOM
JavaScript is used to interact with the DOM. This entails responding to DOM-generated events such as a user clicking a button. It also entails updating content and HTML elements, such as displaying a notification message.
Elements in the DOM are objects. The document object that's provided by JavaScript contains the element objects. It also contains methods for accessing the elements and updating them.
The DOM HTML Element Object
The HTML elements are represented as objects. Since they are objects, there are methods and properties we can use for them. These properties and methods are inherited from a hierarchy of DOM objects that are provided by the web browser, starting with an object called Node. For example, the ol element shares methods and properties from the following hierarchy of DOM objects:
Node⇒Element⇒HTMLElement⇒HTMLOListElement
It is not necessary to understand all the objects involved, but it is good to be aware of them. It's better to learn about some of the properties and methods that are derived from all of those objects. The following are a few of the properties and methods that are inherited from a hierarchy of DOM elements above it:
- innerHTML: With the source element, this is the HTML and content contained in an element.
- innerText: With the source HTMLElement, this is the rendered text of an element.
- addEventListener(…): With the source element event target, this is used to register a function to respond to events such as a user clicking on the element.
- appendChild(…): With the source node, this adds a node to a parent node; for example, to add an li element to the end of an ol element, or to add a p element to the end of a div element.
Getting Access to Elements in the DOM
The following are document objects that contain methods that we can use to get one or more HTMLElement objects from the DOM:
- getElementById(element-id): The element ID is the ID attribute of the element. Returned as an HTMLElement object.
- getElementsByTagName(element-name): The element name is the static name of HTML elements such as body, div, p, footer, ol, and ul. This returns a NodeList object. A NodeList object is similar to an array of objects.
- getElementsByClassName(css-class-name): The css class name is the class attribute of the elements. This returns a NodeList object. A NodeList object is similar to an array of objects.
- querySelector(selectors): The selectors are like the selectors that are used in CSS. This returns an HTMLElement object for the first element that's matched.
- querySelectorAll(selectors): The selectors are like the selectors that are used in CSS. This returns a NodeList object. A NodeList object is similar to an array of objects for each element that's matched.
- createElement(tag name): This creates an HTMLElement object for the HTML tag name that's supplied.
- createTextNode(data): This creates a Text object that can be placed inside an HTML element, for example, inside an h1 or a p element. The data argument is a string.
The following is an example of the document object's getElementById method being used to access a DOM element. This creates an object from an element DOM that has the id attribute of user-id:
let userIdEle = getElementById("user-id");
This is an example of the document object's getElementByTagName method being used to access DOM elements. This creates a collection of objects representing all the div elements in the document. Further steps are needed to access each element, such as a loop:
let divEles = getElementByTagName("div");
This creates a collection of objects representing all the elements that use the notice class in the document. Further steps are needed to access each element, such as a loop:
let noticeEles = getElementByClassName("notice");
This is an example of the document object's getElementByClassName method being used to access DOM elements. This creates a collection of objects representing all the elements that use the notice class in the document. Further steps are needed to access each element, such as a loop:
let noticeEles = getElementByClassName("notice");
Creating Elements and Content in the DOM
You may want JavaScript to add HTML elements and content to a web page. This is done by updating the DOM. The document object has two methods that are useful for this:
- createElement(tag name): Creates an HTMLElement object for the HTML tag name that's supplied.
- createTextNode(data): Creates a text object that can be placed inside an HTML element, for example, inside an h1 or a p element. The data argument is a string.
The following is an example of the document object's createElement method being used to create an li element:
let liEle = document.createElement("li");
The following is an example of the document object's createTextNode method being used to create a Milk Moon element:
let elementTextNode = document.createTextNode("Milk Moon");
Putting this all together, we can append elements and text nodes to the DOM. Consider the following HTML list of names for the November full moons:
<ul>
<li>Flower Moon</li>
<li>Planting Moon</li>
/ul>
Suppose we want to append another li element to the Milk Moon. To do that, we use the document object's createElement method to create an li element:
let liEle = document.createElement("li");
The createElement method returns an HTMLElement object. It provides the appendChild method, which we can use in this instance. For the appendChild method argument, the document object's createTextNode method can supply the required text node:
liEle.appendChild(document.createTextNode("Milk Moon"));
The resulting DOM is as follows:
<ul>
<li>Flower Moon</li>
<li>Planting Moon</li>
<li>Milk Moon</li>
</ul>
Let's take this a bit further and assume that we have a list of full moon names in an array:
let mayMoons = [
"Flower Moon",
"Planting Moon",
"Milk Moon"
];
Now, we want to use the array to populate a ul element that has the id attribute of full-moons:
<ul id ="full-moons">
<li>Grass Moon</li>
<li>Egg Moon</li>
<li>Pink Moon</li>
</ul>
First, you may want to remove the existing li elements from the ul element. You can do that by using the document.getElementById method and the innerHTML property of the element:
let moonsEle = document.getElementById("full-moons");
moonsEle.innerHTML = "";
Next, we can loop through the array, appending li elements to the moon names:
for (let i= 0; i<= mayMoons.length - 1; i++){
let liEle = document.createElement("li");
liEle.appendChild(document.createTextNode(mayMoons.length[i]));
listEle.appendChild(liEle);
}
The resulting DOM is as follows:
<ul id ="full-moons">
li>Flower Moon</li>
<li>Planting Moon</li>
<li>Milk Moon</li>
</ul>
DOM Events
Events are messages that you can provide to code so that it can handle it; for example, the user clicking a button on an HTML page. The document model objects use the addEventListener method to add your code so that it is processed when the event occurs. The syntax is as follows:
target.addEventListener(event-type, listener)
The target is an object that has the addEventListener method. Objects representing elements in the DOM have this method.
The event type parameter is a predefined name for the event. For example, click is the name for a mouse click event. The listener is an object that has the ability to "listen" to events. Functions are objects that can "listen" to events. Functions that are used as event listeners have one parameter, which is an Event object.
For example, the addEventListener method for a click event that uses a function literal can be written as follows:
helpButtonEle.addEventListener("click", function(e){
console.log("Something was clicked");
}
Exercise 3.17: Working with DOM Manipulation and Events
This exercise will accept an input value from a web page that aims to guess a number from 1 to 10. A button is used to check the input value against a random number that's generated from 1 to 10. Depending on whether there's a match, the display property of the other elements on the web page is toggled to hide or show the element. Also, the number that's generated is displayed on the page. Let's get started:
- Open the number-guess.html document in your web browser.
- Open the web developer console window using your web browser.
- First, we can start with the web browser document object.
- Type the items on the lines beginning with the > symbol. The console window will show a response on the lines beginning with the <· symbol.
- Open the number-guess.html document in your code editor.
Let's review some of the elements that will be accessed in JavaScript. First is the input element, which is used for entering the guess value. Note that its id attribute value is number-guessed. We are going to use the id attributes to get access to all the elements we use in JavaScript:
<input id="number-guessed" type="text" maxlength="2">
The next is the button element. The id attribute is test-button:
<button id="test-button">Test Your Guess</button>
The next is the p element. The id attribute is results-msg. This is the container for all the result messages. It has a class value of hidden. The number-guess.css file has the display property set to none for the hidden class:
.hidden{
display:none;
}
When the web page loads, this p element is not shown. The JavaScript will either hide or unhide this element:
<p id="results-msg" class="hidden" …</p>
Inside the p element, there are two span elements that contain the message for a guess that either matches or does not. They also use the hidden class. This is because, if their parent element is unhidden, these remain hidden until the code determines which to unhide. Each span element has an id attribute. The JavaScript will either hide or unhide each of these span elements:
<span id="match-msg" class="hidden">Congratulations!</span><span id="no-match-msg" class="hidden">Sorry!</span>
One more element inside the p element is a span element to show the number to guess. The JavaScript will update this:
<span id="number-to-guess"></span>
- Open the number-guess.js document in your code editor, replace all of its content with the following code, and then save it.
The first line creates an object for the element with idtest-button using the document object's getElementByID method.
The second line adds the function named testMatch as the listener for the click event on the button.
The following is the testMatch function and a message to the console so that we can test it:
let testButtonEle = document.getElementById('test-button');
testButtonEle.addEventListener('click', testMatch);
function testMatch(e){
console.log("Clicked!");
}
- Reload the number-guess.html web page in your web browser with the console window open and click the Test Your Guess button.
You should see the following message in the console window:
Clicked!
- Edit the number-guess.js document, update it using the bolded text, and then save it.
At the top of the file, all the elements we need to access in the HTML have been assigned to a variable:
let resultsMsgEle = document.getElementById('results-msg');
let matchedMsgEle = document.getElementById('match-msg');
let noMatchMsgEle = document.getElementById('no-match-msg');
let numberToGuessEle = document.getElementById('number-to-guess');
let guessInputEle = document.getElementById('number-guessed');
let testButtonEle = document.getElementById('test-button');
Next, add the DOM interface to get the value property from the input element's guessInputEle object. In case the user did not enter an integer, the parseInt JavaScript built-in function will flag that as not a number. Then, the if statement expression is true only if the number is between 1 and 10 inclusive:
function testMatch(e){
let numberGuessed = parseInt(guessInputEle.value);
if(!isNaN(numberGuessed) &&numberGuessed> 0 &&numberGuessed<= 10){
}
}
In the if statement block, the first step is to get a random integer from 1 to 10. Then, we use an if...else statement block if the input number matches the generated number.
For now, we can test this with outputs to the console window:
if(!isNaN(numberGuessed) &&numberGuessed> 0 &&numberGuessed<= 10){
let numberToGuess = Math.floor(Math.random() * 10 + 1);
if (numberGuessed == numberToGuess){
console.log("MATCHED!");
}else{
console.log("NOT MATCHED!");
}
console.log("Number guessed:", numberGuessed);
console.log("Number to match:", numberToGuess);
}
- Reload the number-guess.html web page in your web browser with the console window open, enter an integer from 1 to 10, and click the Test Your Guess button a few times.
Here are two test results:
NOT MATCHED!
Number guessed: 1
Number to match: 9
MATCHED!
Number guessed: 1
Number to match: 1
Try invalid values such as letters. There should be no output to the console.
- Edit the number-guess.js document, update it using the bolded text, and then save it.
Now, we can add in the steps for updating the DOM elements with the results. To start, all the result elements are hidden when the button is clicked:
function testMatch(e){
matchedMsgEle.style.display = 'none';
noMatchMsgEle.style.display = 'none';
resultsMsgEle.style.display = 'none';
let numberGuessed = parseInt(guessInputEle.value);
First, the hidden container for the message elements is displayed. Then, depending on whether there is a match or not, the element showing that result is displayed. Finally, the number to guess is updated in the element that was created for it:
if(!isNaN(numberGuessed) &&numberGuessed> 0 &&numberGuessed<= 10){
resultsMsgEle.style.display = 'block';
let numberToGuess = Math.floor(Math.random() * 10 + 1);
if (numberGuessed == numberToGuess){
matchedMsgEle.style.display = 'inline';
}else{
noMatchMsgEle.style.display = 'inline';
}
numberToGuessEle.innerText = numberToGuess;
}
- Reload the number-guess.html web page in your web browser with the console window open and repeatedly click the Test Your Guess button with a value entered.
The result of a matching output is as follows:

Figure 3.13: Matched value
The result of a non-matching output is as follows:

Figure 3.14: Non matched value
The result of an invalid entry output is as follows:

Figure 3.15: Invalid entry
Debugging
JavaScript programs may not work as intended. When that happens, it is usually called a bug.
Silent Fail
The people viewing your web page will not see any error message unless they know about the web developer console. This is called a silent fail approach. Silent fails keep web pages free of messages that would be cryptic to visitors. However, visitors may be puzzled when they try to interact with the web page and nothing happens and there are no messages.
There are two general categories of bugs: syntax and logic:
- Syntax: A syntax bug is a malformed JavaScript code.
- Logic: A logic error occurs when code that is syntactically correct does not perform as intended.
Syntax Bugs
Your console window will show you syntax errors so that they are easy to find and correct. Here is an example that shows an error at line 25 of the JavaScript file named convert-celsius-fahrenheit.js:

Figure 3.16: Syntax errors in the console window
The error code has a description and a link to the line number in the file. When you click that link, the source code file is opened in a window and the line that's involved is brought into view, as shown here:

Figure 3.17: Source code for the syntax error
In this case, the error reports an Unexpected token else. Now, you need to look at the code to find out where the syntax is malformed. In this case, it is a missing { following the if statement on line 21.
Now, you can fix the syntax error in the source file and then reload the page:

Figure 3.18: Syntax error at load time
The syntax error appeared at load time. This means that when the JavaScript file was loaded by the web browser, the syntax error was revealed.
However, a syntax error can appear at runtime. This happens while the code is executing, which does not need to happen when it's loaded, such as with a button click, as shown in the following screenshot:

Figure 3.19: Code executed on the web page
Here is an example of where the code is executed after the user clicks the Convert button on the web page, the user sees no error. Nothing appears to happen when the button is clicked. If the web browser console window is open, we will see the offending syntax error.
The error message also includes a call stack. A call stack is a list of functions and methods that were called to reach the line that was reported in the error message. This call stack shows the getFahrenheit function containing the failed line. Then, it shows that the function was called inside the convertButtonClickEventHandler method that was assigned to an HTMLButtonElement object. Notice that each item in the call stack will branch you to a line in the file.
We start at the link that is part of the error message, which opens the source view window and takes you to line 38. The incorrect line is followed by a comment showing the correct line. You can see that it is a simple omission of the assignment operator. The code line now has to be fixed in the source file and then reloaded. Then, the Convert button is clicked again to see whether the syntax error has been fixed.
Logic Bugs
A logic error occurs when code that is syntactically correct does not perform as intended. Logic errors often occur due to data and expressions not using or computing the correct values.
When a JavaScript program encounters a logic bug, it stops executing the remaining code statements. There is often no error message to pursue.
This makes logic errors more challenging to resolve and you want to use debugging tools to aid in their resolution.
Debugging
Fixing bugs is called debugging. Debugging requires tools, skills, and techniques. It usually involves correcting the source code.
Using the console.log method and showing the values in the console window is one tool we can use. This allows you to view values at certain points in the program to see whether they are the expected values. One of the drawbacks of this approach is that this requires you to put the console.log method in your source code, which ultimately needs to be removed as a best practice. Another issue is that arguments to the console.log methods are potentially bugs themselves.
The other alternative is to use a debugger. The top desktop web browsers have a JavaScript debugger.
Debuggers
To help resolve logical errors, you usually need a debugger. A debugger is a tool that lets you pause a program, follow each step, and examine the data values at those steps. Most desktop web browsers have a debugger built into its web developer view. Here is an example of the debugger for the Chrome web browser's developer tools:

Figure 3.20: Setting breaking points for the Chrome web browser
One of its most important features is setting breakpoints. A breakpoint pauses the code's execution. In this example, there is a breakpoint at line 34. It is shown not only in a Breakpoints panel but also in the source window, with a symbol on the line number. The symbol on the line number is actually a toggle to set or unset breakpoints. The Breakpoints panel is handy when you have multiple breakpoints spread out in the code and you need to enable or disable them without having to find the code line in the source window.
Once the code execution hits the breakpoint, then you can inspect the expressions by hovering a mouse pointer over the code. There is also a window that keeps all the data values organized, ready for inspection. For example, the guessedNumber variable is shown as 5 in two places in the following screenshot:

Figure 3.21: Data value organized in the debugger tool
Once the execution has been paused, you can control the execution of the code using the debugger menu:

Figure 3.22: Debugger Menu
The first four choices are a good place to start:
- Resume: The first choice restarts the JavaScript code's execution until it ends or reaches another breakpoint.
- Step Over: The second choice will not step into a function, but into all the code in a function it calls is executed. This is useful because there may not only be many functions you have written but also third-party functions that have been written that do not require a step-by-step investigation.
- Step Into: The third choice does step into a function where you can proceed. You can think of this as executing one line at a time.
- Step Out: The fourth choice is a way to step out of a function to the line that called it.
Activity 3.01: The To-Do List Shuffle Program
This activity will help you build a todo list web page. We will use JavaScript to load the list and shuffle the list after it has been loaded. A button labeled Shuffle has been added to the HTML file and the ol element has been assigned the ID, todo-list.
The high-level steps for the activity are as follows:
- Use the activity.js file to write your code. This contains coding hint comments you may use if you desire. It also includes a function named getNewShuffledArray.
- You need to load the li element todo items from JavaScript and then allow the user to randomize the list. You may approach the activity in two parts.
In Part 1, you need to use an array for the todo list items and create a function that updates the HTML DOM list items using an ol element and an array as parameters. The function will remove the previous li elements and iterate through the array to add new li elements with the values in the array parameter. Test before proceeding. You can find the HTML file at https://packt.live/2XcP1GU
- In Part 2, add a function to respond to the Shuffle button's click event. The function will use the original array of todo items and your previous function to update the ol element's list items. It also will use the getNewShuffledArray function to randomly shuffle an array and return the shuffled array.
The output of this activity is as follows:

Figure 3.23: The To-Do list Shuffle program
Note
The solution to this activity can be found on page 715.