LoginSignup
2
0

More than 1 year has passed since last update.

Litで作ったWebコンポーネントをSpring Web MVCのWebアプリに組み込んだ話

Posted at

LitはWebコンポーネントを開発するのに使用できるモダンなフロントエンドフレームワークです。

Webコンポーネント(Web Components | MDN)は、簡単に言えば以下のような感じで使用できる再利用可能なHTMLタグという感じです。

<html>

  <script type="module" src="sample-web-components.js"></script>

  <sample-web-components param="なんらかのパラメータ"></sample-web-components>

</html>

私は普段フロントエンド開発でAngularやVue等のフレームワークを使っているので、それらに対応したオープンソースの部品を見つけてきたり、自分で実装したりすれば事足りていました。そのため、特にWebコンポーネントを使う理由はあまり無いと思っていました。

しかし、ある案件でWebコンポーネントが良い感じにハマる構成があったので、本記事で紹介したいと思います。

アプリの概要

このアプリはいわゆる典型的なSpring Web MVCのマルチページアプリケーションで、JavaのロジックやボイラープレートコードとHTMLテンプレートで構成されていました。

image.png

このアプリの機能改修である画面を追加することになり、画面の要件が「サーバーから取得したデータをリッチなUIで色々と編集し、保存ボタンでサーバーに送信して保存する」という物でした。

「リッチなUI」にはCanvasでいい感じにデータを表示したり、Canvas上のマウス操作でデータを編集するといった機能が含まれていたため、JavaScriptのロジックが必要でした。
しかし、Spring自体はフロントエンド側の実装とは関係が無いため、JavaScriptを自分で書く必要があります。
昔はjQuery等のライブラリで頑張って実装したものですが、最近はVSCode/TypeScript/ESLintといった開発環境に慣れてしまったためもはや昔のような生JavaScriptを書きたくありませんでした。

そこで考えたのが、以下のような方式です。

  • 編集対象のデータはJSONで表現する。
  • 画面の表示時は、DBから取り出したデータをJSON化してViewに埋め込む。
    • <script>タグ内にJavaScriptオブジェクトとしてグローバル定数にする。
  • リッチなUI部分はWebコンポーネントで実装する。
    • 実装したWebコンポーネントはHTMLテンプレート内で読み込んで使用する。
  • フロントエンドからサーバーへの送信は通常のフォーム送信(formタグ)で行う。
    • Webコンポーネントでは編集後のデータをJSON化→文字列化して<input type="hidden">要素のvalueに設定する。
    • その状態でformをsubmitすることで、編集後のデータを文字列としてサーバーに送信する。
  • サーバーでは受信したJSONを適宜DBに格納する形に変換して保存する。

image.png

この方式には以下の利点があると思っています。

  • リッチなUI部分をLitを使って実装できる(TypeScriptでコーディングできる!)
  • バックエンド(Java)側はシンプルなデータ形式の変換ロジックのみで実装できる。
    • 特殊な処理は必要なく、HTML内でWebコンポーネントのタグを書くだけで使用できる

Litコード

公式ドキュメントlit.devにはLit TypeScript starter projectが掲載されていますが、たまたま調べていたViteのテンプレートにLitの物があったため、こちらを使用します。

$ npm create vite@latest my-lit-component -- --template lit-ts

以下のようなディレクトリ構成が出力されます。

my-lit-component/
  public/
    vite.svg       生成されたサンプルページ内に表示される画像アセット(後で消す)
  src/
    assets/
      lit.svg      生成されたサンプルページ内に表示される画像アセットその2(後で消す)
    index.css      index.htmlから読み込む用のCSS
    my-element.ts  Webコンポーネントのコード本体
    vite-env.d.ts
  .gitignore
  index.html       テスト用のメインHTML
  package.json
  tsconfig.json

以下のように実行することで、テスト用のメインHTML内にWebコンポーネントが埋め込まれた状態で起動します。

$ npm install
$ npm run dev

image.png
↑ボタンをクリックすると数字が増えるよくあるサンプル

サーバーからの値を表示

image.png

サーバーからの値をLitコンポーネント側から参照する部分の概略です。
まず、テスト用のメインHTMLをSpring側のテンプレートHTMLを想定した物に書き換えます。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + Lit + TS</title>
    <link rel="stylesheet" href="./src/index.css" />

    <!-- 追加: サーバーでデータを埋め込む想定のscriptタグ -->
    <script type="module">
      window.myAppData = {
        "data": [
          {"name": "John", "age": 20},
          {"name": "Charlie", "age": 35}
        ]
      };
    </script>

    <script type="module" src="/src/my-element.ts"></script>
  </head>
  <body>
    <my-element></my-element>
  </body>
</html>

追加: とある部分が追加した箇所です。

コンポーネント側(my-element.ts)からは以下のように参照できます。

  data: any = (window as any).myAppData; // TODO 型定義

  render() {
    const dataElements = this.data.data.map(
      (item: any) => html`<div>name = ${item.name}, age = ${item.age}</div>`
    );
    return html` <div>${dataElements}</div> `;
  }

(型定義の部分は適宜d.tsで定義することが可能ですが今回は省略しています)

サーバーに値を送信

image.png

サーバーに値を送信する箇所は以下のような感じになります。
HTML側はformタグと、デバッグ用にformで送信されるはずの値を表示できるようにします。

  ...
  <body>

    <!-- 追加: ユーザー操作で送信するフォーム -->
    <section class="debug-form">
      <form id="debugForm" method="post">
        <input id="dataOutput" name="data" type="hidden">
        <input type="submit" value="送信">
      </form>
    </section>

    <!-- 追加: デバッグ用に送信されたフォームのJSON値を表示する処理 -->
    <div id="debugOutput" class="debug-output"></div>
    <script type="module">
      function debugSubmit(e) {
        e.preventDefault();
        // <input id="dataOutput">から値を取得して<div id="debugOutput">内に表示する
        const jsonValue = document.getElementById("dataOutput").value
        document.getElementById("debugOutput").textContent = jsonValue;
      }
      document.getElementById("debugForm").addEventListener("submit", debugSubmit);
    </script>

    <my-element></my-element>

  </body>

リッチなUI部分は省きますが、コンポーネント側(my-element.ts)からformに値をセットする部分は以下のようなシンプルなコードになります。

  private _setOutputValue() {
    const jsonString = JSON.stringify(this.data); // this.dataはリッチなUIにより書き換え済み
    const inputElement = document.getElementById("dataOutput") as HTMLInputElement;
    inputElement.value = jsonString;
  }

あとは、Litのコンポーネントで好きなだけリッチなUIのロジックを作るだけです。

まとめ

本記事では、LitコンポーネントをSpring Web MVCのマルチページアプリケーションに組み込んだらいい感じにできた話を紹介しました。
今後も、同様の構成ではLitコンポーネントの使用を検討したいと思います。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0