JavaScript in the browser — Dom and Events Fundamentals
Project - 1 — Guess my number!
Guess the number
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.
What's the DOM and DOM manipulation
illustration of the DOMFor 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 SummaryWeb 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.
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.
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.
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.
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.
Coding Challenge - 1
Implement a game rest functionality, so that the player can make a new guess!
Your Tasks:-
Select the element with the 'again' class and attach a click event handler.
-
In the handler function, restore initial values of the 'score' and 'secretNumber' variables
-
Restore the initial conditions of the message, number, score and guess input fields
-
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 = '';
});
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.
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.
Project -2 — Modal window
To close this window, either click the 'X' in top right corner or click on the gradient overlay that is around this text box or press the 'Esc' key.
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.
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.
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 SummaryWhen 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.
Project - 3 — The pig game
Player 1
43
Current
0
Player 2
24
Current
0
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.
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.
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.
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.
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.
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.