Bent and hammered nail with an emoji face illustration. Done by Andre Coetzer
Section 7

JavaScript in the browser — Dom and Events Fundamentals

Lecture 70

Project - 1 — Guess my number!

< Between 1 and 20 >

Guess the number

?

Waiting for a guess...

Score: 20

Highscore: 0

                    
                      const guessButton = document.querySelector('.guess-btn');

                      let secretNumber = Math.trunc(Math.random() * 20) + 1;
                      let score = 20;
                      let highscore = 0;
                                          
                      guessButton.addEventListener('click', () => {
                        let guess = parseInt(document.querySelector('.guess').value);
                        console.log(guess, typeof guess);
                      
                        // Check if there is input
                        if (!guess) {
                          document.querySelector('.message').textContent = '⛔ No Number Chosen';
                        } else if (guess === secretNumber) {
                          document.querySelector('.message').textContent = "🥳 Yay! That's correct!";
                          document.querySelector('.message').style.color = '#4ade80';
                          document.querySelector('.secret-number').textContent = secretNumber;
                          document.querySelector('.secret-number').style.color = '#4ade80';
                          if (score > highscore) {
                            highscore = score;
                          }
                          document.querySelector('.highscore').textContent = highscore;
                        } else if (guess > secretNumber) {
                          if (score > 1) {
                            document.querySelector('.message').textContent = '📈 Too High!';
                            score--;
                            document.querySelector('.score').textContent = score;
                          } else {
                            document.querySelector('.message').textContent = '💥 You Lost';
                            document.querySelector('.message').style.color = '#f87171';
                            document.querySelector('.secret-number').textContent = secretNumber;
                            document.querySelector('.secret-number').style.color = '#f87171';
                            document.querySelector('.score').textContent = 0;
                          }
                        } else if (guess < secretNumber) {
                          if (score > 1) {
                            document.querySelector('.message').textContent = '📉 Too Low!';
                            score--;
                            document.querySelector('.score').textContent = score;
                          } else {
                            document.querySelector('.message').textContent = '💥 You Lost';
                            document.querySelector('.message').style.color = '#f87171';
                            document.querySelector('.secret-number').textContent = secretNumber;
                            document.querySelector('.secret-number').style.color = '#f87171';
                            document.querySelector('.score').textContent = 0;
                          }
                        }
                      });
                      
                      const againButton = document.querySelector('.again-btn');
                      
                      againButton.addEventListener('click', function () {
                        secretNumber = Math.trunc(Math.random() * 20) + 1;
                        score = 20;
                        document.querySelector('.message').textContent = 'Waiting for a guess...';
                        document.querySelector('.message').style.color = 'var(--clr-tint)';
                        document.querySelector('.score').textContent = score;
                        document.querySelector('.secret-number').textContent = '?';
                        document.querySelector('.secret-number').style.color = 'var(--clr-tint)';
                        document.querySelector('.guess').value = '';
                      })
                    
                  

Above is both the completed project and the code which is not refactored as we will look into that later.

                    
                      // Legacy ways of selecting elements
                      document.getElementByTagName();
                      document.getElementById();
                      document.getElementByClass();

                      // This is the more modern way of selecting elements
                      document.querySelector();
                      document.querySelectorAll();

                      // To view all properties and methods on an element node

                      const headingPrimaryEl = document.querySelector('.heading-primary');

                      console.log(headingPrimaryEl);
                    
                  

Before we dive into the DOM, let's learn how to select and element. We can do this using document.querySelector. The 'querySelector' is a method on the document object. When you pass in a selector, it has to be within a string or quotation marks.

We can view the available to properties and methods if we assign an element node to a variable.

Lecture 71

What's the DOM and DOM manipulation

illustration of the DOM The DOM tree and how it looks

For each element in the HTML page, there is an element node in the DOM tree.

DOM stands for the Document Object Model, it's a structured representation of HTML documents. It allows us or JavaScript to access HTML and styles to manipulate them. We will be able to change text, HTML attributes and even CSS styles.

The DOM is automatically created by the browser in a tree like structure. In this tree structure, each HTML element is an object.

The DOM however is not part of JavaScript, but the DOM and DOM Methods are web APIs. 'API' stands for Application Programming Interface, which we will learn more about later. Web APIs are like libraries that browsers implement and what we can access using JavaScript code.

Apart from the DOM, there are a ton more web APIs such as timers, fetch etc...

In Summary

Web APIs are automatically given to us through the browser and web APIs are written in JavaScript which act as a sort of library, giving us access to the DOM.

