はじめに
Phoenix LiveView は Elixir の Web フレームワークです
JavaScript をほぼ書くことなく、 Elixir でフロントエンドもバックエンドも実装できます
Flowbite は Tailwind CSS を使った UI コンポーネントのライブラリです
簡単に導入でき、様々な Web フレームワークと一緒に利用できます
Phoenix LiveView アプリケーションに Flowbite を導入する手順について、 Flowbite のドキュメントに掲載されています
しかし、情報が古い(Phoenix 1.6 時点)ので、2024年12月現在(Phoenix 1.7 時点)での手順を残しておきます
実装したアプリケーションはこちら
実行環境(前提条件)
Erlang と Elixir 、 Node.js をインストールしているものとします
各言語のバージョン
- Erlang 27.2
- Elixir 1.17.3
- Node.js 22.12.0
macOS であれば mise や asdf を使ってインストールするのがおすすめです
mise の場合
mise use -g erlang@27.2
mise use -g elixir@1.17.3
mise use -g node@22.12.0
asdf の場合
asdf plugin add erlang
asdf install erlang 27.2
asdf global erlang 27.2
asdf plugin add elixir
asdf install elixir 1.17.3
asdf global elixir 1.17.3
asdf plugin add nodejs
asdf install nodejs 22.12.0
asdf global nodejs 22.12.0
elixir のパッケージマネージャー Hex をインストールします
mix local.hex
Phoenix プロジェクトを生成するための phx_new をインストールします
mix archive.install hex phx_new
Phoenix プロジェクトの作成
Phoenix プロジェクトを作成します
今回は UI だけ見たいので DB 操作をしないため --no-ecto
のオプションを付けています
mix phx.new phoenix_flowbite_sample --no-ecto
Fetch and install dependencies? [Yn]
と質問されるので、そのまま Enter を押下(デフォルトの yes で回答)します
phoenix_flowbite_sample ディレクトリーが作成されるので、中に移動します
cd phoenix_flowbite_sample
Phoenix アプリケーションを起動します
mix phx.server
http://localhost:4000 にアクセスすると、以下のような画面が表示されます
一旦、 Ctrl + C でアプリケーションを停止します
Flowbite の導入
Flowbite のドキュメントでは Tailwind CSS 導入の手順を書いていますが、 Phoenix 1.7 ではデフォルトで Tailwind CSS インストール済になっています
Tailwind CSS 導入の手順を飛ばして、 Flowbite を導入します
assets ディレクトリーに移動します
cd assets
以下の内容で assets/package.json
ファイルを作成します
{
"name": "phoenix_flowbite_sample",
"version": "1.0.0",
"main": "js/app.jp",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"flowbite": "^2.5.2"
}
}
Flowbite をインストールします
npm install
上のディレクトリーに戻っておきます
cd ..
assets/tailwind.config.js
を以下のように編集します
...
module.exports = {
content: [
"./js/**/*.js",
+ "./node_modules/flowbite/**/*.js",
"../lib/phoenix_flowbite_sample_web.ex",
"../lib/phoenix_flowbite_sample_web/**/*.*ex"
],
...
plugins: [
+ require('flowbite/plugin'),
require("@tailwindcss/forms"),
...
}
assets/js/app.js
を以下のように編集します
...
import topbar from "../vendor/topbar"
+import "flowbite/dist/flowbite.phoenix.js"
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
...
Flowbite コンポーネントの配置
lib/phoenix_flowbite_sample_web/controllers/page_html/home.html.heex
を以下の内容に書き換えます
<.flash_group flash={@flash} />
<div class="px-20 py-10 sm:px-20 sm:py-28 lg:px-20 xl:px-60 xl:py-32">
<div id="alerts">
<div
class="flex items-center p-4 mb-4 text-sm text-blue-800 rounded-lg bg-blue-50 dark:bg-gray-800 dark:text-blue-400"
role="alert"
>
<svg
class="flex-shrink-0 inline w-4 h-4 me-3"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 20 20"
>
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z" />
</svg>
<span class="sr-only">Info</span>
<div>
<span class="font-medium">Info alert!</span>
Change a few things up and try submitting again.
</div>
</div>
<div
class="flex items-center p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400"
role="alert"
>
<svg
class="flex-shrink-0 inline w-4 h-4 me-3"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 20 20"
>
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z" />
</svg>
<span class="sr-only">Info</span>
<div>
<span class="font-medium">Danger alert!</span>
Change a few things up and try submitting again.
</div>
</div>
<div
class="flex items-center p-4 mb-4 text-sm text-green-800 rounded-lg bg-green-50 dark:bg-gray-800 dark:text-green-400"
role="alert"
>
<svg
class="flex-shrink-0 inline w-4 h-4 me-3"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 20 20"
>
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z" />
</svg>
<span class="sr-only">Info</span>
<div>
<span class="font-medium">Success alert!</span>
Change a few things up and try submitting again.
</div>
</div>
<div
class="flex items-center p-4 mb-4 text-sm text-yellow-800 rounded-lg bg-yellow-50 dark:bg-gray-800 dark:text-yellow-300"
role="alert"
>
<svg
class="flex-shrink-0 inline w-4 h-4 me-3"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 20 20"
>
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z" />
</svg>
<span class="sr-only">Info</span>
<div>
<span class="font-medium">Warning alert!</span>
Change a few things up and try submitting again.
</div>
</div>
<div
class="flex items-center p-4 text-sm text-gray-800 rounded-lg bg-gray-50 dark:bg-gray-800 dark:text-gray-300"
role="alert"
>
<svg
class="flex-shrink-0 inline w-4 h-4 me-3"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 20 20"
>
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z" />
</svg>
<span class="sr-only">Info</span>
<div>
<span class="font-medium">Dark alert!</span>
Change a few things up and try submitting again.
</div>
</div>
</div>
</div>
<div id="alerts">...</div>
内部については Flowbite のドキュメントからコピーしたものです(貼り付けて保存後、 mix format
を実行して整形しています)
フロントエンドだけで動作が完結するものに関してはコピー&ペーストだけで実装終了です
この状態でアプリケーションを起動します
mix phx.server
http://localhost:4000 にアクセスすると、以下の画面が表示されます(ライトモードの表示)
その他、アコーディオンなど、様々なコンポーネントが Flowbite 公式ドキュメントに掲載されています
コンポーネント化
このままだと扱いにくいので、 Phoenix LiveView のコンポーネントにしてしまいましょう
以下の内容で lib/phoenix_flowbite_sample_web/components/flowbite_components.ex
を作成します
defmodule PhoenixFlowbiteSampleWeb.FlowbiteComponents do
use Phoenix.Component
use Gettext, backend: PhoenixFlowbiteSampleWeb.Gettext
attr :id, :string, required: true
attr :type, :atom, default: :info, values: [:info, :success, :warning, :danger, :dark]
slot :inner_block, required: true
def alert(assigns) do
~H"""
<div
id={@id}
class={[
"flex items-center p-4 mb-4 text-sm rounded-lg dark:bg-gray-800",
case @type do
:info -> "text-blue-800 bg-blue-50 dark:text-blue-400"
:success -> "text-green-800 bg-green-50 dark:text-green-400"
:warning -> "text-yellow-800 bg-yellow-50 dark:text-yellow-400"
:danger -> "text-red-800 bg-red-50 dark:text-red-400"
:dark -> "text-gray-800 bg-gray-50 dark:text-gray-400"
end
]}
role="alert"
>
{render_slot(@inner_block)}
</div>
"""
end
def info_icon(assigns) do
~H"""
<svg
class="flex-shrink-0 inline w-4 h-4 me-3"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 20 20"
>
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z" />
</svg>
"""
end
end
lib/phoenix_flowbite_sample_web.ex
を以下のように編集します
defmodule PhoenixFlowbiteSampleWeb do
...
defp html_helpers do
quote do
# Translation
use Gettext, backend: PhoenixFlowbiteSampleWeb.Gettext
# HTML escaping functionality
import Phoenix.HTML
# Core UI components
import PhoenixFlowbiteSampleWeb.CoreComponents
+ import PhoenixFlowbiteSampleWeb.FlowbiteComponents
# Shortcut for generating JS commands
alias Phoenix.LiveView.JS
# Routes generation with the ~p sigil
unquote(verified_routes())
end
end
...
end
これにより、 lib/phoenix_flowbite_sample_web/controllers/page_html/home.html.heex
は以下のように書き換えられます
<.flash_group flash={@flash} />
<div class="px-20 py-10 sm:px-20 sm:py-28 lg:px-20 xl:px-60 xl:py-32">
<div id="alerts">
<.alert id="info-alert" type={:info}>
<.info_icon />
<span class="sr-only">Info</span>
<div>
<span class="font-medium">Info alert!</span>
Change a few things up and try submitting again.
</div>
</.alert>
<.alert id="danger-alert" type={:danger}>
<.info_icon />
<span class="sr-only">Danger</span>
<div>
<span class="font-medium">Danger alert!</span>
Change a few things up and try submitting again.
</div>
</.alert>
<.alert id="success-alert" type={:success}>
<.info_icon />
<span class="sr-only">Success</span>
<div>
<span class="font-medium">Success alert!</span>
Change a few things up and try submitting again.
</div>
</.alert>
<.alert id="yellow-alert" type={:warning}>
<.info_icon />
<span class="sr-only">Warning</span>
<div>
<span class="font-medium">Warning alert!</span>
Change a few things up and try submitting again.
</div>
</.alert>
<.alert id="gray-alert" type={:dark}>
<.info_icon />
<span class="sr-only">Dark</span>
<div>
<span class="font-medium">Dark alert!</span>
Change a few things up and try submitting again.
</div>
</.alert>
</div>
</div>
かなりスッキリしましたね
コンポーネント化する場合、 CoreComponents で定義されているコンポーネントとの衝突(同じ名前の関数)に気をつけましょう
Flowbite Datepicker の導入
Datepicker は JavaScript 実装の都合上、フックを利用する必要があります
assets/js/app.js
を以下のように編集します
...
import Datepicker from "flowbite-datepicker/Datepicker"
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
Hooks = {}
Hooks.Datepicker = {
mounted() {
const datepickerEl = this.el;
new Datepicker(datepickerEl, {
// options
});
},
updated() {
this.mounted();
}
}
let liveSocket = new LiveSocket("/live", Socket, {
longPollFallbackMs: 2500,
params: {_csrf_token: csrfToken},
hooks: Hooks
})
...
Datepicker をインポートし、 Datepicker
のフックを持つ DOM 要素が追加されたときに Datepicker が動くようにしています
lib/phoenix_flowbite_sample_web/controllers/page_html/home.html.heex
について、以下のように phx-hook="Datepicker"
を持つ input 要素を追加してみます
<.flash_group flash={@flash} />
<div class="px-20 py-10 sm:px-20 sm:py-28 lg:px-20 xl:px-60 xl:py-32">
<input
phx-hook="Datepicker"
id="myInput"
type="text"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-10 p-2.5 mb-4 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Select date"
/>
...
この状態でアプリケーションを起動すると、 DatePicker で日付選択ができます
まとめ
Flowbite を Phoenix LiveView アプリケーションに導入できました
Pines や SaladUI など、コンポーネントライブラリーは色々あるので、デザインが良いもの、便利なものなどを選びましょう