This library helps you operate more easily at the frontier of Elm and JavaScript. It provides three main functions:
toJson
- automatically encodes an Elm object to a Json stringfromJson
- automatically decodes a Json string to an Elm objectcall
- calls a JavaScript function that returns a value to Elm as aTask
Each function returns an Elm Task
with a response value or an explanatory
String error. It works with both 0.17
and 0.18
.
Using Frontier's toJson
and fromJson
provides an easy and non-tedious way to
serialize and desearialize Elm objects. The usual method is to write Json
Encoders or Decoders, but this takes a long time and is prone to
programming errors. Frontier's fromJson
and toJson
automatically convert
without the use of developer-specified encoders/decoders.
Since toJson
, fromJson
, and call
each return a Task
, they can
be chained together with other Task
s using Task.andThen
. For example,
fromJson
could be chained after a call to Http.getString
to convert Json from a REST Api.
Using the standard ports system in Elm, calling into JavaScript
requires that you generate a Cmd
to run the JavaScript
function, and then separately subscribe to a response as a Sub
. This
disconnect between the call and return of a function makes some
difficulties. For instance, you can't be sure which port response Sub
corresponds to which call you made with the port Cmd
unless you pass back
some id or the original args. Most limiting of all, you can't chain
together external javascript functions as Task
s.
Frontier's call
allows you to call any JavaScript function and
receive the results as a normal Elm Task
, immediately or delayed.
You may have noticed that the Elm ports system already automatically converts Elm objects to JavaScript and vice versa. Whenever you declare an outgoing port, the Elm compiler will generate a converter function that converts an Elm object of the port's type to JavaScript. It also generates a converter from JavaScript to Elm when you declare an incoming port.
Elm-Frontier uses these converter functions that the Elm compiler
generates for ports. With Frontier, ports are used merely to convert
data and are passed in as arguments to toJson
, fromJson
, or
call
. Ports are not mapped to individual JavaScript functions, so
you need only declare one port per datatype and direction
(input/output).
The Elm objects that you can convert automatically are limited to the ones that can be converted by Elm's port system: Ints, Floats, Bools, Strings, Maybes, Lists, Arrays, Tuples, Json.Values, and concrete records.
These are type aliases for outgoing/incoming port declarations. msg
should remain polymorphic in your ports for optimum compatibility.
type alias OutputPort a msg =
a -> Cmd msg
This is a standard port definition for an output port. For example, port intOut : Int -> Cmd x
would declare an OutputPort
named intOut
that converts outgoing
Elm Ints.
type alias InputPort a msg =
(a -> msg) -> Sub msg
Standard input port definition. port intIn : (Int -> x) -> Sub x
would declare an input port named intIn
that converts incoming
JavaScript ints.
toJson : OutputPort elmObject msg -> elmObject -> Task String JsonString
Converts elmObject
to a Json string. It uses JavaScript's JSON.stringify
.
JsonString
is just a type alias for String
. It's guaranteed to
succeed if JSON.stringify
is available in JavaScript.
fromJson : InputPort elmObject msg -> JsonString -> Task String elmObject
Tries to convert a Json string to an elmObject
. It uses JavaScript's
JSON.parse
to convert the Json to a JavaScript object, then Elm's
port converter to turn it into an Elm object. If there is an error in
either of these conversions, the task will return an explanatory
String error.
fromJson
is best used to convert Json that was created with
Frontier's toJson
, but you can also use it to convert well-formed
foreign Json. Just use the same field names and use Maybe
for
nullable Json values. It's ok to ignore some Json fields.
You might run into trouble if the json field names have dashes. In
this case, you might want to make a custom Decoder, manually convert
the field names, or use call
to convert the field names in
JavaScript before passing the Json to fromJson
.
call : OutputPort a x -> InputPort b y -> OuterFunctionName -> a -> Task String b
Calls a JavaScript function named OuterFunctionName
(a
String
), passing it argument a
.
The JavaScript function should take two arguments. The first is a
ret
object that has two functions, succeed (a)
and fail (String)
, which you call to return the computation. The second
argument is the actual value from Elm.
Using the ports intOut
and intIn
declared above, here is a trite
example of using a JavaScript function that returns n + n/2
three
seconds after it is called.
In Elm:
import Frontier
port intOut : Int -> Cmd x
port intIn : (Int -> x) -> Sub x
delayedAddingTask : Int -> Task String Int
delayedAddingTask =
Frontier.call intOut intIn "delayedAddOneHalf"
In JavaScript:
<script>
var app = Elm.Example.fullscreen();
function delayedAddOneHalf(ret, n) {
setTimeout(function(){ret.succeed(n + parseInt(n/2));}, 3000);
}
</script>
Then calling delayedAddingTask 8
using Task.attempt
or
Task.perform
would return 12
three seconds later.
If you want to indicate that the JavaScript function has failed, you can use
ret.fail("The function failed because...");
in your JavaScript
function to send back an error to Elm.
Note that JavaScript's eval
is called on the OuterFunctionName
string that you supply to resolve the function. To avoid security
problems, you shouldn't let users somehow supply the function name.
Elm-Frontier uses a native javascript file and hasn't been approved to
be included in the Elm package repository (I haven't tried),
so to use you must currently download src/Frontier.elm
into the
src/
folder of your project, and download src/Native/Frontier.js
into
src/Native/
.
Then, open up your src/Native/Frontier.js
in a text editor. The
first line of the file is this:
var _mpdairy$elm_frontier$Native_Frontier = function() {
You must change mpdairy
and elm_frontier
to the github username
and github project name specified as the repository
in your
project's elm-package.json
file. You also need to add
"native-modules": true
to your elm-package.json
file.
For example, if this were your project's elm-package.json
file (with the
native-modules
option added):
{
"version": "2.0.1",
"summary": "this is my special project that I love",
"repository": "https://github.com/jburleydog/my-special-project.git",
"license": "UNLICENSED",
"source-directories": [
"src"
],
"exposed-modules": [],
"dependencies": {
"elm-lang/core": "4.0.5 <= v < 5.0.0",
"elm-lang/html": "1.0.0 <= v < 2.0.0"
},
"native-modules": true,
"elm-version": "0.17.1 <= v < 0.18.0"
}
You would change the first line of your src/Native/Frontier.js
file
to:
var _jburleydog$my_special_project$Native_Frontier = function() {
See
example.html
and
src/Example.elm
for using elm-frontier
with complex Elm objects.
Elm-Frontier works with Elm 0.17 and 0.18. It is my hope that Evan will build "official" functionality similar to Frontier's into 0.19.
However, there is no guarantee that he will and he might even somehow block access to the port converter functions that are essential for Frontier to work, so use at your own risk.