Modular HTML
Tags: javascript, webdev, html, design
Published: 2022-03-01 08:22:34

# Table of contents

[Introduction](#introduction)
[Inject HTML dynamically](#inject-html-dynamically)
[Create modular HTML](#create-modular-html)
[Conclusion](#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`.

```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>
```

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:

```html
<h1>My cool site</h1>
```

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:

```javascript
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")
);
```

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`.

```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>
```
If you execute this file now, you'll see the following:

![Header](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2q6qgz21kuzrcmld6cxq.png)

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:

```javascript
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);
    }
}
```

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:

```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" 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>
```

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:

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

injectAll();
```

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:

```javascript
/**
 * 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();
```
## 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!