javascripthtmldom-events

Load data from different HTML files with click event on list items


What I want to achieve:

<!DOCTYPE html>
<html lang="en">
  <meta charset="utf-8">
  <title>Read file into div</title>
  <article>
    <h1>My HTMl</h1>
    <ul class = "listen">
      <li><a class="loc" href="file1.html">Select file 1</a>
      <li><a class="loc" href="file2.htm">Select file 2</a>
      <li><a class="loc" href="file2.txt">Select file 3</a>
      <li><a        href="https://example.com/">Load external file in new window with noopener</a>
    </ul>
    <h1>Please find selected content below:</h1>
    <div id = "SelectedText">
      <p>Replace me
    </div>
    <script> magic here </script>
  </article>
<!-- Local file -->
<p>file i contains for example:
<h1>Ipsum dolor sit amet</h1>

I tried:

<!DOCTYPE html>
<html lang="nl">
<meta charset="utf-8">
<title>Test</title>
  <nav>
    <ul>
      <li><a href="#" onclick="loadDoc('/nieuws.htm'); return false;">Nieuws</a>
      <li><a href="#" onclick="loadDoc('/teams/teams.htm'); return false;">Teams</a>
      <li><a href="#" onclick="loadDoc('/links/links.htm'); return false;">Links</a>
    </ul>
  </nav>
  <main id="content">
  <p>Replace me
  </main>

with JavaScript function:

    <script>
    // Load external HTML document in div.
    function loadDoc(x) {
      fetch(x)
      .then((result) => {
        if (result.status != 200) { throw new Error("Bad Server Response"); }
        return result.text();
      })
      .then((content) => {
        document.getElementById("content").innerHTML = content;
      })
      .catch((error) => { console.log(error); });
    }
    </script>

But I find a solution with event-handlers more elegant.

Update How to open the file in new window when an "Access-Control-Allow-Origin" error occurs.

Note: After the catch (err) I would like to execute the default behaviour, e.g. load the file in a separate window (_blank). No idea how to do this.

Application
Consider a website with choices:

Then if the user select choice 1 I would like to put the text regarding this choice in the rest of the page.

Note

I have a small old website. My question is about menu management: how can I insert web page content within the main page? Over time, I have realised that using an iframe is the most obvious solution.


Solution

  • Based on this similar answer on how to fetch partial HTML content from files

    Having HTML partials like: page1.html in the root of your project

    pageN.html

    <h1>This is page N</h1>
    

    this would be your main page:

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Read file into div</title>
    </head>
    
    <body>
        <h1>My HTML</h1>
        <ul class="listen">
            <li><a href="file1.html" class="loc">Load file 1</a>
            <li><a href="file2.html" class="loc">Load file 2</a>
            <li><a href="file3.html" class="loc">Load file 3</a>
        </ul>
        <h1>Please find selected content below:</h1>
        <div id="SelectedText">
            <p>Replace me</p>
        </div>
        <script>
            const loadPartial = async (elAnchor, elTarget) => {
                const file = elAnchor.getAttribute("href");
                try {
                    const res = await fetch(file);
                    const html = await res.text();
                    const domp = new DOMParser();
                    const doc = domp.parseFromString(html, "text/html");
                    elTarget.innerHTML = ""; // clear old content
                    elTarget.append(doc.documentElement);
                } catch (err) {
                    console.error(err);
                    open(file, "_blank", "noopener,noreferrer");
                };
            };
    
    
            const elTarget = document.querySelector("#SelectedText");
            const elListen = document.querySelector(".listen");
    
            elListen.addEventListener("click", (evt) => {
                const elAnchor = evt.target.closest(".loc");
                if (!elAnchor) return;
                evt.preventDefault(); // Prevent browser following the anchor
                loadPartial(elAnchor, elTarget);
            });
        </script>
    </body>
    </html>
    

    I've used DOMParser for injecting elements into your page as a security measure (XSS Vulnerability). If you trust the content that's being inserted you might instead use this simpler (and faster) solution inside the try block:

    const res = await fetch(file);
    const html = await res.text();
    elTarget.innerHTML = html;
    

    Tip:

    developers in need to dynamically (AJAX) populate/fetch partials into their page often forget that, as the page content drastically and meaningfully changes, there's the need for the visitors to navigate back / forward the history, bookmark or share the current page's content direct link. With the above that's impossible, so I would suggest to explore the History API - namely the pushState, popState, and replaceState methods to achieve single-page application routing.