DEV Community

Kristian Pedersen
Kristian Pedersen

Posted on

#30daysofelm Day 8: Data from JS and auto-reload

This is day 8 of my 30 day Elm challenge

Merry Christmas!

Today's project consists of two things, really:

  1. Setting up auto-reload of elm make ... and browser auto-reload of index.html.
  2. Getting some values from JavaScript, using flags.

This can be useful if you want to create some dummy data with a couple of your JavaScript tricks.

For the most part, I've just followed this guide: https://guide.elm-lang.org/interop/flags.html

Getting up and running

In previous projects, I've used elm reactor and visited http://localhost:8000/src/Main.elm.

Today, we need to generate a JavaScript file, and reference it in an index.html file.

  1. In your project root directory, run elm make src/Main.elm --output=main.js.
  2. Create an index.html with the code from the flags tutorial above.

This can however lead to some annoying repetition. Imagine doing this manually for every change:

  • Make changes to Main.elm
  • Re-run elm make src/Main.elm --output=main.js
  • Reload the browser.

Setup

To solve this, I use nodemon and live-server. If you have Node.js installed, you can install them like this: npm install -g nodemon live-server

1. Automatically run "elm make ..." on save

nodemon is typically used to re-run Node.js scripts when you hit save, but I've also used it with Python and Ruby before!

In our case, we can just enter nodemon --exec elm make src/Main.elm --output=main.js, and let it do its thing in the background. :)

2. Automatic browser reload with live-server

Open a new terminal, navigate to the folder with your index.html file, and enter live-server.

A new browser tab will be opened. Any file changes will reload the browser.

1. Create the HTML file, and add static data

I just copied the code from https://guide.elm-lang.org/interop/flags.html

The main.js file we're referencing was created when we ran elm make src/Main.elm --output=main.js

The flags object is the stuff that gets sent to Elm, which you can see here:

<html>

<head>
    <meta charset="UTF-8">
    <title>Main</title>
    <script src="main.js"></script>
</head>

<body>
    <div id="myapp"></div>
    <script>
        const d = new Date()
        const isXmas = d.getDate() === 24 && d.getMonth() === 11 // lol wtf

        var app = Elm.Main.init({
            node: document.getElementById('myapp'),
            flags: {
                isXmas,
                santaMsg: "Merry Christmas!",
                rnd42: [...Array(1000)]
                    .map(n => Math.random() * 1000)
                    .map(Math.floor)
                    .filter(n => n % 42 === 0 && n !== 0)
            }
        });
    </script>
</body>

</html>
Enter fullscreen mode Exit fullscreen mode

2. Setup, subscriptions

I haven't read much about subscriptions yet, but they're needed to receive data from index.html.

module Main exposing (..)

import Browser
import Html exposing (Html, div, text)


main : Program Model Model Msg
main =
    Browser.element
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        }


subscriptions : Model -> Sub Msg
subscriptions _ =
    Sub.none
Enter fullscreen mode Exit fullscreen mode

Here's the documentation on commands and subscriptions, which I'll definitely get into one of these next days: https://guide.elm-lang.org/effects/

3. Model

type alias Model =
    { isXmas : Bool
    , santaMsg : String
    , rnd42 : List Int
    }


init : Model -> ( Model, Cmd Msg )
init dataFromIndexHTML =
    let
        { isXmas, santaMsg, rnd42 } =
            dataFromIndexHTML
    in
    ( { isXmas = isXmas
      , santaMsg = santaMsg
      , rnd42 = rnd42
      }
    , Cmd.none
    )
Enter fullscreen mode Exit fullscreen mode

First, the Model type alias has to match the data structure coming from index.html.

Then it's passed to the init function. I still think Cmd.none looks weird.

It would have been nice if I could just do the destructuring in the let part of the statement, and not having to write repetitive stuff like isXmas = isXmas.

4. Update

Doesn't do anything as far as I can tell:

type Msg
    = NoOp


update : Msg -> Model -> ( Model, Cmd Msg )
update _ model =
    ( model, Cmd.none )
Enter fullscreen mode Exit fullscreen mode

5. View

view : Model -> Html Msg
view model =
    div []
        [ if model.isXmas then
            text model.santaMsg

          else
            text
                ("Not Christmas yet, but here are some random numbers that are divisible by 42: "
                    ++ (model.rnd42
                            |> List.map String.fromInt
                            |> String.join ", "
                       )
                )
        ]
Enter fullscreen mode Exit fullscreen mode

Nothing crazy going on in the view.

You don't have to use the arrows, but I find them nicer to read than this:

String.join ", " (List.map String.fromInt model.rnd42)
Enter fullscreen mode Exit fullscreen mode

I hope this project was useful to you. Merry Christmas, and see you tomorrow!

Top comments (1)

Collapse
 
wolfadex profile image
Wolfgang Schuster •

You shouldn't have had to destructure and rebuild your model. Though maybe because it's coming from JS into Elm you're forced to? It's definitely a bit safer to have your init be some like

type alias Flags = Json.Decode.Value

init : Flags -> ( Model, Cmd Msg )
Enter fullscreen mode Exit fullscreen mode

and then use elm/json to decode your incoming JS values. That will prevent any mistakes on the JS side from crashing your app.


You also might really like to check out elm-live.com/ it covers everything you're doing with this setup by just running elm-live src/Your.elm and it has a lot of nice options for proxies and other stuff.