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>
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>
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