Elm + Golden Layout, any hope?

Hi all,
I recently started to hack with Elm and I am having quite a lot of fun! Thanks to the community!

After a successful prototype integrating CodeMirror, semantics-ui, elm-markdown and elm-split-pane, I wanted to go a step further and replace elm-split-pane with Golden Layout that makes a remarkable job at Compiler Explorer.

I am wondering if anyone already tried to build an Elm app interacting with Golden Layout or any library with a similar architecture / philosophy?

At first sight it seems kind of hopeless to integrate golden layout with TEA because the library maintains and mutates its own sub-tree of the DOM:

  • it takes ownership of a DOM element as its rendering root.
  • it builds a sub-tree of containers and splitters.
  • the leaf items are instances of user-defined “template component”.
  • you can subscribe to all kind of events (item creation, deletion …), so in theory you can track what matters in the Elm model state using a few ports.
  • but… human interactions with the layout trigger reshuffling and updates of the sub-tree owned by the library. Although I assume the items are simply moved around so user-defined content integrity is preserved.

The reshuffling of DOM elements performed by the library on human (or programmatic) interaction prevents from managing that part with Elm as part of a regular view update.

Is it correct to say that TEA enforces that one can only update a single view backed by a single root node of the (virtual) DOM? Or is it only a restriction of Browser.document ?

My hope to integrate with golden layout would be to find a way to control several DOM nodes instead of just one. For instance the view function could return a mapping from DOM ids to Html, view : Model -> Dict String (Html msg), and there would be one virtual DOM maintained for each key.

With such a view function, I could manage the leaf items with Elm regardless of the rest of the DOM sub-tree owned by golden layout

Does this makes any sense? Is there a better solution that I’m not aware of?

Thanks,
Quentin.

Usually, I’d say go for custom elements for something like “child node trees that are outside of elm’s scope” then use attributes+events to interact with the element through some interface you define.

But, if I’m understanding it correctly, golden layout would need to be wrapping elm views, right? You want to have an app with different views that are layouted through golden layout?

By looking at the getting started, jquery dependency, bower setup - it seems this library is not the easiest thing to integrate with an elm app… out the top of my head I can only think of a really weird setup where I have multiple elm apps… something like:

root (elm)
  golden layout (custom element)
    golden layout pane (custom element that wraps another elm app)

lots of plumbing to make things communicate with each other. lots of possibilities to make your state be out of sync as well because of the plumbing + multiple TEAs.

I’m also curious if someone will think of something more sane than this! :sweat_smile:

I think it should be OK to do it by having each pane of the Golden Layout be an Elm app. Communication between panes could be done with ports.

Is it correct to say that TEA enforces that one can only update a single view backed by a single root node of the (virtual) DOM?

Yes. One node per app. You can have multiple apps on the same page but not multiple nodes inside each app.

Browser.document takes over the body. This makes it not appropriate for this use case. You will need Browser.element with mount nodes.

What about an new kind of VirtualDom node similar to keyedNode that would traverse the DOM from its current point, search (getElementById ?) for an element with a given id and resume compare/patch the virtual DOM from there ?

findNodeById : String -> Node msg -> Node msg

Applied to golden layout, we would direct VirtualDom (or the Html layer) to search for our item element by id from the golden layout root element:

view model =
  div [id "golden-layout-root"] 
    [ Html.findById "my-item" (renderMyItem model.first),
    , Html.findById "second-item" (renderMyItem model.second)
    ]

Changes to VirtualDom are very difficult. You would have to come up with the implementation, make a strong case for its utility, do a performance impact analysis, etc.

Elm is very conservative in its use of Javascript.

In any case, as I said, the best way to do it with current tech would be to have elm apps for each pane or a single app that can be configured from outside to select for the functionality and mount the app with different init configurations .

I tested this with the counter example and it seams to work just fine

Main.elm :

module Main exposing (..)

import Browser
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)


