-
-
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> |
@cakesmith: Thanks for the heads up. While Result
is definitely is a spiritual successor to Either
I think it is more correct to update to a custom type like Action = Dock String Int | Undock String
since going out of the port is not really an "error".
Thank you very much for Elm - really enjoying it - anyone know what happened to elm --only-js
??
@spacious try to use elm-make Shanghai.elm
You'd now use elm make Shanghai.elm --output=elm.js
, where the file extension controls whether HTML or JS is written.
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
I found that I had to change Shanghai.elm to: