Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's a good way to dynamically render DOM elements with plain old JS?

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!

like image 899
EFH Avatar asked Oct 16 '16 21:10

EFH


2 Answers

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 =.

like image 139
Madara's Ghost Avatar answered Sep 22 '22 00:09

Madara's Ghost


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>
like image 30
Keith Avatar answered Sep 18 '22 00:09

Keith