Web Componentsの構成要素まとめ（基本編）

Web Components の構成要素の特徴についてのまとめ

# 構成要素
1. Custom Elements
2. Template
3. Shadow DOM
4. HTMLImports

## Custom Elements
- 任意の名前の要素を作成できる。
- 要素名には`-`が必須

```html
<script>
var proto = Object.create(HTMLElement.prototype);
document.registerElement('my-element', {prototype: proto});
</script>

<my-element></my-element>
```

## Template
- `<template>`を使ってテンプレートを作成できる。
- `<template>`内に記述した内容はDOMとして認識されないため、JSの実行やCSSの読み込みは起こらない。
- 別のDOM要素内に取り込まれて初めてレンダリングされる。

```html
<script>
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() {
  var template = document.getElementById('thisIsTemplate');
  var templateClone = document.importNode(template.content, true);
  this.appendChild(templateClone);
}
document.registerElement('this-is-template', {prototype: proto});
</script>
<template id="thisIsTemplate">
<div>This is Template</div>
</template>

<this-is-template></this-is-template>
```

## Shadow DOM
- カプセル化した要素を作成できる。
- JSやCSSの影響を限定できる。

```html
<script>
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() {
  var root = this.createShadowRoot();
  root.innerHTML = '<div class="shadow">This is shadow</div>';
}
document.registerElement('this-is-shadow', {prototype: proto});
</script>

<this-is-shadow></this-is-shadow>
```

## HTML Import
- 外部HTMLファイルの要素を取り込むことができる。

```html:index.html
<script>
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() {
  var link = document.querySelector('link[href="import.html"]');
  var template = link.import.getElementById('thisIsTemplate');
  var templateClone = document.importNode(template.content, true);
  var root = this.createShadowRoot();
  root.appendChild(templateClone);
}
document.registerElement('this-is-import', {prototype: proto});
</script>

<this-is-import></this-is-import>
```

```html:import.html
<template id="thisIsTemplate">
  <div>This is Import</div>
</template>
```

# 基本的な使い方

## プロトタイプの生成
```js
var poroto = Object.create(HTMLElement.prototype);
```

- 要素のプロトタイプをインスタンス化
- 新しいを要素を作成する場合、HTMLElement.prototypeを渡す。
- 既存の要素を拡張する場合、HTML____Element.prototype（目的の[要素インターフェース](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)のプロトタイプ）を渡す。

## コールバック
```js
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() {...}
```

コールバックは下記の4つがある。

|コールバック名|タイミング|
|:--------------|:-------------------------|
|createdCallback|要素のインスタンスが作られた時|
|attachedCallback|インスタンスがドキュメントに追加された時|
|detachedCallback|インスタンスがドキュメントから取り除かれた時|
|attributeChangedCallback(attrName, oldVal, newVal)|属性が追加、削除、更新された時|

## テンプレートのインポート
```js
var template = document.getElementById('template');
var templateClone = document.importNode(template.content. true);
```

## Shadow DOM の使用
```js
proto.createdCallback = function() {
  ...
  var root = this.createShadowRoot();
}
```

- `this`は`proto`を取り込む要素
- つまり`document.registerElement()`の第一引数にセットする要素


## 新規要素の生成

```js
document.registerElement('my-element', {prototype: proto});
```

## 既存要素の拡張

```html
<script>
var proto = Object.create(HTMLButtonElement.prototype);
proto.createdCallback = function() {
  this.addEventListener('click', function() {
    alert('alert-button is clicked!');
  });
}
document.registerElement('alert-button', {
  prototype: proto,
  extends: 'button'
});
</script>
<button is="alert-button"></button>
```

# まとめ

- 要素の元（`Object.create()`）に`<template>`の内容を取り込んで、その元を自分の好きな要素名にして（`document.registerElement()`）表示する。
- 「コンポーネントのカプセル化」を実現するためには、ShadowRootを生成してそこに要素を取り込む。

