-
-
Save evancz/8521339 to your computer and use it in GitHub Desktop.
// initialize the Shanghai component which keeps track of | |
// shipping data in and out of the Port of Shanghai. | |
var shanghai = Elm.worker(Elm.Shanghai, { | |
coordinates:[0,0], | |
incomingShip: { name:"", capacity:0 }, | |
outgoingShip: "" | |
}); | |
function logger(x) { console.log(x) } | |
shanghai.ports.totalCapacity.subscribe(logger); | |
// send some ships to the port of Shanghai | |
shanghai.ports.incomingShip.send({ | |
name:"Mary Mærsk", | |
capacity:18270 | |
}); | |
shanghai.ports.incomingShip.send({ | |
name:"Emma Mærsk", | |
capacity:15500 | |
}); | |
// have those ships leave the port of Shanghai | |
shanghai.ports.outgoingShip.send("Mary Mærsk"); | |
shanghai.ports.outgoingShip.send("Emma Mærsk"); |
module Shanghai where | |
import Either (..) | |
import Dict | |
port coordinates : (Int,Int) | |
port incomingShip : Signal { name:String, capacity:Int } | |
port outgoingShip : Signal String | |
ships = merge (Right <~ incomingShip) (Left <~ outgoingShip) | |
updateDocks ship docks = | |
case ship of | |
Right {name,capacity} -> Dict.insert name capacity docks | |
Left name -> Dict.remove name docks | |
dock = foldp updateDocks Dict.empty ships | |
port totalCapacity : Signal Int | |
port totalCapacity = lift (sum . Dict.values) dock |
<html> | |
<head> | |
<title>Embedding Elm in HTML!</title> | |
<script type="text/javascript" src="http://elm-lang.org/elm-runtime.js"></script> | |
<script type="text/javascript" src="build/Shanghai.js"></script> | |
</head> | |
<body> | |
<h1>Ports of Shanghai</h1> | |
<p>Check out the developer console. Try sending values to <code>shanghai.ports.incomingShip</code>.</p> | |
</body> | |
<script type="text/javascript" src="Ports.js"></script> | |
</html> |
I had to change Shanghai.elm
, mostly to replace the map
operator in @cakesmith's version:
module Shanghai where
import Signal exposing (..)
import Dict
import List exposing (sum)
-- Input ports
port coordinates : (Int,Int)
port incomingShip : Signal { name:String, capacity:Int }
port outgoingShip : Signal String
ships = merge (Signal.map Ok incomingShip) (Signal.map Err outgoingShip)
updateDocks ship docks =
case ship of
Ok {name,capacity} -> Dict.insert name capacity docks
Err name -> Dict.remove name docks
dock = foldp updateDocks Dict.empty ships
-- Output ports
port totalCapacity : Signal Int
port totalCapacity = Signal.map (sum << Dict.values) dock
just for completeness, if elm make Shanghai.elm --output=elm.js
is used to compile, index.html will need
<script type="text/javascript" src="elm.js"></script>
The example with Elm v0.18, ports, headless program and sending a single value to JS from the start of the program (no Html at all): https://gist.github.com/shamansir/1e9b22851f1c5d97ad9e66682f95b754 (not reliable anymore)
Since you can not send some value just immediately from the headless program (or else subscriber won't receive it), I was required to trigger sending it with incoming port from JS, just after the subscription was performed.
An attempt at a remaster for 0.18:
port module Shanghai exposing (..)
{- Shanghai.elm - Updated for 0.18
Build with: $elm-make --warn --output Shanghai.js Shanghai.elm
-}
import Json.Decode
-- https://github.com/elm-lang/elm-make/issues/127
import Dict exposing (Dict)
type alias Model =
Dict String Int
type alias ShipInfo =
{ name : String
, capacity : Int
}
-- Browser-bound (-> Cmd msg)
port totalCapacity : Int -> Cmd msg
-- Elm-bound (-> Sub msg)
port incomingShip : (ShipInfo -> msg) -> Sub msg
port outgoingShip : (String -> msg) -> Sub msg
type Msg -- see also "subscriptions" below
= Dock ShipInfo -- ShipInfo as specified by incomingShip
| Sail String -- ship name as specified by outgoingShip
{-
Specify which Msg data constructors to use for
the various Elm-bound port values
-}
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[ incomingShip Dock
, outgoingShip Sail
]
tallyCapacity : Model -> Int
tallyCapacity =
List.sum << Dict.values
{- 1. "regular" boring version
tallyCapacity model =
List.sum (Dict.values model)
2. Use backward function application (<|) to "avoid parentheses"
tallyCapacity model =
List.sum <| Dict.values model
3. Use function composition (<<) for a "pointfree style"
representation (parameter is implied and therefore "unseen")
tallyCapacity =
List.sum << Dict.values
4. Alternately with (>>)
tallyCapacity =
Dict.values >> List.sum
-}
dock : ShipInfo -> Model -> ( Model , Cmd Msg )
dock info model =
let
newModel = Dict.insert info.name info.capacity model
in
( newModel, totalCapacity <| tallyCapacity newModel )
sail : String -> Model -> ( Model, Cmd Msg )
sail name model =
let
newModel = Dict.remove name model
in
( newModel, tallyCapacity newModel |> totalCapacity )
-- alternately used forward function application (|>) instead
{- Alternately Platform.programWithFlags
accepts initialization data to be passed
to "init" function
-}
init : ( Model, Cmd msg )
init =
( Dict.empty, totalCapacity 0 )
-- ( Dict.empty, Cmd.none )
-- as an alternate if initial capacity report is not desired.
-- Process the incoming values and dispatch the updated capacity
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Dock info ->
dock info model
Sail name ->
sail name model
{-
Platform.program is "headless" - it does not
generate an HTML view
-}
main : Program Never Model Msg
main =
Platform.program
{ init = init
, update = update
, subscriptions = subscriptions
}
/* ports.js - Chrome 55.0.2883.95 (64-bit)
It is assumed that "this.Elm" is set
and valid for this IIFE (i.e. "Window.Elm.Shanghai"
was set by Shanghai.js beforehand).
*/
; (function() {
function connectElmWorker(elm) {
function logDockedCapacity (value) {
console.log(
'[' + (new Date).toISOString() + ']: ' + value
);
}
/* "Elm.Shanghai" as in "module Shanghai"
from file "Shanghai.elm""
compiled into file "Shanghai.js.
*/
let elmWorker = elm.Shanghai.worker();
// log docked capacity changes
elmWorker.ports.totalCapacity.subscribe(logDockedCapacity);
return elmWorker;
}
function dock(worker, name, capacity) {
worker.ports.incomingShip.send({
name: name,
capacity: capacity
});
}
function sail(worker, name) {
worker.ports.outgoingShip.send(name);
}
function scheduleActions(shanghai) {
function dockAt(worker, name, capacity){
return dock.bind(null, worker, name, capacity);
}
function sailFrom(worker, name) {
return sail.bind(null, worker, name);
}
function at(delay, func) {
setTimeout(func, delay);
}
const mary = 'Mary Mærsk',
emma = 'Emma Mærsk';
// send some ships to the port of Shanghai
at(500, dockAt(shanghai, mary, 18270));
at(1000, dockAt(shanghai, emma, 15500));
// have these ships leave the port of Shanghai
at(1500, sailFrom(shanghai, mary));
at(2000, sailFrom(shanghai, emma));
}
const moduleName = 'shanghaiPorts';
let elmWorker = connectElmWorker(this.Elm);
scheduleActions(elmWorker);
if (typeof this[moduleName] === 'undefined') {
this[moduleName] = {
dock: dock.bind(null, elmWorker),
sail: sail.bind(null, elmWorker),
shanghai: elmWorker
};
}
}).call(this);
<!DOCTYPE html>
<!-- shanghai.html -->
<html>
<head>
<title>Embedding Elm in HTML!</title>
</head>
<body>
<h1>Ports of Shanghai</h1>
<p>Check out the developer console, Try sending values to <code>shanghai.ports.incomingShip</code></p>
<script type="text/javascript" src="Shanghai.js"></script>
<script type="text/javascript" src="ports.js"></script>
<script type="text/javascript">
; (function() {
// Allow access to "shanghai" via the developer console
const propName = 'shanghai';
let module = this['shanghaiPorts'];
if (typeof module === 'object'
&& propName in module
&& typeof this[propName] === 'undefined') {
this[propName] = module[propName];
} else {
console.log(
'Any one or more of the following PROBLEMS were encountered:\
\n - "shanghai" is ALREADY DEFINED.\
\n - "shanghaiPorts" is NOT DEFINED or isn\'t an "object".\
\n - "shanghaiPorts.shanghai" is NOT DEFINED.'
);
}
/* In the developer console try:
shanghai.ports.incomingShip.send({ name: 'Mary Mærsk',capacity: 18270 });
shanghai.ports.incomingShip.send({ name: 'Emma Mærsk', capacity: 15500 });
shanghai.ports.outgoingShip.send('Mary Mærsk');
shanghai.ports.outgoingShip.send('Emma Mærsk');
Or:
shanghaiPorts.dock('Mary Mærsk', 18270);
shanghaiPorts.dock('Emma Mærsk', 15500);
shanghaiPorts.sail('Mary Mærsk');
shanghaiPorts.sail('Emma Mærsk');
*/
}).call(this);
</script>
</body>
</html>
I messed up my example, sorry, @peerreynders yours is awesome!
what's not clear to me in the docs or this example is when you have a Main
"app" already, but then want to add a port module within that existing app. That is to say, all of the examples i've yet seen are as though the port
module is also the entrypoint itself
You'd now use
elm make Shanghai.elm --output=elm.js
, where the file extension controls whether HTML or JS is written.