Lit Web-component 使ってカスタム HTMLタグのフォームを作成してみました
挨拶
ポートの就活会議でフロントを担当している新卒の @eicho.chinです。
最近、カスタムタグの開発要望がありまして、それを Lit で実装することがありました。
紹介
Web_Componentsについて
主な技術は3つあります:
- カスタム要素
- シャドウ DOM
- HTML テンプレート(
<template>
と<slot>
)
詳細が気になる方は上記のMdnリンクをご参照ください。
今回使う Litは 上記のWeb_componentsを拡張したライブラリになります。
Lit Simple. Fast. Web Components.
Lit導入の経緯
Litの特徴としては、軽量で開発しやすく、ライフサイクルが豊富で使い勝手が良いところです。今後もカスタムタグが多く出現と予想されるため、 Litを導入しました。
Litで実現したいこと
- カスタムHTMLタグを使えるようにする。
- フォームを表示するのですが、そのフォームのタイトルとsubmitボタンの文言を表示したい。
依頼者からの理想な形(可能の限りこちらの形で使いたい):<register-from> <register-from-title>コントロールタイトル</register-from-title> <register-from-button>コントロールボタン文言</register-from-button> </register-from>
バックエンド送信の必要な前提条件
フォーム送信時に、csrf token
がデータとともに送信されることです。
これをformのhidden inputにセットに使用するちょうどいいライフサイクルフック firstUpdated()を使用します。
コンポーネントがrenderした後に、非同期でdomツリーにあるcsrfトークンをコンポーネントのformにセットします。
これで、コンポーネントがrenderした時、csrf tokenがまだない場合を避けられます。
export default class RegisterForm extends LitElement {
...somecode
firstUpdated() {
const csrf = document.querySelector('meta[name="csrf-token"]')?.content;
this.renderRoot.querySelector('input[name="authenticity_token"]').value = csrf;
}
shadow domを使用して、コンポーネントの中に子コンポーネント
子コンポーネントの転送位置をコントラストするためにLit using-named-slotsを使用します。
コンポーネント:
export default class RegisterForm extends LitElement {
constructor() {
super();
}
render() {
return html`
<form method="post" action="/action">
<slot name="title"></slot>
<slot name="button"></slot>
</form>
`;
}
html:
<register-from>
<register-from-title slot="title">コントロールタイトル</register-from-title>
<register-from-button slot="button">コントロールボタン文言</register-from-button>
</register-from>
slot に name プロパティを設置、指定した場所にタグを転送します。dev ツールにも確認ができます。
form送信のbuttonがslotの場合、formアクションが実行されない!?
button typeはsubmit
で作成され、slotを通して formに送信されました。
クリックしても、action に送信されません。
そして、htmlのバリデーションも実行しません。
これは Mdnでなにかヒントが潜んでる予感がします🧐!
HTMLFormElement
FormElement を調べてみると、便利なメソッドが見つかりました
これはバリデーションなのでは?reportValidity()かつ、boolean値が戻り値です。
そして、これはおなじみのsubmit()、バリデーションとsubmitがあれば、盤石のような安心感ですね! (その他のバリデーションももちろんあります!)
この2つのメソッドを使えば、フォーム送信ができます。
HTMlで実行しないなら、Javascriptで手動でやりましょ!
コンポーネントに onClick メソッドを追加します。その中でバリデーションと送信操作をします。
export default class RegisterForm extends LitElement {
...somecode
render() {
return html`
...somecode
<slot name="button" @click=${this.clickSubmit} id="js-slot-button"></slot>
`
}
clickSubmit() {
const formValidity = this.renderRoot.querySelector("#js-register-form").reportValidity();
if(!formValidity) return;
this.renderRoot.querySelector("#js-register-form").submit();
}
}
これで無事form送信できました
最後に
Lit web-componentsを使って、 Web-componentの中に複数子 web-componentを入れた実装になります。単純な displayより form を作成したことで、意外に落し穴がありました。困った時は、Mdnに探しするようにしますね。
仮想DOM と異なって、 Web-components少しい癖がありますが、軽量早いのがいい感じです。
Discussion