```html
<html>
  <head>
    <style>
      div {
        color: red;
      }
    </style>
    <link rel="import" href="import.html">
    <link rel="import" href="library.html">
    <script src="platform.js"></script>
    <script>
      // Create Elementsの機能で新しい要素を生成
      var proto = Object.create(HTMLElement.prototype);
      proto.createdCallback = function() {
        this.innerHTML = '<div>This is normal</div>';
      }
      document.registerElement('this-is-normal', {prototype: proto});
      
      // Shadow DOMの機能を使って要素をカプセル化
      // CSSがShadow DOM要素内のみに適用される。
      var protoShadow = Object.create(HTMLElement.prototype);
      protoShadow.createdCallback = function() {
        var root = this.createShadowRoot();
        root.innerHTML = '<div class="shadow"><style>div {color: red;}</style>This is shadow</div>';
      }
      document.registerElement('this-is-shadow', {prototype: protoShadow});
      

      // Template機能を使ってテンプレート要素を読み込む。
      // カプセル化されていないなので、appendChildで取り込まれるとCSSが全体に影響する。
      var protoTemplate = Object.create(HTMLElement.prototype);
      protoTemplate.createdCallback = function() {
        var template = document.getElementById('thisIsTemplate');
        var templateClone = document.importNode(template.content, true);
        this.appendChild(templateClone);
      }
      document.registerElement('this-is-template', {prototype: protoTemplate});
      
      // TemplateとShadow DOM機能を使ってテンプレート内容をカプセル化
      // テンプレート内の要素にのみCSSが適用される。
      var protoShadowTemplate = Object.create(HTMLElement.prototype);
      protoShadowTemplate.createdCallback = function() {
        var template = document.getElementById('thisIsShadowTemplate');
        var templateClone = document.importNode(template.content, true);
        var root = this.createShadowRoot();
        root.appendChild(templateClone);
      }
      document.registerElement('this-is-shadow-template', {prototype: protoShadowTemplate});
      
      // HTML Imports機能を使って外部HTMLファイルのテンプレート内容を取り込む。
      var protoImportShadowTemplate = Object.create(HTMLElement.prototype);
      protoImportShadowTemplate.createdCallback = function() {
        var link = document.querySelector('link[href="import.html"]');
        var template = link.import.getElementById('thisIsImportShadowTemplate');
        var templateClone = document.importNode(template.content, true);
        var root = this.createShadowRoot();
        root.appendChild(templateClone);
      }
      document.registerElement('this-is-import-shadow-template', {prototype: protoImportShadowTemplate});
      
      // 外部HTMLファイルのテンプレート内にライブラリのリンクをまとめて記述しておけば一括で取り込める。
      var protoImportLibrary = Object.create(HTMLElement.prototype);
      protoImportLibrary.createdCallback = function() {
        var link = document.querySelector('link[href="library.html"]');
        var template = link.import.getElementById('thisIsImportLibrary');
        var templateClone = document.importNode(template.content, true);
        this.appendChild(templateClone);
      }
      document.registerElement('this-is-import-library', {prototype: protoImportLibrary});
    </script>
    
    <template id="thisIsTemplate">
      <style>div {color: blue;}</style>
      <div>This is Template</div>
    </template>
    
    <template id="thisIsShadowTemplate">
      <style>div {color: green;}</style>
      <div class="shadow">This is TemplateShadow</div>
    </template>
  
  </head>
  <body>
    <this-is-normal></this-is-normal>
    <this-is-shadow></this-is-shadow>
    <this-is-template></this-is-template>
    <this-is-shadow-template></this-is-shadow-template>
    <this-is-import-shadow-template></this-is-import-shadow-template>
    <this-is-import-library></this-is-import-library>
  </body>
</html>
```

```html:import.html
<template id="thisIsImportShadowTemplate">
  <style>div {color: gray;}</style>
  <div>This is Import</div>
</template>
```

```html:library.html
<template id="thisIsImportLibrary">
  <div>This is Import Library</div>
  <link rel="stylesheet" href="font-awesome-4.1.0/css/font-awesome.min.css">
</template>
```

# 参考
[Web開発者に革命をもたらす！「Web Components」超入門](http://liginc.co.jp/web/html-css/html/58267)
[なぜ Web Components はウェブ開発に革命を起こすのか](http://blog.agektmr.com/2014/05/web-components.html)
[Shadow DOM 101](http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom/)
[Shadow DOM 201](http://www.html5rocks.com/ja/tutorials/webcomponents/shadowdom-201/)