logo logo
Elm Multiview


Updates

2024-05-04 elm-portal example

I've added a portal demonstration using elm-portal with the same functionality as the Elm Multiview example. I found it was relatively straightforward and easy to use elm-portal.

2024-05-02 Elm discourse update

In the Elm discourse ↗︎, Wolfgang gave an example of another way to achieve this functionality using custom elements which does not require patching or changing the Elm init() api.

See the following blog post for more information:




What is Elm Multiview?

Elm Multiview is a pair of small scripts which patch the output of the Elm ↗︎ compiler enabling an Elm element or application to render multiple "top-level" DOM views from a single Model.

What problem does Elm Multiview solve?

As explained in the Embedding in HTML ↗︎ section of the Elm guide, an Elm module is included in an HTML page with a <script> element and initialized with a short fragment of Javascript like the one below specifying the DOM element to show the result of the Elm view.

    <div id="myapp"></div>
    <script>
      var app = Elm.Main.init({
        node: document.getElementById('myapp') ◀────────────────
      });
    </script>
how Elm modules are initialized

Because Elm's init() interface only supports passing a single node to a module, each instance of an Elm application or element on a page may only render into a single node. This limitation becomes problematic for complex pages which require information to be rendered in different areas of a page.

How do apps work around this today?

One solution is to live within this limitation and design pages so that a single Elm module can render all areas requiring interaction. Another solution is to instanciate multiple Elm modules on a page and give each instance a separate node for their view.

However each of these approaches has significant drawbacks:

  • Pages incorporating components from frameworks other than Elm and pages generated from management systems may not be able to give Elm responsibility for all the areas of a page.

  • Instanciating multiple Elm modules introduces the problem of keeping the state of distributed modules consistent.

The added complexity to address these problems can easily impose engineering costs outweighing their advantages.

How does Elm Multiview make things better?

Elm Multiview extends the Elm init() interface to directly support multiple DOM nodes allowing the module's view function to render output for each input node.

Applications using Elm Multiview are not limited to a single node. They may render whatever they need into every node they are given.

How does the modified application work?

Elm Multiview modifies the _Browser_element() function generated by the Elm compiler to check if a nodes argument was passed to init(). If not, it falls back to the current behavior. If a nodes argument is present, the modified _Browser_element() passes a function to _Browser_makeAnimator() which iterates over each input node in the nodes array and

  • sets the currentView variable to the 0-based index of the node in the array
  • passes the node to the Elm module's view function
  • computes and applies esulting patches to the node

In short, an Elm Multiview application just asks view to do the appropriate work for all its nodes.

How is the modified Elm module initialized?

The html of the example demonstrates how this can be done:

    <script>
      var nodeids = ["elm-example-card", "elm-example-details"];
      var nodes   = nodeids.map(i => document.getElementById(i));

      if (nodes.every((n) => n !== null)) {
        let flags = {};
        if (Elm.Example) {
          elmExample = Elm.Example.init({
            nodes: nodes,  ◀────────────────────────────────────
            flags: flags,
          });
        }
      } else {
        console.log(
          "elm_multiview.html: some nodes missing",
          "cannot initialize Elm",
          nodeids, nodes
        );
      }
    </script>
instead of a single node, an array of nodes is passed to init()

How does the view know what to render?

The view function inspects the currentView and renders the appropriate html for the corresponding node.

The view in the example demonstrates this:

currentView : Int
currentView =
    0
    
view : Model -> Html Msg
view model =
    case currentView of
        0 ->
            Example.Card.view model

        1 ->
            Example.Details.view model

        _ ->
            Html.div [] []

What are the limitations of Elm Multiview?

Elm Multiview relies on the specific function names generated by the Elm compiler and so only works on "unoptimized" Elm modules.

Should I use Elm Multiview?

Probably not given that nearly equivalent functionality can be obtained with elm-portal.

Patching compiled output is strong medicine. While Elm Multiview eliminated a lot of complexity in Securepub ↗︎, most apps probably shouldn't use it unless they already understand the problem it solves and are currently suffering from the disadvantages of the alternatives.

The author would be very happy if a future version or fork of Elm supported an implementation of this idea with a better developer experience. For instance, the currentView calling convention could be improved.

Questions?

The author will happily answer questions about Elm Multiview via email to elm-multiview at securepub.org