データに応じて要素を生成するときのスマートな方法

サーバーから受け取ったデータに応じて、要素を生成したいということはよくある。

テンプレートエンジンなどを使うと、そのデータを綺麗に処理することができる。 けど、それを使わずに素のJSで書くのはなかなかめんどくさい。

たとえば、こんなデータがあるとする。

var users = [
  { name: 'tom', age: 18 },
  { name: 'bob', age: 24 },
  { name: 'john', age: 36 }
];

このデータから、以下のHTMLを生成する方法を考える。

<dl>
  <dt>name</dt>
  <dd>tom</dd>
  <dt>age</dt>
  <dd>18</dd>
</dl>

<dl>
  <dt>name</dt>
  <dd>bob</dd>
  <dt>age</dt>
  <dd>24</dd>
</dl>

<dl>
  <dt>name</dt>
  <dd>john</dd>
  <dt>age</dt>
  <dd>36</dd>
</dl>

まず、普通に書くとこんな感じだと思う。

var frag = document.createDocumentFragment();

for (var i = 0, l = users.length; i < l; i++) {
  var user = users[i];
  var dl = document.createElement('dl');

  var nameTerm = document.createElement('dt');
  nameTerm.textContent = 'name';
  dl.appendChild(nameTerm);

  var nameDesc = document.createElement('dd');
  nameDesc.textContent = user.name;
  dl.appendChild(nameDesc);

  var ageTerm = document.createElement('dt');
  ageTerm.textContent = 'age';
  dl.appendChild(ageTerm);

  var ageDesc = document.createElement('dd');
  ageDesc.textContent = user.age;
  dl.appendChild(ageDesc);

  frag.appendChild(dl);
}

var result = document.getElementById('result');

result.appendChild(frag);

書くコードが無駄に多くて、わかりにくくてあまりスマートでない。 これを、わかりやすく書いてみる。

var html = users.map(function (user) {
  return `
    <dl>
      <dt>name</dt>
      <dd>${user.name}</dd>
      <dt>age</dt>
      <dd>${user.age}</dd>
    </dl>
  `;
});

var result = document.getElementById('result');

result.insertAdjacentHTML('beforeend', html.join(''));

最初の方法みたいに、document.createElementを実行するとNodeListが返るので、個々に別の要素に追加していかないといけない。

それを、配列の中にStringとして生成すると、実際にDOMに追加するタイミングで全て連結してそのままHTMLにパースできる。

insertAdjacentHTMLの第一引数にbeforeendを指定すると要素の末尾に、afterbeginを指定すると要素の先頭に第二引数のHTMLが追加される。

要素の中身を空にしてから追加したいというときは、innerHTMLで追加してもいいけど、速度的に微妙なのでwhile文とかで除去してからinsertAdjacentHTMLで追加するのがいいと思う。

var empty = function (el) {
  while (el.firstChild) {
    el.removeChild(el.firstChild);
  }
};

var container = document.getElementById('container');
empty(container);