Vanilla JavaScript Trivia using Modules

I have been developing a Trivia game for years now using Vanilla JavaScript and I finally using AI to help me. Yes, I know some people consider it cheating, but you still need to know how to code and do HTML/CSS as AI isn’t perfect and I can tell you there have been plenty of times that I felt like I was arguing with a person. I have this broken down in modules and commented (AI help me with that), but I thought I like to share as it gives a pretty good breakdown of the code.

Here’s the basic HTML

<main id="content" class="main">
    <div class="image-header">
        <img src="assets/images/img-brainwave-header.jpg" alt="Brain Wave Blitz">
    </div>
    <div id="quiz" class="displayMessage">
        <!-- Main game section, initially hidden -->
        <div id="mainGame" style="display: none;">
            <!-- Section for displaying the question and answers -->
            <div id="triviaSection">
                <div id="questionBox">
                    <!-- Current question and score information -->
                    <div id="current" class="info-bar">
                        <p>Current question is <span id="currentQuestion" data-record=""></span></p>
                        <p>Your score: <span id="score">0</span></p>
                    </div>
                    <h2 id="question">Question Goes Here!</h2>
                    <div id="answers">
                        <button class="buttonStyle" id="ans1"></button>
                        <button class="buttonStyle" id="ans2"></button>
                        <button class="buttonStyle" id="ans3"></button>
                        <button class="buttonStyle" id="ans4"></button>
                    </div>
                    <!-- Area for showing whether the answer was correct or not -->
                    <p id="result"></p>
                </div>
                <!-- Next button for moving to the next question -->
                <button id="next" class="nextBtn">Next</button>
            </div>
        </div>
    </div>

    <!-- Selector for choosing the category of questions -->
    <div id="categorySelector">
        <label for="category">Choose a category:</label>
        <select id="category" name="category">
            <option value="">--Please choose a category--</option>
            <option value="lego">LEGO</option>
            <option value="photography">Photography</option>
            <option value="space">Space</option>
            <option value="movie">Movies</option>
            <option value="sport">Sports</option>
        </select>
    </div>
</main>

this is the main JavaScript module called main.js which is appropriate

// Import necessary modules
import Game from "./game.js"; // Game logic module
import UI from "./ui.js"; // User interface module
import API from "./api.js"; // API interaction module

// Create instances of the Game, UI, and API classes
const game = new Game();
const ui = new UI();
const api = new API();

// Event listener for category selection changes
ui.categorySelect.addEventListener("change", () => {
    // Get the selected category value
    const selectedCategory = ui.categorySelect.value;

    // Reset the game state
    game.resetGame();

    // Enable answer buttons
    ui.enableButtons();

    // Check if a category is selected
    if (selectedCategory) {
        // Display the main game elements
        ui.displayMainGame();

        // Fetch trivia questions and answers from the API
        api.fetchTriviaQuestionsAnswers(`fetch_questions.php?category=${selectedCategory}`)
            // Start the game with the fetched data
            .then(data => game.startGame(data))
            // Catch and log any errors
            .catch(error => console.error(error));
    } else {
        // Hide the main game elements if no category is selected
        ui.hideMainGame();
    }
});

// Event listener for the next button click
ui.nextButton.addEventListener("click", () => {
    // Proceed to the next question in the game
    game.nextQuestion();

    // Update the UI after proceeding to the next question
    ui.updateUIAfterNextQuestion();
});

and you would put that in your HTML →

<!-- Link to the external JavaScript file -->
<script type="module" src="assets/js/main.js"></script>

next is the api.js:

// Define the API class to handle API requests
class API {
    // Constructor method to initialize the API class
    constructor() {}

    /**
     * Fetch trivia questions and answers from the specified URL
     *
     * @param {string} url - The URL to send the GET request to
     * @returns {Promise} A promise that resolves with the fetched data in JSON format
     */
    fetchTriviaQuestionsAnswers(url) {
        // Send a GET request to the specified URL
        return fetch(url)
            // Handle any errors that occur during the request
            .then((response) => this.handleErrors(response))
            // Parse the response data as JSON
            .then((data) => data.json())
            // Catch and log any errors that occur during the request
            .catch((error) => console.error(error));
    }

