Say you have that job to create a web app, and what you want to do is generate some HTML elements with JS, based on some JSON response. It's a small app, so you decide against using big-ass frameworks and libraries (and JSX). Now that you actually have to implement the element generation from some kind of template, you find out that there are several ways to actually do that.
You start to wonder: which way is the best one to actually add elements or even element trees to a DOM? What's the most used one and why? How good is the performance of each and what about events, like click, focus, hover, etc., especially on re-render? Do the different ways have specific use-cases? What are advantages and disadvantages?
I consciously do not want to add a poll here, because that would make people click whatever they feel comfortable with instead of giving it a moment to think, re-evaluate the methods and find arguments.
Some methods, which are available using VanillaJS are
document.createElement(); //..., add attributes etc. and append to targetNodetargetNode.innerHTML += '... ${something} ...';, (imagine a template literal here)targetNode.appendChild(hiddenDiv.cloneNode(true));targetNode.appendChild(document.importNode(template.content), true);I will write my view on the above ways into an answer below. What are your thoughts on the topic?
In Vanilla JavaScript Components I have a section called "What about a template/view/UI?"
You just use right tool for the right job.
Do you need to create span with textContent and append it? Use createElement.NEVER use innerHTML, use only textContent.
Do you need to insert many elements at once? Use documentFragment;
Do you need to insert same small HTML widget/component like post box or product card many times? Use <template> . Moreover, you can insert your data from JSON into templates with basic string replace or a bit of a custom parser. Here I have a proposal for native variables for <template> and a polyfill.
I use <template> all the time for all components bigger than just one, two tags. For example, I use it with my DataTable. I don't need to write any custom JS at all anymore. Just almost empty <table> and I define row in <template>. I NEVER had any performance issues with Vanilla JS and <template> what I can not say about jQuery DataTables, those fancy frameworks with JSX/TS/whatever overcomplicated garbage.
Finally, just create a UI object or add UI methods/logic into same object if you have a very basic component and if in the future you would like to change your CommentUI.create() implementation, for example to replace createElement with <template> or just use handlebars, whatever, you might do so easily without changing anything else in your architecture. Let the UI layer handle it for you. Main component's object should NOT be coupled to either UI/view layer nor Model/API/JSON layer.
You can read my article on Medium to see also code samples of a basic Like button component using nothing but a few lines of Vanilla JS.
Last note about performance. You could ask this question 5+ years ago, today all browsers will handle optimization even when you do not use documentFragment on their own. (However, last time I played with huge lists in Firefox, I have found that there is a very big difference between appending element at the end of a huge list and before the last element, I am not sure if there is still same bug/issue). Anyway for a modern browsers and devices even for heavy DOM operations, there is almost no difference in a real world scenario.
I echo the sentiment of a few others about not reinventing the wheel and simply using a library; you'll probably end writing your own in achieving your objective. There are several minimalistic libraries that would moderately address your concern about bloat. If your question is a purely academic question, then that's another thing.
Given the constraint of "small app", Option 1 would be the option that I would go with. It requires the least amount of extra design to achieve. Going one step further, similar to the helper functions that Tommy Hodgins described, you could generalize that approach even more into a simple node builder framework (similar to XmlBuilder in other languages or some of the compiled output of React). You essentially need builder functions for elements and text nodes (for extra credit you could add builders for the other node types like comments, CDATA sections, etc.)
An example in TypeScript. (I just threw this together, so don't critique it too much. But it does work):
type BuilderFunction = (parent?: Node) => void;
function elementBuilder(
tagName: string,
children: BuilderFunction[] = [],
attributes: any = {},
events: { [key: string]: Function } = {}
): BuilderFunction {
return (parent: Node) => {
const element = document.createElement(tagName);
Object.keys(attributes).forEach((name) => {
element.setAttribute(name, attributes[name]);
});
Object.keys(events).forEach((name) => {
element.addEventListener(name, (evt) => events[name](evt));
});
children.forEach((child) => {
const childNode = child(element);
});
if (parent !== undefined) {
parent.appendChild(element);
}
};
}
function textBuilder(text: string): BuilderFunction {
return (parent: Node) => {
parent.appendChild(document.createTextNode(text));
};
}
const builder = elementBuilder('div', [
elementBuilder('form', [
elementBuilder('input', [], { type: 'text' }),
elementBuilder(
'button',
[textBuilder('Submit Button')],
{ type: 'submit' },
{ click: (evt) => { alert('Clicked') } }
)
])
]);
builder(document.body);
Give these functions some aliases, and you've got yourself a very simple and terse domain-specific language.
Options 2, 3, and 4 each have the advantage of being defined with markup but also have the disadvantage of being unable to wire up event handlers or any other type of dynamic linkage. At this point you're verging into territory where frameworks really shine. If you still have the constraint of not using a framework, you could take a few approaches:
v-on:click="handler" or data-bind="click: handler") in markup that your engine will scan for after it has created a DOM subtree. This option too will probably require a custom grammar but a more simplistic one.React's JSX and Angular's AOT effectively take care of these steps at compile-time and generate builder-type code to be executed at runtime.
Bonus option, one that I have not explored much yet but is supposed to have a lot of potential: HyperHTML. It uses tagged template literals to achieve the change detection features of a virtual DOM without the overhead.
ImportNode is too new and poorly documented, I can't use it yet since I still have clients using IE8/earlier... and even if I could I'm not sure I even understand what it's for.
cloneNode often loses/drops properties on elements, and you have to HAVE a node to clone in the first place, meaning you're using createElement anyways -- that our you're putting scripting only markup in the static markup where it doesn't belong.
innerHTML is right out since if you're going to use the DOM, USE THE BLOODY HUFFING DOM! InnerHTML means the live DOM has to be reverse engineered back into markup, edited, then reparsed back into a news DOM with a complete re-render. It introduces excess overhead for no reason and WORST of all? It's insecure since you can put code into it. Unless you create your own JavaScript equivalent of PHP's htmlspecialchars you're just begging to create XSS expliots by using it! For the most part I view innerHTML as being in the same class as document.write, you REALLY shouldn't use it in production code unless there's no other choice. It just feels to me like the same idiocy as using string addition to put variables into a query string when using PHP. We have prepare/execute now, use it.
... so that leaves document.createElement and document.createTextNode, and that's what I use. Admittedly I build helper functions to simplify it in a manner where I can actually create a JSON structure that is turned into markup -- and since it bypasses innerHTML it renders faster.
My own little library (under 8k once minified and gzipped) does exactly this using a format I'm calling DOM-JON... which I'm still documenting and separating from the reference pages for the make and write methods.
elementalsjs.com/reference/DOM_JON
It uses a selector style creation where you have:
tagName#id.class.class~textContents
The outer array can contain a selector of the above format, or an array where the first element is a selector and the second element is attribute style instructions of both attributes and commands for appending to the DOM. Which using elementals _.make ends up like this:
// element using content command attribute
var divTest = _.make('div#test', {
content : [
// element + text
[ 'h2~This is a DOM created subsection' ],
// element + innerHTML
[ 'p', { innerHTML : 'A child paragraph <strong>that can use markup!</strong>' } ],
// element + multiple children
[ 'p', [
// text only
'A child paragraph',
// tag + text, markup is escaped to entities
[ 'strong', 'that directly assigns the <strong> tag.'],
// text only passed via selector
[ '~ Note that markup is escaped when you pass a normal string!' ]
] ],
// element using content "command attribute"
[ 'div.details' , { content : [
// tag + text
[ 'span', '13 November 2017' ],
// just text
' Jason M. Knight'
] } ]
],
// instruction on where to add the new element
last : document.body
});
If I were implementing this as vanilla.js, I'd probably end up creating a similar set of handlers for quickly creating markup and content in a similar manner.
Notice that it DOES allow for innerHTML, using innerHTML on a element you created with document.createElement BEFORE you append it to the live document DOM removes most of the overhead woes of parsing. It's still there but greatly reduced since it only applies to that little piece. Still has the lack of sanitation woe though which is why I would use that technique with a eye-dropper, not the paint-roller most people slather it on with. I actually just added a couple days ago (will be in tonight's update) an object .madeCollection that will contain all elements created with ID's by .make and _.write with the ID as the property name. (I may end up trying to make it a legitimate iterable collection)
Bottom line if you're going to use the DOM, USE IT! ...and that means createElement, createTextNode, appendChild and insertBefore!
Well WebComponent is one word solution which combines/uses all of the options that you have stated.
One simple example is here: time2hack.com/2018/01/todo-app-with-webcomponents… which I wrote as post.
Of course the things are tedious in VanillaJS but you have freedom to take things in any direction and the level of complexity.
Personally I choose frameworks/libs on the basis of size of app and time available. And if I have enough time, I will definitely go for WebComponents.
Few things I would suggest
I think I'd do something like #2. I understand the virtues of using createElement() for a few, small elements that don't contain many elements, but by the time you get down to the level of a passage of text with links, <strong> tags and other markup - creating each element that way gets a little overwhelming.
I've been having a ton of fun using JS template literals for composing CSS stylesheets, and it makes it really nice to extend with JS functions that return strings.
Recently I was playing around with some JS helper functions that might help me compose HTML, but I don't really know when or where I'd use these - so for now they're just some explorations. I was trying to see if I could create some simple functions for writing HTML and putting some of my understanding about how sites were built into JS functions. I'm sure people have been doing stuff like this with PHP on the backend forever too :P
First we have our generic HTML tag assembler. This accepts a tagName, some content which can be a string or a function that returns a string ;), as well as optionally some attributes if we want to apply those to the element as well.
All HTML tags have closing tags except for 'void' tags, so rather than trying to whitelist all valid HTML tags we don't even need to know or care what those are. All we really need to know is the much shorter list of 'void' tags that don't have closing tags and if we notice our tagName is one of those we don't close it.
// HTML Tag Generator
function html(tagName, contentString, attributeObject) {
tagName = tagName || 'div'
contentString = contentString || ''
let voidTags = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr']
let attr = attributeObject
? Object.entries(attributeObject).map(item => `${item[0]}='${item[1]}'`)
: []
.join(' ')
return voidTags.includes(tagName.toLowerCase())
? `<${tagName} ${attr}>`
: `<${tagName} ${attr}>${contentString}</${tagName}>`
}
Now we can create elements with code like this:
html('div', 'Hello World')
Now, if we combine our html() function with other functions it becomes easy to template certain types of things: wrappers, wrapped lists, sets of siblings, complex sets of HTML tags like video embeds, link helpers, etc.
Here's a little function that takes an array of content and creates 1 tag for each content item:
// Siblings helper
function siblings(tagName, contentArray) {
return contentArray
.map(tag => Array.isArray(tag)
? html(tagName, tag[0], tag[1])
: html(tagName, tag)
).join('')
}
Now if we wanted to create a set of <li> elements with the texts "one", "two", and "three" and wrap that in a <ul>, we can write it like this:
html ('ul', siblings('li', ['one', 'two', 'three']))
Here's a link helper that will accept as much content as you have and return an <a> element, it wants url at a minimum, it would be nice if you also gave it link text, and if you also supply a title it will be very happy to use that as well:
// Link helper
function link(url, text, title) {
url = url || '#'
text = text || url
title = title || text
return `<a href="${url}" title="${title}">${text}</a>`
}
Now I'm not sure why I returned a template literal here rather than something like return html('a', …), but it works and it's simple. If we give it examples like this:
link()
link('staticresource.com/&)
link('staticresource.com/& 'StaticResource')
link('staticresource.com/& 'StaticResource', 'Visit StaticResource')
We will get this as the output HTML:
<a href="#" title="#">#</a>
<a href="staticresource.com/" title="staticresource.com/">https://staticresour…
<a href="staticresource.com/" title="StaticResource">StaticResource</a>
<a href="staticresource.com/" title="Visit StaticResource">StaticResource</a>
Lastly, here's a fun one - a responsive video embed container that outputs a wrapped <iframe> and the styles the wrapper and iframe need in order to resize and maintain its aspect ratio:
// Responsive Embed Helper
function embed(url, width, height) {
url = url || '#'
width = width || 560
height = height || 315
return html('div',
html('iframe','',
{
src: url,
width: width,
height: height,
frameborder: 0,
style: `
position: absolute;
width: 100%;
height: 100%;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
`
}
),
{
style: `
position: relative;
width: 100%;
padding-bottom: calc(100% / (${width} / ${height}));
`
}
)
}
Here I did make use of html() to return the HTML as a string and you can see how it let me store my CSS as template strings as well. Let's give it a little test with this JS:
embed('youtube.com/embed/LsoLEjrDogU&)
And this is the HTML we get back:
<div style=" position: relative; width: 100%; padding-bottom: calc(100% / (560/315));">
<iframe src="youtube.com/embed/LsoLEjrDogU" width="560" height="315" frameborder="0" style="position: absolute; width: 100%; height: 100%; top: 50%; left: 50%; transform: translateX(-50%) translateY(-50%);"></iframe>
</div>
Anyway, I am not sure if that helps, or what this might be good for but I had a lot of fun passing functions as arguments and trying to figure out if I could encapsulate some of my HTML-building understanding into code logic that could be run.
Some projects you might want to have a peek at:
Seems similar in some ways - creating HTML from JS functions, but the way the arguments get passed in is a lot more verbose than the functions I came up with, it seems like it would be a hassle to write that much JS instead of just HTML.
This is the HTML-creating part of the Elm language. It seems to 'do too much work' as well, like they try to whitelist all valid HTML tags: github.com/elm-lang/html/blob/master/src/Html.elm
This project seems interesting, and they're all about using template literals in JS for extending HTML templating. I think these people will discover and develop some really useful techniques in this area!
| Advantages | Disadvantages |
| No string parsing, only JS functions | Tedious, because hella lot of LoC -> bad overview |
| Just hang a new element into the DOM | |
| Add events on the element before even hanging it into the DOM |
| Advantages | Disadvantages |
| Very simple | String parsing, DOM has to rebuilt partly |
| Might have problems in an editor with highlighting and auto-complete | |
| Might lead to bad overview, if the snippets are big | |
| Re-add all event-listeners inside targetNode -> lots of trouble! |
| Advantages | Disadvantages |
| Just hang a new element into the DOM | template is part of your layout code |
| Add event listeners after cloning; needs a query to find elements |
| Advantages | Disadvantages |
| Just hang a new element into the DOM | template is part of your layout code, but clearly marked with template tag |
| Add event listeners after cloning; needs a query to find elements |
Steven Ventimiglia
Creative Technologist & Sr. Front-End Developer
Juha Lindstedt
Creator of RE:DOM and Liike, web architect
Check out RE:DOM: redom.js.org 😉