Lecture 72

Selecting and manipulating elements

                    
                      // Legacy ways of selecting elements
                      document.getElementByTagName();
                      document.getElementById();
                      document.getElementByClass();

                      // This is the more modern way of selecting elements
                      document.querySelector();
                      document.querySelectorAll();

                      const messageEl = document.querySelector(.message);

                      // To assign a value
                      messageEl.textContent = 'Something';

                      // or read a property
                      console.log(messageEl.textContent);
                    
                  

When it comes to selecting element nodes in JavaScript, There are some legacy ways and some more modern ways, as labeled above. The difference between 'querySelector' and 'querySelectorAll' is that if you have a button that is used in multiple instances on the page, just using 'querySelector' will return the first instance of that element.

While 'querySelectorAll' will return something called a node list which is an array that we can loop over. We first select an element and then with one of the available properties we can manipulate it but assigning a value related to that property.

Lecture 73

Handling click events

                    
                      // The breakdown of the event handler function
                      .addEventListener(' Event Name ', Event Handler Function (event) {})

                      // Ways we can implement an event listener
                      document.querySelector('.message').addEventListener('click', function () {});

                      const messageEl =  querySelector('.message');

                      messageEl.add EventListener('click', function () {});
                    
                  

An event is something that happens on the page, whether it be a mouse click, a button pressed or mouse hover etc... With an event listener, we can listen for a certain event and then react to it.

The '.addEventListener('event name' function (e) {})' is a special type of method that's expects an event handler function as the second argument.

Lecture 74

Implementing game logic

                    
                      if (!guess) {
                        displayMessage('⛔ No Number Chosen');

                        //
                      } else if (guess === secretNumber) {
                        displayMessage("🥳 Yay! That's correct!");
                        displayMessageColor('#4ade80');
                        displaySecretNumber(secretNumber);
                        displaySecretNumberColor('#4ade80');
                      
                        // Below we check for the highscore, if the score is higher than
                        // the highscore, then the highscore becomes that score.
                        if (score > highscore) {
                          highscore = score;
                        }
                        highscoreEl.textContent = highscore;
                      
                        //  Below we check if the number is different to the secret number
                        // and if its still above the score of 1.
                      } else if (guess !== secretNumber) {
                        if (score > 1) {
                        
                          // Instead of writing 2 else if statements, we use a ternary 
                          // operator to provide a value of either too high, or too low.
                          displayMessage(guess > secretNumber ? '📈 Too High!' : '📉 Too Low!');
                          score--;
                          displayScore(score);
                        } else {
                          displayMessage('💥 You Lost');
                          displayMessageColor('#f87171');
                          displaySecretNumber(secretNumber);
                          displaySecretNumberColor('#f87171');
                          displayScore(0);
                        }
                      }
                    
                  

Above is the refactored game logic which we will see later. The comments should highlight the purpose for the game logic applied, including some logic we have gotten to yet.

Lecture 75

Manipulating CSS styles

Color Code

Press 'G' or 'touch' your screen.

                    
                      const colorController = document.querySelector('.color-controller');
                      const colorValue = document.querySelector('.color-option--value');

                      const randomColor = function () {
                        const h = Math.trunc(Math.random() * 360) + 1;
                        const s = Math.trunc(Math.random() * 100) + 1;
                        const l = Math.trunc(Math.random() * 100) + 1;
                      
                        return `hsl(${h}, ${s}%, ${l}%)`;
                      };

                      document.addEventListener('keydown', function (e) {
                        const currentColor = randomColor();
                      
                        if (e.key === 'g' || e.key === 'G') {
                          colorController.style.backgroundColor = currentColor;
                          colorValue.textContent = currentColor;
                        }
                      });

                      document.addEventListener('touchstart', function () {
                        const currentColor = randomColor();

                        colorController.style.backgroundColor = currentColor;
                        colorValue.textContent = currentColor;
                      });
                    
                  

In JavaScript the property names, such as 'font-family', now adopts the camel casing way of writing which looks like this 'fontFamily'. Whenever we manipulate a style, we always need to use a string as a value.

It's important to know that styles applied using JavaScript become inline styles. Apart from the '!important' keyword, we know that inline styles have a higher specificity than that of styles inside a CSS document.

Lecture 76

Coding Challenge - 1

Implement a game rest functionality, so that the player can make a new guess!

