チェック状態の変更をハンドリングしてスタイルを変更する
さて、現在の実装では、チェックボックスをクリックしたときにチェック状態が切り替わったように見えても、 tasks プロパティの内容は変わっていません。DOMツリーの状態が独自に変わっているだけです。ですが、打ち消し線を制御する過程で、 tasks の内容も変更されていたほうが都合が良いので、変更する仕組みを実装していきましょう。チェックボックスのクリックイベントを受けて、状態の更新を行うので、まずはクリックイベントを受け取る処理を記述します(リスト5)。
<input
type="checkbox"
?checked=${task.completed}
@click=${(event) => this.setChecked(task, event)}
/>
イベントリスナーを登録するので、 @ のプレフィックスをつけた @click に関数を登録します。ここで呼び出す関数は、クラス内のメソッドとして定義します(リスト6)。
setChecked(task, event) {
task.completed = event.target.checked; // (1)
this.requestUpdate(); // (2)
}
ここでは、(1)でデータを更新し、(2)でDOMへの再描画を依頼しています。 this.tasks を直接書き換えれば自動で再描画されるのですが、今回は配列の要素を書き換えているので、(2)のように明示的に再描画のメソッドを呼び出しています。
これでデータとUIが同期するようになったので、次は完了状態のタスクに打ち消し線を表示できるようにしましょう。Litでカスタム要素内のスタイルを表現する場合は、styles という名前のstaticプロパティを定義します(リスト7)。
// (1) cssを追加する
import {LitElement, html, map, css} from 'https://cdn.jsdelivr.net/gh/lit/dist@2/all/lit-all.min.js';
// 略
// (2) スタイルを定義する
static styles = css`
.completed {
text-decoration-line: line-through;
color: #777;
}
`;
// 略
スタイルの定義には、 css というタグ付きのテンプレートリテラルを利用します。 css は(1)のようにインポートして使用します。テンプレートリテラル内には(2)のようにCSSを記述できます。
スタイルを定義したので、テンプレート側にも対応するclass属性を定義します(リスト8)。
<li class=${task.completed ? 'completed' : ''}>
<li> 要素にclass属性を追加しました。 task.completed がtrueの場合だけ、 class="completed" が設定されることになります。リスト7で .completed のセレクタに対して打ち消し線と色変更を設定してあるので、実行すると図4のようになります。
動的なデータ変更に応じて、スタイルを変更できるようになりました。
完了したタスクを隠す
最後に、プロパティをもう一つ追加して、完了した項目を隠せるようにしてみましょう。リスト9のようにプロパティを追加します。
static properties = {
tasks: {
state: true,
},
// (1) 追加
hideCompleted: {
type: Boolean,
reflect: true, // (2)
},
};
tasks の定義の下に、(1)のように hideCompleted というプロパティを追加しました。(2)で reflect: true を設定したのは、後でスタイルの切り替えに使用するためです。
では、次は hideCompleted のオンオフを切り替えるためのチェックボックスを追加しましょう(リスト10)。
// 略
<h1>ToDoリスト</h1>
${map(this.tasks, (task) => {
// 略
})}
<hr />
<div>
<!-- (1) -->
<label>
<input type="checkbox"
@change=${this.setHideCompleted /* (2) */ }
?checked=${this.hideCompleted}>
完了したタスクを隠す
</label>
</div>
// 略
(1)にチェックボックスを追加しました。表示すると図5のようになります。
まだ(2)で @change にイベントリスナーとして登録している setHideCompleted メソッドを実装していないので、実装します(リスト11)。
setHideCompleted(event) {
this.hideCompleted = event.target.checked;
}
今回はリスト6とは違い、 requestUpdate() は使用しません。 hideCompleted には reflect: true を設定してあるので、データを更新した時点で変更が反映されます。
次はスタイルの追加……といきたいところですが、まずは reflect: true の効果について確認しておきましょう。 reflect: true はカスタム要素内部でのプロパティの更新を、属性の変更としてDOMに反映するためのオプションです(図6)。
カスタム要素の内部で hideCompleted がtrueになっているとき、DOM上では <todo-list hidecompleted> のように、 hidecompleted 属性が設定されるのです。これは hideCompleted が更新されるたびに反映されます。
この仕組みは、スタイル定義と組み合わせた場合に上手く機能します。スタイルの定義に、リスト12のような新しいスタイルを追加します。
static styles = css`
.completed {/* 略 */}
/* (1) 追加 */
:host([hideCompleted]) li.completed {
display: none;
}
`;
(1)での :host() はCSSの擬似クラス関数で、引数にセレクタを渡すことで、シャドウホスト(ここでは <todo-list>)の属性を見ながらスタイルを指定できます。そのため、(1)が意味するところは「シャドウホストに hideCompleted 属性が指定されている場合、class属性に completed が指定された <li> 要素を表示しない」というものになります。実際に動かしてみると、図7のようになります。
しっかりとスタイルが効いていますね。 reflect を設定したプロパティをCSSセレクタに利用する使い方は便利なので、ぜひご活用ください。
まとめ
今回は、ToDoリストを作りながらLitで状態管理を行う方法について学びました。次回は外部のUIライブラリを利用してリッチなUIを作成する方法について解説します。
