Autocomplete widget in Elm

Code Examples

Autocomplete (or Autosuggest) fields seems simple but if you consider all possible interaction (with mouse, with keyboards, width other items on the page) they can get quite complicated.

In Elm there is elm-autocomplete that is a generic library, very powerful but also not simple to implement. Even in its simpler implementation, it requires more than 300 lines of boilerplate code.

This is a first attempt to write a simple wrapper around elm-autocomplete that allow to create one or more autocompleting input fields easily.

First install Autocomplete with

elm-package install thebritican/elm-autocomplete

Then import Autocomplete and the wrapper:

import Autocomplete
import AutocompleteWrapper

In the model and the init add the all the fields that we need. In this example there are three of them:

type alias Model =
{ field1 : AutocompleteWrapper.Model
, field2 : AutocompleteWrapper.Model
, field3 : AutocompleteWrapper.Model
}
init : ( Model, Cmd Msg )
init =
( { field1 = AutocompleteWrapper.init "id1" "State" menuItems1
, field2 = AutocompleteWrapper.init "id2" "Language" menuItems2
, field3 = AutocompleteWrapper.init "id3" "Color" menuItems3
}
, Cmd.none
)

AutocompleteWrapper.init has three parameters

  • the first is an ID that is used both as ID in the html element that wrap the input box and used internally by Elm to differentiate the different instances of the fields
  • the second parameter is the list of menu items in the form of a list of strings. For example:
menuItems3 : List AutocompleteWrapper.MenuItem
menuItems3 =
[ "Blue"
, "Yellow"
, "Red"
, "Green"
]

Now let’s add a new message in our messages list. This is the message that will intercept all the interaction with the autocomplete fields

type Msg
= OriginMessage String AutocompleteWrapper.Msg

The update now need to take care of this message. This is the largest part of the wrapper:

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
let
msgConverter : Msg -> ( String, AutocompleteWrapper.Msg )
msgConverter msg =
case msg of
OriginMessage fieldId autocomMsg ->
( fieldId, autocomMsg )
in
case msg of
OriginMessage fieldId autocomMsg ->
if fieldId == model.field1.fieldId then
let
( newModel, newMsg ) =
AutocompleteWrapper.update autocomMsg
model.field1 OriginMessage msgConverter
in
{ model | field1 = newModel } ! [ newMsg ]
else if fieldId == model.field2.fieldId then
let
( newModel, newMsg ) =
AutocompleteWrapper.update autocomMsg
model.field2 OriginMessage msgConverter
in
{ model | field2 = newModel } ! [ newMsg ]
else if fieldId == model.field3.fieldId then
let
( newModel, newMsg ) =
AutocompleteWrapper.update autocomMsg
model.field3 OriginMessage msgConverter
in
{ model | field3 = newModel } ! [ newMsg ]
else
model ! []

There is a block for each field. You notice that we have three similar blocks because in this example we are implementing three fields.

The autocomplete module require subscription because it need to detects keyboards and mouse events. In this configuration we need to repeat the subscription for each field:

subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[ Sub.map (OriginMessage model.field1.fieldId <<
AutocompleteWrapper.SetAutoState) Autocomplete.subscription
, Sub.map (OriginMessage model.field2.fieldId <<
AutocompleteWrapper.SetAutoState) Autocomplete.subscription
, Sub.map (OriginMessage model.field3.fieldId <<
AutocompleteWrapper.SetAutoState) Autocomplete.subscription
]

For the view, this is all you need:

view : Model -> Html Msg
view model =
div [ class "form-container" ]
[ div
[]
[ AutocompleteWrapper.view model.field1 OriginMessage
, AutocompleteWrapper.view model.field2 OriginMessage
, AutocompleteWrapper.view model.field3 OriginMessage
] ]
]

Add some style and it is done!

The changes in the model during the interaction

Here you can see how history of the model is changing during the interaction with the fields.

This is my fist attempt of using this library and there will be better way to implement.

One thing that is not pleasing me is the triple subscription to the KeyDown event that fire anytime a key is pressed (see the console log before)

Chain of messages while the user is interacting with the page

I was quickly testing the autocomplete feature of mdgriffith/style-elements, this is an example (very WIP): http://guupa.com/elm-autocomplete-wrapper/Example_Auto_SE_1.html

If you want to go deeper in understanding elm-autocomplete I suggest you the vision of this video https://www.youtube.com/watch?v=KSuCYUqY058

Thank you for reading.