前回に引き続き、サンプルアプリケーションを構築しながらElmを学んでいきましょう。
今回はカウントアップ機能を拡張しながら、入力フォームの使い方やエラーハンドリングの処理を学んでいきましょう。
- 増加幅のパラメータ化
- フォーム入力によるパラメータの更新
- 文字列の数値変換とエラーハンドリング
増加幅をパラメータ化する
前回までの内容では Increase
は +1
するのみでした。
まずはこの +1
をパラメータにしてみましょう。
type Msg
= NoOp
- | Increase
+ | Increase Int
引数を設定したので、 Increase 1
などのようにアクションに対応できるように update
を変更します。
update : Msg -> Model -> Model
update msg model =
case msg of
NoOp ->
model
- Increase ->
- { model | count = model.count + 1 }
+ Increase num ->
+ { model | count = model.count + num }
この Increase
はViewの onClick
で呼び出されているのでした。こちらも呼び出し方を変更します。
increaseButton : Html Msg
increaseButton =
div []
- [ button [ onClick Increase ]
+ [ button [ onClick (Increase 1) ]
[ text "+1" ]
]
ここで気をつけたいのが、 onClick Increase 1
と書くとエラーになる点です。
括弧がない場合は 1
が onClick
の第2引数とみなされてしまうので、必ず (Increase 1)
と括弧で囲います。
これでコンパイル可能です。ただ、増加幅をパラメータ化しただけで値は同じなので、前回までの動きと一切替わりません。
試しに Increase 5
のように呼び出し方を変えたものを追加してみましょう。
increaseButton : Html Msg
increaseButton =
div []
[ button [ onClick (Increase 1) ] [ text "+1" ]
, button [ onClick (Increase 5) ] [ text "+5" ]
, button [ onClick (Increase -1) ] [ text "-1" ]
]
コンパイルしてみて動作確認できるでしょうか?
ココまでのコード -> https://ellie-app.com/46Wjz8WRPzda1/1
増加幅のフォーム入力
増加幅の数値自体をアプリケーションの入力項目にしてみましょう。
まず、入力フォームの文字列(入力時点では数値ではない)をアプリケーション内部の状態として受け取れるようにします。
type alias Model =
{ count : Int
+ , countStepInput : String
}
初期値は空文字にしておきましょう。
initModel : Model
initModel =
{ count = 0
+ , countStepInput = ""
}
続いて、この値を更新するためのアクションを追加します。
type Msg
= NoOp
| Increase Int
+ | UpdateCountStepInput String
update : Msg -> Model -> Model
update msg model =
case msg of
NoOp ->
model
Increase num ->
{ model | count = model.count + num }
+ UpdateCountStepInput s ->
+ { model | countStepInput = s }
最後に、Viewからこのアクションを呼び出すフォームを作ります。
stepInput : Model -> Html Msg
stepInput model =
form []
[ input [ onInput UpdateCountStepInput, value model.countStepInput ] []
]
フォームを全体のViewに組み込みます。(ついでに内部状態が見えるように出力を追加してます)
view : Model -> Html Msg
view model =
div []
[ counter model
, increaseButton
+ , stepInput model
+ , text ("countStepInput = " ++ model.countStepInput)
]
追加したタグやイベント、更にHTMLの属性値 (value) が増えました。 import を変更しておきましょう。
-import Html exposing (Html, text, p, div, button)
-import Html.Events exposing (onClick)
+import Html exposing (Html, text, p, div, button, form, input)
+import Html.Events exposing (onClick, onInput)
+import Html.Attributes exposing (value)
コンパイルして動作確認してみましょう。入力した内容がちゃんと出力されていますか?
onInput
について
onClick (Increase 1)
のときと異なり、 onInput UpdateCountStepInput
ではアクションに渡す引数を指定しませんでした。公式ドキュメントを確認してシグネチャの違いを確認しましょう。
onClick : msg -> Attribute msg
onInput : (String -> msg) -> Attribute msg
ここで、 msg
は型引数(各アプリケーションで実際の方が決まる)で、今回のアプリケションでは Msg
になります。
つまり、 onClick
では Msg
そのものを引数にするのに対し、 onInput
では String -> Msg
という関数を引数にしています。
UpdateCountStepInput
は String
を引数にして Msg
を返す関数そのものであるため、 onInput
の引数にすることができました。
ココまでのコード -> https://ellie-app.com/46Wjz8WRPzda1/2
入力の数値変換とエラーハンドリング
さて、入力を取得することができたので入力された値を増加幅にしましょう。
増加幅を定義します。
type alias Model =
{ count : Int
, countStepInput : String
+ , countStepNum : Int
}
initModel : Model
initModel =
{ count = 0
, countStepInput = ""
+ , countStepNum = 0
}
アクションも追加しましょう。このルーチンにだんだん慣れてきましたか?
type Msg
= NoOp
| Increase Int
| UpdateCountStepInput String
+ | UpdateCountStepNum Int
update : Msg -> Model -> Model
update msg model =
case msg of
NoOp ->
model
Increase num ->
{ model | count = model.count + num }
UpdateCountStepInput s ->
{ model | countStepInput = s }
+
+ UpdateCountStepNum num ->
+ { model | countStepNum = num }
Viewを作成する前にちょっと考えておくべきことがあります。
countStepInput
はどんな文字列でも入力してしまえるため、数値に変換しようとするときに数値以外のケースで問題が起こることは容易に想像できます。
その問題は文字列を数値に変換する関数 toInt
のシグネチャに具体的に現れています。
toInt : String -> Result String Int
さらに Result
型の定義はこちらです。
type Result error value
= Ok value
| Err error
Ok
は成功したときの値を返し、 Err
でエラーが発生していることを表現しています。つまり、 toInt
の実行にエラーが起こりうることを Result String Int
型として表現してます。
今回はエラーが発生した場合はなにもしないことにしましょう。
convertInputToMsg : String -> Msg
convertInputToMsg s =
case (toInt s) of
Ok num ->
UpdateCountStepNum num
Err msg ->
NoOp
ここで初めて NoOp
を実際に使いました。「何もしない」というアクションが定義されていることで利点のある例です。
さて、フォームに入力された値を変換してモデルを更新できるようにしましょう。
stepInput : Model -> Html Msg
stepInput model =
form []
[ input [ onInput UpdateCountStepInput, value model.countStepInput ] []
+ , input [ type_ "button", onClick (convertInputToMsg model.countStepInput), value "Update"] []
]
type_
というボタン用の属性値が増えました。これを import しておきましょう。
なお、 type
は予約語であるためHTMLの属性を表す関数は type_
となっています。
-import Html.Attributes exposing (value)
+import Html.Attributes exposing (value, type_)
+import String exposing (toInt)
ただ、このままでは実際に増加させるボタンがありません。ボタンを変更しておきましょう。
-increaseButton : Html Msg
-increaseButton =
+increaseButton : Model -> Html Msg
+increaseButton model =
div []
[ button [ onClick (Increase 1) ] [ text "+1" ]
- , button [ onClick (Increase 5) ] [ text "+5" ]
- , button [ onClick (Increase -1) ] [ text "-1" ]
+ , button [ onClick (Increase model.countStepNum) ] [ text ("Add " ++ (toString model.countStepNum)) ]
]
モデルの状態を入力にしたので、呼び出し元も変更しましょう。
view : Model -> Html Msg
view model =
div []
[ counter model
- , increaseButton
+ , increaseButton model
, stepInput model
, text ("countStepInput = " ++ model.countStepInput)
]
さて、上手く行ったでしょうか?
他に数値でない文字列や、ゼロやマイナスの値などを入力して動作確認してみましょう。
また、DEBUGを押して実際に発行されているアクションに関してもチェックしてみましょう。
ココまでのコード -> https://ellie-app.com/46Wjz8WRPzda1/3
まとめ
パラメータ付きのアクションの定義と、数値変換のエラーハンドリングについて見てきました。
いかがだったでしょうか?
次回は beginnerProgram
から飛び出してアクションの連鎖などをやってみましょう。
http://qiita.com/mather314/items/01acf5d72b36dc55bbe1