スポンサーリンク

Elmで複数ページからなる極簡単なSPA(シングルページアプリケーション)を作成(ソースコードあり)

2019年6月8日

前回は、『基礎からわかるElm』 をとにかく写経して、SinglePageApplicationを作成しました。

Navigationのところが、いまいち理解できないので、An Introduction to Elm のナビゲーションのところを写経してみたいと思います。

→ と思ったら、『基礎からわかるElm』を写経してみる(5)ナビゲーション のコードが、 An Introduction to Elm のナビゲーションのコードと一緒だったことに気づきました。なので、今回は少しだけVIEWのところを変えてみたいと思います。

スポンサーリンク

開発環境

Windows 10 Pro
Chrome
VisualStudioCode 1.32.3
git version 2.20.1.windows.1
nvm 1.1.7
node 10.2.0
npm 6.4.1
elm 0.19.0-bugfix6
elm-format 0.8.1
VisualStudioCodeの拡張機能でelmをインストールして、settings.jsonに以下のようにelmを設定。
(『Alt + Shift + F』と『Ctrl + S』を使用。)

    "[elm]": {
        "editor.formatOnSave": true
    },
elm-live 3.4.1
elm-test 0.19.0-rev6

作るもの

  • 複数ページからなるSPA(SinglePageApplication)
  • リンクをクリックすると、ページが変わる

ソースコード
https://github.com/adash333/elm-navigation2

DEMOサイト
https://determined-colden-a15e32.netlify.com/

新規Elmアプリの作成

C:/elm/ フォルダに、elm-intro-navigation/ フォルダを作成し、VisualStudioCodeで開き、Ctrl+@でコマンドプロンプトを開き、以下を入力します。
途中で何か聞かれたらEnterを押します。

elm init
elm install elm/url

Elmでbulmaを使用し、Netlifyにデプロイするための準備

次に、

https://github.com/adash333/elm-github-viewer1

から、.gitignore, index.html, netlify.toml をコピペします。

index.html

netlify.toml

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

src/Main.elmの新規作成

src/Main.elmを新規作成し、以下を入力します。SPA(SinglePageApplication)を作成するため、main関数はBrowser.applicationを用いることになります。

参考:Elm 0.19 の初期化方法 6 種類

module Main exposing (main)

import Browser
import Browser.Navigation as Nav
import Html exposing (..)
import Html.Attributes exposing (..)
import Url



-- MAIN


main : Program () Model Msg
main =
    Browser.application
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        , onUrlChange = UrlChanged
        , onUrlRequest = LinkClicked
        }

-- SUBSCRIPTIONS

subscriptions : Model -> Sub Msg
subscriptions _ =
    Sub.none

  
-- MODEL

-- UPDATE

-- VIEW

MODELの作成

An Introduction to Elm のナビゲーションのコードのMODELのところを写経してみます。

type alias Model =
    { key : Nav.Key
    , url : Url.Url
    }


init : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg )
init flags url key =
    ( Model key url, Cmd.none )

いきなり、type alias Modelの中身がわけわかりません。

urlはなんとなく、ブラウザのアドレス欄に表示されるURLだとわかるのですが、Nav.keyがこのアプリの何の状態を示しているのかさっぱりわかりません。

このModelを見る限り、アプリのもつ状態としては、

  • ブラウザに表示されるURL
  • その他の情報(Nav.key) → ???

があるらしいのです。Nav.keyは、Browser.Navigation.keyのことらしく、Browser.Navigationの解説ページを読んでみることにします。

 
https://package.elm-lang.org/packages/elm/browser/latest/Browser-Navigation

ちょっと軽く読んでみます。

pageがloadされるってどういうこと?

Browser.Navigationでは、pushUrl 関数が、一番重要です。pushUrl 関数は、WEBブラウザのアドレスバーのURLを変化させること無く、ページpageのloadを始めます。

pageがloadされるとき、以下の4つの手順を踏むそうです。

  1. HTMLドキュメントをrequestすると同時に、ページが真っ白blankになる
  2. HTMLがloadされると、さらに、<script>または<link>関係がrequestされる
  3. <script>の中身が、HTMLドキュメントを変化させる
  4. すべてのアセットassetsがloadされて、ページが表示される

