Reputation: 6251
As pictured here, I've created a small sample program that has some controls to modify parts of the model.
What I've been unsuccessfully trying to do is to make a HTTP request to get the initial data (it's hardcoded right now), or later on replace the data with the response from said HTTP request when a Reset message is received. I did read the HTTP chaper of the introduction to Elm, but I can't seem to piece things together.
The goal is to have a loadTraits function that takes a String (SomeId) and returns a List of type TraitWithRelevance, so I can replace the model with that incoming data.
module Main exposing (..)
import Html exposing (Html, button, div, text, input, ul, img)
import Html.Attributes as Attr
import Html.Events exposing (onClick, onInput)
import Http exposing (..)
import Json.Decode as Decode
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = \_ -> Sub.none
}
-- MODEL
type alias ContentWithTraits =
{ someId : SomeId
, traits : List TraitWithRelevance
}
type alias MetaInfo =
{ name : String
, imageUrl : String
}
type alias Name =
String
type alias SomeId =
String
type alias Relevance =
String
type alias TraitWithRelevance =
( Name, SomeId, Relevance )
type TraitToAdd
= Nothing
| TraitWithRelevance
type alias Model =
{ contentWithTraits : ContentWithTraits
, metaInfo : MetaInfo
, traitToAdd : TraitToAdd
}
init : ( Model, Cmd Msg )
init =
( Model contentWithTraits { name = "content name", imageUrl = "http://weknowmemes.com/generator/uploads/generated/g1369409960206058073.jpg" } Nothing, Cmd.none )
contentWithTraits : ContentWithTraits
contentWithTraits =
{ someId = "some default id"
, traits =
[ ( "name for trait a", "a", "1" )
, ( "this is the name for trait b", "b", "50" )
]
}
-- UPDATE
type Msg
= EditTrait SomeId Relevance
| Reset
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
EditTrait someId relevance ->
let
_ =
Debug.log "model: " model
in
( replaceTraits model <| List.map (updateTrait ( someId, relevance ))
, Cmd.none
)
Reset ->
-- ( replaceTraits model <| loadTraits model.contentWithTraits.someId, Cmd.none )
{-
NOTE: I'm stuck here...
should make HTTP GET request, then replace the model.contentWithTraits.traits with the decoded JSON's traits field
-}
( model, Cmd.none )
replaceTraits : Model -> (List TraitWithRelevance -> List TraitWithRelevance) -> Model
replaceTraits model func =
{ model
| contentWithTraits =
{ someId = model.contentWithTraits.someId
, traits = func model.contentWithTraits.traits
}
}
updateTrait : ( SomeId, Relevance ) -> TraitWithRelevance -> TraitWithRelevance
updateTrait updatedTrait originalTrait =
let
( name, someId, _ ) =
originalTrait
( someIdVerification, newValue ) =
updatedTrait
_ =
Debug.log "updatedTrait: " updatedTrait
in
if someId == someIdVerification then
( name, someId, newValue )
else
originalTrait
-- VIEW
valueRange : String -> TraitWithRelevance -> Html Msg
valueRange typ trait =
let
( name, someId, relevance ) =
trait
in
input [ Attr.type_ typ, Attr.min <| toString 0, Attr.max <| toString 100, Attr.value relevance, Attr.step <| toString 1, onInput <| EditTrait someId ] []
traitView : TraitWithRelevance -> Html Msg
traitView trait =
let
( name, someId, relevance ) =
trait
in
div []
[ text someId
, valueRange "range" trait
, valueRange "number" trait
, text name
]
view : Model -> Html Msg
view model =
div []
[ text model.contentWithTraits.someId
, img [ Attr.src model.metaInfo.imageUrl, Attr.width 300 ] []
, ul [] (List.map traitView model.contentWithTraits.traits)
, button [ onClick Reset ] [ text "Reset" ]
]
Here's an example response from the http server. I've chosen this format, because I thought it'd map easiest to the elm model. I can easily change the response if there's a better way to consume this data in elm.
{"traits":[["name for trait a","a",1],["this is the name for trait b,"b",50]]}
P.S. Even though there's plenty of lines of code, please be aware that I tried to strip down the problem as much as possible, while keeping enough context.
Upvotes: 0
Views: 156
Reputation: 6251
While both answers have been correct and helpful, it took me still another mile to reach my destination.
First off, I didn't want to rely on a non elm-lang package. Json.Decode.Pipeline therefore doesn't meet that requirement.
With the examples posted, I still had the trouble of decoding the list of traits. What I ended up doing was changing the response from the server so that the traits list is not of type list, but of type object with name, someId, and relevance as keys and its values as strings. This helped me distinguish between what's coming back from the server and what's being represented internally in my elm model.
So the Reset functionality works nicely, the next step is to get those values into the initial state of the model without user interaction.
module Main exposing (..)
import Html exposing (Html, button, div, text, input, ul, img)
import Html.Attributes as Attr
import Html.Events exposing (onClick, onInput)
import Http exposing (..)
import Json.Decode as Decode exposing (Decoder)
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = \_ -> Sub.none
}
-- MODEL
type alias ContentWithTraits =
{ someId : SomeId
, traits : List TraitWithRelevance
}
type alias MetaInfo =
{ name : String
, imageUrl : String
}
type alias Name =
String
type alias SomeId =
String
type alias Relevance =
String
type alias TraitWithRelevance =
( Name, SomeId, Relevance )
type TraitToAdd
= Nothing
| TraitWithRelevance
type alias Model =
{ contentWithTraits : ContentWithTraits
, metaInfo : MetaInfo
, traitToAdd : TraitToAdd
}
type alias TraitInfo =
{ traits :
List TraitObject
}
type Traits
= TraitInfoFromServer (List TraitObject)
type alias TraitObject =
{ name : String, someId : String, relevance : String }
fetchTraits : String -> Cmd Msg
fetchTraits someId =
Http.get
("http://localhost:8000/traits/" ++ someId)
decodeTraits
|> Http.send OnFetchTraits
decodeTraits : Decoder TraitInfo
decodeTraits =
Decode.map TraitInfo
(Decode.field "traits" (Decode.list decodeTrait))
decodeTrait : Decoder TraitObject
decodeTrait =
(Decode.map3 TraitObject
(Decode.field "name" Decode.string)
(Decode.field "someId" Decode.string)
(Decode.field "relevance" Decode.string)
)
init : ( Model, Cmd Msg )
init =
( Model contentWithTraits { name = "content name", imageUrl = "http://weknowmemes.com/generator/uploads/generated/g1369409960206058073.jpg" } Nothing, Cmd.none )
contentWithTraits : ContentWithTraits
contentWithTraits =
{ someId = "someIdToStartWith"
, traits =
[ ( "trait a", "a", "1" )
, ( "trait b", "b", "50" )
]
}
-- UPDATE
type Msg
= EditTrait SomeId Relevance
| Reset
| OnFetchTraits (Result Http.Error TraitInfo)
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
EditTrait someId relevance ->
( replaceTraits model <| List.map (updateTrait ( someId, relevance ))
, Cmd.none
)
Reset ->
( model, fetchTraits model.contentWithTraits.someId )
OnFetchTraits resp ->
let
newTraits =
case resp of
Ok val ->
val.traits
Result.Err e ->
[]
in
( { model
| contentWithTraits =
{ someId = model.contentWithTraits.someId
, traits = List.map traitObjToTuple newTraits
}
}
, Cmd.none
)
traitObjToTuple : TraitObject -> TraitWithRelevance
traitObjToTuple obj =
( obj.name, obj.someId, obj.relevance )
replaceTraits : Model -> (List TraitWithRelevance -> List TraitWithRelevance) -> Model
replaceTraits model func =
{ model
| contentWithTraits =
{ someId = model.contentWithTraits.someId
, traits = func model.contentWithTraits.traits
}
}
updateTrait : ( SomeId, Relevance ) -> TraitWithRelevance -> TraitWithRelevance
updateTrait updatedTrait originalTrait =
let
( name, someId, _ ) =
originalTrait
( someIdVerification, newValue ) =
updatedTrait
in
if someId == someIdVerification then
( name, someId, newValue )
else
originalTrait
-- VIEW
valueRange : String -> TraitWithRelevance -> Html Msg
valueRange typ trait =
let
( name, someId, relevance ) =
trait
in
input [ Attr.type_ typ, Attr.min <| toString 0, Attr.max <| toString 100, Attr.value relevance, Attr.step <| toString 1, onInput <| EditTrait someId ] []
traitView : TraitWithRelevance -> Html Msg
traitView trait =
let
( name, someId, relevance ) =
trait
in
div []
[ text name
, valueRange "range" trait
, valueRange "number" trait
, text someId
]
view : Model -> Html Msg
view model =
div []
[ text model.contentWithTraits.someId
, img [ Attr.src model.metaInfo.imageUrl, Attr.width 100 ] []
, ul [] (List.map traitView model.contentWithTraits.traits)
, button [ onClick Reset ] [ text "Reset" ]
]
Upvotes: 0
Reputation: 25
So basically you need to do two things, the reset button should call a command to get the traits. Then, in the update you have to handle the response from the command. After getting the Result back you can use it to update your model.
Here is an update to your code. I added a person to the model which gets updated when the user presses the reset button.
module Main exposing (..)
import Html exposing (Html, button, div, text, input, ul, img)
import Html.Attributes as Attr
import Html.Events exposing (onClick, onInput)
import Http exposing (..)
import Json.Decode exposing (Decoder, string)
import Json.Decode.Pipeline exposing (decode, required)
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = \_ -> Sub.none
}
-- Commands
type alias Person =
{ name : String
, gender : String
}
decodePerson : Decoder Person
decodePerson =
decode Person
|> required "name" string
|> required "gender" string
getTraits =
let
url =
"http://swapi.co/api/people/1/"
request =
Http.get url decodePerson
in
Http.send GetTraitResponse request
-- MODEL
type alias ContentWithTraits =
{ someId : SomeId
, traits : List TraitWithRelevance
}
type alias MetaInfo =
{ name : String
, imageUrl : String
}
type alias Name =
String
type alias SomeId =
String
type alias Relevance =
String
type alias TraitWithRelevance =
( Name, SomeId, Relevance )
type TraitToAdd
= Nothing
| TraitWithRelevance
type alias Model =
{ contentWithTraits : ContentWithTraits
, metaInfo : MetaInfo
, traitToAdd : TraitToAdd
, person : Person
}
init : ( Model, Cmd Msg )
init =
( Model contentWithTraits { name = "content name", imageUrl = "http://weknowmemes.com/generator/uploads/generated/g1369409960206058073.jpg"} Nothing {name = "", gender=""}, Cmd.none )
contentWithTraits : ContentWithTraits
contentWithTraits =
{ someId = "some default id"
, traits =
[ ( "name for trait a", "a", "1" )
, ( "this is the name for trait b", "b", "50" )
]
}
-- UPDATE
type Msg
= EditTrait SomeId Relevance
| GetTraitResponse (Result Http.Error Person)
| Reset
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
EditTrait someId relevance ->
let
_ =
Debug.log "model: " model
in
( replaceTraits model <| List.map (updateTrait ( someId, relevance ))
, Cmd.none
)
-- handle the response
GetTraitResponse resp ->
let
_ =
Debug.log "response" resp
person =
case resp of
Ok val ->
val
Result.Err e ->
{name = "", gender=""}
in
( {model | person = person }, Cmd.none )
Reset ->
-- ( replaceTraits model <| loadTraits model.contentWithTraits.someId, Cmd.none )
{-
NOTE: I'm stuck here...
should make HTTP GET request, then replace the model.contentWithTraits.traits with the decoded JSON's traits field
-}
-- call the command to get the traits
( model, getTraits )
replaceTraits : Model -> (List TraitWithRelevance -> List TraitWithRelevance) -> Model
replaceTraits model func =
{ model
| contentWithTraits =
{ someId = model.contentWithTraits.someId
, traits = func model.contentWithTraits.traits
}
}
updateTrait : ( SomeId, Relevance ) -> TraitWithRelevance -> TraitWithRelevance
updateTrait updatedTrait originalTrait =
let
( name, someId, _ ) =
originalTrait
( someIdVerification, newValue ) =
updatedTrait
_ =
Debug.log "updatedTrait: " updatedTrait
in
if someId == someIdVerification then
( name, someId, newValue )
else
originalTrait
-- VIEW
valueRange : String -> TraitWithRelevance -> Html Msg
valueRange typ trait =
let
( name, someId, relevance ) =
trait
in
input [ Attr.type_ typ, Attr.min <| toString 0, Attr.max <| toString 100, Attr.value relevance, Attr.step <| toString 1, onInput <| EditTrait someId ] []
traitView : TraitWithRelevance -> Html Msg
traitView trait =
let
( name, someId, relevance ) =
trait
in
div []
[ text someId
, valueRange "range" trait
, valueRange "number" trait
, text name
]
view : Model -> Html Msg
view model =
div []
[ text model.contentWithTraits.someId
, img [ Attr.src model.metaInfo.imageUrl, Attr.width 300 ] []
, ul [] (List.map traitView model.contentWithTraits.traits)
, button [ onClick Reset ] [ text "Reset" ]
, text <| toString model
]
Upvotes: 1
Reputation: 22742
You need to have a message which returns the data. Assuming it's a simple list of Trait
s:
type Msg
= EditTrait SomeId Relevance
| Reset
| OnFetchTraits (Result Http.Error (List Traits))
Then you need a command to send the request, something like
fetchTraits : Cmd Msg
fetchTraits =
Http.get "http://localhost:4000/traits" traitListDecoder
|> Http.send OnFetchTraits
and you need to implement traitListDecoder
, to decode your JSON into the list that is returned in the msg.
Then instead of returning Cmd.none
in your update
function where you are stuck, you return fetchTraits
. Elm will then make the request and you will get an OnFetchTraits
msg passed into update
. You need a separate case to handle this. You unpack the Result
type and extract your data if the request was successful, or handle the error if not.
Upvotes: 1