HTML Partials Rendering
After HTML rendering, the next used approach is HTML partials rendering. By this design, the goal is to achieve that the server processes only part of the page that is affected by the user's interaction. So, instead of responding to every request with full-page content, the server responds only with relevant HTML Partial.
This functionality is achieved using Javascript.
To cover the idea behind this approach we proceed with the existing web application example. When we use our example application, we can recognize that our footer links list never changes. It stays fixed during the complete application usage. So, with this approach, we will make sure to have Javascript that would perform HTTP requests for us (instead of using native HTML behavior with links and forms) and place response in the "Main Area" of the HTML page.
Additionally, our server will be capable of returning just parts of HTML related to the requested operation (HTML partials).
So, staring with index.js by defining a request to /partial-home that will return only the home page section (without header and footer):
src/index.js
...
app.get('/partial-home', (req, res) => {
res.render('partials/_home');
});
...
Following it with view changes by creating views/partials/_home.ejs and reusing it in views/home.ejs (one dedicated for a full page response).
views/partials/_home.ejs
<h1>Example Project Body</h1>
views/home.ejs
<%- include('partials/header', { title: 'Example Project' }); %>
<div id="main-area">
<%- include('partials/_home'); %>
</div>
<%- include('partials/footer'); %>
Note that we added div with id main-area around the _home partial. This id is relevant for javascript implementation to define the place where server partial HTML responses need to be put.
After these changes, if we access the http://localhost:3000/partial-home endpoint, we get the response that only contains "Example Project Body" content.
No styles, and no navigation, since only partial is rendered.
The goal is to have javascript that will fill in the main-area div with partial results whenever the navigation item is clicked.
To achieve this, first, we prepare all partial endpoints and then we change views/partials/footer.ejs file content to target the "partial" endpoints. That implementation looks as follows:
src/index.js
...
app.get('/partial-home', (req, res) => {
res.render('partials/_home');
});
app.get('/partial-greet', (req, res) => {
const name = req.query.name ? req.query.name : null;
const message = name ? `Hello ${name}!` : `Hello!`;
res.render('partials/_greet', {
message
});
});
app.get('/partial-add-name', (req, res) => {
res.render('partials/_add-name');
});
...
views/partials/_greet.ejs
<h1><%= message %></h1>
views/partials/_add-name.ejs
<h1>Add your name:</h1>
<form method="get" action="/partial-greet">
<label for="name">Name:</label>
<input id="name" type="text" name="name">
<button type="submit">Submit</button>
</form>
views/greet.ejs
<%- include('partials/header', { title: 'Greeting Example' }); %>
<div id="main-area">
<%- include('partials/_greet'); %>
</div>
<%- include('partials/footer'); %>
views/add-name.ejs
<%- include('partials/header', { title: 'Example Project' }); %>
<div id="main-area">
<%- include('partials/_home'); %>
</div>
<%- include('partials/footer'); %>
views/partials/footer.ejs
<%- include('navbar', { navItems: [
{
path: '/partial-home',
title: 'Home'
},
{
path: '/partial-greet',
title: 'Default Greet'
},
{
path: '/partial-add-name',
title: 'Add Name'
}
] }); %>
</body>
</html>
With these changes, it is achieved that every endpoint that returned a full HTML page still returns the full pages, so when the user arrives at any of the original URLs, it gets the whole page loaded. Links in navigation, though, now target only partial HTML rendering endpoints.
Now, we want to achieve that clicking on every one of these links (and submitting every HTML form) triggers request for appropriate partial HTML endpoint. What we do not want is for the browser to refresh the page with new HTML result, as it does by default behavior.
So, the following implementation will override the default browser behavior on user's interaction. Goal is to make a request, take the HTML response, and place it in the main-area div.
The following Javascript solution achieves that:
public/example-application.js
// Execute this function when the page is loaded in the browser.
window.onload = function () {
const navigationLinks = document.getElementsByTagName("a");
const navigationLinksCount = navigationLinks.length;
for (let i = 0; i < navigationLinksCount; i++) {
const navLink = navigationLinks[i];
// Behavior that needs to be performed every time navigation link is clicked.
navLink.onclick = function (event) {
const url = event.target.attributes['href'].nodeValue;
makeRequestAndUpdate(url);
event.preventDefault();
}
}
overrideFormSubmit();
}
// Functionality that overrides default browser "Form Submit" behavior.
function overrideFormSubmit() {
const forms = document.getElementsByTagName("form");
const formsCount = forms.length;
for (let i = 0; i < formsCount; i++) {
const formElement = forms[i];
formElement.onsubmit = function (event) {
const url = event.target.attributes['action'].nodeValue;
const fd = new FormData(event.target);
const queryParams = new URLSearchParams(fd).toString();
makeRequestAndUpdate(`${url}?${queryParams}`);
event.preventDefault();
};
}
}
// Executing actual HTTP request to the server and placing the response into 'main-area' div.
function makeRequestAndUpdate(url) {
const xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onreadystatechange = function () {
// readyState being 4 means that request processing is "Done".
// status being 200 means that http request processing was "successful" by the server.
if (this.readyState == 4 && this.status == 200) {
const partialHtmlResponse = this.responseText;
document.getElementById('main-area').innerHTML = partialHtmlResponse;
overrideFormSubmit();
}
}
xhr.send();
}
and including it in the footer as follows:
views/partials/footer.ejs
...
<script src="/example-application.js"></script>
</body>
</html>
With these steps, our application renders only partials on every link click and form submit.
Note: Javascript presented in this example is made for this example application, and needs to be significantly adapted for general usage.
In the following screenshot, one can see all http requests sent from our application utilizing 'Network tab'. One can see individual requests made for partials.