skip to Main Content

I have about 100 lines of boilerplate HTML that every file on my site begins with. It contains the following elements:

<!-- START OF BOILERPLATE TEXT -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta ...>
  <link href="..." rel="stylesheet">
  <script src="..."></script>
  <title>MY_TITLE</title>
</head>
<body>
  <div> Page banner ... </div>
  <hr>
<!-- END OF BOILERPLATE TEXT -->

I’d like to minimize the actual amount of boilerplate code needed for each page, and I’ve come up with the following solution:

<!-- Line 1 of actual code will load external script:

     <script src="/test/scripts/boot.js"></script> 

     For this demo I'm inlining it.   -->

<script>
  async function boot(title) {

    // Will insert header boilerplate in document.documentElement.
    // Actual code will fetch boilerplate code from server.

    const my_demo = `
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- ****************** page-specific <head> stuff ***************** -->
    <title>${title}</title>
  <!-- ****************** end page-specific stuff    ***************** -->
</head>
<body>
    <h1>Banner injected by boot.js</h1>
    <hr> <!-- end of page banner -->
    <!-- ****************** begin page content ***************** -->
`;

    try {
      document.documentElement.innerHTML = my_demo;
    } catch (my_error) {
      document.write('Error: ', my_error);
    }
  }
</script>

<!-- Line 2 of actual code will invoke boot() -->

<script>boot("Test Title")</script>

<!-- Now we begin adding our body html -->

<p>
  This is the actual content of the page. It should appear under the injected H1 and HR.
</p>

Here is the way I want it to look:
Here is the way I want it to look:

The boot() script appears to correctly insert my <head> and banner html, but my "actual content" doesn’t appear underneath. According to the Safari debugger, there was a </body> tag inserted after the last line inserted by boot(). How can I prevent that?

2

Answers


  1. Brother, document.documentElement.innerHTML rewrites the entire html element including the <head> and <body>.
    If you want to keep the existing content you can use insertAdjacentHTML!
    here is the correct code:

    async function boot(title) {
      const headContent = `
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>${title}</title>
      `;
    
      const bodyContent = `
        <h1>Injected by boot.js</h1>
        <hr> <!-- end of page header -->
      `;
    
      // Insert into head
      document.head.insertAdjacentHTML('beforeend', headContent);
      
      // Insert into body without replacing existing content
      document.body.insertAdjacentHTML('afterbegin', bodyContent);
    }
    
    boot("Test Title");
    Login or Signup to reply.
  2. When you insert text into the DOM (your .innerHTML = my_demo), the browser will close off any elements in order to keep the DOM as correct as it can. Which is why you are seeing a </body> that you did not put there.

    Here is an example of doing what I think you are asking, by surrounding your real HTML body in an element (<main> in my example) with an id unique enough not to be found in your HTML otherwise. "MainHtmlToBeAppended" in my example.

    Then you can retrieve your HTML and include with your my_demo string when setting .innerHTML.

    I use "defer" in the script tag to ensure the DOM is loaded before trying to access it.

    Note that I moved the boot("Test Title") invocation into the .js file, but you could leave it in your html file if you prefer. It just keeps the "extra" content in your .html file to a minimal single <script> line.

    boot("Test Title")
    
    
    function boot(title) {
    
        // Will insert header boilerplate in document.documentElement.
        // Actual code will fetch boilerplate code from server.
    
        const my_demo = `
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <!-- ****************** page-specific <head> stuff ***************** -->
        <title>${title}</title>
      <!-- ****************** end page-specific stuff    ***************** -->
    </head>
    <body>
        <h1>Injected by boot.js</h1>
        <hr> <!-- end of page header -->
        <!-- ****************** begin page content ***************** -->
    `;
    
        // EDIT Get the "real" content
        let mainHtml = document.querySelector( "#MainHtmlToBeAppended" ).innerHTML
    
        try {
          // EDIT Append the "real" content with the my_demo string
          // document.documentElement.innerHTML = my_demo;
          document.documentElement.innerHTML = my_demo + mainHtml;
        } catch (my_error) {
          // EDIT Better to set .innerHTML than .write()
          // document.write('Error: ', my_error);
          document.documentElement.innerHTML = `MY ERROR: ${my_error}`;
        }
    }
    <!-- Note: test.js would contain the Javascript shown in the snippet -->
    <script defer src="test.js"> </script>
    
    <!-- Now we begin adding our body html -->
    
    <main id="MainHtmlToBeAppended">
      <p>
        This is the actual content of the page. It should appear under the injected H1 and HR.
      </p>
    </main>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search