Ways of Adding DOM with JavaScript
I am not really sure how many people would like such article on different ways of adding or inserting HTML without the use of framework as many people are adopting React, Angular, Vue, or Polymer as their frontend architecture. Each of these frameworks/libraries has their own implementation for manipulating DOM and creating list in their most optimized way. However, recently I have encountered the need of doing such thing without any framework.
The task was to show product reviews after pressing a button to fetch new reviews. Since JavaScript framework was not preferred for this task as there were SEO and web crawler concerns, so I went for just vanilla JavaScript. The typical workflow for such situation where we need to show more data or add more data to the list without refreshing the page is to have a HTML layout ready in underscore and append them using JavaSscript. There are many ways of doing this and here I will list them out and hope you will find them useful the next time you need to do similar things.
Imagine we have a template like:
<script type="text/template" id="template">
<% \_.each(productReviews, function(review){ %>
<div>
<%= review.content %>
</div>
<% }) %>
</script>
By having type="text/template"
, browser will skip this whole section when parsing the DOMs. It makes sense as we do not want the parser to actually parse this HTML and present them on the view. We will later populate this view with some data and add them into this DOM:
Using jQuery
Most people would probably agree that jQuery is the easiest to use here.
async function getData() {
let url = "your api url";
let data = await (await fetch(url)).json();
let productReviewTemplate = $('#template').html();
let template= \_.template(productReviewTemplate);
$("#container").append(template({productReviews : data}));
}
This basically retrieves the data, get the template instance, put in the data, then append that HTML string using jQuery. I’m using async await to make the code look a bit prettier. If you don’t know what this is, async await is part of ES7 standard that helps you deal with asynchronous process. Instead of resolving the promise by using then
, it will wait for it to finish and return the data that has been resolved with.
I personally would like to move away from jQuery as I didn’t want to import a huge library just to do simple stuffs and what’s wrong with vanilla JavaScript anyway? So I seek another way.
Vanilla JavaScript
async function getData() {
let url = "your api url";
let data = await (await fetch(url)).json();
let elem = document.createElement("div");
let content = document.createTextNode(data.content);
elem.appendChild(content);
let container = document.querySelector("#container");
if (container) {
container.appendChild(elem);
}
}
If your HTML is not too complex, you could certainly create the element in JavaScript and append them using appendChild
. This method existed long time ago so it has the best support across all the major browsers. However, using createElement
for a complex UI will not be feasible as the code would be super ugly and you probably will pull all of your hair out before you finish writing it. The use of underscore to create a template is easy but it returns a string. If you try to append that it will actually append that string instead of parsed DOM. This is when I use DOMParser.
DOMParser parses the string into a DOM object. This is particularly useful in this case because I have a gigantic HTML that is in string and by using this I can instantly get a DOM object that I can play with in JavaScript. In this case I just want to append them. The end result looks something like below:
async function getData() {
let url = "your api url";
let reviews = await (await fetch(url)).json();
let reviewTemplateHtml = document.querySelector("#product-review-tpl");
if (reviewTemplateHtml) {
let template = \_.template(reviewTemplateHtml.innerHTML);
let productReviews = document.querySelector(".product-reviews .list");
if (productReviews) {
let elems = new DOMParser().parseFromString(template({reviews}), "text/html");
if (elems) {
for (let elem of elems.body.childNodes) {
productReviews.appendChild(elem);
}
}
}
}
}
Eventually I am still appending them using JavaScript but I don’t need to create a relatively complicated HTML inside JavaScript. If it’s jsx then that’s fine but not in pure JavaScript. At some point I did try to just use innerHTML
to replace the existing DOMs. It worked out great except it actually destroyed the event listeners I had on the existing DOM since it replaced the whole thing with the new one. With appendChild
, it just adds new DOM without destroying the current DOM tree so that’s good for me.
I hope this helps you in some way and if you like this please “clap” it and share. Till next time!