Your Tasks:
  1. Select the element with the 'again' class and attach a click event handler.

  2. In the handler function, restore initial values of the 'score' and 'secretNumber' variables

  3. Restore the initial conditions of the message, number, score and guess input fields

  4. Also restore the original background color (#222) and number width (15rem)

                    
                      // Here we listen if the the again button was clicked and then
                      // reset all the values to their default value. Note that highscore 
                      // was not included in the reset

                      againBtnEl.addEventListener('click', function () {
                        secretNumber = Math.trunc(Math.random() * 20) + 1;
                        score = 20;
                        displayMessage('Waiting for a guess...');
                        displayMessageColor('var(--clr-tint)');
                        displayScore(score);
                        displaySecretNumber('?');
                        displaySecretNumberColor('var(--clr-tint)');
                        guessEl.value = '';
                      });
                    
                  
Lecture 77

Implementing high scores

                    
                          // Below we check for the highscore, if the score is higher than
                          // the highscore, then the highscore becomes that score.

                          if (score > highscore) {
                            highscore = score;
                          }
                          highscoreEl.textContent = highscore;
                    
                  

Above is how we implemented the high score functionality into the game, if we look at the reset you will see this was not included into the reset as we would like to keep the highscore.

Lecture 78

Refactoring our code — The DRY principle

                    

                      const guessEl = document.querySelector('.guess');
                      const guessBtnEl = document.querySelector('.guess-btn');
                      const againBtnEl = document.querySelector('.again-btn');
                      const messageEl = document.querySelector('.message');
                      const secretNumberEl = document.querySelector('.secret-number');
                      const scoreEl = document.querySelector('.score');
                      const highscoreEl = document.querySelector('.highscore');
                                          
                      const displayMessage = function (message) {
                        messageEl.textContent = message;
                      };
                      
                      const displayMessageColor = function (color) {
                        messageEl.style.color = color;
                      };
                      
                      const displaySecretNumber = function (value) {
                        secretNumberEl.textContent = value;
                      };
                      
                      const displaySecretNumberColor = function (color) {
                        secretNumberEl.style.color = color;
                      };
                      
                      const displayScore = function (value) {
                        scoreEl.textContent = value;
                      };
                      
                      let secretNumber = Math.trunc(Math.random() * 20) + 1;
                      let score = 20;
                      let highscore = 0;
                      
                      guessBtnEl.addEventListener('click', function () {
                        let guess = parseInt(guessEl.value);
                      
                        if (!guess) {
                          displayMessage('⛔ No Number Chosen');
                        } else if (guess === secretNumber) {
                          displayMessage("🥳 Yay! That's correct!");
                          displayMessageColor('#4ade80');
                          displaySecretNumber(secretNumber);
                          displaySecretNumberColor('#4ade80');
                          if (score > highscore) {
                            highscore = score;
                          }
                          highscoreEl.textContent = highscore;
                        } else if (guess !== secretNumber) {
                          if (score > 1) {
                            displayMessage(guess > secretNumber ? '📈 Too High!' : '📉 Too Low!');
                            score--;
                            displayScore(score);
                          } else {
                            displayMessage('💥 You Lost');
                            displayMessageColor('#f87171');
                            displaySecretNumber(secretNumber);
                            displaySecretNumberColor('#f87171');
                            displayScore(0);
                          }
                        }
                      });

                      againBtnEl.addEventListener('click', function () {
                        secretNumber = Math.trunc(Math.random() * 20) + 1;
                        score = 20;
                        displayMessage('Waiting for a guess...');
                        displayMessageColor('var(--clr-tint)');
                        displayScore(score);
                        displaySecretNumber('?');
                        displaySecretNumberColor('var(--clr-tint)');
                        guessEl.value = '';
                      });
                    
                  

As you can see above, we refactored the code using the DRY principle. One thing to note is when selecting element nodes from the DOM, instead of saying document.querySel.. on the event, we select them before hand and place them near the top of the js file.

Lecture 79

Project -2 — Modal window

                    
                      const modalBtn = document.querySelectorAll('.modal-btn');
                      const modalBox = document.querySelector('.modal-box');
                      const modalCloseBtn = document.querySelector('.modal-icon-close');
                      const overlay = document.querySelector('.modal-overlay');


                      const openModal = function () {
                        modalBox.classList.remove('modal-hidden');
                        overlay.classList.remove('modal-hidden');
                      }

                      const closeModal = function () {
                        modalBox.classList.add('modal-hidden');
                        overlay.classList.add('modal-hidden');
                      };

                      for (let i = 0; i < modalBtn.length; i++)
                        modalBtn[i].addEventListener('click', openModal)

                      modalCloseBtn.addEventListener('click', closeModal);

                      overlay.addEventListener('click', closeModal)

                      document.addEventListener('keydown', function (e) {
                        if (e.key === 'Escape' && !modalBox.classList.contains('modal-hidden')) {
                          closeModal()
                          console.log(`${e.key} was pressed`)
                        }
                      });
                    
                  

Within this project, we will be looking at how we store our DOM selections in variables, therefor we don't need to select them each time. These selections are usually placed at the top of the document.

In this project, we will be using the '.querySelectorAll' for the first time, as we have multiple buttons with the same class. We will then use the 'for loop' to iterate over each button, and then for each iteration apply the event listener.

Lecture 80

Working with classes

                    
                      const openModal = function () {
                        modalBox.classList.remove('modal-hidden');
                        overlay.classList.remove('modal-hidden');
                      }

                      const closeModal = function () {
                        modalBox.classList.add('modal-hidden');
                        overlay.classList.add('modal-hidden');
                      };

                      for (let i = 0; i < modalBtn.length; i++)
                        modalBtn[i].addEventListener('click', openModal)
                    
                  

With the 'for loop' we place an event handler on each iteration. In the event handler, we can specify a named function, meaning we can declare the function somewhere else and use that as the event handler method.

Though, we don't call the function, so this means we declare it without the parenthesis and allow the event to call it.

Lecture 81

Handling and 'Esc' Keypress event

                    
                      document.addEventListener('keydown', function (e) {
                        if (e.key === 'Escape' && !modalBox.classList.contains('modal-hidden')) {
                          closeModal()
                          console.log(`${e.key} was pressed`)
                        }
                      });
                    
                  

Keyboard events are so-called global events, as they don't happen on a specific element, but rather the entire document. Each event listener stores information about the event in the form of an object when the event has executed.

To access this event information, we can add in a parameter to the event handler function and then log that to the console.

In Summary

When an event occurs, we can have access to that information through the event handler function. This works because we don't call the event function, we only declare it and the event calls the function and passes the event object as an argument.

Another useful thing which is done a lot is to check if an element already has a class present using the 'classList.contains(' ... ')' method.

Lecture 82

Project - 3 — The pig game

Player 1

43

Current

0

Player 2

24

Current

0

Playing dice
                    
                      const player0El = document.querySelector('.player--0');
                      const player1El = document.querySelector('.player--1');
                      const score0El = document.getElementById('score--0');
                      const current0El = document.getElementById('current--0');
                      const score1El = document.getElementById('score--1');
                      const current1El = document.getElementById('current--1');

                      const newGameBtnEl = document.querySelector('.game-btn--new');
                      const rollDiceBtnEl = document.querySelector('.game-btn--roll');
                      const holdBtnEl = document.querySelector('.game-btn--hold');
                      const diceEl = document.querySelector('.game-dice');

                      let scores, currentScore, activePlayer, isGameOver;

                      const init = function () {
                        scores = [0, 0];
                        currentScore = 0;
                        activePlayer = 0;
                        isGameOver = 0;
                      
                        score0El.textContent = 0;
                        current0El.textContent = 0;
                        score1El.textContent = 0;
                        current1El.textContent = 0;
                      
                        player0El.classList.add('player--active');
                        player1El.classList.remove('player--active');
                        diceEl.classList.add('dice--hidden');
                        player0El.classList.remove('game-winner');
                        player1El.classList.remove('game-winner');
                      };

                      init();

                      const switchPlayer = function () {
                        document.getElementById(`current--${activePlayer}`).textContent = 0;
                        currentScore = 0;
                        activePlayer = activePlayer === 0 ? 1 : 0;
                        player0El.classList.toggle('player--active');
                        player1El.classList.toggle('player--active');
                      };

                      rollDiceBtnEl.addEventListener('click', function () {
                        if (!isGameOver) {
                          const dice = Math.trunc(Math.random() * 6) + 1;
                        
                          diceEl.classList.remove('dice--hidden');
                          diceEl.src = `../resources/images/javascript/dice-${dice}.png`;
                        
                          if (dice !== 1) {
                            currentScore += dice;
                            console.log(currentScore);
                            document.getElementById(`current--${activePlayer}`).textContent =
                              currentScore;
                          } else {
                            switchPlayer();
                          }
                        }
                      });

                      holdBtnEl.addEventListener('click', function () {
                        if (!isGameOver) {
                          scores[activePlayer] += currentScore;
                        
                          document.getElementById(`score--${activePlayer}`).textContent =
                            scores[activePlayer];
                        
                          if (scores[activePlayer] >= 30) {
                            isGameOver = true;
                            document
                              .querySelector(`.player--${activePlayer}`)
                              .classList.add('game-winner');
                          } else {
                            switchPlayer();
                            diceEl.classList.add('dice--hidden');
                          }
                        }
                      });

                      newGameBtnEl.addEventListener('click', init);
                    
                  

This game is about not rolling a 1, and adding up your current score, though if the player rolls a 1 they lose their current score and their turn is over.

The DOM tree and how it looks

To understand the game better, we can break down the problem using a flow chart and listing all possible scenarios. Utilizing the flow chart when building applications is a great way to visualize how the application will operate. It doesn't have to be a complete plan and can be added onto over time.

Getting to the lecture, we select the score elements by their ID using both '.querySelector('#IDName')' and '.getElementByID('IDName')'. Generally with much larger applications you will want to use '.getElementByID('IDName')' as it's a lot faster.

We also went a made a hidden helper CSS class, setting the display property to none, and added that to the dice image using JavaScript instead of adding it directly to the HTML. Why, I don't know, maybe just because we can or because if the script doesn't load, we can still show there is something there.

Lecture 83

Rolling the dice

                    
                      rollDiceBtnEl.addEventListener('click', function () {
                        if (!isGameOver) {
                          const dice = Math.trunc(Math.random() * 6) + 1;
                        
                          diceEl.classList.remove('dice--hidden');
                          diceEl.src = `../resources/images/javascript/dice-${dice}.png`;
                        
                          if (dice !== 1) {
                            currentScore += dice;
                            console.log(currentScore);
                            document.getElementById(`current--${activePlayer}`).textContent =
                              currentScore;
                          } else {
                            switchPlayer();
                          }
                        }
                      });
                    
                  

Within this lecture, we start by implementing the roll dice functionality. This, providing the random number, and showing its corresponding dice image using dynamically type string literals.

Lecture 84

Switching active player

                    
                      const switchPlayer = function () {
                        document.getElementById(`current--${activePlayer}`).textContent = 0;
                        currentScore = 0;
                        activePlayer = activePlayer === 0 ? 1 : 0;
                        player0El.classList.toggle('player--active');
                        player1El.classList.toggle('player--active');
                      };
                    
                  

Within our HTML, we set the players ID starting from zero, 'player--0' as player 1 and 'player--1' as player 2. This is because of how we will set up our scores and active players when we start using dynamically type string literals. If anyone ever reads this, this is kinda hard to explain, which is why it's best to view the example.

We then make an active player variable and set that to 0, as we always want 'player--0' or player 1 in other words to be the first to start the game. Then within our conditions and if statement, we can use a ternary operator to adjust the active player status by saying if 'activePlayer' is set to 0 then change it to 1 if not then change it to 0.

Lecture 85

Holding current score

                    
                      holdBtnEl.addEventListener('click', function () {
                        if (!isGameOver) {
                          scores[activePlayer] += currentScore;
                        
                          document.getElementById(`score--${activePlayer}`).textContent =
                            scores[activePlayer];
                        
                          if (scores[activePlayer] >= 30) {
                            isGameOver = true;
                            document
                              .querySelector(`.player--${activePlayer}`)
                              .classList.add('game-winner');
                          } else {
                            switchPlayer();
                            diceEl.classList.add('dice--hidden');
                          }
                        }
                      });
                    
                  

Basically, we just sum the current score to the current player's total score when the player decides to hold their score. We then check that the active player score meets the condition of winning, if so, we set the isGameOver Variable to true and display that visually.

Lecture 86

Resetting the game

                    
                      let scores, currentScore, activePlayer, isGameOver;

                      const init = function () {
                        scores = [0, 0];
                        currentScore = 0;
                        activePlayer = 0;
                        isGameOver = 0;
                      
                        score0El.textContent = 0;
                        current0El.textContent = 0;
                        score1El.textContent = 0;
                        current1El.textContent = 0;
                      
                        player0El.classList.add('player--active');
                        player1El.classList.remove('player--active');
                        diceEl.classList.add('dice--hidden');
                        player0El.classList.remove('game-winner');
                        player1El.classList.remove('game-winner');
                      };

                      init();

                      newGameBtnEl.addEventListener('click', init);
                    
                  

Here we just reset the game and made a function for that.