skip to Main Content

I am trying to altar the background color of dynamically generated div elements on a mouseover event. I am still new to event listeners and feel I am lacking a fundamental concept on how they work. How do I get them to apply to elements created after the DOM has loaded?

const divSelector = document.querySelectorAll('main.child')
let gridSize = gridMaker();
let gridHover = colorChange();

function gridMaker () {
    let size = prompt('Enter a size: ')
    for (let i = 0; i < size; i++) {
        var div = document.createElement("div");
        div.style.width = "100px";
        div.style.height = "100px";
        div.style.background = "red";
        div.style.margin = '10px';
        div.id = 'etch';
    
    document.getElementById("main").appendChild(div);
    }
}
//When mouse hovers over a div, permanently change color of that div
function colorChange () {
    divSelector.forEach((e) => {
        document.body.addEventListener('mouseover', (e) => {
            if (e.target.id == 'etch') {
                console.log('target found')
            }
        });
    })
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Etch-A-Sketch</title>
  <script src="./app.js" defer></script>
</head>

<body>
  <div id="main">

  </div>
</body>

</html>

3

Answers


  1. You can attach an event listener directly to div when you create it and add your logic to the callback.

    const main = document.getElementById("main");
    let gridSize = gridMaker();
    
    function gridMaker() {
      let size = prompt('Enter a size: ')
      for (let i = 0; i < size; i++) {
        var div = document.createElement("div");
        div.style.width = "100px";
        div.style.height = "100px";
        div.style.background = "red";
        div.style.margin = '10px';
        div.classList.add('etch');
        div.addEventListener('mouseover', (e) => {
          e.target.style.background = 'green';
        })
        main.appendChild(div);
      }
    }
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Etch-A-Sketch</title>
    </head>
    
    <body>
      <div id="main">
      </div>
    </body>
    
    </html>
    Login or Signup to reply.
  2. The statement const divSelector = document.querySelectorAll('main.child'); is run at the start of the script, at which point, there are no <main> elements with the class of child in the page. This means that divSelector will be an empty NodeList. So when colorChange() is called as the third statement, the divSelector.forEach() runs over an empty list and so the callback function is never run.

    You shouldn’t need the aforementioned const divSelector = document.querySelectorAll('main.child'); statement or its later .forEach() call if you are going to be listening to mouseover on the document:

    let gridSize = gridMaker();
    let gridHover = colorChange();
    
    function gridMaker() {
      let size = prompt('Enter a size: ')
      for (let i = 0; i < size; i++) {
        var div = document.createElement("div");
        div.style.width = "100px";
        div.style.height = "100px";
        div.style.background = "red";
        div.style.margin = '10px';
        div.id = 'etch';
    
        document.getElementById("main").appendChild(div);
      }
    }
    //When mouse hovers over a div, permanently change color of that div
    function colorChange() {
      document.body.addEventListener('mouseover', (e) => {
        if (e.target.id == 'etch') {
          console.log('target found')
        }
      });
    }
    <div id="main">
    
    </div>
    Login or Signup to reply.
  3. First, the problems with your existing code:

    // this runs once, as soon as it's encountered by the
    // JavaScript engine, and is never updated
    // (document.querySelectorAll() returns a non-live
    // NodeList); on page-load there are no matching elements:
    const divSelector = document.querySelectorAll('main.child')
    
    // the defined gridMaker() function returns nothing
    // to the calling context; therefore this variable
    // will always remain undefined:
    let gridSize = gridMaker();
    let gridHover = colorChange();
    
    function gridMaker () {
        // there are no sanity checks to prevent anyone
        // from silliness, such as "💩" or "spaghetti"...
        // if you're accepting anything from a user assume
        // malicious intent:
        let size = prompt('Enter a size: ')
    
        for (let i = 0; i < size; i++) {
            var div = document.createElement("div");
            div.style.width = "100px";
            div.style.height = "100px";
            div.style.background = "red";
            // inconsistent style, this isn't a problem but
            // makes it easier to make typos in future, try
            // be sensibly consistent in your code (it makes
            // it easier to spot mistakes in future), but 
            // - as noted - it's not really a "problem":
            div.style.margin = '10px';
    
            // only one element can have a particular 'id,'
            // if you're creating multiple elements then
            // all elements will share this 'id' attribute-
            // value which is invalid HTML, and will cause
            // problems in JavaScript because of the
            // by-design functionality of getElementById()
            // querySelector() (though the latter can be
            // mitigated, but it's still bad practice):
            div.id = 'etch';
    
        // causes the page to be redrawn every time the loop
        // runs (browsers are quick, so you may not notice,
        // but it's a lot of work that can be avoided):        
        document.getElementById("main").appendChild(div);
        }
    }
    
    //When mouse hovers over a div, permanently change color of that div
    function colorChange () {
        // will be a null value, which will generate errors:
        divSelector.forEach((e) => {
            document.body.addEventListener('mouseover', (e) => {
                // this should never be used; use either a
                // predictable "id" attribute-value style
                // or a class-name, with:
                // e.target.classList.contains(<className>)
                if (e.target.id == 'etch') {
                    console.log('target found')
                }
            });
        })
    }
    

    Now, to address those problems you can either use event-binding at the point of element-creation, or you can use event-delegation. First, event-binding at the point of creation:

    // creating the named event-handling function:
    const handler = (evt) => evt.currentTarget.style.backgroundColor = "hotpink",
      gridMaker = () => {
        // using the same prompt approach to obtain a user's desired number
        // of elements; we pass the result of that prompt() to parseInt()
        // which should return the integer (or NaN) from the result, having
        // parsed it to a base-10 format:
        let quantity = parseInt(prompt('How many elements should we create?').trim(), 10);
    
        // if no useful value was received, we quit here:
        if (!quantity) {
          return false;
        }
    
        // here we create a <div> element,
        let div = document.createElement('div'),
            // and a document fragment to prevent redraws:
            fragment = document.createDocumentFragment();
    
        // we add the class-name, via the Element.classList API:
        div.classList.add('child');
    
    
        // we use Array.from() to create an Array:
        Array.from({
          // we pass in the retrieved value held in
          // the 'quantity' variable:
          length: quantity
        // we iterate over the Array, using
        // Array.prototype.forEach():
        }).forEach(
          // we pass in the index of the current Array-entry
          // (we also pass in a reference to the current
          // Array-entry, the '_', but this effectively
          // empty/undefined, so not useful):
          (_, index) => {
    
            // we clone the created <div> element:
            let clone = div.cloneNode();
            
            // set its id property (which is reflected in
            // it's attribute) to the string "etch_"
            // concatenated with the value of the "index"
            // using a template-literal String:
            clone.id = `etch_${index}`;
            
            // binding the event-handler - handler() - to
            // the 'mouseenter' event:
            clone.addEventListener('mouseenter', handler);
    
            // appending the cloned, and modified, element
            // to the document fragment:
            fragment.append(clone);
        });
        
        // appending the document fragment to the <main>
        // element:
        document.querySelector('main').append(fragment);
      };
    
    // calling the function (after the function is declared,
    // as using the const approach for function declaration
    // prevents hoisting):
    gridMaker();
    .child {
      width: 100px;
      height: 100px;
      background: red;
      margin: 10px;
    }
    
    .permaColor {
      background-color: fuchsia;
    }
    <main></main>

    Next, event-binding; in which we bind the event-handler, as you were trying to do, to the existing ancestor element of those you wish to act upon:

    // finding the existing ancestor element:
    const ancestor = document.querySelector('main'),
      handler = (evt) => {
    
        // using destructuring assignment to declare the variables
        // 'currentTarget' and 'target', and initialising them to
        // the values held in those property-names of the
        // Event Object passed into the function:
        let {
          currentTarget,
          target
        } = evt;
    
        // if the currentTarget (the element to which the event-handler
        // is bound) is the target (the element upon which the event
        // was initially fired), or the event-target element already
        // has the class-name we plan to add:
        if (currentTarget === target || target.classList.contains('permaColor')) {
          // we quit here
          return false;
        }
    
        // otherwise, if the target's list of class-names contains
        // 'child':
        if (target.classList.contains('child')) {
          // we add the 'permaColor' class:
          target.classList.add('permaColor');
        }
    
      },
      gridMaker = () => {
        // using the same prompt approach to obtain a user's desired number
        // of elements; we pass the result of that prompt() to parseInt()
        // which should return the integer (or NaN) from the result, having
        // parsed it to a base-10 format:
        let quantity = parseInt(prompt('How many elements should we create?').trim(), 10);
    
        // if no useful value was received, we quit here:
        if (!quantity) {
          return false;
        }
    
        // here we create a <div> element,
        let div = document.createElement('div'),
          // and a document fragment to prevent redraws:
          fragment = document.createDocumentFragment();
    
        // we add the class-name, via the Element.classList API:
        div.classList.add('child');
    
    
        // we use Array.from() to create an Array:
        Array.from({
          // we pass in the retrieved value held in
          // the 'quantity' variable:
          length: quantity
          // we iterate over the Array, using
          // Array.prototype.forEach():
        }).forEach(
          // we pass in the index of the current Array-entry
          // (we also pass in a reference to the current
          // Array-entry, the '_', but this effectively
          // empty/undefined, so not useful):
          (_, index) => {
    
            // we clone the created <div> element:
            let clone = div.cloneNode();
    
            // set its id property (which is reflected in
            // it's attribute) to the string "etch_"
            // concatenated with the value of the "index"
            // using a template-literal String:
            clone.id = `etch_${index}`;
    
            // appending the cloned, and modified, element
            // to the document fragment:
            fragment.append(clone);
          });
    
        // appending the document fragment to the <main>
        // element:
        document.querySelector('main').append(fragment);
      };
    
    // calling the function (after the function is declared,
    // as using the const approach for function declaration
    // prevents hoisting):
    gridMaker();
    
    // binding the event-listener to the 'ancestor' element:
    ancestor.addEventListener('mouseover', handler);
    .child {
      width: 100px;
      height: 100px;
      background: red;
      margin: 10px;
    }
    
    .permaColor {
      background-color: fuchsia;
    }
    <main></main>

    JS Fiddle demo.

    References:

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search