このcodelabでは、LitElementを使ったカスタムエレメントの作り方について学べます。ボタンをトグルする簡単なLitElementエレメントを作ります。完成イメージは以下のような感じになります。

これは以下のような簡単なマークアップで利用可能になります。
<icon-toggle></icon-toggle>
このcodelabでは、LitElementを使って作業するための重要なコンセプトについても紹介していきます。
もし全てのコンセプトについて理解できなくても心配しないでください。それらの内容はLitElementの公式ドキュメントにより詳しく書かれています。
チュートリアルを始める前に、以下のソフトウェアがあることを確認してください:
以下の項目について、基本的なスキルや知識が必要になります:
Gitはバージョン管理ツールです
git --versionインストールが成功していれば、Gitのバージョンが表示されるはずです。
もしGitのバージョン番号がgit version 2.19.1のように表示されない場合は、次のリンクを参照してみることをおススメします。公式ドキュメント - Gitのインストール.
NodeはJavaScriptの実行環境です。npmはNodeのパッケージ管理ツールです。これらはNodeをインストールすると両方ともインストールされます。
npm install npm@latest -g 現時点で node -v は v10.14.2 でした
現時点で npm -v は 6.4.1 でした
バージョン番号が表示されなかったら 公式ドキュメントのインストールガイドを参照してください
次のコマンドでリポジトリをクローンしてください。
git clone https://github.com/Polymer-Japan/litelement-first-element.git
ディレトリをクローン先に移動してから、npmコマンドで依存するパッケージをインストールします。
cd litelement-first-elements npm install
インターネット回線が遅いと、とても時間がかかる場合があります....
インストールが終了すると、以下のようなディレクトリ構造になります。

作業する主なファイルはicon-toggle.jsです。このファイルにはカスタムエレメントの定義が入っています。
デモの実行方法はとても簡単です。まだアプリは何も実装していませんが、以下のコマンドを実行してみましょう。
npm start
すると、Polymerの開発サーバーが動いて、デモがブラウザ(chrome)の新しいタブで開きます。アイコントグルは表示されず、テキストだけが表示されるはずです。どうってことないかもしれませんが、これで全てがうまく動いていることが確認できます。

