After reading and listening to a LOT of literature on Elm, I wanted to try it out in a simple program. I have a section on my website for the contact form that seemed like a simple enough place to start that's neither too simple nor too complex.
Functionality
You can try this out in the contact section of this website on the homepage. It's a simple form with mostly static text. The only piece of dynamic functionality is when you click on the different reasons to contact me, I pop in some additional copy
above the form and fill in the subject
line in the form.
Modeling the data
I started by creating a model of the state in the application
{ subject = ""
, copy = ""
}
The way we use the commas here is different but having listened to all the reasoning around it, I think this is a better way and would like to structure commas even in my JS applications this way.
Adding Msg to update my model
Then I started adding all possible messages through which my model can change.
type Msg
= Coffee
| Meetup
| OpenSource
| Talk
This part felt weird coming from JS because of the values that Msg can take are straight up tokens and not strings within quotes. In TS, you could do this but you would have to define what each type meant. Eg. Coffee: String
and then combine them together. I like that you can just type them but it's logic that's very specific to your app .
Flesh out the update method
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Coffee ->
( { model | subject = "Grab coffee", copy = "I'm down to grabbing coffee talking about your startup, a new opportunity or just plain banter." }, Cmd.none )
Meetup ->
( { model | subject = "Organize a meetup", copy = "I love participating in interest-based comnunities in real life. Running a meetup can be hard and I'd love to help in anyway I can." }, Cmd.none )
OpenSource ->
( { model | subject = "Open source help", copy = "I am always looking to contribute to projects in meaningful ways. I enjoy coding and writing clear technicaly documentation." }, Cmd.none )
Talk ->
( { model | subject = "Request a talk", copy = "I'm seeking opportunities to speak at international conferences and local meetups this year. Definitely reach out if you see a good fit." }, Cmd.none )
Hooking them up together was so simple. My view is simply two divs that show whatever is currently the model copy and subject with 4 buttons to toggle between them.
view model =
div []
[ button [ onClick Coffee ] [ text "Coffee" ]
, button [ onClick Meetup ] [ text "Meetup" ]
, button [ onClick OpenSource ] [ text "Open Source" ]
, button [ onClick Talk ] [ text "Talk" ]
, div [] [ text model.subject ]
, div [] [ text model.copy ]
]
This was so simple to hookup and very little boilerplate. Of course, writing the view in Elm felt a little different compared to writing in JSX which feels very natural and HTML-like. But here's the output. Right now, this works as a simple UI when you click a button, it triggers a change in some copy
Styling
I didn't attempt to do any styling in this exercise but if I can figure out how to apply styling the elm way, that's really all I need for this widget. Planning to look into the following libraries to dig further into styling with Elm.
First impressions:
- Honestly, the whole exercise took me less than 10 minutes and this is the first time I'm writing any elm code
- I was very impressed with how little boilerplate is needed - imagine doing this with Redux.
- I used create-elm-app to quickly bootstrap a server with hot reloading
- The compiler was super helpful and pleasant to work with. It was like having a friendly pair programmer watching out for errors every step of the way
- I like the whole everything colocated in the same file. I can see how even when this is more complex, it can help to keep all related files together
- The Elm debugger with time traveling is super cool
- Elm formatter really helps with learning and trying to do a bunch of things BEFORE the compiler steps in to help you
Iteration one: Start Init function with a Msg
A small improvement I wanted to make was to start with an empty model but immediately trigger an update so the initial state is equal to a default selection rather than an empty page.
So I want this:
instead of an empty blank state like this:
Essentially, I want to set the initial state of the app to be equivalent of an Update triggered by the user. Since that logic is already captured in the update function, I would simply like to trigger init with a Msg Coffee
.
How to send command on init in Elm? 🤔
I attempted doing the following but that didn't work and resulted in a type mismatch error. I tried fudging around with the type signature but couldn't get it to work.
init : ( Model, Cmd Msg )
init =
( update Coffee
{ subject = ""
, copy = ""
}
, Cmd.none
)
I haven't run into the idiomatic way in Elm to model this in my research so far. Should the initial state to be the exact state I want (not empty strings) or trigger an update in such a scenario?
I found this reddit thread that helped shed some light. Looks like you have to split that functionality separately and initialize with a function call instead of an empty model.
setCoffee : Model -> Model
setCoffee model =
{ model | subject = "Grab coffee", copy = "I'm down to grabbing coffee talking about your startup, a new opportunity or just plain banter." }
That means, I have to change the update method to look like below. Note the coffee branch is calling the function we just created instead of setting the values directly as before.
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Coffee ->
( setCoffee model, Cmd.none )
Meetup ->
( { model | subject = "Organize a meetup", copy = "I love participating in interest-based comnunities in real life. Running a meetup can be hard and I'd love to help in anyway I can." }, Cmd.none )
OpenSource ->
( { model | subject = "Open source help", copy = "I am always looking to contribute to projects in meaningful ways. I enjoy coding and writing clear technicaly documentation." }, Cmd.none )
Talk ->
( { model | subject = "Request a talk", copy = "I'm seeking opportunities to speak at international conferences and local meetups this year. Definitely reach out if you see a good fit." }, Cmd.none )
Also, needed to change the init method as follows:
init : ( Model, Cmd Msg )
init =
( setCoffee
{ subject = ""
, copy = ""
}
, Cmd.none
)
This essentially means, the initial state is calling a function setCoffee
with the base state we started with and will instantiate the app with what the function returns. Here's the full final program. You can also view it in action here
module Main exposing (..)
import Browser
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
---- MODEL ----
type alias Model =
{ subject : String
, copy : String
}
init : ( Model, Cmd Msg )
init =
( setCoffee
{ subject = ""
, copy = ""
}
, Cmd.none
)
---- UPDATE ----
type Msg
= Coffee
| Meetup
| OpenSource
| Talk
setCoffee : Model -> Model
setCoffee model =
{ model | subject = "Grab coffee", copy = "I'm down to grabbing coffee talking about your startup, a new opportunity or just plain banter." }
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Coffee ->
( setCoffee model, Cmd.none )
Meetup ->
( { model | subject = "Organize a meetup", copy = "I love participating in interest-based comnunities in real life. Running a meetup can be hard and I'd love to help in anyway I can." }, Cmd.none )
OpenSource ->
( { model | subject = "Open source help", copy = "I am always looking to contribute to projects in meaningful ways. I enjoy coding and writing clear technicaly documentation." }, Cmd.none )
Talk ->
( { model | subject = "Request a talk", copy = "I'm seeking opportunities to speak at international conferences and local meetups this year. Definitely reach out if you see a good fit." }, Cmd.none )
---- VIEW ----
view : Model -> Html Msg
view model =
div []
[ button [ onClick Coffee ] [ text "Coffee" ]
, button [ onClick Meetup ] [ text "Meetup" ]
, button [ onClick OpenSource ] [ text "Open Source" ]
, button [ onClick Talk ] [ text "Talk" ]
, div [] [ text model.subject ]
, div [] [ text model.copy ]
]
---- PROGRAM ----
main : Program () Model Msg
main =
Browser.element
{ view = view
, init = \_ -> init
, update = update
, subscriptions = always Sub.none
}
Next steps
Clearly, this is a very scoped exercise and the design and functionality of what I wanted to do was fully fleshed out. I wonder how Elm would be for prototyping when I'm just playing around with the UI and attempting a bunch of different things without a clear idea. Also curious to see how it scaled to more complex projects. Next thing I want to learn is how do I integrate with Next JS / Gatsby type ecosystem.
- Came across this elm-spa and would like to give that a spin.
- Look into using Tailwind with Elm