この途中の『ページが真っ白になる』(本では、よく、『ページがちらつく』と書かれています)のが、ユーザー体験を損なうらしいです。(ページか3秒以上真っ白のままだと、ほとんどのユーザーがそのサイトを離れてしまうというデータがあるそうです。個人的には何か表示されていても3秒以上待つ気はしませんが、、、)

pushUrl 関数は何をしてくれるか?

pushUrl 関数は、URLを変更するが、HTMLは変更せずにそのままにします。そのため、ページが真っ白blankになりません。

ページをloadしている間に、loading barや、『サーバからデータをロード中ですよ。』など、表示し、ページを真っ白にする必要が無いです。

Navigate within Page (アプリ内でのナビゲーション)

type Key

URLを変更するためのnavigationコマンドを作成するために、Navigation Key が必要です。

navigationコマンドとして、pushUrl関数、replaceUrl関数、back関数、forward関数があります。

Browser.applicationを用いた場合のみ、Navigation Keyにアクセスすることができます。(Browser.elementやBrowser.documentでは、Navigation KeyやpushUrl関数を使用することはできない。)

このNavigation Keyをよく理解できなかったのですが、とりあえず、

SPAでルーティングを行う(pushUrl関数などを使用する)ためには、アプリの状態(Model)としてNavigation Keyが必要!

と覚えておきます。つまり、MODELのところには、

-- elm initのあと、elm install elm/url をしておく

import Browser
import Browser.Navigation as Nav
import Url

-- MODEL

type alias Model =
    { key : Nav.Key
    , url : Url.Url
    }


init : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg )
init flags url key =
    ( Model key url, Cmd.none )

と書いておけばよいそうです。

pushUrl関数

pushUrl : Key -> String -> Cmd msg

pushUrl関数は、Navigation.KeyとString(URLの文字列)からCmd msgを返す関数です。

ブラウザのURLを変更しますが、ページのloadは行いません。

browser historyに、URLを追加します。

URLの作成については、elm/url パッケージを参照するとよいそうです。

Navigate to other Pages(アプリ外へのリンク)

load関数を用います。

load : String -> Cmd msg

Stringは外部リンクのアドレスとなります。
load関数は、外部リンクのアドレスを受け取り、ページ(Htmlファイル)のloadも行います。

Browser.Navigationのページを読むのはここまでとします。

UPDATEの作成

基礎からわかるElm』  のp179『SECTION-030 ナビゲーション』のコードを参考に、以下のように写経していきます。

まず、Msgですが、

  • リンクをクリックしたとき "LinkClicked"
  • ブラウザのURLが変化したとき "UrlChanged" (リンクをクリックしてから、ブラウザのアドレスバーのURLが変化するまで、タイムラグがあります。)

の2つが考えられるとのことで、引数も含めて、以下のようになります。

-- UPDATE

type Msg
    = LinkClicked Browser.UrlRequest
    | UrlChanged Url.Url


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        -- (2)画面遷移のリクエストを受けたとき
        LinkClicked urlRequest ->
            --(ここに内部リンクと外部リンクに分けて実装)
        -- (3)ブラウザのアドレス欄のURLが変更されたとき
        UrlChanged url ->
            ( { model | url = url }
            -- 今回は何もしませんが、本当はここでサーバーからデータをもらうことが多い
            , Cmd.none
            )

画面遷移のリクエストを受けたときの実装ですが、

  • 内部リンク
  • 外部リンク

の2つの場合を記載しますが、これは定型文となりますので、覚えるしか無さそうです。

    case msg of
        -- (2)画面遷移のリクエストを受けたとき
        LinkClicked urlRequest ->
            case urlRequest of
                -- 内部リンクならブラウザのURLを更新する(pushUrl関数)
                Browser.Internal url ->
                    ( model, Nav.pushUrl model.key (Url.toString url) )

                -- 外部リンクなら通常の画面遷移を行う(load関数)
                Browser.External href ->
                    ( model, Nav.load href )

