javascripthtmldomtypeerrorevent-listener

How to get Element after it has been inserted from template


I have recently come upon a problem regarding my script not being able to access and element that was inserted via editing the innerHTML in the same script.

So, my HTML has this placeholder code:

<div class="stage_preset_selection_column">
    <div class="stage_preset" id="stage_preset_1">
        <!-- <div class="preset_number">Preset 1</div>
            <div class="preset_title">title of the preset</div>
            <div class="questions_stage stage_preset_information">
                <div class="preset_text">questions per stage:</div>
                <div class="preset_text_number">5</div>
            </div>
            <div class="number_stages stage_preset_information">
                <div class="preset_text">number of stages:</div>
                <div class="preset_text_number">13</div>
            </div>
            <div class="number_failure stage_preset_information">
                <div class="preset_text">number of f for failure:</div>
                <div class="preset_text_number">3</div>
            </div>
            <div class="last_edited">last edited: 30-06-2021 14:51</div>
            <img src="../../assets/elements/cross.png" alt="remove preset" class="remove_preset">
            <img src="../../assets/elements/cross_inverted.png" alt="remove preset" class="remove_preset_inverted"> -->

        <!-- <div class="preset_number toggle_empty_preset">Preset 2</div>
            <div class="empty_preset_title">Empty preset</div> -->
    </div>
    <div class="stage_preset" id="stage_preset_2"></div>
</div>

<div class="stage_preset_selection_column">
    <div class="stage_preset" id="stage_preset_3"></div>
    <div class="stage_preset" id="stage_preset_4"></div>
</div>

which is then upon launch edited with this javascript code:

function updatePreset() {
    fs.readFile(path.join(__dirname, '/stage_presets.json'), 'utf8', (err, data) => {
        if(err) {
            console.log(`Error reading file from disk: ${err}`);
        } else {
            const stage_presets = JSON.parse(data);
    
            Object.values(stage_presets).forEach(preset => {
                let htmlPresetElement = document.getElementById(`stage_preset_${preset.stage_preset_number}`)
                
                if(preset.is_empty) {
                    htmlPresetElement.innerHTML = `<div class="preset_number toggle_empty_preset">Preset ${preset.stage_preset_number}</div>
                    <div class="empty_preset_title">Empty preset</div>`
    
                } else {
                    htmlPresetElement.innerHTML = `<div class="preset_number">Preset ${preset.stage_preset_number}</div>
                    <div class="preset_title">${preset.preset_title}</div>
                    <div class="questions_stage stage_preset_information">
                        <div class="preset_text">questions per stage:</div>
                        <div class="preset_text_number">${preset.questions_per_stage}</div>
                    </div>
                    <div class="number_stages stage_preset_information">
                        <div class="preset_text">number of stages:</div>
                        <div class="preset_text_number">${Object.keys(preset.stages).length}</div>
                    </div>
                    <div class="number_failure stage_preset_information">
                        <div class="preset_text">number of f for failure:</div>
                        <div class="preset_text_number">${preset.f_for_failure}</div>
                    </div>
                    <div class="last_edited">last edited: ${preset.last_edited}</div>
                    <img src="../../assets/elements/cross.png" alt="remove preset" class="remove_preset">
                    <img src="../../assets/elements/cross_inverted.png" alt="remove preset" class="remove_preset_inverted" id="remove_preset_inverted_${preset.stage_preset_number}">`
                }
            });
        }
    });
}

So now my problem is that for some reason when I try to make an EventListener for the element of id "remove_preset_inverted_[num]", I get this error:

Uncaught TypeError: Cannot read property 'addEventListener' of null 

So my question is, why is this happening and how can I fix it? Every solution I have tried has failed so far.

Thanks

EDIT: added runnable snippet For some unknown reason it's working in the code snippet but not on my code. Does it have something to do with me using fs.readFile in the function inserting the elements?

