template要素についてのお勉強

#はじめに

HTML5の新たな要素としてtemplate要素 `<template>` がありますが、これを使うと何が便利になり何が嬉しいのか。
自分が後で見返すためのメモとして書き記しておきます。

#template要素って何さ

template要素はブラウザ上では表示されず、主にJavaScriptによる操作がメインになる要素です。
名前からもわかるようにテンプレートとして使うコンテンツを中に書いてJavaScriptであれこれできるやつです。

```html
<template id="template">
  <p>これはテンプレートだよ</p>
  <p><img src="./this_is_template.jpg" alt="これはテンプレートだよ"></p>
  <div id="templateContent"></div>
</template>
```

template要素内のコンテンツは、JavaScriptの操作で本文へと挿入されない限り本文に何も影響を与えません。
つまり、template要素内で `<img>` で画像を指定していても、挿入されるまでその画像はロードされることはありません。

##&lt;script type="text/template"&gt;&lt;/script&gt; との違い

script要素を `type="text/template"` 等とする事でテンプレートとして扱うライブラリもありますが、script要素内に書かれたコンテンツはあくまでテキストとして保持されているので、JavaScript側でHTML要素として扱えるような処理が必要になります。
それに対してtemplate要素内に書かれたコンテンツはテキストではなくHTML要素として存在できます。

#template要素の使い方

例えば、JSONのデータを元にキャプション付きの画像をたくさん生成するとします。

以下のようなHTMLがあるとします。

```html:HTML
<div id="imageBlock"></div>

<template id="imageTemplate">
  <figure>
    <img>
    <figcaption></figcaption>
  </figure>
</template>
```

JavaScriptでは例えばこのようにしておきます。

```js:JavaScript
// JSONの例
const json = '[{"file":"./image/01.jpg","caption":"キャプション1"},{"file":"./image/02.jpg","caption":"キャプション2"}]';

document.addEventListener('DOMContentLoaded', () => {
  // template要素からコンテンツを取得、
  const content = document.querySelector('#imageTemplate').content;

  // フラグメント
  const fragment = document.createDocumentFragment();

  // JSON文字列を変換
  const imageData = JSON.parse(json);

  for (const data of imageData) {
    // テンプレートのノードを複製
    const clone = document.importNode(content, true);

    // テンプレート内のimg要素
    const image = clone.querySelector('img');

    // テンプレート内のfigcaption要素
    const caption = clone.querySelector('figcaption');

    // テンプレートの要素に適用する
    image.src = data.file;
    image.alt = data.caption;
    caption.textContent = data.caption;

    // 複製したノードをフラグメントに挿入
    fragment.appendChild(clone);
  }

  // HTMLに挿入
  document.querySelector('#imageBlock').appendChild(fragment);
});
```

結果的にこんな感じのHTMLになります。

```html:ブラウザが解釈するHTML構造
<div id="imageBlock">
  <figure>
    <img src="./image/01.jpg" alt="キャプション1">
    <figcaption>キャプション1</figcaption>
  </figure>
  <figure>
    <img src="./image/02.jpg" alt="キャプション2">
    <figcaption>キャプション2</figcaption>
  </figure>
</div>

<template id="imageTemplate">
  <figure>
    <img>
    <figcaption></figcaption>
  </figure>
</template>
```

わざわざループ処理の中で要素を生成したりといった事をしなくて済みます。

##`HTMLTemplateElement.content` って何だ

template要素が持つ読み取り専用のプロパティです。
中身は `[object DocumentFragment]` になっていて、さらにその中にtemplate要素内のHTML要素が納められています。
`DocumentFragment` オブジェクトとは、親ノードが存在しないひとかたまりの文章構造で、`document` の持ち運べる軽量版のようなものです。

template要素内の要素は `DocumentFragment.getElementById()` や `DocumentFragment.querySelector()` や `DocumentFragment.querySelectorAll()` で取得が可能です。
`DocumentFragment.getElementsByTagName()` や `DocumentFragment.getElementsByClassName()` は存在しません。

[DocumentFragment - Web API インターフェイス | MDN](https://developer.mozilla.org/ja/docs/Web/API/DocumentFragment)

##`document.importNode()` って何だ

`Node` または `DocumentFragment` を複製するメソッドです。
`Node.cloneNode()` でも同じように複製ができますが、違う文章・ドキュメントに挿入するという意味もあり使い分けています。

template要素内のコンテンツは前述の通り `DocumentFragment` になっています。
`document.importNode()` でそれを複製して使い回しが効くようにします。

もし、これを行わずに本文に挿入してしまうとtemplate要素内のコンテンツを移動させることになり、template要素内は空っぽになってしまいます。再利用ができない！

[document.importNode() - Web API インターフェイス | MDN](https://developer.mozilla.org/ja/docs/Web/API/Document/importNode)

##jQueryで書けるの？

現状の jQuery (v3.1.1) はtemplate要素の操作は行えないようです。が、使えないこともありません。

jQueryに書き直したバージョンです。

```js:jQueryの場合
// JSONの例
var json = '[{"file":"./image/01.jpg","caption":"キャプション1"},{"file":"./image/02.jpg","caption":"キャプション2"}]';

$(function () {
  var
    // template要素からコンテンツを取得、インスタンスの生成
    content = $('#imageTemplate')[0].content,

    // フラグメント
    fragment = document.createDocumentFragment(),

    // JSON文字列を変換
    imageData = JSON.parse(json);

  $.each(imageData, function (_, data) {
    // テンプレートのノードを複製
    var clone = document.importNode(content, true);

    // テンプレート内のimg要素
    var $image = $('img', clone);

    // テンプレート内のfigcaption要素
    var $caption = $('figcaption', clone);

    // テンプレートの要素に適用する
    $image.prop({ src: data.file, alt: data.caption });
    $caption.text(data.caption);

    // 複製したノードをフラグメントに挿入
    fragment.appendChild(clone);
  });

  // HTMLに挿入
  $('#imageBlock').append(fragment);
});
```

#template要素のメリット・デメリット

自分が思いついたのを挙げただけなので、まだこういうのがあるよ等のツッコミがあればお願いします。

##メリット

- 保守性があがる
- JavaScript側で要素の生成などを行わなくていい
- HTMLソース内に書けるので、一目でどのような構造なのかわかる
- 通常の要素の操作とあまり変わらない

##デメリット

- Internet Explorer は対応していない
- 他のテンプレートエンジンと違い、テンプレート内で条件分岐などは書けない

#おわりに

~~IE非対応という点からまだまだ本格的に使用できるのはまだ先な気がしますね……~~
もうはやIEの事を考えるだけ無駄なので、利用できるのであれば利用していきましょう。