You can use ports in Elm to communicate with javascript in order to publish and subscribe to events in both directions. Let's build an example where a list of images is displayed with masonry layout, and clicking an image will remove it and trigger masonry to layout the remaining images.
Since the Elm app will send multiple types of events to javascript, we can create a single port that sends strings that javascript can then act upon. These strings will be commands that we can interpret in javascript in order to tell masonry to do certain things, like "initialize" and "imageRemoved". This port will also need a mailbox to which we can send messages from inside Elm.
masonryMailbox : Signal.Mailbox String
masonryMailbox =
Signal.mailbox ""
port masonryCommands : Signal String
port masonryCommands =
masonryMailbox.signal
Since you're using StartApp, you can return Effects in your update function, so let's create a function that will send messages to this mailbox from both the StartApp initializer and from inside the update function.
sendMasonryCommand : String -> Effects.Effects Action
sendMasonryCommand cmd =
let
task =
Signal.send masonryMailbox.address cmd
`Task.andThen` \_ -> Task.succeed NoOp
in
Effects.task task
You can send the "initialize" command during the StartApp init function like this:
init =
(initialModel, sendMasonryCommand "initialize")
In the update function, if we have a RemoveImage String action, we can send an "imageRemove" command to javascript:
update action model =
case action of
NoOp ->
(model, Effects.none)
RemoveImage url ->
let model' =
{ model
| images = List.filter ((/=) url) model.images
, message = "Removing image " ++ url
}
in (model', sendMasonryCommand "imageRemoved")
Now we need to wire up the javascript side of things to listen for these events. If javascript gets the command "initialize", then we can wire up masonry. If we get the "imageRemoved" command, we can just tell masonry to trigger the layout command again.
var app = Elm.fullscreen(Elm.Main);
app.ports.masonryCommands.subscribe(function(cmd) {
var $grid = $('.grid')
if (cmd === "initialize") {
$grid.masonry({
itemSelector: '.grid-item',
percentPosition: true,
columnWidth: '.grid-sizer'
});
$grid.imagesLoaded().progress( function() {
$grid.masonry();
});
} else if (cmd === "imageRemoved") {
$grid.masonry();
}
});
We can also wire up ports to send events back into Elm. Let's add onto the example by sending a message to Elm every time masonry completes its rendering. First we'll create a port called setMessage.
port setMessage : Signal String
Since this is a port that javascript will be publishing to, we only define the function signature in Elm, not the function itself. Instead, we have to give the signal an initial value from the javascript side when we call Elm.fullscreen(). The javascript changes to this:
var app = Elm.fullscreen(Elm.Main, { setMessage: "" });
Now, inside the javascript block that handles the "initialize" command, you can wire up masonry's layoutComplete function to send a message to this new port.
$grid.on("layoutComplete", function() {
app.ports.setMessage.send("Masonry layout complete!");
});
In order to consume these messages from the setMessage port in Elm, you'll need a SetMessage String action and you'll need to map the signal inside the StartApp inputs list. Your StartApp initializer code will look like this:
app =
StartApp.start
{ init = init
, view = view
, update = update
, inputs = [ Signal.map SetMessage setMessage ]
}
I've provided a complete working example of all of this in a few gists. Here is a gist containing the Elm code and here is a gist containing the html and javascript.