Skip to content

Writing Native

Noah edited this page Nov 14, 2015 · 17 revisions

Running non-graphical Elm projects

There are three ways of running Elm apps:

  • Elm.fullscreen
  • Elm.embed
  • Elm.worker

Elm.fullscreen and Elm.embed will run an Elm application in a given DOM element. Elm.worker will create a headless instance of an Elm application.

Elm.fullscreen = function(module, args)
{
    return init(Display.FULLSCREEN, container, module, args || {});
};

Elm.embed = function(module, container, args)
{
    var tag = container.tagName;
    if (tag !== 'DIV')
    {
        throw new Error('Elm.node must be given a DIV, not a ' + tag + '.');
    }
    return init(Display.COMPONENT, container, module, args || {});
};

Elm.worker = function(module, args)
{
    return init(Display.NONE, {}, module, args || {});
};

In Elm, the main function is special. It is hardcoded to look for either the Core's Graphics modules, or VirtualDom from evancz's virtual-dom implementation, which is most commonly used with elm-html.

var signalGraph = Module.main;

// make sure the signal graph is actually a signal & extract the visual model
if (!('notify' in signalGraph))
{
    signalGraph = Elm.Signal.make(elm).constant(signalGraph);
}
var initialScene = signalGraph.value;

// Figure out what the render functions should be
var render;
var update;
if (initialScene.props)
{
    var Element = Elm.Native.Graphics.Element.make(elm);
    render = Element.render;
    update = Element.updateAndReplace;
}
else
{
    var VirtualDom = Elm.Native.VirtualDom.make(elm);
    render = VirtualDom.render;
    update = VirtualDom.updateAndReplace;
}

This means that you can only run Elm applications through Elm.embed/Elm.fullscreen, as the call to initGraphics is what runs the main function -

if (display !== Display.NONE)
{
   var graphicsNode = initGraphics(elm, Module);
}

Fortunately, when Elm is run through a worker, ports still work!

function addReceivers(ports)
{
    if ('title' in ports)
    {
        if (typeof ports.title === 'string')
        {
            document.title = ports.title;
        }
        else
        {
            ports.title.subscribe(function(v) { document.title = v; });
        }
    }
    if ('redirect' in ports)
    {
        ports.redirect.subscribe(function(v) {
            if (v.length > 0)
            {
                window.location = v;
            }
        });
    }
}
addReceivers(elm.ports);

which means that whenever we want to run code on startup, we simply make an outgoing port which trigger something else.

Running the Elm runtime

In order to actually run anything, the Elm runtime needs to be called. It's trivial to do this - simply append Elm.worker(Elm.Main); to elm.js, where Elm.Main is the name of your application file containing the ports

Other ways

If you really wanted to, you could create a document global object, then create an Elm application like so

var host = {
    tagName: 'div'
};

Elm.embed(Elm.Main, host);

You would then need to implement a Native module expanding on Elm.Native.VirtualDom, providing two core functions - render and updateAndReplace. Note that these could be overwritten by your module very easily.

Clone this wiki locally