Edited at
nemDay 16

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

うわー!遅なった遅なった!

この記事は NEM Advent Calendar 2018 の 16 日目の記事です。

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

普段はWebアプリ作ったり、RPAやったりしていますが、NEMはとても好きなのでアドベントカレンダー書きます。

今回はNEMをもっと便利にWebアプリに組みこもう的な話をしようかと思います!


TL;DR


  • WebComponentsを使えば誰でも使い回せるウィジェットが作れる!

  • 皆んなでウィジェットを作ってバニラのHTMLを拡張できる!

  • サンプルソースは Github にあります!


WebComponentsってなんぞや

WebComponentsは新しいWeb標準に関する考え方でW3Cで提唱されている考え方です。


WebComponentsの良いところ

Webアプリ作ってるとよく起きる、


また作るのめんどくさいんやけどー!


とか


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


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

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

最近 lit-html などなどライブラリも充実してきていたり、唯一未対応だったブラウザの Edge(caniuseに飛びます) もChromiumに移行するとのことでとてもオススメです!


WebComponents の構成要素

ここでは簡単に説明します。

なお、詳しくは、 Web Componentsとは何か? などで解説してもらっています。


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

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

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

HTMLの属性は、static get observedAttributes() で監視すると定義できます。

詳しくは Github で!

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

ちなみに構造はXEMBookさんのを使っています。

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

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

今回は完全にバニラのJSで書いたんですが、モジュールをインポートしてReactなりjQueryなりAngularを使うのもアリです。

詳しくは Github で!

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によって民主的になるかなぁと思っています。

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

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