type alias Model =
    { count : Int, label : String }


init : String -> Model
init label =
    { count = 0, label = label }


type Msg
    = Increment
    | Decrement


update : Msg -> Model -> Model
update msg model =
    case msg of
        Increment ->
            { model | count = model.count + 1 }

        Decrement ->
            { model | count = model.count - 1 }


view : Model -> Html Msg
view model =
    div [ style "background-color" "white" ]
        [ h1 [] [ text model.label ]
        , button [ onClick Increment ] [ text "+1" ]
        , div [] [ text <| String.fromInt model.count ]
        , button [ onClick Decrement ] [ text "-1" ]
        ]


main : Program String Model Msg
main =
    Browser.element
        { init = \flags -> ( init flags, Cmd.none )
        , view = view
        , update = \msg m -> ( update msg m, Cmd.none )
        , subscriptions = \_ -> Sub.none
        }

index.html :

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="UTF-8">
  <title>Main</title> 
  <script src="main.js"></script>
   <script type="text/javascript" src="http://code.jquery.com/jquery-1.11.1.min.js">
</script>
  <script type="text/javascript" src="https://golden-layout.com/files/latest/js/goldenlayout.min.js"></script>
<link type="text/css" rel="stylesheet" href="https://golden-layout.com/files/latest/css/goldenlayout-base.css" />
  <link type="text/css" rel="stylesheet" href="https://golden-layout.com/files/latest/css/goldenlayout-dark-theme.css" />

</head>
<body>
	<div id="first"></div>
	<div id="second"></div>
  <script>

    var config = {
    content: [{
        type: 'row',
        content:[{
            type: 'component',
            componentName: 'testComponent',
            componentState: { label: 'This is Pane A' }
        }, {
            type: 'component',
            componentName: 'testComponent',
            componentState: { label: 'This is Pane B' }
        }]
     }]
    }
    var myLayout = new GoldenLayout( config );
    myLayout.registerComponent( 'testComponent', function( container, componentState ){
    var elmNode= document.createElement("div")
    Elm.Main.init({ node: elmNode, flags: componentState.label })
    var el = container.getElement().append(elmNode)

    });
    
myLayout.init();
 
</script>
</body>
</html>

The elm compiler does not allow for custom html implementations so if you go that route you’re probably talking about forking the compiler… which has its own (huge) set of problems.

If you can live with a non-elm root that starts all your leaf-elm apps it could be quite simple to go the multi elm app route. It will only get more messy if you wanna make all sub-apps talk to a root app that is also elm (elm app with custom elements that create elm apps that talk to root through ports and then through events + attributes)

Thanks @georgesboris @pdamoc ! Of course having a single app is preferred here, because in a realistic app we don’t have just independent counters. Maintaining a coherent distributed state is hard and the essence of Elm is to avoid that.

Theoretically speaking, would the solution involving the virtual-dom be violating the core principles of Elm? Not that I feel the courage to do it, but as the morale of the story.

As far as I see the solution you proposed kinda requires you to break free of elm’s official compiler - you wouldn’t be able to ship it as a package and even if you maintained it just for yourself you would run into upgrade problems… at least this would be the situation in Elm’s current version (as far as I know… and also afaik this is unlikely to change in the future).

It would be a great exercise to pull it off! Put I don’t think it would be the best practical solution if you just want to get the job done, you know?

I agree with you 100% that the solution of multiple apps with plumbing fights against Elm’s strenghts as well

I guess a nice solution would be to just build the same functionality that golden-layout has in pure elm. I would not start on something like that in react, but with elm and elm-ui, that does not seem impossible now :grinning: Also, golden layout seems a bit limited in functionality, thinking a better solution would be to aim for what vs-code window management does.

2 Likes

Not necessarily recomending this approach to solve your problem, but there was this interesting experiment in writing a completely custom vdom on top of Elm, without hacking the compiler or kernel modules:

1 Like