I want to make a web app using Elm that will contain some video elements, with a dynamic layout that can change depending on the width and height of the browser. When I tried that, it was clear that Elm was generating new elements for the videos, but that doesn't work because the video elements have state that needs to be preserved.
I wrote a demonstration of the problem with counters instead of videos for simplicity. I tried to fix the problem with Html.lazy and Keyed.node but it persisted.
The code here can also be cloned from https://github.com/ijt/elm-dom-elt-reuse.
src/Main.elm:
port module Main exposing (..)
import Browser
import Html exposing (..)
import Html.Attributes as Attribute exposing (id, style)
import Html.Events exposing (onClick)
main =
Browser.element
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
type alias Model =
{ layout : Layout }
type Layout
= Row
| Column
init : () -> ( Model, Cmd Msg )
init _ =
( { layout = Row }, startCounters () )
type Msg
= ToggleLayout
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ToggleLayout ->
let
l2 =
case model.layout of
Row ->
Column
Column ->
Row
in
( { model | layout = l2 }, Cmd.none )
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
view : Model -> Html Msg
view model =
div []
[ button [ onClick ToggleLayout ] [ text "Toggle Layout" ]
, counters model
]
counters : Model -> Html Msg
counters model =
case model.layout of
Column ->
div []
[ div [] [ counter1 ]
, div [] [ counter2 ]
]
Row ->
div [] [ counter1, spacer, counter2 ]
spacer : Html Msg
spacer =
text " "
counter1 : Html Msg
counter1 =
span [ id "counter1" ]
[ text "0" ]
counter2 : Html Msg
counter2 =
span [ id "counter2" ]
[ text "0" ]
port startCounters : () -> Cmd msg
static/index.html:
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Main</title>
<style>body { padding: 0; margin: 0; }</style>
</head>
<body>
<pre id="elm"></pre>
<script src="Main.js"></script>
<script>
window.app = Elm.Main.init( { node: document.getElementById("elm") } );
window.app.ports.startCounters.subscribe(function() {
let c1 = document.getElementById("counter1");
let c2 = document.getElementById("counter2");
function increment(e) {
let n = parseInt(e.innerText);
e.innerText = n + 1;
}
requestAnimationFrame(function() {
setInterval(function() {
increment(c1);
increment(c2);
}, 100)
})
});
</script>
</body>
</html>
Makefile:
static/Main.js: src/Main.elm
elm make src/Main.elm --output=static/Main.js