I add <div>
s to a wrapper <div>
and I need to be able to scroll to the last one added each time. How do I do this in Elm?
<div class="messages" style="height: 7em; overflow: scroll">
<div>Anonymous: Hello</div>
<div>John: Hi</div>
</div>
Intuitively, it seems like I could call a port
that runs the JavaScript code element.scrollTop = element.scrollHeight
:
AddChatMessage chatMessage ->
( { model | chatMessages = model.chatMessages ++ [ chatMessage ] } , scrollToBottomPort "div.messages" )
The problem is scrollToBottom
gets called before the model
gets updated. So no big deal, I convert that into a Task
. But still, even though the model
gets updated first now, the view
does not get updated yet. So I end up scrolling to the 2nd item from the bottom!
This maybe leads to a more general question I'm curious about in Elm, how do you run a Cmd
after the view is updated due to a change in the model?
You could use a port to scroll to the bottom of a list.
In that case, you need to set a javascript to wait for 1 AnimationFrame before doing the scroll. To make sure your list is rendered.
An easier way to do this is to use Elm's Dom.Scroll library.
And use the toBottom
function.
If you include that in your code like this:
case msg of
Add ->
( model ++ [ newItem <| List.length model ]
, Task.attempt (always NoOp) <| Scroll.toBottom "idOfContainer"
)
NoOp ->
model ! []
It should work. I've made a working example here in runelm.io