次に、画面にアイコンを表示するための簡単なエレメントを作ってみましょう。
このステップでは、以下のようなことを学習できます。
エディタでicon-toggle.jsファイルを開いてください。このファイルにはカスタムエレメントのスケルトンが入っています。
既存コードを見ながら進めましょう。
import { LitElement, html } from 'lit-element';
import '@polymer/iron-icons/iron-icons.js';
import '@polymer/iron-icon/iron-icon.js';
キーポイント:
importはES6 Module importです。アプリケーションで利用するエレメントやモジュールをインポートします。iron-icons と iron-iconをインポートします。これらは、この後で登場します。次はエレメント自体を定義していきます。
class IconToggle extends LitElement {
render() {
return html`
<style>
/* local DOM styles go here */
:host {
display: inline-block;
}
</style>
<!-- local DOM goes here -->
<span>Not much here yet.</span>
`;
}
}
キーポイント:
LitElementを継承したクラスを作成します。render関数を定義します。renderはlit-htmlのTemplateResultのインスタンスを返却する必要があります。html ヘルパーを使うとJavaScriptのテンプレートリテラルからTemplateResultインスタンスを生成できます。(html ヘルパーは lit-element.js モジュールからインポートできます)render内の<style>エレメントを使うと、Shadow DOMにscopedな(カプセル化された)スタイルを定義できます。これはドキュメントの他の部分には影響を及ぼしません。:host擬似クラスは、定義するカスタムエレメントそのもの(この場合は<icon-toggle>エレメント自体)です。
window.customElements.define('icon-toggle', IconToggle);
キーポイント:
customElements.defineメソッドの第二引数にエレメントクラスを指定します。エレメントの基本構造にふれたところで、Shadow DOMテンプレートを編集してみましょう。
local DOM goes hereというコメントの後に書いてある<span>タグを見つけてください。
<!-- local DOM goes here -->
<span>Not much here yet.</span>
`;
<span>タグを、以下のように<iron-icon>に置き換えてみてください。
<!-- local DOM goes here -->
<iron-icon icon="polymer">
</iron-icon>
`;
キーポイント:
<iron-icon>エレメントはアイコンを表示するカスタムエレメントです。今は "polymer" という名前をハードコーディングしています。Shadow DOMで利用できる新しいCSSセレクターがいくつかあります。icon-toggle.jsファイルの:hostセレクターはすでに紹介しました。これは<icon-toggle> エレメントそのもののスタイルを指定します。
<iron-icon>エレメントのスタイルを指定するために、<style>タグ内のCSSを以下の内容に書き換えてください:
<style>
/* local styles go here */
:host {
display: inline-block;
}
iron-icon {
fill: rgba(0,0,0,0);
stroke: currentcolor;
}
:host([pressed]) iron-icon {
fill: currentcolor;
}
</style>
キーポイント:
<iron-icon>タグはSVGアイコンを使っています。fillやstrokeは、SVG固有のCSSプロパティです。アイコンの塗りつぶしと輪郭の色をそれぞれ設定します。:host()関数は、引数で指定されたセレクタが一致するホスト要素(エレメント)に適用されます。このときの[pressed]は標準のCSS属性セレクタで、icon-toggleエレメントにpressed属性が設定されている場合に、スタイルが適用されます。これまでの修正で、カスタムエレメント定義は以下のようになっていると思います。
import { LitElement, html } from 'lit-element';
import '@polymer/iron-icons/iron-icons.js';
import '@polymer/iron-icon/iron-icon.js';
/**
* `icon-toggle`
* Get started creating custom elements with LitElement
*
* @customElement
* @demo demo/index.html
*/
class IconToggle extends LitElement {
render() {
return html`
<style>
/* local styles go here */
:host {
display: inline-block;
}
iron-icon {
fill: rgba(0,0,0,0);
stroke: currentcolor;
}
:host([pressed]) iron-icon {
fill: currentcolor;
}
</style>
<!-- local DOM goes here -->
<iron-icon icon="polymer">
</iron-icon>
`;
}
}
window.customElements.define('icon-toggle', IconToggle);
ホスト要素の値に応じてメッセージを表示するようにするため、constructorを追加してisFav属性の初期値を設定するように編集してください。
class IconToggleDemo extends LitElement {
constructor() {
super();
this.isFav = false;
}
デモページをリロードしてください。ハードコーディングしたアイコン表示のトグルボタンが表示されるはずです。

1つのトグルだけが押されたようなスタイルになっています。そのタグにはpressed属性が設定されているためです。しかし、トグルボタンをクリックしても、まだトグルは動きません。pressedプロパティを変更するコードがまだないためです。
今のところエレメントは、変化しません。このステップでは、アイコンをマークアップ上から指定するための属性の使い方と、JavaScriptからプロパティを使う方法について、基本的なAPIを紹介します。
まず、データバインディングから始めましょう。 <iron-icon>エレメントを探して、icon属性の値を"polymer"から"${this.icon}"に変更してみましょう。
<!-- local DOM goes here -->
<iron-icon icon="${this.icon}">
</iron-icon>
キーポイント:
iconは、後でトグルボタンエレメントのプロパティとして定義します。デフォルト値はまだありません。icon="${this.icon}"と記述することでデータバインディングを利用できます。トグルボタンエレメントのiconプロパティの値を<iron-icon>のiconプロパティにリンクします。以下の例のように、エレメントのマークアップで記述するか、JavaScriptを使用してiconプロパティの値を設定できます(このコードをプロジェクトに追加する必要はありません)。
<icon-toggle icon="favorite"></icon-toggle>
var myToggle = document.querySelector('icon-toggle');
myToggle.icon = "favorite";
続いて、iconプロパティの宣言を追加します。
次のようなstatic get properties関数をIconToggleクラスに追加してください:
class IconToggle extends LitElement {
static get properties() {
return {
icon: String,
};
}
キーポイント:
static get properties関数をエレメントクラスに追加します。この関数は、プロパティ宣言を含むオブジェクトを返す必要があります。String)だけを指定します。propertiesオブジェクトは、さらにいくつかの機能をサポートしています。pressedプロパティを利用できるようにするため、以下のように変更します。
constructor() {
super();
this.pressed = false;
}
static get properties() {
return {
icon: String,
pressed: {
type: Boolean,
reflect: true
}
};
}
キーポイント:
constructorメソッドでデフォルト値を指定します。reflectをtrueに設定すると、プロパティ値が変更されたとき属性の値が追従します。これにより、icon-toggle[pressed]のような属性セレクタを使用して要素にスタイルを適用できます。この修正でエレメントの pressed と icon プロパティが動くようになりました。
デモページをリロードすると、前のステップまでハードコーディングされていたアイコンが、星とハートのアイコンに変わって表示されているはずです。

星とハートがどこで指定されたのか興味があれば、demo/icon-toggle-demo.jsを見てください。以下のように記述されています。
<icon-toggle icon="star"></icon-toggle>
<icon-toggle icon="star" pressed></icon-toggle>
もちろん、クリックできないボタンはボタンではありません。ボタンをトグルするには、イベントリスナーを追加します。iron-iconエレメントにイベントリスナーを追加するには、以下のように@clickプロパティを要素に追加します。
<iron-icon icon="${this.icon}" @click="${this.toggle}">
キーポイント:
@イベント名を利用すると、イベントを取得できるようになります。イベントリスナーが呼び出すハンドラーを追加します。
toggle() {
this.pressed = !this.pressed;
}
attributeChangedCallback(name, oldval, newval) {
super.attributeChangedCallback(name, oldval, newval);
if(name === 'pressed') this.dispatchEvent(new CustomEvent('pressed-changed', { detail: this.pressed }));
}
キーポイント:
attributeChangedCallbackを利用すると、属性値の変更を監視できるようになります。値が変更されたらpressed-changedイベントを投げてicon-toggleエレメント利用者に状態が変わったことを通知します。icon-toggle.jsファイルを保存し、デモをリロードします。ボタンを押すと、押した状態と押していない状態を切り替えることができるはずです。

これまで、ベーシックな機能のボタンを作りました。しかし、押された状態と押されていない状態の両方ともに、標準テキストカラーを使用しています。ちょっと派手にしたいと思ったら、どうしたら良いでしょう?
Shadow DOMは、ユーザーが意図せずにエレメントの内部にスタイルを適用するのを防ぎます(同時に外部からスタイルを指定できなくなります)。こうしたときのために、カスタムプロパティを使うと、エレメント内のスタイルをユーザーが設定できるように特定のプロパティセットを提供できます。
var関数を使って、エレメント内部にカスタムプロパティを適用します。
background-color: var(--my-custom-property, defaultValue);
--my-custom-propertyはカスタムプロパティ名で、常に2つのダッシュ(--)で始まり、defaultValueはカスタムプロパティが設定されていない場合に使用される(オプションの)CSS値です。
エレメントの<style>タグを編集し、現在のfillとstrokeの値をカスタムプロパティに変更します。
<style>
/* local styles go here */
:host {
display: inline-block;
}
iron-icon {
fill: var(--icon-toggle-color, rgba(0,0,0,0));
stroke: var(--icon-toggle-outline-color, currentcolor);
}
:host([pressed]) iron-icon {
fill: var(--icon-toggle-pressed-color, currentcolor);
}
</style>
SVGのデフォルト値として、colorを設定するだけで<icon-toggle>のスタイルを変更することもできますが、他のオプションを使ってみます。 demo/icon-toggle-demo.jsを開き、以下のようにカスタムプロパティを設定します
<style>
:host {
font-family: sans-serif;
--icon-toggle-color: lightgrey;
--icon-toggle-outline-color: black;
--icon-toggle-pressed-color: red;
};
</style>
デモページをリロードするとカラフルになっているはずです。

これでチュートリアルは終わりです。基本的なUIやAPIとカスタムスタイリングプロパティを持つ要素を作成しました。
もし作業に問題があったときは、完成版のコードを参照してください。