2

I need to make a web page with a lot of content. In order to be more efficient when modifying this content, I decided to put it in separate files and then include these files, following this tutorial: https://www.w3schools.com/howto/howto_html_include.asp.

For example one of these files may contain some clickable links to book descriptions, which are modal boxes. So I need to get them in a loading script to get these clickable links and make them trigger some events. But it seems this loading script is called before JavaScript gets the included nodes, even if I add an event listener after reading some threads (I tried to run it at 'DOMContentLoaded' or 'load') : document.getElementById or document.getElementsByClassName still returns null so it fails to define an onclick function. Let me show an example code:

script.js

function includeHTML()  { /* Some code replacing the div by some-content.html, which is : <a id="clickable">Hello</a> */}

var button = null

window.addEventListener('load', function()  {
  button = document.getElementById("clickable");
  button.onclick = function() { alert('Hello'); }
});

index.html

<!DOCTYPE html>
<html>
<head>
  <script type="text/javascript" src="script.js"></script>
</head>

<body>
  <p>Here is a trigger button : </p>
  <div include-html="some-content.html"></div>
  
  <script>includeHTML();</script>
</body>
</html>

On Firefox, this will fail on defining button.onclick as button is still null. Any idea on how to fix it?

Not only should I be adding links, but also modal boxes. Here is a script code, more complete, for what my guess was:

script.js

var boxNames = ["bibliography", "about", "book1", "book2" ];
var boxes = null /* Contains the boxes to be displayed */
var trigs = null /* Contains the trigger buttons for each box */
var close = null /* Contains the close buttons for each box */

function setTrigger(i) {
    trigs[i].onclick = function() { setBoxVisible(true, i); }
}

function setClose(i) {
    trigs[i].onclick = function() { setBoxVisible(false, i); }
}

function load() {
    boxes = new Array(4);
    trigs = new Array(4);
    close = new Array(4);
    for(var i = 0; i < boxNames.length; i++) {
        boxes[i]=document.getElementById(boxNames[i]+"-box");
        trigs[i]=document.getElementById(boxNames[i]+"-trig");
        close[i]=document.getElementById(boxNames[i]+"-close");
        setTrigger(i); setClose(i);
    }
}

window.onload = function() { load(); }

For the code of includeHTML(), you can have a look at the tutorial I shared, I copy/pasted.

I think this kind of function would be more elegant if dealing with such stuff, but I would need it to be launched once everything is loaded, as if I was running it manually.

Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122

1 Answers1

2

Your code only added the event listener when the page was loading, likely before the link existed.

You need to delegate from the nearest static container.

Here in your code it is document

Give the link a class instead of ID and do

window.addEventListener('load', function(e) {
  document.addEventListener('click', function(e) {
    const tgt = e.target;
    if (tgt.classList.contains("clickable")) {
      e.preventDefault(); // because it is a link
      alert('Hello');
    }
  });
});
<a class="clickable" href="#">Click</a>

Update after new code

  1. You overwrite the trigs code
  2. You can very simply extend my code so you do not need to loop

window.addEventListener('load', function(e) {
  document.addEventListener('click', function(e) {
    const tgt = e.target;

    if (tgt.classList.contains("clickable")) {
      e.preventDefault(); // because it is a link
      alert('Hello');
    }
    else if (tgt.classList.contains("box")) {
      e.preventDefault(); // because it is a link
      const [id, what] = tgt.id.split("-")
      console.log(id)
      if (what === "trig") {
        document.getElementById(id).classList.remove("hide")
      }
      else if (what === "close") {
        document.getElementById(id).classList.add("hide"); // or tgt.closest("div").classList.add("hide")
      }  
    }
  });
});
.hide { display:none; }
<a class="clickable" href="#">Click</a>
<hr/>

<a class="box" id="bibliography-trig" href="#">Trigger Biblio</a>
<a class="box" id="about-trig" href="#">Trigger About</a>

<div id="bibliography" class="hide">Biblio
  <a class="box" id="bibliography-close" href="#">Close</a>
</div>

<div id="about" class="hide">About
  <a class="box" id="about-close" href="#">Close</a>
</div>
mplungjan
  • 169,008
  • 28
  • 173
  • 236
  • Thanks, works well, but is there another way for more complicated situation. For example my modal boxes are also included from external files. Therefore it would be convenient to save them in variables to set them visible or not. But then I encounter the same issue again, as these variables remain null. Btw, when running my loading script manually, no problem, so is there a way to make it wait the new nodes to be loaded? – Florian Tarazona Jan 12 '21 at 13:11
  • Very likely yes. But you need to show more code. `includeHTML()` what does it do? You can delegate anything. when you delegate from document, anything on the page is monitored at any time. You can add more tests in the code I wrote: `else if (tgt.classList.contains("modal")) { ... }` – mplungjan Jan 12 '21 at 13:14
  • I've edited the post so you can see what I had in mind precisely – Florian Tarazona Jan 12 '21 at 13:34