LoginSignup
1

More than 1 year has passed since last update.

Surfaceをつかってみる(Elixir/Phoenix)

Last updated at Posted at 2020-11-29

この記事は、Elixir その2 Advent Calendar 2020 3日目です。
前日は、LiveView uploadsを動かす 🎉🎉🎉(Elixir/Phoenix)でした。

2021/07/29(月) 追記

この記事はSurface 0.1.0時点のものです。
2021/07/19(月)時点では、だいぶ変わっているようですので、公式のドキュメントご確認ください :pray::pray_tone1::pray_tone2::pray_tone3::pray_tone4::pray_tone5:
@piacerex さんにコメントいただきました。ありがとうございます!
https://qiita.com/torifukukaiou/items/b5ae9eac42bd304b7aa3#comment-b883f2c4088f456d49a3

はじめに

  • Surfaceをつかってみます
    • 「A server-side rendering component library for Phoenix」です
    • Vue.jsにインスパイアされたそうで、An HTML-centric templating language with built-in directives (:for, :if, ...) and syntactic sugar for attributes (inspired by Vue.js).と書いてあります
    • Surfaceと聞くとこちらのWindowsマシンのことを私はまず想像してしまいますが、そうではありません
  • つい先日、0.1.0リリースされたばかりのHexです
    • 2020/11/28現在、最新は0.1.1です
  • この記事では、導入方法とりあえず使ってみましたということまで書いておきます

使ったバージョン

  • Elixir: 1.10.4-otp-23
  • mix phx.new -v: v1.5.6
  • node -v: v12.18.3
  • psql --version: psql (PostgreSQL) 12.4

ソースコード

デモ

output.gif

  • 今回公開している内容は以上です :rocket::rocket:

0. 準備

  • それではまずElixirをインストールしましょう
  • 手前味噌な記事ですがインストールなどを参考にしてください
  • phx_newnode.jsPostgreSQLなどを公式ドキュメントを参考にインストールしてください

1. プロジェクトの作成

$ mix phx.new my_web --live
$ cd my_web
$ mix ecto.create

2. Surfaceの導入

deps/0

mix.exs
@@ -37,7 +37,7 @@ defmodule MyApp.MixProject do
       {:phoenix_ecto, "~> 4.1"},
       {:ecto_sql, "~> 3.4"},
       {:postgrex, ">= 0.0.0"},
-      {:phoenix_live_view, "~> 0.14.6"},
+      {:phoenix_live_view, "~> 0.15.0", override: true},
       {:floki, ">= 0.27.0", only: :test},
       {:phoenix_html, "~> 2.11"},
       {:phoenix_live_reload, "~> 1.2", only: :dev},
@@ -46,7 +46,8 @@ defmodule MyApp.MixProject do
       {:telemetry_poller, "~> 0.4"},
       {:gettext, "~> 0.11"},
       {:jason, "~> 1.0"},
-      {:plug_cowboy, "~> 2.0"}
+      {:plug_cowboy, "~> 2.0"},
+      {:surface, "~> 0.1.0"}
     ]
   end

import Surfaceを追加します

lib/my_app_web.ex
  def view do
    quote do
      use Phoenix.View,
        root: "lib/my_app_web/templates",
        namespace: MyAppWeb

      # Import convenience functions from controllers
      import Phoenix.Controller,
        only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1]

      # Include shared imports and aliases for views
      unquote(view_helpers())
      import Surface
    end
  end

Bulmaを追加

lib/my_app_web/templates/layout/root.html.leex
@@ -8,6 +8,7 @@
     <%= live_title_tag assigns[:page_title] || "MyApp", suffix: " · Phoenix Framework" %>
     <link phx-track-static rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
     <script defer phx-track-static type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.8.0/css/bulma.min.css" />
   </head>
   <body>
     <header>

.formatter.exs

.formatter.exs
 [
-  import_deps: [:ecto, :phoenix],
+  import_deps: [:ecto, :phoenix, :surface],
   inputs: ["*.{ex,exs}", "priv/*/seeds.exs", "{config,lib,test}/**/*.{ex,exs}"],
   subdirectories: ["priv/*/migrations"]
 ]

Visual Studio Code

mix setup

$ mix setup
  • mix setupmix.exsに定義されているaliasです
  • ["deps.get", "ecto.setup", "cmd npm install --prefix assets"]をやってくれます
    • ecto.setup["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"]をやってくれます
  • これで準備は整いました :tada::tada::tada:

2. ソースコードを書く

lib/my_app_web/live/components/my_button.ex

lib/my_app_web/live/components/my_button.ex
defmodule MyAppWeb.Components.MyButton do
  use Surface.Component

  prop loading, :boolean
  prop rounded, :boolean

  def render(assigns) do
    ~H"""
    <button class={{ "button", "is-info", "is-loading": @loading, "is-rounded": @rounded }}>
      <slot/>
    </button>
    """
  end
end

lib/my_app_web/live/my_button_live.ex

lib/my_app_web/live/my_button_live.ex
defmodule MyAppWeb.MyButtonLive do
  use Surface.LiveView

  alias MyAppWeb.Components.MyButton

  data loading, :boolean, default: false
  data rounded, :boolean, default: false

  def mount(_params, _session, socket) do
    socket = Surface.init(socket)
    {:ok, assign(socket, checkboxes: [])}
  end

  def render(assigns) do
    ~H"""
    <MyButton loading={{ @loading }} rounded={{ @rounded }}>
      Change my style!
    </MyButton>

    <form phx-change="check_changed" style="margin-top: 30px">
      <input type="hidden" name="checkboxes[]" value="" />
      <label class="checkbox">
        <input type="checkbox" name="checkboxes[]" value="loading" checked={{ @loading }}>
        Loading
      </label>
      <label class="checkbox" style="margin-left: 20px">
        <input type="checkbox" name="checkboxes[]" value="rounded" checked={{ @rounded }}>
        Rounded
      </label>
    </form>

    <a href="https://qiita.com/torifukukaiou/items/b5ae9eac42bd304b7aa3">Surfaceをつかってみる(Elixir/Phoenix)</a>
    """
  end

  def handle_event(
        "check_changed",
        %{"_target" => ["checkboxes"], "checkboxes" => checkboxes},
        socket
      ) do
    loading = Enum.any?(checkboxes, &(&1 == "loading"))
    rounded = Enum.any?(checkboxes, &(&1 == "rounded"))
    {:noreply, assign(socket, loading: loading, rounded: rounded)}
  end
end

lib/my_app_web/router.ex

lib/my_app_web/router.ex
  scope "/", MyAppWeb do
    pipe_through :browser

    live "/", PageLive, :index
    live "/my-button", MyButtonLive # add
  end

3. Run

$ mix phx.server

Wrapping Up :lgtm: :qiita-fabicon: :lgtm:

  • Surfaceをとりあえず使ってみました
  • 大注目のHexです
  • 私はVue.jsは詳しくない1のですが、ファミリアーな方は<span v-once>This will never change: {{ msg }}</span>こういう書き方と似ているようように感じていただけたのではないでしょうか
  • Enjoy Elixir !!!

  1. たいていの他のこともたいして詳しいわけではない :man: 

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
1