    /**
     * Fetch the correct answer for a given question ID
     *
     * @param {number} id - The ID of the question to fetch the correct answer for
     * @returns {Promise} A promise that resolves with the fetched data in JSON format
     */
    fetchCorrectAnswer(id) {
        // Send a POST request to the server with the question ID
        return fetch('fetch_correct_answer.php', {
            method: 'POST',
            body: JSON.stringify({ id: id })
        })
            // Handle any errors that occur during the request
            .then((response) => this.handleErrors(response))
            // Parse the response data as JSON
            .then((data) => data.json())
            // Catch and log any errors that occur during the request
            .catch((error) => console.error(error));
    }

    /**
     * Handle errors that occur during API requests
     *
     * @param {Response} response - The response object from the server
     * @returns {Response} The response object if it's OK, or throws an error if it's not
     */
    handleErrors(response) {
        // Check if the response status is OK (200-299)
        if (!response.ok) {
            // Throw an error if the response status is not OK
            throw response.status + " : " + response.statusText;
        }
        // Return the response object if it's OK
        return response;
    }
}

// Export the API class as the default export
export default API;

the ui.js module:

// Define the UI class to handle user interface updates and interactions
class UI {
    // Constructor method to initialize the UI class
    constructor() {
        // Select the category select element from the DOM
        this.categorySelect = document.querySelector("#category");

        // Select the main game container from the DOM
        this.mainGame = document.querySelector("#mainGame");

        // Select the brainwave header element from the DOM
        this.brainwaveheader = document.querySelector(".image-header");

        // Select the next button element from the DOM
        this.nextButton = document.querySelector("#next");

        // Select all answer button elements from the DOM
        this.answerButtons = document.querySelectorAll(".buttonStyle");
    }

    /**
     * Enable Answer Buttons
     *
     * Enables all answer buttons to allow user input.
     */
    enableButtons() {
        // Iterate over each answer button and set its disabled property to false
        this.answerButtons.forEach(button => {
            button.disabled = false;
        });
    }

    /**
     * Disable Answer Buttons
     *
     * Disables all answer buttons to prevent user input.
     */
    disableButtons() {
        // Iterate over each answer button and set its disabled property to true
        this.answerButtons.forEach(button => {
            button.disabled = true;
        });
    }

    /**
     * Show Next Button
     *
     * Displays the next button to allow the user to proceed to the next question.
     */
    showNextButton() {
        // Set the display property of the next button to "block" to make it visible
        this.nextButton.style.display = "block";
    }

    /**
     * Hide Next Button
     *
     * Hides the next button to prevent the user from proceeding to the next question.
     */
    hideNextButton() {
        // Set the display property of the next button to "none" to make it invisible
        this.nextButton.style.display = "none";
    }

    /**
     * Display Main Game
     *
     * Displays the main game container and hides the brainwave header.
     */
    displayMainGame() {
        // Set the display property of the main game container to "block" to make it visible
        this.mainGame.style.display = "block";

        // Set the display property of the brainwave header to "none" to make it invisible
        this.brainwaveheader.style.display = "none";
    }

    /**
     * Hide Main Game
     *
     * Hides the main game container.
     */
    hideMainGame() {
        // Set the display property of the main game container to "none" to make it invisible
        this.mainGame.style.display = "none";
    }

    /**
     * Update UI After Next Question
     *
     * Hides the next button and enables the answer buttons after the user proceeds to the next question.
     */
    updateUIAfterNextQuestion() {
        // Hide the next button
        this.hideNextButton();

        // Enable the answer buttons
        this.enableButtons();
    }
}

// Export the UI class as the default export
export default UI;

and finally the game.js module:

// Import the API class from the api.js module
import API from './api.js';

