0

My HTML pages dynamically load my header menu and page footer using the following method.

# in my page-includes.js file

fetch("nav-menu.html")
  .then(response => {
    return response.text()
  })
  .then(data => {
    document.querySelector("nav").innerHTML = data;
  });

fetch("footer.html")
  .then(response => {
    return response.text()
  })
  .then(data => {
    document.querySelector("footer").innerHTML = data;
  })

# code i tried at the bottom of my main page

    <script>
        
      document.getElementsByClassName("copyright-year")[0].innerHTML = "28 BC";
      var spanTags = document.getElementsByClassName("copyright-year");
      spanTags[0].text("13 BC");
      spanTags[1].innetHTML = "28 BC";
      setYear();

      function setYear(){
        if (document.readyState === 'interactive') {
          let curYr = new Date().getFullYear();
          for (i=0; i<span.length; i++) {
             spanTags[i].innerHTML = curYr;
          }
      }
      
    /* though I really don't know JQuery I tried this first */
    /* $(".copyright-year").text(new Date().getFullYear()); */
    
    </script>
<mainpage>
    <nav class="navbar" id="sectionsNav">
    </nav>  

    <footer class="footer footer-default">
    </footer>
</mainpage>

<included-footer-html>
<div class="container">
    <div class="row">
        <div class="col-lg-12">
            <div class="copyright float-left">
                &copy;
                <span class="copyright-year">
                    <script defer>document.write(new Date().getFullYear());</script>
                </span>
                &nbsp;Kopacz, LLC
            </div>
            <div class="float-right">
                All Rights Reserved
                <sub><i class="material-icons">public</i></sub> Worldwide
            </div>
        </div>
    </div>
    <div class="row">
        <div class="col-lg-12">
            <div class="copyright float-none">
                Astronomy Photographs &copy;
                <span class="copyright-year">
                    <script defer>document.write(new Date().getFullYear());</script>
                </span>
                &nbsp;David Kopacz
            </div>
        </div>
    </div>
</div>
</included-footer-html>

The problem is that whether I include the date function in the footer.html that is loaded by included.js or I simply embed a simple span tag and then try to modify it's text after the page loads, the date never gets placed on the page.

If I copy and paste the footer HTML with the script onto the bottom of any web page, the year is displayed perfectly, but when included via this Fetch function, nothing is displayed.

What is most interested to note is the fact that when I view the source of any web page on my site, I only see the tags that Fetch uses to locate and place the nav and footer HTML. The actual nav menu and footer HTML never appear in the source, only their container tags and .

The included HTML can be seen using Inspect in the browser.

So, how do I access these "semi-hidden" HTML elements and script in my date function?

  • 2
    scripts in html added this way do not get executed – Jaromanda X Aug 31 '20 at 00:02
  • 1
    You should also avoid `document.write` or use it with EXTREME caution : https://stackoverflow.com/questions/802854/why-is-document-write-considered-a-bad-practice#:~:text=write()%20is%20mostly%20used,bad%20idea%20to%20have%20document. – Jon P Aug 31 '20 at 00:07
  • @Jaromanda - That behavior is evident by the fact the date is not displayed. What I do not understand is why can I not access the span element after the page is loaded. If that is not possible, why not and is there a workaround? I am not keen on having to modify the header menu and footer on dozens of website pages each time a change is needed. I could hard code the year, but I will never remember to update it in January, and I wouldn't want to do it on dozens of websites. There must be a way to do accomplish this with script and use Fetch to include the nav and footer from a single source. – David Kopacz Aug 31 '20 at 00:44
  • 1
    `when I view the source` - the view source function shows the HTML of the page as loaded, not "current" state ... you could select the whole page (ctrl-a) and right click and "view selection source" - then you'll see the current state ... or just use developer tools inspector - you'll see all the html including dynamically created – Jaromanda X Aug 31 '20 at 00:47
  • @Jon P - I don't disagree. It is, however, a simple way to test whether or the script is executing. I intended to use something more along the line of the code I attempted at the bottom of the web page shown above, but even that doesn't have access to the span element after the page is completely loaded. – David Kopacz Aug 31 '20 at 00:56
  • @Jaromanda - I understand. I see the HTML using Inspect, and it is obviously on the web pages. My question then becomes, how do I access the objects (span element) and manipulate them. I can access and manipulate every object on the web page EXCEPT those that were inserted with the Fetch API. – David Kopacz Aug 31 '20 at 00:59
  • you use `document.querySelector` - you just have to wait for those elements to actually be present, i.e. AFTER the `document.querySelector("footer").innerHTML = data;` is executed ... since it's in a Promise chain, you'll need to either do it in that code block, or chain another `.then` and do it in there – Jaromanda X Aug 31 '20 at 01:04
  • I need to go read about the promise chain. That's new to me. Thank you for the education. You have helped immensely as I have been trying to figure this out for hours. – David Kopacz Aug 31 '20 at 01:41

1 Answers1

1

Here's something I whipped up a few years ago that will execute scripts in dynamically loaded HTML

The trick is to duplicate the script tags in code and replace them in-place - et voila - the scripts run

const loadHtml = ((text, dest, replace = false) => {
    if (typeof dest == 'string') {
        dest = document.querySelector(dest);
    }
    const p = new DOMParser();
    const doc = p.parseFromString(text, 'text/html');
    const frag = document.createDocumentFragment();
    while (doc.body.firstChild) {
        frag.appendChild(doc.body.firstChild);
    }
    // handle script tags
    const ret = Promise.all(Array.from(frag.querySelectorAll('script'), script => new Promise(resolve => {
        const scriptParent = script.parentNode || frag;
        const newScript = document.createElement('script');
        if (script.src) {
            newScript.addEventListener('load', e => resolve({ src: script.src, loaded: true }));
            newScript.addEventListener('error', e => resolve({ src: script.src, loaded: false}));
            newScript.src = script.src;
        } else {
            newScript.textContent = script.textContent;
            resolve({ src: false, loaded: true });
        }
        scriptParent.replaceChild(newScript, script);
    })));
    if (replace) {
        dest.innerHTML = '';
    }
    dest.appendChild(frag);
    return ret;
});

Use it in your code like

fetch("footer.html")
.then(response => response.text())
.then(data => loadHtml(data, "footer"))
// this last line isn't really needed, just logs failed scripts
.then(r => r.filter(({loaded}) => !loaded).forEach(({src}) => console.log(`failed to load ${src}`));

However ... your use of document.write will result in unexpected side effects as mentioned in a comment - do not use document.write use DOM manipulation

e.g.

<div class="copyright float-left">
    &copy;
    <span class="copyright-year">
      <script>
        var span = document.createElement('span');
        span.innerHTML = new Date().getFullYear();
        document.querySelector(".copyright-year").appendChild(span);
      </script>
    </span>
  </div>
Jaromanda X
  • 47,382
  • 4
  • 58
  • 76
  • Thank you. I read the code, understand most of it, installed it and visual studio code tells me there is an error at the first parenthesis following loadHtml where a comma is expected. Not really sure why. Any idea? – David Kopacz Aug 31 '20 at 01:20
  • I may have mis-typed it ... hang on – Jaromanda X Aug 31 '20 at 01:22
  • @DavidKopacz - I missed an `=` in the first line :p (this is part of a library of mine, so the code is not **exactly** like the above - sorry for the typo – Jaromanda X Aug 31 '20 at 01:23
  • Ok, I fixed that issue by placing an equal sign after loadHtml and it works, except that it now displays the year 2020 twice where the first span is located and not at all for the second span. I can probably figure that out. Thank you very much sir. This looks like it's going to solve my issue quite nicely. As soon as I sort out the double 2020 issue I'll mark this as the solution. You can see it in use at www dot my last name dot com – David Kopacz Aug 31 '20 at 01:29
  • No need to solve the issue because I am removing the second copyright notice anyway since I've decided to give the astronomy images on my website away for public use. So, this works perfectly. Thank you so much sir. – David Kopacz Aug 31 '20 at 01:39
  • ahh, because they both have the same class try `document.querySelector("footer .copyright-year")` for the footer, for example – Jaromanda X Aug 31 '20 at 01:39