The challenge I'm facing is to build a single-page app with plain old Javascript, no libraries or frameworks allowed. While creating dynamic DOM elements in React and Angular is fairly straightforward, the vanilla JS solution I've come up with seems clunky. I'm wondering if there's a particularly more concise or efficient way of building dynamically rendered DOM elements?
The function below takes in an array received from a GET request and renders a div for each item, passing in the value (much like you would map over results in React and render child elements).
function loadResults(array) {
array.forEach(videoObject => {
let videoData = videoObject.snippet;
let video = {
title : videoData.title,
img : videoData.thumbnails.default.url,
description : videoData.description
};
let div = document.createElement("DIV");
let img = document.createElement("IMG");
img.src = video.img;
let h4 = document.createElement("h4");
let title = document.createTextNode(video.title);
h4.appendChild(title);
let p = document.createElement("p");
let desc = document.createTextNode(video.description);
p.appendChild(desc);
div.appendChild(img);
div.appendChild(h4);
div.appendChild(p);
document.getElementById('results')
.appendChild(div);
});
}
This feels unnecessarily clunky but I haven't yet found an easier way to do this.
Thanks in advance!
Note: Everything I say here is on the proof of concept level and nothing more. It does not handle errors or exceptional cases, nor was it tested in production. Use at your own discretion.
A good way to go about would be to create a function that creates elements for you. Something like this:
const crEl = (tagName, attributes = {}, text) => {
const el = document.createElement(tagName);
Object.assign(el, attributes);
if (text) { el.appendChild(document.createTextNode(text)); }
return el;
};
Then you can use it like so:
results
.map(item => crEl(div, whateverAttributes, item.text))
.forEach(el => someParentElement.appendChild(el));
Another cool proof of concept I've seen is the use of ES6 Proxies as a sort of templating engine.
const t = new Proxy({}, {
get(target, property, receiver) {
return (children, attrs) => {
const el = document.createElement(property);
for (let attr in attrs) {
el.setAttribute(attr, attrs[attr]);
}
for (let child of(Array.isArray(children) ? children : [children])) {
el.appendChild(typeof child === "string" ? document.createTextNode(child) : child);
}
return el;
}
}
})
const el = t.div([
t.span(
["Hello ", t.b("world!")], {
style: "background: red;"
}
)
])
document.body.appendChild(el);
The Proxy traps the get
on the target object (which is empty), and renders an element with the name of the called method. This leads to the really cool syntax you see as of const el =
.
If you can use ES6, template strings is another idea ->
var vids = [
{
snippet: {
description: 'hello',
title: 'test',
img: '#',
thumbnails: { default: {url: 'http://placehold.it/64x64'} }
}
}
];
function loadResults(array) {
array.forEach(videoObject => {
let videoData = videoObject.snippet;
let video = {
title : videoData.title,
img : videoData.thumbnails.default.url,
description : videoData.description
};
document.getElementById('results').innerHTML = `
<div>
<img src="${video.img}"/>
<h4>${video.title}</h4>
<p>${video.description}</p>
</div>
`;
});
}
loadResults(vids);
<div id="results"></div>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With