Elm is a functional language focused on making user interfaces which compile to javascript and makes highly dynamic webapps very easy to write correctly. The libraries available handle some aspects of web programming (eg: Canvas graphics or HTML generation) very well with some nice APIs. However, at the time of writing there are some gaps in the browser API for which you must drop down into javascript to access. This article aims to show how to nicely integrate Elm with window.location
and the HTML5 browser history API. I'm assuming you know what the browser history API is for and have had at least a cursory overview of Elm so that you understand it's syntax, Signals and Ports.
We'd like both a way to write to the browser location history, and also to read the initial location from the browser as the app starts up. We're assuming for now that there's no other javascript running on the page that might set the location.
The standard way of manipulating a time-varying value in Elm is to use a Signal
, and the standard library provides many signals to process user input, such as the Window.dimensions
signal which gives you the width and height of the browser window. However, these are read-only - the standard way to send a signal out of Elm is to use a Port
. That leads us to think that we need a constant incoming value giving the initial browser location, and a time-varying outgoing value. For simplicity, we'll read the location in and write it out as a String
.
port initialLocation : String -- incoming
port location : Signal String -- outgoing
We can then link those up into our app to give the desired behaviour. For example, to bind the address location to an <input>
field:
-- Set up a channel to submit textfield content, initialized with the initial location
textContent : Channel Content
textContent = channel {noContent | string <- initialLocation}
-- Style the field and render
main : Signal Element
main = field defaultStyle (send textContent) "Type here" <~ subscribe textContent
All that now remains is to link our Elm app up to the actual history API using javascript. Use elm-make
or elm-reactor
to compile the .elm file into elm.js and add the following index.html
page. Here we are reading and writing from the query portion of the URL only, so that refreshing the page in elm-reactor
doesn't result in a 404, but it should be clear how to extend this to cover the full url.
<!DOCTYPE html>
<html>
<head></head>
<body>
<div id="elm"><!-- We will insert the elm app here --></div>
<script src="elm.js"></script>
<script type="text/javascript">
var div = document.getElementById('elm');
// Embed the elm app into a div and pass the initial location
var elmApp = Elm.embed(Elm.BrowserIntegrationExample, div,
{ initialLocation: window.location.search.substring(1) });
// Listen to output on the location port and pass to browser
elmApp.ports.location.subscribe(function (newValue) {
history.replaceState({}, "", "?" + newValue);
})
</script>
</div>
</body>
</html>
It works! The content of the text field is now bound to the location search. Editing the text field will update the address bar, and changing the address and loading the page will update the text field.
This roof calculator is a more realistic example using browser integration, source code available here.
Here is the complete example source for this article.