Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retrieving a DOM value from Elm ports

Tags:

elm

My elm app uses an auto scrolling function, which gets the Y position of an element and uses Dom.Scroll.toY to scroll there.

Two do this, I set up two ports; a subscription and sender.

ports.elm

port setYofElementById : Maybe String -> Cmd msg
port getYofElementById : (Value -> msg) -> Sub msg

index.html

app.ports.setYofElementById.subscribe(function(id) {
  var element = document.getElementById(id);
  var rect = element.getBoundingClientRect();
  app.ports.getYofElementById.send({"number": rect.top});
})

The listener is a subscription

subscriptions : Model -> Sub Msg
subscriptions model =
    Ports.getYofElementById getYofElementById

getYofElementById : Decode.Value -> Msg
getYofElementById value =
    let
        result =
            Decode.decodeValue bSimpleIntValueDecoder value
    in
    case result of
        Ok simpleIntValue ->
            SetSelectedElementYPosition (Just simpleIntValue.number)

        Err id ->
            SetSelectedElementYPosition Nothing

SetSelectedElementYPosition just sets the model.

Now, the action that executes this does two things: call Port.setYofElementById, then scrolls to the Y value in the model, assuming that it has already been set.

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of

        ScrollToY idString ->
            model
                => Cmd.batch
                    [ Ports.setYofElementById (Just idString)
                    , Task.attempt (always NoOp) <| Dom.Scroll.toY "ul" model.selectedElementYPosition
                    ]

However, this doesn't happen sequentially. When the action first fires, nothing happens. If I fire it again, it scrolls to the location called for in the first action. So it seems like it is calling Dom.Scroll.toY before the value is set.

Is there a way to force the Cmds in ScrollToY to happen in sequence? Or is there a better way to do this in general?

like image 587
Mark Karavan Avatar asked Mar 07 '23 02:03

Mark Karavan


1 Answers

You can get the Cmds to execute in sequence by making the second, the one that does the Dom.Scroll.toY, happen as a response to the first, the one that does the setYofElementById. The following update function accomplishes this:

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        ScrollToY idString ->
            (model, Ports.setYofElementById idString)
        SetSelectedElementYPosition (Just newY) -> 
            (model, Task.attempt (always NoOp) <| Dom.Scroll.toY "ul" newY)
        SetSelectedElementYPosition Nothing -> 
            (model, Cmd.none)
        NoOp -> 
            (model, Cmd.none)

With the Cmds correctly sequenced, you will need to make sure that the newY argument to Dom.Scroll.toY is in the correct frame of reference to get the effect that you want.

like image 88
Dave Compton Avatar answered Mar 15 '23 22:03

Dave Compton