WebComponents で NEM APIをラッピングした再利用可能な部品を作る

うわー！遅なった遅なった！

この記事は [NEM Advent Calendar 2018](https://qiita.com/advent-calendar/2018/nem) の 16 日目の記事です。

みやとも と言います！何気に初投稿です！

普段はWebアプリ作ったり、RPAやったりしていますが、NEMはとても好きなのでアドベントカレンダー書きます。
今回はNEMをもっと便利にWebアプリに組みこもう的な話をしようかと思います！

# TL;DR
* WebComponentsを使えば誰でも使い回せるウィジェットが作れる！
* 皆んなでウィジェットを作ってバニラのHTMLを拡張できる！
* サンプルソースは [Github](https://github.com/tomohidemiya/nem_webcomponents_sample) にあります！

# WebComponentsってなんぞや
WebComponentsは新しいWeb標準に関する考え方でW3Cで提唱されている考え方です。



## WebComponentsの良いところ
Webアプリ作ってるとよく起きる、
> また作るのめんどくさいんやけどー！

とか

> コピペしたら他の要素がおかしくなったんやけどー！

っていうあたりの問題が解決できます。


何より、再利用性がきく + 他のコンポーネントを取り込んでも元のHTMLがほとんど汚れない素敵な取り組みです。


最近 [lit-html](https://lit-html.polymer-project.org/) などなどライブラリも充実してきていたり、唯一未対応だったブラウザの [Edge（caniuseに飛びます）](https://caniuse.com/#search=web%20components) もChromiumに移行するとのことでとてもオススメです！

## WebComponents の構成要素
ここでは簡単に説明します。
なお、詳しくは、 [Web Componentsとは何か？](https://qiita.com/jtakiguchi/items/b1315f53b3726ff11b61) などで解説してもらっています。

### CustomElements
WebComponentsの中心的な概念です。
例えば、`<nem-banzai></nem-banzai>` みたいなオリジナルのタグを作ることができて、このnem-banzaiタグが描画する内容も独自で定義することができます。

なので、ブランドロゴを示す要素とかアカウント情報を表示するウィジェットみたいな使いまわしたい部品を定義しておくととても便利になります。

```
// HTMLElementを継承したDOMの拡張クラスとして作る
class NemBanzaiElement extends HTMLElement {

    // 初期処理でやりたいことを書く
    constructor() {
        super();
    }

    // コンポーネントがDOMにレンダリングされるたびに呼び出される
    connectedCallback() {
        this._updateRendering();
    }

    // レンダリングされる時の描画内容
    _updateRendering() {
        this.textContent = 'Hello, NEMber!';
    }
}

// HTMLタグ要素とHTMLElementを紐付ける
customElements.define('nem-banzai', NemBanzaiElement);
```

### ShadowDOM
CustomElementsで定義したタグはもしその中に他の要素が書いてあると、自動的に上書いてしまいます。
これはCustomeElementsをカプセル化して、外の要素から汚されない為にやっています。

下の例で`<nem-banzai>`タグが外のCSSから簡単に上書きされてしまうとわざわざ分離している意味がありません。
「NEM半端ないってマジで」はどこにも表示されません

```
<nem-banzai>NEM半端ないってマジで</nem-banzai>
```

ShadowDOMではデザイン・機能と構造を綺麗に分離する為に、CustomElementsの内と外をつなぐインターフェースとカプセル化をサポートします。

ShadowDOMでインターフェースを設定するには、htmlのテンプレートをコンポーネントの中に定義します。
`<slot>`タグは

```
static get template() {
        return `
            <style>
                .nem--orange {
                    color: #F5A841;
                }
                .nem--green {
                    color: #59CCBB;
                }
                .nem--blue {
                    color: #49B8E9;
                }
            </style>
            <div>
                <span class="nem--orange">Hello NEMber,</span>
                <span class="nem--green"> <slot></slot> </span>
                <span class="nem--blue">san!</span>
            </div>
        `;
    }
```


### ES Modules
他のJavaScriptのモジュール（ライブラリとか）をコンポーネントにインポートすることができます。
例えば、Reactを`<nem-banzai>`にインポートしたりできます。

```
<script type="module">
    import React from 'react';
    import ReactDom from 'react-dom';
</script>
```


# NEMでWebComponentsを使う場合
NEMはAPIにアクセスすることで簡単に情報を取得したり、モザイク送るところを汎用化したり、色々コンポーネントにできます。
 `<nem-account address="XXX"></nem-account>`って書くだけで、アカウント情報を諸々出したりできるので、後発の人がどんどん楽にサービス作ったりできるかもしれません。

## Hello NEMberしてみるコンポーネント
こんな感じにしてみました。
このコンポーネントがあると、NEMberの人たちにHelloすることができますね。
![スクリーンショット 2018-12-18 22.01.44.png](https://qiita-image-store.s3.amazonaws.com/0/239882/8d4b4b23-0b15-94e7-04e2-865bccb9969b.png)

HTMLのテンプレートはこんな感じです。

```
<nem-hello name="miyatomo"></nem-hello>
```

HTMLの属性は、`static get observedAttributes()` で監視すると定義できます。
詳しくは [Github](https://github.com/tomohidemiya/nem_webcomponents_sample) で！

```
class NemHelloElement extends HTMLElement {
    constructor() {
        super();
        // name属性は初期化しておきます
        this._name = null;   
    }
    ・
    ・
    ・
    // name属性の監視
    static get observedAttributes() {
        return ['name'];
    }

    // name属性のゲッター
    get name() {
        return this._name;
    }

    // name属性のセッター
    set name(v) {
        this.setAttribute("name", v);
    }
    ・
    ・
    ・
    // テンプレートのレンダリング
    connectedCallback() {
        this
        .attachShadow({
            mode: 'open'
        })
        .innerHTML = NemHelloElement.template;
    }
}
customElements.define('nem-hello', NemHelloElement);
```


## NEM APIからアカウント情報を取得するコンポーネント
このコンポーネントがあると、NEMウォレットのアドレスをインプットにアカウント情報を出力するウィジェットなんかも作れます。

こんな感じにしてみました。CSSが設定されていなさすぎてアレですが、
![スクリーンショット 2018-12-18 22.15.19.png](https://qiita-image-store.s3.amazonaws.com/0/239882/434b54f8-3044-a4d7-69d1-3a525a985295.png)
ちなみに構造は[XEMBookさん](http://xembook.net/)のを使っています。

HTMLのテンプレートはこんな感じです。

```
<nem-account address="NBZNQL2JDWTGUAW237PXV4SSXSPORY43GUSWGSB7"></nem-account>
```

今回は完全にバニラのJSで書いたんですが、モジュールをインポートしてReactなりjQueryなりAngularを使うのもアリです。
詳しくは [Github](https://github.com/tomohidemiya/nem_webcomponents_sample) で！

```
class NemAccountElement extends HTMLElement {
    ・
    ・
    ・
    // テンプレートは強引にslotで、、、もっといい方法あるかも。。。
    static get template() {
        return `
            <div class="account">
                <h2>アカウント</h2>
                <dl>
                    <dt>口座名</dt><dd><span id="account_address"><slot name="address"></slot></span></dd>
                    <dt>残高</dt><dd><span id="account_balance"><slot name="balance"></slot>XEM</span></dd>
                    <dt>時価総額</dt><dd><span id="polo_price"><slot name="priceJpy"></slot>円 [<slot name="jpy"></slot>JPY/XEM換算]</span></dd>
                    <dt>重要度</dt><dd><span id="account_importance"><slot name="importance"></slot></span></dd>
                </dl>
            </div>
        `;
    }
    ・
    ・
    ・
    // アカウント情報をとってくるAPIを叩くFunctionを定義
    getAccount(address, shadowRoot) {
        const jpy = 7.251;
        let xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function() {
            if (this.readyState == 4 && this.status == 200) {
                const account = JSON.parse(this.responseText).account;
                const balance = account.balance / 1000000;
                const importance = account.importance * 10000;
                const priceJpy = jpy * balance;

                let slots = shadowRoot.querySelectorAll('slot');
                slots[0].innerHTML = address;
                slots[1].innerHTML = balance;
                slots[2].innerHTML = priceJpy;
                slots[3].innerHTML = jpy;
                slots[4].innerHTML = importance;
            }
        };
        const url = 'http://153.122.13.93:7890/account/get?address=' + address;
        xhr.open('GET', url, true);
        xhr.send();
     }
    ・
    ・
    ・
}
customElements.define('nem-account', NemAccountElement);
```


# まとめ
NEMはAPIを叩くだけで呼べるので、エンジニアにとってすごく良いサービスだなぁと常々思っていますが、WebComponentsによって民主的になるかなぁと思っています。

あととにかくバニラで全部やるもんではないですね！！！

若干説明が性急なので、もう少し詳しくします！