
Exploring Elm with a Typeahead
Javascript is no longer the only option for working on front end stuff, kinda.
In the last few years several efforts have been made at targeting Javascript in order to write front end code in other ways and still have them work everywhere that Javascript does. Some that stick out to me are -
Purescript
Clojurescript
Coffeescript
Elm
Having to compile to JS introduces an interesting step in our workflow, we now have a compiler to check over programs for errors before they get to the screen.
This is why Elm can claim no runtime errors. We can still write programs that don’t work in many ways, but no more Undefined is not a function or Cannot call blah blah on null.
Elm also gives us some strict restrictions, for example -
Pure Functions
You can’t change stuff in Elm. No reassigning variables or using variables outside of your scope. All mutations must be done through something called a Signal.
Strict Types
Your Javascript can never really be typed, but Elm’s compiler enforces it’s type system and helps you by inferring types when you don’t write them yourself.
So does all of this actually end up helping us write apps???
Let’s build a quick Elm widget and see.
Our app will be made of three parts, a model, a way to update that model, and a view for that model.
Model
type alias Model =
{ searchList : List String
, search : String
}
empty : Model
empty =
{ searchList = ["First", "Second", "Third"]
, search = ""
}
Here we’re just describing our app, its’ going to be a a list of strings to search and a string to search on that list. Then, we define our initial state underneath with a type of Model.
Next we need a way to update the model.
Update
type Action = Search String
update : Action
-> { model | searchList : List String }
-> { model | searchList : List String }
update action model =
case action of
Search search ->
{ model | searchList = checkList model.searchList search }
checkList : List String -> String -> List String
checkList itemList search =
if length search < 1 then
empty.searchList
else
List.filter (\n -> Regex.contains (regex search) n) itemList
First, we declare a type named Action, and two functions. The first is update, it has a case function in it with a matching case for our Search action. As our app grows we can just add more actions underneath that one.
The second function’s job is to take a list and a search string and return a list of items from the previous list that contain our string. You’ll notice that the type signature for update says it takes an action and a model, then returns a model. Now if we look at checkList, it’s type signature is more generic. It takes a list of strings and a string, then returns a string. That function should work on any lists of strings and string combinations, so we declare it more generically.
(\n -> Regex.contains (regex search) n)
Above is an inline function that we use to filter over the list passed in.
Our view will be made up of a few functions to render our data and compose it them together.
View
renderItem : String -> Html
renderItem item =
li [listItemStyle] [text item]
renderList : List String -> Html
renderList itemList =
let
l = List.map renderItem itemList
in
ul [listStyle] l
renderContainer address itemList search =
div [mainContainerStyles]
[ div [searchContainerStyles]
[ input
[ style [ ("border", "1px solid #fff")
, ("border-radius", "5px")
, ("padding", "1%")
, ("width", "120px")
]
, type' "text"
, placeholder ""
, on "input" targetValue (\string -> Signal.message address (Search string))
]
[]
]
,
div [containerStyles] [renderList itemList]
]
view : Address Action -> Model -> Html
view address model =
div [appStyles]
[ renderContainer address model.searchList model.search ]
renderItem and renderList build a list of values and apply some classes to them then renderContainer puts them together with an input field. Lastly, a view function wraps the whole thing as a container. Here are the styles I applied while building this, they’re optional.
Styles
appStyles : Html.Attribute
appStyles =
style
[ ("display", "flex")
, ("flex-direction", "column")
, ("justify-content", "center")
, ("align-items", "center")
, ("width", "100%")
]
containerStyles : Html.Attribute
containerStyles =
style
[ ("display", "flex")
, ("flex-direction", "column")
, ("justify-content", "center")
, ("align-items", "center")
, ("width", "100%")
]
mainContainerStyles : Html.Attribute
mainContainerStyles =
style
[ ("display", "flex")
, ("flex-direction", "column")
, ("justify-content", "center")
, ("align-items", "center")
, ("width", "200px")
, ("margin-top", "200px")
, ("background-color", "#333")
, ("border-radius", "5px")
, ("padding", "1%")
]
searchContainerStyles : Html.Attribute
searchContainerStyles =
style
[ ("display", "flex")
, ("flex-direction", "column")
, ("justify-content", "center")
, ("align-items", "center")
, ("width", "100%")
]
listStyle : Html.Attribute
listStyle =
style
[ ("display", "flex")
, ("flex-direction", "column")
, ("justify-content", "center")
, ("align-items", "center")
, ("list-style-type", "none")
, ("border", "1px solid #fff")
, ("border-radius", "5px")
, ("padding", "1%")
, ("width", "120px")
]
listItemStyle : Html.Attribute
listItemStyle =
style
[ ("font-size", "1em")
, ("text-align", "center")
, ("text-decoration", "none")
, ("width", "100%")
, ("color", "#fff")
, ("margin-top", "20px")
, ("margin-bottom", "20px")
]
That’s it! Our mini module is ready to go. In order to run this without having to dive into more concepts like Mailboxes, we’re going to use a package called StartApp, made by Evan Czaplicki, the author of Elm. We also need to import libraries for HTML, Signals, Strings, and Regex.
This should go at the top of your file.
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (on, targetValue, onClick)
import Signal exposing (Address)
import Regex exposing (regex, contains)
import StartApp.Simple as StartApp
import String exposing (..)
main =
StartApp.start { model = empty, view = view, update = update }
This syntax (..) just means import the entire module. If you don’t want to run a server, you can run the code on this online repl or just check out this repo.
Thanks for reading, if you enjoyed this please hit that heart and share it for others to explore Elm. 🍻
Originally published on my personal blog, aristid.es.