Edited at
ElmDay 24

Elmでカウントダウンアプリを爆速で作った時のはなし


はじめに

Elm Advent Calendar 2018 に登録したので記事を書いています。

ほぼElmを使ってみた感想レベルのことしか書いていません。

Elmでカウントダウンアプリを作ったのとき、たった6時間でデプロイまでできたので、その時の話を書こうと思います。


なにを作ったのか?

合コンに行ったときに知り合った女の子と、デートに行くまでの時間をカウントダウンをするアプリです。

https://github.com/EgumaYuto/gyoza-countdown

こんな見た目のWebアプリでした。

スクリーンショット 2018-12-24 21.04.12.png


なぜ作ったのか?

なんかオシャレなアプリを作ったらネタになるかなと思っただけです。


なぜElmだったのか?

ababup1192さんにおすすめされてElmと出会ったのですが、個人的にも使いやすそうだなぁと思ったからです。

Javascriptが苦手なので、別の言語でフロントエンド実装したいなぁと思っていたのもあります。

結果的にElmという技術選定は最適だったことに気づきます。なんたってたった6時間で言語の勉強からデプロイまでやりきりましたから。


HTML/CSSを書く

CSSを書くのはめんどくさかったので、BULMAというCSSフレームワークを使い、

なんかイイ感じに見えるように実装しました。

ドキュメントをみつつ書いたHTMLは以下のような感じになりました。

ほぼ公式からのコピペだったように思います。


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%= htmlWebpackPlugin.options.title %></title>
<link rel="stylesheet" href="./bulma.min.css">
<link href="https://fonts.googleapis.com/css?family=Charmonman" rel="stylesheet">
</head>
<body>
<section class="hero is-fullheight is-dark">
<div class="hero-body">
<div class="container">
<div class="columns is-mobile">
<div class="column">
<h1 class="title is-3">Gyoza</h1>
</div>
</div>
<div class="columns is-mobile">
<div class="column">
<p class="is-size-2">00</p>
</div>
<div class="column">
<p class="is-size-2">00</p>
</div>
<div class="column">
<p class="is-size-2">00</p>
</div>
<div class="column">
<p class="is-size-2">00</p>
</div>
</div>
<div class="columns is-mobile">
<div class="column">
Days
</div>
<div class="column">
Hours
</div>
<div class="column">
Mins
</div>
<div class="column">
Sec
</div>
</div>
</div>
</div>
</section>
<style>
.column {
text-align: center;
}
* {
font-family: 'Pacifico', cursive;
}
</style>
</body>
</html>


Elm化する

ababup1192さんから、HTMLをElmに変換するサービス

教えていただいていたのでこれを使い、sectionタグの中身をそのままElm化してしまいました。

そうするとこんな感じになりました。


section [ class "hero is-fullheight is-dark" ]
[ div [ class "hero-body" ]
[ div [ class "container" ]
[ div [ class "columns is-mobile" ]
[ div [ class "column" ]
[ h1 [ class "title is-3" ]
[ text "Gyoza" ]
]
]
, div [ class "columns is-mobile" ]
[ div [ class "column" ]
[ p [ class "is-size-2" ]
[ text "00" ]
]
, div [ class "column" ]
[ p [ class "is-size-2" ]
[ text "00" ]
]
, div [ class "column" ]
[ p [ class "is-size-2" ]
[ text "00" ]
]
, div [ class "column" ]
[ p [ class "is-size-2" ]
[ text "00" ]
]
]
, div [ class "columns is-mobile" ]
[ div [ class "column" ]
[ text "Days" ]
, div [ class "column" ]
[ text "Hours" ]
, div [ class "column" ]
[ text "Mins" ]
, div [ class "column" ]
[ text "Sec" ]
]
]
]
]

ただ、このままだと、時間の情報が入るdivタグと、

単位が入るdivタグが似たような形式で存在していて重複となっていたため、

引数で可変な部分を渡せば良いように関数に切り出しました。


section [ class "hero is-fullheight is-dark" ]
[ div [ class "hero-body" ]
[ div [ class "container" ]
[ div [ class "columns is-mobile" ]
[ div [ class "column" ]
[ h1 [ class "title is-3" ]
[ text "Gyoza" ]
]
]
, div [ class "columns is-mobile" ]
[ timeElement "00"
, timeElement "00"
, timeElement "00"
, timeElement "00"
]
, div [ class "columns is-mobile" ]
[ unitElement "Days"
, unitElement "Hours"
, unitElement "Mins"
, unitElement "Sec"
]
]
]
]

-- 切り出されたコンポーネント
timeElement : String -> Html Msg
timeElement tm =
div [ class "column" ]
[ p [ class "is-size-2" ]
[ text tm ]
]

unitElement : String -> Html Msg
unitElement unit =
div [ class "column" ]
[ text unit ]


時間を扱う

時間を扱うために、Modelに保持したい値は何であるかを記述しました。

type alias Model =

{ zone : Time.Zone
, posix : Time.Posix
}

(今見ると、zoneの情報はいらなかったなぁと思っています。。。)

そして、Modelの初期化は以下のようにしました。


type Msg
= SetSystemTime ( Time.Zone, Time.Posix )
| SetCurrentTime Time.Posix

init : () -> ( Model, Cmd Msg )
init _ =
( { zone = Time.utc, posix = Time.millisToPosix 0 }, setSystemTime )

setSystemTime : Cmd Msg
setSystemTime =
Task.perform SetSystemTime <| Task.map2 Tuple.pair Time.here Time.now

また、一秒ごとに更新をしたいので、subscriptionを設定しました。

subscriptions : Model -> Sub Msg

subscriptions _ =
Time.every 1000 SetCurrentTime

そして、イベントが発火したら以下のようにmodelの中身を書き換えます。

update : Msg -> Model -> ( Model, Cmd Msg )

update msg model =
case msg of
SetSystemTime ( zone, time ) ->
( { zone = zone, posix = time }, Cmd.none )

SetCurrentTime time ->
( { model | posix = time }, Cmd.none ) ## ここが、1秒ごとに model をうわがいて今の時間を保持させている部分

これで、アプリケーションの実装は一通り完了しました。


デプロイ

Docker化して、GCP上にデプロイしたのですが、Elmの話とは全然関係ないので割愛します。

そして、今は特にニーズもないので閉鎖しています。


さいごに

言語の勉強も含めてここまで6時間しかかからなかったです。Elm生産性高いなぁと思いました。

別のフロントエンドのフレームワークでこれが成し遂げられる気がしないです。

ただ、ここまでやってElmの深い思想等に関してはまだあまり理解できていないので、引き続き勉強は続けたいと思います。

ちなみに、12月24日にこんな投稿をしている時点で色々察している方もいるかと思いますが、

結局アプリのウケもよくなく、何もうまくいきませんでした。