DEV Community

Cover image for Modular HTML
Andrey Germanov
Andrey Germanov

Posted on • Updated on

Modular HTML

Table of contents

Introduction
Inject HTML dynamically
Create modular HTML
Conclusion

Introduction

In this article, I wanted to show a simple way of how to include one HTML file in another HTML file using Javascript. It can be helpful in a case if your project is not enough big to make it using a framework, but at the same time is not so small to keep all HTML in a single file. This is how to do this using a pure Javascript.

Inject HTML dynamically

In the next example, there is a web page, that consists of a header, side menu, main content, and footer and is located in index.html.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">       
    <title>Site</title>
</head>
<body>
    <div class="header"></div>
    <div class="container">
        <div class="side-menu"></div>
        <div class="main"></div>
    </div>
    <div class="footer"></div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

I want to have the content of these divs to be in separate files, and do not want to use any framework or backend to achieve this (at least in the development stage).

Let's start with header, which exists inside a header.html file:

<h1>My cool site</h1>
Enter fullscreen mode Exit fullscreen mode

Now let's create a function, that will load the content of header.html and insert it to a div with class header of the index.html file:

async function injectHTML(filePath,elem) {
    try {
        const response = await fetch(filePath);
        if (!response.ok) {
            return;
        }
        const text = await response.text();
        elem.innerHTML = text;
    } catch (err) {
        console.error(err.message);
    }
}

injectHTML("./header.html",
    document.querySelector(".header")
);
Enter fullscreen mode Exit fullscreen mode

This function takes a path to a file to inject as a filePath argument and an HTML node of a container element as an elem argument.

Then this function fetches the content of the specified file and parses the response HTML as a text.

Finally, the function injects this text as an HTML content of the provided element.

At the end of this file, this function is executed to inject the content of the header.html file to a div element with a class header.

Now, you can save this Javascript as an index.js and include in an index.html.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Site</title>
</head>
<body>
    <div class="header"></div>
    <div class="container">
        <div class="side-menu"></div>
        <div class="main"></div>
    </div>
    <div class="footer"></div>
    <script src="./index.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

If you execute this file now, you'll see the following:

Header

To make it work correctly, you have to run this on some server, for example on a live server of VS Code. If you just open index.html in a browser, it will not work, because fetch should request a file on a server.

However, injectHTML function is not completed. If the injected file contains a script, it will not work. In a moment, when you set innerHTML property, scripts are not executed. The only way to execute the included scripts is to reinject them later after insert. You can use this trick to do that:

async function injectHTML(filePath,elem) {
    try {
        const response = await fetch(filePath);
        if (!response.ok) {
            return;
        }
        const text = await response.text();
        elem.innerHTML = text;
        // reinject all <script> tags
        // for each <script> tag on injected html
        elem.querySelectorAll("script").forEach(script => {
            // create a new empty <script> tag
            const newScript = document.createElement("script");
            // copy an attributes of existing script tag 
            // to the new one
            Array.from(script.attributes).forEach(attr =>
                newScript.setAttribute(attr.name, attr.value)
            );
            // inject content of existing script tag 
            // to the new one
            newScript.appendChild(
                document.createTextNode(script.innerHTML)
            )
            // replace existing script tag to the new one
            script.parentNode.replaceChild(newScript, script);
        })
    } catch (err) {
        console.error(err.message);
    }
}
Enter fullscreen mode Exit fullscreen mode

The inserted code goes through all script tags in injected HTML and creates a copy of each of them: first, it copies all attributes of the script tag and then, the content of the script tag. Then, it replaces the script tag with its copy. At this moment, the content of that copy will be executed by a web browser.

So, this function can be used to inject HTML snippets of any complexity.

Create modular HTML

Of course, this way you can create footer.html, sidemenu.html, and others and then write Javascript that will use injectHTML function to inject each of them one by one. However, in this section, I will go one step further in automating this. What if create a special attribute in the <div> elements, named include that will specify, which files should be inserted in these divs, like this:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Site</title>
</head>
<body>
    <div class="header" include="./header.html"></div>
    <div class="container">
        <div class="side-menu" include="./side-menu.html"></div>
        <div class="main"></div>
    </div>
    <div class="footer" include="./footer.html"></div>
    <script src="./index.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

And then, make a function, which will automatically inject files, specified as values of include attributes to appropriate divs?

This can be simple like this:

function injectAll() {
    document.querySelectorAll("div[include]")
            .forEach((elem) => {
                injectHTML(elem.getAttribute("include"),elem);
    })
}

injectAll();
Enter fullscreen mode Exit fullscreen mode

The code of this function selects all div elements that have include attribute, and applies injectHTML function for each of these elements, using a value of include attribute as a file name to inject. Finally, the content of these containers should be replaced with the content of included files.

So, this way you can modularize your big HTML files without using any frameworks. Here is a full source code:

/**
 * Function injects specified HTML file to specified HTML 
 * node of the current file
 * 
 * @param filePath - a path to a source HTML file to inject
 * @param elem - an HTML element to which this content will 
 * be injected
 */
async function injectHTML(filePath,elem) {
    try {
        const response = await fetch(filePath);
        if (!response.ok) {
            return;
        }
        const text = await response.text();
        elem.innerHTML = text;
        // reinject all <script> tags
        // for each <script> tag on injected html
        elem.querySelectorAll("script").forEach(script => {
            // create a new empty <script> tag
            const newScript = document.createElement("script");
            // copy attributes of existing script tag 
            // to a new one
            Array.from(script.attributes).forEach(attr =>
                newScript.setAttribute(attr.name, attr.value)
            );
            // inject a content of existing script tag 
            // to a new one
            newScript.appendChild(
                document.createTextNode(script.innerHTML)
            )
            // replace existing script tag to a new one
            script.parentNode.replaceChild(newScript, script);
        })
    } catch (err) {
        console.error(err.message);
    }
}

/**
 * Function used to process all HTML tags of the following
 * format: <div include="<filename>"></div>
 * 
 * This function injects a content of <filename> to
 * each div with the "include" attribute
 */
function injectAll() {
    document.querySelectorAll("div[include]")
            .forEach((elem) => {
                injectHTML(elem.getAttribute("include"),elem);
    })
}

injectAll();
Enter fullscreen mode Exit fullscreen mode

Conclusion

You can save this Javascript as a file and include it in any project to modularize HTML this way. On the one hand, you can use injectHTML function to insert an external HTML file to any place if the user, for example, presses the button. On the other hand, you can use injectAll function to automatically inject many HTML files using include attribute of div containers to which these files should be injected.

Please write if you have something to add or found bugs or what to improve.

Feel free to connect and follow me on social networks where I publish announcements about my new articles, similar to this one and other software development news:

LinkedIn: https://www.linkedin.com/in/andrey-germanov-dev/
Facebook: https://web.facebook.com/AndreyGermanovDev
Twitter: https://twitter.com/GermanovDev

My online services website: https://germanov.dev

Happy coding guys!

Top comments (2)

Collapse
 
codarbind profile image
ADEKOLA Abdwahab

nice one Andrey!

Collapse
 
andreygermanov profile image
Andrey Germanov • Edited

Thank you! Here is a project, where this approach used on practice:

github.com/AndreyGermanov/smartsha...