0
votes

I have the following code trying to roll my own dropMenu implementation using style-elements. I am running into the same bug I had with the menu implementation in the style-elements package. The problem is that the view does not seem to update on a Msg until another Msg is sent, meaning the view is always one step behind the current state. How can I get around this?

type alias Menu =
    { label : String
    , group : Group
    , choices : List Choice
    , isOpen : Bool
    , selected : String
    }


type alias Choice =
    { label : String
    , item : Item
    , selected : Bool
    }


type Item
    = Diazepam
    | Lorazepam
    | Midazolam
    | NoneChosen


model : Menu
model =
    { label = "First line agents"
    , group = FirstLine
    , choices =
        [ { label = "Diazepam"
          , item = Diazepam
          , selected = False
          }
        , { label = "Lorazepam"
          , item = Lorazepam
          , selected = False
          }
        , { label = "Midazolam"
          , item = Midazolam
          , selected = False
          }
        ]
    , isOpen = False
    , selected = ""
    }


type Group
    = FirstLine
    | SecondLine
    | ThirdLine
    | FourthLine


menuChoice : Choice -> Element MyStyles variation Msg
menuChoice choice =
        if
            choice.selected
                == False
        then
            el Selection [ onClick (SelectChoice choice) ] (text choice.label)
        else
            el Selected [ onClick NoOp ] (text (choice.label ++ "oogityboogity"))


prepend : List a -> List a -> List a
prepend listA listB =
    List.append listB listA


menuDown : Menu -> Element MyStyles variation Msg
menuDown menu =
    column Content
        []
        (case menu.isOpen of
            True ->
                List.append
                    [ el Header [ onClick CloseMenu ] (text menu.label) ]
                    (List.map
                        (\choice ->
                            menuChoice choice
                        )
                        menu.choices
                    )

            False ->
                [ el Header [ onClick OpenMenu ] (text menu.label) ]
        )


menuUp : Menu -> Element MyStyles variation Msg
menuUp menu =
    column Content
        []
        (case menu.isOpen of
            True ->
                List.append
                    (List.map
                        (\choice ->
                            if choice.label == menu.selected then
                                menuChoice choice
                            else
                                menuChoice choice
                        )
                        menu.choices
                    )
                    [ el SubHead [] (text menu.label) ]

            False ->
                [ el SubHead [] (text menu.label) ]
        )



-- UPDATE --


update : Msg -> Menu -> Menu
update msg menu =
    case msg of
        CloseMenu ->
            { menu | isOpen = False }

        OpenMenu ->
            { menu | isOpen = True }

        SelectChoice choice ->
            markSelected menu choice

        NoOp ->
            menu


type Msg
    = CloseMenu
    | OpenMenu
    | SelectChoice Choice
    | NoOp


markSelected : Menu -> Choice -> Menu
markSelected menu selection =
    let
        newMenu =
            { menu
                | selected = selection.label
                , isOpen = False
                , choices =
                    List.map
                        (\choice ->
                            if choice.label == menu.selected then
                                { choice | selected = True }
                            else
                                { choice | selected = False }
                        )
                        menu.choices
            }
    in
        update NoOp newMenu


view : Menu -> Html.Html Msg
view model =
    Element.viewport stylesheet <| menuDown model


main =
    Html.beginnerProgram
        { model = model
        , update = update
        , view = view
        }
1
Made a quick Ellie (ellie-app.com/5JdZwqf8Ca1/4) but couldn't reproduce your problem, or maybe I didn't unterstand it properly. It looks like the view gets rendered as expected. - hal
Thanks for the Ellie, I had issues getting it to work this morning. Updated it here: ellie-app.com/5Y7kNPjKMa1/0 which shows the problem. Background should be blue if the item is already marked as selected, red if unselected. Sequence for re-creating the bug: Open menu->Click item->Menu closes->Open menu->All items still red-> Click item->menu closes->open menu->First selected item now blue - stevensonmt

1 Answers

5
votes

The problem is in your markSelected function. Here's the content of said function:

markSelected : Menu -> Choice -> Menu
markSelected menu selection =
    let
        newMenu =
            { menu
                | selected = selection.label
                , isOpen = False
                , choices =
                    List.map
                        (\choice ->
                            if choice.label == menu.selected then
                                { choice | selected = True }
                            else
                                { choice | selected = False }
                        )
                        menu.choices
            }
    in
        update NoOp newMenu

You're mapping over the choices in order to update their selected state. To check which one should be True, you're comparing the choice's label to menu.selected. But menu.selected is the previous menu state, which is why the model seems to reflect the previous state for one update.

Instead, you can compare to the selection:

if choice.label == selection.label then

Also, this is unrelated to that issue, but why are you calling update NoOp newMenu? Just return the new menu!

markSelected : Menu -> Choice -> Menu
markSelected menu selection =
    { menu
        | selected = selection.label
        , isOpen = False
        , choices =
            List.map
                (\choice ->
                    if choice.label == selection.label then
                        { choice | selected = True }
                    else
                        { choice | selected = False }
                )
                menu.choices
    }

Here's the updated Ellie (I've also cleaned up a few if statements): https://ellie-app.com/6KMKX9tMTa1/0