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!