stage_presets = {
  "stage_preset_1": {
    "stage_preset_number": 1,
    "preset_title": "Test Preset!",
    "is_empty": false,
    "questions_per_stage": 5,
    "last_edited": "Sun, 04 Jul 2021 20:13:19 GMT",
    "f_for_failure": 3,
    "probability_easy": 60,
    "probability_medium": 25,
    "probability_hard": 15,
    "stages": {
      "grade_1": {
        "title": "Grade 1",
        "questions": {
          "question_1": {
            "question": "When mokey do funny, what happen?",
            "answer": "everyone laughs :)",
            "difficulty": "easy",
            "subject": "science"
          },
          "question_2": {
            "question": "What's 1+1?",
            "answer": "idk",
            "difficulty": "hard",
            "subject": "maths"
          }
        }
      },
      "grade_2": {
        "title": "Grade 2",
        "questions": {
          "question_1": {
            "question": "What was the word 'crap' named after?",
            "answer": "Thomas Crapper",
            "difficulty": "easy",
            "subject": "history"
          },
          "question_2": {
            "question": "",
            "answer": "",
            "difficulty": "medium",
            "subject": "chaos"
          }
        }
      },
      "grade_3": {
        "title": "Grade 3",
        "questions": {
          "question_1": {
            "question": "How does banana market fluctuation influence prices?",
            "answer": "monkey regulation",
            "difficulty": "easy",
            "subject": "political_science"
          },
          "question_2": {
            "question": "",
            "answer": "",
            "difficulty": "hard",
            "subject": "litterature"
          },
          "question_3": {
            "question": "rohulk?",
            "answer": "this is gonna be messy",
            "difficulty": "medium",
            "subject": "osu"
          }
        }
      },
      "grade_4": {
        "title": "Grade 4",
        "questions": {
          "question_1": {
            "question": "",
            "answer": "",
            "difficulty": "hard",
            "subject": "pe"
          }
        }
      }
    }
  },
  "stage_preset_2": {
    "stage_preset_number": 2,
    "preset_title": "New preset",
    "is_empty": false,
    "questions_per_stage": 5,
    "last_edited": "Sun, 04 Jul 2021 18:49:54 GMT",
    "f_for_failure": 3,
    "probability_easy": 60,
    "probability_medium": 25,
    "probability_hard": 15,
    "stages": {}
  },
  "stage_preset_3": {
    "stage_preset_number": 3,
    "preset_title": "New preset",
    "is_empty": false,
    "questions_per_stage": 5,
    "last_edited": "Sun, 04 Jul 2021 18:49:52 GMT",
    "f_for_failure": 3,
    "probability_easy": 60,
    "probability_medium": 25,
    "probability_hard": 15,
    "stages": {}
  },
  "stage_preset_4": {
    "stage_preset_number": 4,
    "preset_title": "New preset",
    "is_empty": false,
    "questions_per_stage": 5,
    "last_edited": "Sun, 04 Jul 2021 19:02:58 GMT",
    "f_for_failure": 3,
    "probability_easy": 60,
    "probability_medium": 25,
    "probability_hard": 15,
    "stages": {}
  }
}


function updatePreset(stage_presets) {
  Object.values(stage_presets).forEach(preset => {
      let htmlPresetElement = document.getElementById(`stage_preset_${preset.stage_preset_number}`)

      if(preset.is_empty) {
          htmlPresetElement.innerHTML = `<div class="preset_number toggle_empty_preset">Preset ${preset.stage_preset_number}</div>
          <div class="empty_preset_title">Empty preset</div>`

      } else {
          htmlPresetElement.innerHTML = `<div class="preset_number">Preset ${preset.stage_preset_number}</div>
          <div class="preset_title">${preset.preset_title}</div>
          <div class="questions_stage stage_preset_information">
              <div class="preset_text">questions per stage:</div>
              <div class="preset_text_number">${preset.questions_per_stage}</div>
          </div>
          <div class="number_stages stage_preset_information">
              <div class="preset_text">number of stages:</div>
              <div class="preset_text_number">${Object.keys(preset.stages).length}</div>
          </div>
          <div class="number_failure stage_preset_information">
              <div class="preset_text">number of f for failure:</div>
              <div class="preset_text_number">${preset.f_for_failure}</div>
          </div>
          <div class="last_edited">last edited: ${preset.last_edited}</div>
          <img src="../../assets/elements/cross.png" alt="remove preset" class="remove_preset">
          <img src="../../assets/elements/cross_inverted.png" alt="remove preset" class="remove_preset_inverted" id="remove_preset_inverted_${preset.stage_preset_number}">`
      }
  });

}

updatePreset(stage_presets)

function presetRemove(e) {
  console.log("successfully ran the function!")
}

document.querySelector('#remove_preset_inverted_1').addEventListener('click', presetRemove)
* {
    margin: 0;
    padding: 0;
    font-family: 'Arial';

    box-sizing: border-box;
}

body {
    font-weight: 200;
    font-size: 0.46vh;

    background-color: #cde480;

    height: 100vh;
    width: 100vw;

    overflow: hidden;
}

.stage_preset_selection_container {
    display: flex;
    flex-direction: row;

    justify-content: center;
    align-items: center;

    height: 100vh;
    width: 100vw;
    
    transition: transform .5s;
}

.stage_preset_selection_column {
    display: flex;
    flex-direction: column;

    justify-content: space-around;
    align-items: center;
}

.preset_number {
    font-size: 11em;
    color: #394a00;
    font-weight: 400;
    letter-spacing: .1em;
    user-select: none;
    transition: color .3s;
}

.preset_title {
    font-size: 10em;
    color: #394a00;
    letter-spacing: .1em;
    margin-bottom: 3vh;
    user-select: none;
    transition: color .3s;
}

