进行HTTP调用以使用elm

时间:2017-02-03 00:43:14

标签: elm

如图所示,我创建了一个小样本程序,其中包含一些控件来修改模型的各个部分。

我尝试做的不成功的是发出HTTP请求以获取初始数据(现在它已经硬编码),或者稍后用来自所述HTTP请求的响应替换数据收到重置消息。我确实阅读了榆树介绍的the HTTP chaper,但我似乎无法把事情拼凑起来。

screenshot of running compiled elm program in browser

目标是有一个 loadTraits 函数,它接受一个String( SomeId )并返回一个类型为 TraitWithRelevance 的List,所以我可以用传入的数据替换模型。

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" ]
        ]

这是来自http服务器的示例响应。我选择了这种格式,因为我认为它最容易映射到榆树模型。如果有更好的方法在elm中使用这些数据,我可以轻松更改响应。

{"traits":[["name for trait a","a",1],["this is the name for trait b,"b",50]]}

P.S。即使有大量代码,请注意我尽可能地解决问题,同时保持足够的上下文。

3 个答案:

答案 0 :(得分:1)

您需要有一条返回数据的消息。假设它是Trait s:

的简单列表
type Msg
    = EditTrait SomeId Relevance
    | Reset
    | OnFetchTraits (Result Http.Error (List Traits))

然后你需要一个发送请求的命令,比如

fetchTraits : Cmd Msg
fetchTraits =
    Http.get "http://localhost:4000/traits" traitListDecoder
        |> Http.send OnFetchTraits

并且您需要实现traitListDecoder,将您的JSON解码为msg中返回的列表。

然后,您返回Cmd.none而不是在update函数中返回fetchTraits。然后,Elm将发出请求,您将OnFetchTraits msg传递到update。您需要一个单独的案例来处理这个问题。如果请求成功,则解压缩Result类型并提取数据,否则处理错误。

答案 1 :(得分:1)

所以基本上你需要做两件事,重置按钮应该调用命令来获得特征。然后,在更新中,您必须处理命令的响应。获得结果后,您可以使用它来更新模型。

以下是您的代码的更新。我在模型中添加了一个人,当用户按下重置按钮时,该模型会更新。

    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
            ]

答案 2 :(得分:0)

虽然这两个答案都是正确且有帮助的,但我还有一英里到达目的地。

首先,我并不想依赖非琅琅包。 因此,Json.Decode.Pipeline不能满足这一要求。

通过发布的示例,我仍然有解码特征列表的麻烦。我最终做的是更改服务器的响应,以便traits列表不是类型列表,而是类型为object的对象,someId和相关性作为键,其值作为字符串。这有助于我区分从服务器返回的内容以及我的榆树模型内部代表的内容。

因此,重置功能可以很好地工作,下一步是在没有用户交互的情况下将这些值放入模型的初始状态。

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" ]
        ]