Update model value only if button pressed

I am new to Elm and am currently experimenting with receiving data from a server. I am also new to programming WebApps which is why I am having a hard time to formulate my problem.

What I want to achieve is

  • press button
  • get a value from server (“multiplier”)
  • update model field with that value
  • trigger calculation using the multiplier and upate model field “result”

However, I am not able to achieve this. My below code works. But I use a workaround and don’t use the model field result for storing the result of calcResult.

My question is, however, how can I update the model field result when pressing the button and at the same time also get the server data.

module NPV exposing (..)

import Browser
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick, onInput)
import Http

type alias Model = 
    {initialState : Bool
    , value :  Maybe Float
    , multiplier :  Maybe Float
    , result :  Maybe Float
    }
    
initialModel : Model
initialModel = 
     {initialState = True
     , value = Just 0
     , multiplier = Just 0
     , result = Nothing
     }
type Msg = 
    GetValue String
    | PressedOk
    | ReceiveMultiplier (Result Http.Error String) 


url : String
url = 
    "http://localhost:8989/multiplier"

getMultiplier : Cmd Msg
getMultiplier = 
    Http.get 
    { url = url
    , expect = Http.expectString ReceiveMultiplier
    }


update : Msg -> Model -> (Model, Cmd Msg)
update msg model = 
    case msg of
        GetValue inputValue -> 
            ({model | value = String.toFloat inputValue}, Cmd.none)
        PressedOk ->
            ({model | initialState = False}, getMultiplier)
        ReceiveMultiplier result ->
            case result of 
                Ok value  -> 
                    ({model | multiplier = String.toFloat value}, Cmd.none)
                Err error ->
                    (model, Cmd.none)

viewResultOrError : Model -> Html Msg
viewResultOrError model = 
    if model.initialState == True then 
        viewInitial 
    else   
        case (model.value, model.multiplier) of 
            (Just v, Just m) -> 
                viewResult (v*m)
            _ -> 
                viewError

viewError : Html Msg
viewError = 
    div [][text "Ups, something went wrong!"]

viewResult : Float -> Html Msg
viewResult value = 
    div [][text ("Input * Multiplier is: " ++  (value |> Debug.toString))]

viewInitial : Html Msg
viewInitial = 
    div [][]

view : Model -> Html Msg
view model = 
      div [][h2 [] [text "My first Webapp"]
             , input [placeholder "Give me your value",  onInput GetValue][]
             , button [onClick PressedOk][text "OK"]
             , viewResultOrError model
            , div [][h3 [][text "Debug"]
                    , text (Debug.toString model)
                    ]
      
      ]
      
calcResult : Maybe Float -> Maybe Float -> Maybe Float
calcResult value multiplier = 
    case (value, multiplier) of 
        (Just a, Just b) ->
            Just (a * b)
        (_,_) -> 
            Nothing

 
main : Program () Model Msg
main = 
      Browser.element 
      {init = \flags -> (initialModel, Cmd.none)
      , view = view
      , update = update
      , subscriptions = \_ -> Sub.none
      }
     

Thanks for your help!

So you want to calculate result using one value from user and another value from server, but only once after button has been pressed?

You can’t calculate the result immediately when button is pressed as you don’t have value from server yet, but you can calculate it when you get the value from server:

update : Msg -> Model -> (Model, Cmd Msg)
update msg model = 
    case msg of
        GetValue inputValue -> 
            ({model | value = String.toFloat inputValue}, Cmd.none)
        PressedOk ->
            ({model | initialState = False}, getMultiplier)
        ReceiveMultiplier result ->
            case result of 
                Ok value  -> 
                    ({model
                        | multiplier = String.toFloat value
                        , result = calcResult model.value (String.toFloat value)
                     }, Cmd.none)
                Err error ->
                    (model, Cmd.none)

Thanks for your swift reply. This works. Thanks!

I tried a similar approach before. For case GetValue I used model.value in calcResult instead of inputValue. That didn’t work and I was to blind to see the obvious issue with that.

Thanks!

Yes, it’s easy mistake to use e.g. model.multiplier while you are updating multiplier, so model.multiplier still refers to previous value and not new value.

Here’s alternative solution to avoid that bug.

First change calcResult to update Model directly:

calcResult : Model -> Model
calcResult model = 
    case (model.value, model.multiplier) of 
        (Just a, Just b) ->
            { model | result = Just (a * b) }
        (_,_) -> 
            model

Then in update pass the updated model to calcResult, which will update model again:

update : Msg -> Model -> (Model, Cmd Msg)
update msg model = 
    case msg of
        GetValue inputValue -> 
            ({model | value = String.toFloat inputValue}, Cmd.none)
        PressedOk ->
            ({model | initialState = False}, getMultiplier)
        ReceiveMultiplier result ->
            case result of 
                Ok value  -> 
                    (calcResult {model | multiplier = String.toFloat value}, Cmd.none)
                Err error ->
                    (model, Cmd.none)

Great. Thanks for the alternative solution. Very helpful!

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.