.preset_text_number,
.preset_text {
    font-size: 6em;
    color: #727f45;
    letter-spacing: .1em;
    user-select: none;
}

.last_edited {
    font-size: 4em;
    color: #727f45;
    letter-spacing: .1em;
    margin-top: 3vh;
    user-select: none;
    white-space: nowrap;
}

.empty_preset_title {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, 0%);

    font-size: 10em;
    color: #727f45;
    font-style: italic;
    letter-spacing: .1em;
    user-select: none;

    white-space: nowrap;
}

.stage_preset_information {
    display: flex;
    flex-direction: row;
    justify-content: space-between;
}

.stage_preset {
    position: relative;
    border: 0.56vh solid #394a00;
    border-radius: 4.63vh;
    width: 45vw;
    height: 42vh;
    padding: 3vh 12vh 4vh 4vh;
    margin: 2vh 3vh;
    cursor: pointer;
    box-shadow: 0 1.5vh 1.7vh rgba(0, 0, 0, 0.3);
    user-select: none;
    transition: all .3s;
}

.stage_preset:hover {
    background-color: #394a00;
    transform: translateY(-1vh);
    box-shadow: 0 2.5vh 2.7vh rgba(0, 0, 0, 0.4);
}

.stage_preset:active {
    background-color: #212b00;
    border: 0.56vh solid #212b00;
    transform: translateY(-.5vh);
    box-shadow: 0 2vh 2.2vh rgba(0, 0, 0, 0.35);
}

.stage_preset:hover > .remove_preset_inverted {
    opacity: 100%;
    display: block;
}

.stage_preset:active > .preset_title,
.stage_preset:active > .preset_number,
.stage_preset:hover > .preset_title,
.stage_preset:hover > .preset_number {
    color: #cde480;
}

.toggle_empty_preset {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -100%);
}

.remove_preset {
    position: absolute;
    top: 14%;
    left: 92%;
    transform: translate(-50%, -50%);
    width: 3.33vh;
    height: 3.33vh;
    cursor: pointer;
    transition: all 0.3s;
}

.remove_preset_inverted {
    position: absolute;
    top: 14%;
    left: 92%;
    transform: translate(-50%, -50%);
    width: 3.33vh;
    height: 3.33vh;
    cursor: pointer;
    opacity: 0;
    display: none;
    transition: all 0.3s;
}

.remove_preset_inverted:hover {
    transform: translate(-50%, -60%);
}

.remove_preset_inverted:active {
    transform: translate(-50%, -55%)
}

.stage_preset_selected {
    transform: translateX(-100%);
}

.preset_setup_container {
    position: absolute;
    display: flex;
    top: 0;
    left: 0;
    transform: translateX(100vw);
    transition: all 0.5s;
}

.preset_setup_selected {
    transform: translateX(0);
}
<!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">
</head>
<body>
<div class="stage_preset_selection_container">
        <div class="stage_preset_selection_column">
            <div class="stage_preset" id="stage_preset_1">
                <!-- <div class="preset_number">Preset 1</div>
                    <div class="preset_title">title of the preset</div>
                    <div class="questions_stage stage_preset_information">
                        <div class="preset_text">questions per stage:</div>
                        <div class="preset_text_number">5</div>
                    </div>
                    <div class="number_stages stage_preset_information">
                        <div class="preset_text">number of stages:</div>
                        <div class="preset_text_number">13</div>
                    </div>
                    <div class="number_failure stage_preset_information">
                        <div class="preset_text">number of f for failure:</div>
                        <div class="preset_text_number">3</div>
                    </div>
                    <div class="last_edited">last edited: 30-06-2021 14:51</div>
                    <img src="../../assets/elements/cross.png" alt="remove preset" class="remove_preset">
                    <img src="../../assets/elements/cross_inverted.png" alt="remove preset" class="remove_preset_inverted"> -->

                <!-- <div class="preset_number toggle_empty_preset">Preset 2</div>
                    <div class="empty_preset_title">Empty preset</div> -->
            </div>
            <div class="stage_preset" id="stage_preset_2"></div>
        </div>

        <div class="stage_preset_selection_column">
            <div class="stage_preset" id="stage_preset_3"></div>
            <div class="stage_preset" id="stage_preset_4"></div>
        </div>
    </div>
</body>
</html>


Solution

  • The answer is most certainly to attach your listeners through event delegation, rather than directly on the dynamic element. This would relieve you from having to place a new listener on every '#remove_preset_inverted[num]`.

    You could simply do this in the initial page load

    window.addEventListener('load', () => {
      document.addEventListener('click', (e) => {
        if (e.target.classList.contains('remove_preset_inverted')) {
          // this is one of those elements. If you need to test for the id:
          let id = e.target.getAttribute('id').split('remove_preset_inverted_')[1];
        }
      })
    })