チェック状態の変更をハンドリングしてスタイルを変更する
さて、現在の実装では、チェックボックスをクリックしたときにチェック状態が切り替わったように見えても、 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を作成する方法について解説します。