Let's allow outgoing ports to contain Elements, like this:
clicks : Input ()
clicks = Input ()
port takeClicks : Signal Element
port takeClicks = constant <| clickable clicks.handle () (spacer 40 40)
port showClicks : Signal Element
port showClicks = asText <~ count clicks.signal
The intent is that clicking on takeClicks
on one part of the page will increment showClicks
on some other part of the page.
OK, we need a JS subscription interface for these Element ports. Assume I've made divs #takeClicks
and #showClicks
somewhere on the page beforehand:
var clicker = Elm.worker(Elm.Clicker, {});
clicker.ports.takeClicks.subscribe(document.getElementById("#takeClicks"));
clicker.ports.showClicks.subscribe(document.getElementById("#showClicks"));
To subscribe to an Element port, you provide a DOM element into which the Elm runtime will render/merge updates, rather than a JS function to handle updates.
Where's main
in our Elm, and how come we didn't use Elm.embed
in our JS? Well, what do we need them for? main
is just a special case of an Element port.
port main : Signal Element
port main = asText <~ constant "foo"
Actually, we can get rid of Elm.embed
and Elm.fullscreen
entirely. If you want to fullscreen an Element
port, we could have a sentinel 'handler' like this:
worker.ports.display.subscribe(Elm.fullscreen);
Now the name main
only matters if you're compiling from Elm to a self-contained HTML file (just like the C or Java main() entry points). If you're using Elm as a library embedded in HTML/JS, you can call your Element ports whatever you want, have as many as you want, and just subscribe divs to them.
This changes how we use the 'component model'. Rather than needing at least one module per viewport/component, and having them totally disconnected (unless you awkwardly glue them together from JS), we can now use a single Elm 'reactor' and have multiple viewports subscribed to parts of it. Little reactive windows in the wall of HTML, peeking into an underlying Elm program. Then we can minimize the amount of JS glue code we need to write and move as much logic into Elm as possible for many kinds of applications.
Some similar discussion on the favicon thread and the Elm-Test thread.
Potential problems / questions:
- What happens to 'window-wide' things like
Window.dimensions
,Mouse.position
, andMouse.clicks
if there is no longer one 'window' for a given worker? - Will this encourage bad/undesirable design, like using HTML/CSS for layout rather than exploring what Elm can do there?
- How does this work for constant Element ports? Or ports which don't fire after you subscribe? Maybe we should force subscription on worker start?
- Should we force people to specify a type signature for
main
now? - Especially if it needs a type signature,
port main
adds some more complexity for beginning users (at the very least, you now have to typeport
). Right now is also kind of confusing, though, because it's a special case.