8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Phoenix+Elmでマルチページ

Last updated at Posted at 2018-04-02

 Phoenix+Elmでマルチページを実現するための枠組みの基本を考えてみました。SPA(シングルページアプリケーション)ではなくマルチページです。Brunchのなじみが浅い私の個人的な備忘録なので、もっとベターな方法があると思いますが、ご指摘いただければ幸いです。

#1. ページ設計

 まず今回の実験のページ設計ですが、以下のように3枚のページ(html)を用意するつもりです。それぞれのパスでアクセスすれば、elmプログラムを含んだページを表示します。目標はシンプルです。

"/"           -- トップページ (Elm無し)
"/page1"      -- ページ1(Hello.elm)
"/page2"      -- ページ2(MyButtons.elm)

#2. Plug.Staticの設定

 それではプロジェクトを作成します。

mix phx.new multi_pages --no-ecto

 後で述べますが、Elmプログラムをコンパイルした結果のjsファイルはテンプレートファイルから以下のようにして直接ロードします。

<script src="/js/mybuttons.js"></script>

 そのためPhoenixの設定でstaticファイルを読み込む設定を変えます。onlyをコメントアウトします。許可ファイルを追加しても良いですが、ここではコメントアウトしpriv/staticにあるファイルを全てpublicなものにします。PhoenixでStatic Fileをサーブする - Qiita

lib/multi_pages_web/endpoint.ex
#
  plug Plug.Static,
    at: "/", from: :multi_pages, gzip: false
    # only: ~w(css fonts images js favicon.ico robots.txt)
#

#3. routerの設定

上の「1.ページ設計」で示した通りにrouter.exを変更します。

lib/multi_pages_web/router.ex
#
  scope "/", MultiPagesWeb do
    pipe_through :browser # Use the default browser stack
    get "/", PageController, :index
    get "/page1", Page1Controller, :hello
    get "/page2", Page2Controller, :buttons
  end
#

#4. トップページの設定

 最初にトップページを変更します。ヘッダーに他のページ(page1, page2)へのリンクを貼ります。デフォルトのレイアウト(app.html.eex)を変更します。

lib/multi_pages_web/templates/layout/app.html.eex
#
      <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
      <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
      <a href="/page1">ページ1</a> /
      <a href="/page2">ページ2</a>

      <main role="main">
        <%= render @view_module, @view_template, assigns %>
      </main>

    </div> <!-- /container -->

  </body>
</html>

 またこの時以下の行を削除しておきます。トップページではjsやelmは使わないので不要です。

    <script src="<%= static_path(@conn, "/js/app.js") %>"></script>

 またトップページのテンプレートは変更しません。
lib/multi_pages_web/templates/page/index.html.eex

 controllerもviewもデフォルトのままで変更しません。

 以上でトップページは終わりです。

#5. ページ1の設定

 ページ1を追加していきます。controllerを以下のように定義します。今回は実験ですのでレイアウトも変更しています。

lib/multi_pages_web/controllers/page1_controller.ex
defmodule MultiPagesWeb.Page1Controller do
  use MultiPagesWeb, :controller

  def hello(conn, _params) do
    conn
    |> put_layout("layout_hello.html")
    |> render("hello.html")
  end
end

 viewも定義します。中身はデフォルトのままです。

lib/multi_pages_web/views/page1_view.ex
defmodule MultiPagesWeb.Page1View do
  use MultiPagesWeb, :view
end

 テンプレートを追加するので、ディレクトリを作成します。

mkdir lib/multi_pages_web/templates/page1

 テンプレートを追加します。elmプログラムのためのプレースフォルダを設定し、elmプログラムを明示的に指定していることに注意してください。まあ、通常のelmの設定ですね。

lib/multi_pages_web/templates/page1/hello.html.eex
<h1>Page1</h1>
<h3>Hello page</h3>

<div id="elm-hello-area"></div>
<script src="/js/vendor/hello.js"></script>
<script>
    Elm.Hello.embed(document.getElementById("elm-hello-area"));
</script>

 レイアウトはトップページのものをコピーし、リンクを以下のように変更します。

lib/multi_pages_web/templates/layout/layout_hello.html.eex
#
      <a href="/">トップページ</a> /
      <a href="/page2">ページ2</a>
#

 以上でページ1の設定は終わりです。

#6. ページ2の設定

 ページ2の設定は、ページ1とほぼ同じです。ファイル名やモジュール名、関数名などが異なるだけです。

 controllerです。

lib/multi_pages_web/controllers/page2_controller.ex
defmodule MultiPagesWeb.Page2Controller do
  use MultiPagesWeb, :controller

  def buttons(conn, _params) do
    conn
    |> put_layout("layout_buttons.html")
    |> render("buttons.html")
  end
end

 viewです。

