Adding an EventListener to Elements in a Class Using Bubbling

I have a parent DIV called workspace and it contains a child DIV called test1. When the DIV test1 is clicked, 3 new DIVs are created dynamically. These 3 DIVs belong to a CSS class called handle and they will be removed from the DOM when the grandparent DIV (workspace) is clicked. I’m trying to add an eventlistener to each of the 3 DIVs using their class name and Bubbling but it is not working.

It works if I give each of the DIVs an ID, get all of them in an array using querySelectorAll with their class name, loop through the array, and finally identify which element is clicked using their IDs before adding the eventlistener in if statements. However I would like to use Bubbling instead of loop to add eventlisteners. If you are wondering why I used their IDs it is because I want each of the three DIVs to have a different behavior when they are clicked, dragged, etc. Please see my code on jsfiddle here.

I created an online trivia game, but I think the following might help you out a little?

First I create the question and answers by populating the with data (I don’t know exactly what you are doing, so populating with data could be ignored? ) -

/* Populate Question, Create Answer Buttons */
const createQuiz = (gameData) => {

    startTimer(dSec);

    question.textContent = gameData.question;

    /*
     * Create Buttons then insert answers into buttons that were
     * create.
     */
    gameData.answers.forEach((value, index) => {


        let gameButton = buttonContainer.appendChild(d.createElement('button'));
        gameButton.id = 'answer' + (index + 1);
        gameButton.className = 'answerButton';
        gameButton.setAttribute('data-correct', (index + 1).toString());
        gameButton.addEventListener('click', clickHandler, false);
        /*
         * Don't Show Answers that have a Blank Field
         */
        if (value !== "") {
            gameButton.appendChild(d.createTextNode("📷 " + value));
        } else {
            gameButton.appendChild(d.createTextNode(" "));
            gameButton.style.pointerEvents = "none"; // Disable Click on Empty Field
        }
    });
};

Then after the user has answer, I need to reset the question and answers -

/* Remove Question & Answers */
const removeQuiz = () => {
    removeAnswers(); // Call removeAnswers FCN:
    next.style.display = "none";
    next.removeEventListener('click', removeQuiz, false);
    gameIndex++;
    window.scrollTo(0, 0);

    if (gameIndex < totalQuestions && shotsRemaining > 0) {
        createQuiz(gameData[gameIndex]); // Recreate the Quiz Display:
    } else {
        scoreboard();
    }
};

Removing the answer buttons:

/* Remove answers from Screen */
const removeAnswers = () => {
    let element = d.querySelector('#buttonContainer');
    while (element.firstChild) {
        element.removeChild(element.firstChild);
    }
};

What I do is look at the HTML first to see what I need to do. Here is the HTML for the question and answers:

    <div class="triviaContainer" data-records=" ">
        <div id="mainGame">
            <div id="current">Question No. <span id="currentQuestion"></span></div>
            <div id="triviaSection" data-correct="">
                <div id="questionBox">
                    <h2 id="question">What is the Question?</h2>
                </div>
                <div id="buttonContainer"></div>
            </div>

            <div id="headerStyle" data-user="">
                <div class="gauge">
                    <div class="gauge__body">
                        <div class="gauge__fill"></div>
                        <div class="gauge__cover">Battery 100%</div>
                    </div>
                </div>
                <p id="score">0 Points</p>
                <p id="percent">100% Correct</p>

                <button id="next" class="nextBtn">Next</button>
            </div>

        </div>
    </div>
</div>

That way I can do a mock-up of it before writing the JavaScript code.

Hope that helps a little.

Thanks for the reply but it is not at all similar to what I’m trying to do

Hi @oangodd, you just have an ID mismatch in your fiddle – the handles have the IDs d1, d2 and d3 but your’re checking if the ID is div1.

1 Like

Hi m3g4p0p,
Thanks for your reply. Good catch I was using the variable div1 instead of the id d1 but that’s only part of the problem. The element d1 is created along with the elements d2 and d3 after clicking on the parent div (test1). I noticed that ev.target.id doesn’t change to (d1) when I click on it and instead it still points to the parent (test1).

Sure? I just added console.log(ev.target.id) to the top of the click handler and it seems to work as expected…

Peek 2022-01-14 11-54

2 Likes

You are omitting the last parameter of the AddEventListener method

parentDiv.addEventListener("click", (ev)=>{......}, false);

By adding false as the last argument, the event target will be the element that was clicked, which I think is what you are trying to achieve.

Hi dennisjn, Thanks for your reply. I noticed that I forgot to create the global variable originalEl which throws an error but it had the unexpected side effect of correctly displaying the id of the clicked element some of the time.

After creating the global variable originalEl it no longer throws an error but when I click on the test1 DIV and then click on the DIVs d1, d2, or d3, ev.target.id kept pointing to the id of test1 even though I added false as the last argument of addEventListener as you have suggested.

Ah yes sry forgot to mention that… you probably forgot to add "use strict" in your original code. ;-) Anyway here’s the fork of your fiddle where the above screencast was taken from:

BTW you can indeed omit the useCapture parameter here as false is the default.

I added at line 15 of the js in the fiddle

alert("Target id: " + ev.target.id);

The first click on the Test 1 box and the alert reported “test1” as expected.
I then clicked on the middle box and the alert reported “d2”

You are using outdated code, you should use at least ES6.
You should also use strict equality that is if(x === y) and if(x !== y) This could be where your problem is.

If the problem is adding click event handlers to the three div elements created in the addHandles() function, then add them as you create the div elements.
You have the option to create the click handler as a separate function and attach it to all three div elements or you could create separate listener functions for each div element.

Hi dennisjn,
Thanks for your help. I will surely use some of your suggestions in my final code. The purpose of this exercise was to flesh out adding event listeners to dynamically added elements via the use of event bubbling then resolve any issue that may arise. Sure enough there were many issues I needed to resolve.

I refactored my code and added only the basic code for various events just enough to allow me to see what’s going on. I removed the mousedown event listener from the body of the click event listener and placed it outside of the click event listener. The issue now is the the mousedown event conflicts with the click event causing the click event to not work.

Please see my code here in jsfiddle.

Well it does not conflict with the click event per se – it’s only the alert() modal blocking the UI after the mouse down event, so the click event will never fire. If you replace the alert()s with console.log()s it works though:

1 Like