この内部リンクのときにpushUrl関数を使用するため(引数にkey と URLが必要)に、Navigation KeyをMODELに設定する必要があるのですね。よくわかりませんが、そういうものと理解しておきます。

VIEWの作成

VIEWには

  • リンクをクリックするための場所
  • どのURLのときに、どの内容を表示するか

を記載していくことになります。

-- VIEW


view : Model -> Browser.Document Msg
view model =
  { title = "URL Interceptor"
  , body =
      [ text "The current URL is: "
      , b [] [ text (Url.toString model.url) ]
      , ul []
          [ viewLink "/home"
          , viewLink "/profile"
          , viewLink "/reviews/the-century-of-the-self"
          , viewLink "/reviews/public-opinion"
          ]
      ]
  }


viewLink : String -> Html msg
viewLink path =
  li [] [ a [ href path ] [ text path ] ]

いらすとや 
https://www.irasutoya.com/2016/06/blog-post_575.html  から、画像をダウンロードして、index.htmlと同じ階層(一番上の階層)に画像をhome.pngとして保存します。

“/home"のときに、/home.png を表示したい場合は、とりあえず、開発サーバで表示する場合は、以下のように記載する必要がありました。

      , case (Url.toString model.url) of
          "http://localhost:8000/home" ->
              img [ src "/home.png" ] []
          "/profile" ->
              div [] []
          "/reviews/the-century-of-the-self" ->
              div [] []
          "/reviews/public-opinion" ->
              div [] []
          _ ->
              div [] []

elmに特徴的なところとして、

          _ ->
              div [] []

を記載しないと、コンパイルエラーが出ていました。

elm-liveで開発サーバで表示してみます。

elm-live src/Main.elm --open -- --output=main.js

elm/urlについて

公式サイトのUrlパッケージのページ 
https://package.elm-lang.org/packages/elm/url/1.0.0/ を見てみます。

今回のアプリで抜き出したい部分は、

path

の部分なので、

      , case (Url.toString model.url) of
          "http://localhost:8000/home" ->
              img [ src "/home.png" ] []

のところを、

      , case model.url.path of
          "/home" ->
              img [ src "/home.png" ] []

に変更すればよいのかなと思いました。やってみます。

うまくいきました。

Urlの型が、以下のように、elmの”レコード”型となっているので、URL(http:localhost:8000/home)から、path(/homeの部分)を取り出すためには、

model.url.path

と記載すればよく、返り値はString型となり、文字列と比較することができるのですね。

type alias Url =
    { protocol : Protocol
    , host : String
    , port_ : Maybe Int
    , path : String
    , query : Maybe String
    , fragment : Maybe String
    }

参考:
https://matsubara0507.github.io/posts/2018-12-11-detail-elm-url.html
詳解 elm/url !!
Dec 11, 2018

bulmaで少しだけ見栄えを変更

いらすとや https://www.irasutoya.com/  からもう少し画像をダウンロードしてindex.htmlと同じ階層に保存し、bulmaで少しだけ見栄えを変更してみて、以下を入力して開発サーバで確認してみます。

elm-live src/Main.elm --open -- --output=main.js

GitHubへpush

https://github.com/ から新規リポジトリを作成し、pushします。今回は、elm-navigation2という名前にしました。


ターミナル画面で以下を入力して、production用のmain.jsを作成します。(今回はUglify.jsは無視します。)

elm make src/Main.elm --output=main.js --optimize
git init
git add .
git commit -m "first commit"
git remote add origin https://github.com/adash333/elm-navigation2.git
git push -u origin master

Netlifyにデプロイ

https://www.netlify.com/ にログインして、上記GitHubのリポジトリを選択して、デプロイします。設定欄は空白のままです。


無事、デプロイできたようです。
https://determined-colden-a15e32.netlify.com/

ソースコードとDEMOサイト

ソースコード
https://github.com/adash333/elm-navigation2

DEMOサイト
https://determined-colden-a15e32.netlify.com/

参考:
https://gist.github.com/yuizho/c0b08ee59d3da81891426b17209fff4d
公式ガイドのナビゲーションのサンプルコードに画面遷移を加えた感じのもの