lib/multi_pages_web/views/page2_view.ex
defmodule MultiPagesWeb.Page2View do
  use MultiPagesWeb, :view
end

 テンプレートディレクトリを作成します。

mkdir lib/multi_pages_web/templates/page2

 テンプレートです。

lib/multi_pages_web/templates/page2/buttons.html.eex
<h1>Page2</h1>
<h3>My Buttons page</h3>

<div id="elm-buttons-area"></div>
<script src="/js/vendor/mybuttons.js"></script>
<script>
    Elm.MyButtons.embed(document.getElementById("elm-buttons-area"));
</script>

 レイアウトです。

lib/multi_pages_web/templates/layout/layout_buttons.html.eex
#
      <a href="/">トップページ</a> /
      <a href="/page1">ページ1</a>
#

 以上でページ2の設定を終わります。

#7. Elmクライアントの設定

 Brunchの設定をしていきます。

cd assets/
npm install --save-dev elm-brunch

 brunch-config.jsを変更します。今回の肝の部分です。

assets/brunch-config.js
#
  // Phoenix paths configuration
  paths: {
    // Dependencies and current project directories to watch
    // (1)"elm"を追加
    watched: ["static", "css", "js", "elm", "vendor"],
    // Where to compile files to
    public: "../priv/static"
  },

  // Configure your plugins
  plugins: {
    babel: {
      // Do not use ES6 compiler in vendor code
      ignore: [/vendor/]
    },
    // (2)elmBrunchを追加
    elmBrunch: {
      elmFolder: "elm",
      mainModules: ["Hello.elm", "MyButtons.elm"], // Elmプログラム
      outputFolder: "../static/js/vendor"  // Elmの実行形をstaticに吐き出す
    }
  },
#

(※重要)outputFolderにはvendorサブディレクトリが指定されていることに注意してください。brunchにおいてはvendorにあるfileが優先的にロードされるようです。つまりElmのjsファイルが安全にロードされます。単にoutputFolder を "../static/js"と vendor抜きで指定すると、かなりの確率でElmのjsファイルのロードに失敗してしまいます。

 今回は2つのelmプログラムを使います。page1でHello.elmを、page2でMyButtons.elmを走らせます。elmプログラムをmainModulesで指定し、assets/static/jsにコンパイルするように指定しました。brunchはassets/static/ 下のファイルををそのままpriv/static/にコピーします。priv/static/は「2.Plug.Staticの設定」での設定によりpublicとなっており、テンプレートから以下のように指定することができます。

<script src="/js/vendor/hello.js"></script>
<script src="/js/vendor/mybuttons.js"></script>

 次にassets/static/jsディレクトリとasstes/elmディレクトリを作成し、必要なelmパッケージをインストールしておきます。

mkdir static/js
mkdir elm
cd elm
elm-package install elm-lang/html

 Hello.elmがpage1で走らせるelmプログラムです。

assets/elm/Hello.elm
module Hello exposing (..)
import Html exposing (text)

main =
  text "Hello, World!"

 MyButtons.elmがpage2で走らせるelmプログラムです。これはMyButtonsBody.elmをimportしています。動作を確認するためにわざとElmプログラムを分割してみました。そもそもelmコンパイラは、複数の分割ファイルをリンクしてくれますので、この点ではbrunchに頼る必要がありません。複数ファイルを一つの実行形にまとめるのはelmコンパイラであり、brunchではありません。ちなみにbrunchがまとめたapp.jsファイルは使わないで無視します。

assets/elm/MyButtons.elm
module MyButtons exposing (..)

import Html
import MyButtonsBody exposing (model, update, view)

main =
  Html.beginnerProgram { model = model, view = view, update = update }

 MyButtons.elmはMyButtonsBody.elmをimportしています。

assets/elm/MyButtonsBody.elm
module MyButtonsBody exposing (model, update, view)

import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)

-- MODEL
type alias Model = Int

model : Model
model =
  0

-- UPDATE
type Msg = Increment | Decrement

update : Msg -> Model -> Model
update msg model =
  case msg of
    Increment ->
      model + 1

    Decrement ->
      model - 1

-- VIEW
view : Model -> Html Msg
view model =
  div []
    [ button [ onClick Decrement ] [ text "-" ]
    , div [] [ text (toString model) ]
    , button [ onClick Increment ] [ text "+" ]
    ]

 以上でクライアント側のElmの設定は終わりです。

#8. プログラムの実行

 トップページです。レイアウトを変更してページ1とページ2のリンクを設けました。

image.png

 ページ1です。リンクを変更しました。「Hello world!」はelmプログラムが走った結果です。

image.png

 ページ2です。リンクを変更しました。カウンターを増減させるボタンが表示されています。これもelmプログラムです。

image.png

 以上です。

8
4
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
8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?