/**
 * Game Class
 *
 * Handles game logic and interactions.
 */
class Game {
    /**
     * Constructor
     *
     * Initializes the Game class by setting initial game state and creating an instance of the API class.
     */
    constructor() {
        // Initialize game state variables
        this.index = 0; // Current question index
        this.triviaData = []; // Array of trivia questions and answers
        this.score = 0; // Player's current score
        this.choice = 0; // Player's current answer choice

        // Create an instance of the API class for making API requests
        this.api = new API();
    }

    /**
     * Start Game
     *
     * Initializes the game with the provided trivia data and displays the first question.
     *
     * @param {array} data - The trivia data to use for the game.
     */
    startGame(data) {
        // Set the trivia data for the game
        this.triviaData = data;

        // Reset game state variables
        this.index = 0;
        this.score = 0;
        this.choice = 0;

        // Display the first question
        this.displayQuestion();

        // Hide the next button initially
        document.querySelector("#next").style.display = "none";
    }

    /**
     * Display Question
     *
     * Displays the current question and its possible answers.
     */
    displayQuestion() {
        // Get the current question from the trivia data
        const currentQuestion = this.triviaData[this.index];

        // Set the current question's ID as a data attribute on the #currentQuestion element
        document.querySelector("#currentQuestion").setAttribute("data-record", currentQuestion.id);

        // Display the current question number
        document.querySelector("#currentQuestion").textContent = (this.index + 1).toString();

        // Display the current question text
        document.querySelector("#question").textContent = currentQuestion.question;

        // Display the possible answers for the current question
        this.displayAnswers(currentQuestion);
    }

    /**
     * Display Answers
     *
     * Displays the possible answers for the given question.
     *
     * @param {object} question - The question object containing the possible answers.
     */
    displayAnswers(question) {
        // Get all answer button elements
        const answerButtons = document.querySelectorAll(".buttonStyle");

        // Iterate over each answer button
        answerButtons.forEach((button, index) => {
            // Remove any existing event listener for the button
            const previousPickAnswer = button.__pickAnswer__;
            if (previousPickAnswer) {
                button.removeEventListener("click", previousPickAnswer);
            }

            // Create a new event listener for the button
            const newPickAnswer = this.pickAnswer(index + 1);
            button.addEventListener("click", newPickAnswer, false);
            button.__pickAnswer__ = newPickAnswer;

            // Get the answer text for the current button
            let answerText = [question.ans1, question.ans2, question.ans3, question.ans4][index];

            // Display the answer text on the button
            if (answerText) {
                button.textContent = `📷 ${answerText}`;
                button.style.display = "block"; // Show the button
                button.style.pointerEvents = "auto"; // Enable the button
            } else {
                button.textContent = "";
                button.style.display = "none"; // Hide the button
                button.style.pointerEvents = "none"; // Disable the button
            }
        });
    }

    /**
     * Check Answer Against Table
     *
     * Checks the player's answer against the correct answer and updates the game state accordingly.
     *
     * @param {object} data - The correct answer data from the API.
     */
    checkAnswerAgainstTable(data) {
        // Get the correct answer from the data
        const correctAnswer = data.correct;

        // Increment the question index
        this.index++;

        // Check if the player's answer is correct
        if (correctAnswer === this.choice) {
            // Display a success message
            document.querySelector("#result").textContent = "The answer was indeed number " + correctAnswer + "!";
            document.querySelector("#result").style.color = "green";

            // Increment the player's score
            this.score++;
            document.querySelector("#score").textContent = `${this.score}`;
        } else {
            // Display an error message
            document.querySelector("#result").textContent = "Incorrect. The correct answer was: " + correctAnswer;
            document.querySelector("#result").style.color = "red";
        }
    }

