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>
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>
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")
);
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>
If you execute this file now, you'll see the following:
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);
}
}
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>
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();
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();
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)
nice one Andrey!
Thank you! Here is a project, where this approach used on practice:
github.com/AndreyGermanov/smartsha...