Elmで性格診断的なサイトを作ってみた

## こんなサイト作ってみました
- [愛を伝える5つの言語診断](https://your-five-love-languages.netlify.com/)
- [5役者の賜物診断](https://five-fold-ministries.netlify.com/)

ソースコード
https://github.com/niso1985/FiveLoveLanguages

両方とも、自分が通ってる教会で教えてもらった内容ですが、紙でしかもらわなかったので、Webでできるといいなと思い、Elmも勉強中だったため作ってみました。
フロントはHTML/CSS/JavaScriptがどうも性に合わなくて手を出していなかったので、Web初心者がElmでフロントアプリ作ってみて得られた所感をお伝えできたらなと思います。
あんまりElm関係ない内容のほうが多いと思います。

## ハマったところ
### radioボタンにハマる
#### 表示にハマる
Elmでのradioボタンの実装の資料がすぐに見つからなかったので、そもそもradioボタンをどう表示ささせるんだ？というところにハマりました。

```elm
input [ type_ "radio" ][]
```
これでOK。

※追記
@ossan_pg さんがわかりやすい記事書かれていました！
https://qiita.com/ossan_pg/items/43063b8be6b0c63c4ce8

#### ModelとViewは1:1対応にした方が良さげ
具体的にはinputタグの`value`と`onInput`と、Modelの関係性でハマりました。

もともと、Modelは下記のように、**どの属性のボタンが何個選択されているか**、という形で管理しようとしていました。

```elm
type alias Model =
     { ministries : Dict String Int }

init : Model
init =
    { ministries = |
        Dict.fromList |
             [ ( "Apostles", 0 )
             , ( "Prophets", 0 )
             , ( "Evangelists", 0 )
             , ( "Shepherds", 0 )
             , ( "Teachers", 0 )
             ]
    }
```

各属性("Apostles"など)を持つradioボタンを押すと、その属性に対応した連想配列の値が加算されていくという考え方です。
ところが、Modelをこのような形にしてしまうと、同じグループのradioボタンを押した時に、別のradioボタンの属性の値を減らす必要が出てきてしまいます。
例えば、

- [x] radioA
- [ ] radioB 

の時、

```
A: 1
B: 0
```

だったのが、radioBを選択すると、Bに+1は良いとして、Aを-1しないといけません。
しかし、inputタグの`value`は`String`しか値を取りえないため、もう一方のradioボタンの属性値を渡す際にいろいろと工夫が必要になってきます。 [^1] [^2]

これを簡単にするためには、下記のように**1radioボタンの状態を1要素で管理する**とすると、コードがとても簡単になりました。

```elm
type alias Model =
    { answers : Array MinisterType }

init : Model
init =
    { answers = Array.repeat 30 None }
```

このあたりは@ababup1192さんに色々とアドバイスを頂きました。ありがとうございます。

[^1]: そのとき、いろいろと工夫したのが[こちらのcommit](https://github.com/niso1985/FiveFoldMinistries/commit/e6ba41998882c18411c67c79335185d17f276104?diff=split)になります。気合で文字列を生成して、その順番でどちらが押されているかをとるという力技です。
[^2]: あとで分かったのですが、`onInput`は`value`のStringの値をとって`msg`になればよいので、`onInput`に渡す`Msg`に減らす属性を渡すという方法もありでした。

### CSS使うときにBrowser.sandboxからBrowser.elementに変更するところにハマる
elm-guideの[このページ](https://guide.elm-lang.jp/interop/)の内容。
単純に見逃してハマってました。
`<div id="elm"></div>`
がサラッと書かれすぎててスルーしちゃってました。

こちらは[Gadaさん](https://twitter.com/gada_twt)に助けていただきました。ありがとうございます。

### モーダルの実装に悩む
モーダルの実装をはじめはHTML/CSSでなんとかやってみようと思ったのですが、巷のモーダルの実装の解説ページはJavaScriptが絡んでいる方法が多かったり、HTML/CSSもごちゃごちゃしてたりでイマイチうまくいきませんでした。[^3]

[^3]: elm-modalやelm-dialogなんてのもありましたが、どれも古く、サポートされてないようでした。

調べてみると、[Bulma-Model-Fx](https://postare.github.io/bulma-modal-fx/)というものを見つけて、これであっという間に解決しました。
Bulmaがマジで有能です。

### GAS使おうとしてCORSにハマる
診断結果を保存するために、GASにPOSTしようとしたのですが、CORS制約で引っかかってしまいました。
はじめはJSONPの実装を行おうかなと思ったのですが、@arowMさんにアドバイス頂き、[NetlifyだとCORS制約を回避できる](https://twitter.com/gada_twt/status/1143698375381934080)ということを聞き、見事うまくいきました。
ありがとうございます。

方法はすごく簡単。
まずelm-httpで普通にpost requestを書きます。

```elm
postResult : Model -> Cmd Msg
postResult model =
    Http.request
        { method = "POST"
        , headers = [ Http.header "Accept" "application/json", Http.header "Content-Type" "application/json" ]
        , url = "/api/"
        , body = Http.jsonBody (makeJsonBody model)
        , expect = Http.expectString Response
        , timeout = Nothing
        , tracker = Nothing
        }
```

このとき、urlをNetlifyローカルのendpointを指すようにします。(今回は/api/としました。)
次に、_redirectsという下記のように、request時のendpointとPOSTしたいGASのURLを書いたファイルをprojectのルートディレクトリに配置します。

```
/api/  https://script.google.com/macros/s/XXXXXXXXXXXXXXXX/exec  200
```

これだけで、GASへのCORS制約を回避することができます。


## 所感まとめ
### Elm(・∀・)ｲｲ!!
やっぱりHTMLをプログラマブルに書けるのは良いですね。多少のボイラープレートはありますが、HTMLベタ書きよりも断然書きやすいです。
Scalaを普段使用しているのですが、パイプを利用すれば、ScalaのコレクションとElmのコレクションの使用感が殆ど変わらないのも良いです。

また、よく言われますが、Elmは実行時エラーがほとんどないのもとても良いです。
JavaScriptはundefinedに悩まされたトラウマがあるので、コンパイルが通りさえすれば動作が保証されるという安心感はとても良いです。

### CSS書かなくてもなんとかなるやん
HTML/CSS/JSの御三家のなかで、一番苦手なのがCSSなのですが、Bulmaで作ってみたところ、ほとんどCSSらしいCSS書かなくても、それっぽくなったのに感動しました。
CSSとかワケワカラン、デザインとか無理ゲーすぎる、と考えているバックエンドや組み込み畑の人でも、それなりのデザインや動きを作れるCSSフレームワークはすげーです。

### deployがほんとに簡単
Github pagesもNetlifyもそうですが、本当に簡単にWebページが公開できるなと感じました。
自分のWebページを公開するために、パソコン潰してサーバーにしてApacheたてて・・・ってやってた時代が懐かしい笑
CI的なことは規模も小さいのでスルーしてます。

### サーバーレス便利
今回はバックエンドというほどではないですが、データ保存の要件もあったので、GASを使いました。
GASは軽くデータを保存したいときにはほんとに便利ですね。

## 謝辞
Elmではじめての作品を作るに当たり、elm-jpコミュニティの方にいろいろアドバイスを頂きました。
Elmコミュニティの方は回答も早く、とても初学者に優しい、学べる環境だなと思います。
コミュニティ自体にElmの言語的特性が浸透している気がします。

Elmのおかげでフロントが楽しくなってきたので、次は何を作ろうかなぁと考えています。
最終的にはWebGL使ったゴリゴリな3D描画とかやりたいなぁ。