    /**
     * Pick Answer
     *
     * Creates an event listener for the given answer index.
     *
     * @param {number} answerIndex - The index of the answer to create an event listener for.
     * @returns {function} The event listener function.
     */
    pickAnswer(answerIndex) {
        return () => {
            // Set the player's answer choice
            this.choice = answerIndex;

            // Check the answer
            this.checkAnswer();

            // Show the next button if there are more questions
            if (this.index < this.triviaData.length - 1) {
                document.querySelector("#next").style.display = "block"; // Show the next button
                document.querySelector("#next").addEventListener("click", () => {
                    console.log('Index before nextQuestion:', this.index);
                    this.nextQuestion();
                    console.log('Index after nextQuestion:', this.index);
                    document.querySelector("#next").style.display = "none"; // Hide the next button
                }, { once: true }); // Ensure the event listener is only triggered once per click
            }
        };
    }

    /**
     * Check Answer
     *
     * Checks the player's answer by making an API request to the server.
     */
    checkAnswer() {
        // Get all answer button elements
        const answerButtons = document.querySelectorAll(".buttonStyle");

        // Disable the answer buttons
        this.disableButtons(answerButtons);

        // Get the current question's ID
        const id = document.querySelector("#currentQuestion").getAttribute("data-record");

        // Make an API request to check the answer
        this.api.fetchCorrectAnswer(id)
            .then(data => this.checkAnswerAgainstTable(data))
            .catch(error => console.error(error));
    }

    /**
     * Handle Errors
     *
     * Handles errors that occur during API requests.
     *
     * @param {Response} response - The response object from the API.
     * @returns {Promise} A promise that resolves with the response data or throws an error.
     */
    handleErrors(response) {
        // Check if the response is not OK (200-299)
        if (!response.ok) {
            // Throw an error with the status code and text
            throw response.status + " : " + response.statusText;
        }
        // Return the response data
        return response.json();
    }

    /**
     * Disable Buttons
     *
     * Disables the given buttons.
     *
     * @param {array} buttons - The buttons to disable.
     */
    disableButtons(buttons) {
        // Iterate over each button and set its disabled property to true
        buttons.forEach(button => {
            button.disabled = true;
        });
    }

    /**
     * Enable Buttons
     *
     * Enables the given buttons.
     *
     * @param {array} buttons - The buttons to enable.
     */
    enableButtons(buttons) {
        // Iterate over each button and set its disabled property to false
        buttons.forEach(button => {
            button.disabled = false;
        });
    }

    /**
     * Reset Game
     *
     * Resets the game state to its initial values.
     */
    resetGame() {
        // Reset game state variables
        this.choice = 0;
        this.score = 0;
        document.querySelector("#result").textContent = "";
        document.querySelector("#score").textContent = `${this.score}`;
    }

    /**
     * Next Question
     *
     * Proceeds to the next question in the game.
     */
    nextQuestion() {
        // Clear the result message
        this.clearResultMessage();

        // Check if there are more questions
        if (this.index < this.triviaData.length) {
            // Display the next question
            this.displayQuestion();
        } else {
            // Log a message indicating the game is over
            console.log("Game over");
        }
    }

    /**
     * Clear Result Message
     *
     * Clears the result message displayed on the screen.
     */
    clearResultMessage() {
        // Clear the result message
        document.querySelector("#result").textContent = "";
    }
}

// Export the Game class as the default export
export default Game;

I think AI explains it best :
Modules:

  • Game Module (game.js): Handles game logic, including resetting the game state, starting the game, and proceeding to the next question.
  • UI Module (ui.js): Manages the user interface, including displaying and hiding game elements, enabling and disabling answer buttons, and updating the UI after proceeding to the next question.
  • API Module (api.js): Interacts with the API to fetch trivia questions and answers.

I think this will allow me to easily add features to the trivia game and I hope this will help someone out as well. I like this approach as I don’t have to learn a library or something language based off of JavaScript. Don’t get me wrong, but I learn more this way and in the process learn more of the inner workings. HTH

and yes it does work: https://www.clearwebconcepts.com/brainwaveblitz.php

Sponsor our Newsletter | Privacy Policy | Terms of Service