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) 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 share it for others to explore Elm. 🍻