🥺

Lit Web-component 使ってカスタム HTMLタグのフォームを作成してみました

2022/11/10に公開

挨拶

ポートの就活会議でフロントを担当している新卒の @eicho.chinです。
最近、カスタムタグの開発要望がありまして、それを Lit で実装することがありました。

紹介

Web_Componentsについて
主な技術は3つあります:

  1. カスタム要素
  2. シャドウ DOM
  3. HTML テンプレート(<template><slot>)
    詳細が気になる方は上記のMdnリンクをご参照ください。

今回使う Litは 上記のWeb_componentsを拡張したライブラリになります。

Lit Simple. Fast. Web Components.

Lit導入の経緯

Litの特徴としては、軽量で開発しやすく、ライフサイクルが豊富で使い勝手が良いところです。今後もカスタムタグが多く出現と予想されるため、 Litを導入しました。

Litで実現したいこと

  1. カスタムHTMLタグを使えるようにする。
  2. フォームを表示するのですが、そのフォームのタイトルと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に送信されました。
try-web-component-1
クリックしても、action に送信されません。

try-web-component-2
そして、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