This is a project used as an intro to Elm workshop. It builds a filterable dropdown similar to Select2. It takes a lot of small steps and doesn't make assumptions of existing Elm knowledge.
I wrote the dropdown in ~30 minutes with the expectation that a workshop could go through it in 90-120 minutes.
Each commit is a step with a detailed commit message explaining what was done and why. For ease of trying things out, I've linked the full source for each commit as an Ellie along with the accompanying commit message on this README. Unfortunately, GitHub doesn't allow iframe embedding in markdown docs.
Just display some static text on the page. Boring
Display an unordered HTML list with each element in the list
fruit.The helper functions from
Htmltake two arguments: a list of attributes and a list of children. SinceList.mapreturns a list, we don't need to wrap it in brackets.
Remove the logic from
mainand into aviewfunction that handles turning a list of values into an Html list.
Create a model that contains both the list of values and the idea of being open or closed. The view is rendered differently depending on whether it is open or closed.
The closed view is static so it doesn't require any arguments.
Note: There is a bug in the commit where we're passing an argument to the
function viewClosed when it takes no arguments. This is fixed in the Ellie
embed. It also gets fixed in the source in a later commit.
Using the record type is awkward so alias it as
Modelso we can use it in signatures easily.Note that this is different than the
Statetype which is not an alias but instead is its own thing.
The
mainfunction is no longer static but instead usesHtml.program. TheHtml.programfunction takes in record with three vallues: an initial model, aviewfunction, and anupdatefunction. Note that functions can be passed as arguments to other functions.The
updatefunction takes in an event and the current model and returns a new model. In this simplest implementation, we're ignoring the event and always returning the current model. Because the view only changes if the model changes, we still effectively have a static app.
We add a
Msgtype that defines two eventsOpenSelectandCloseSelect. Theupdatehandles both of these by correctly changing the model. The view gets re-rendered whenever the model changes.We emit the events from the views in response to a click event handler.
Note that the type signatures for the view functions changed from
Html atoHtml Maybe. That's because the views are now emittingMsgevents.
The whole point of this is to be able to select values. Since it's possible for no values to be selected, we wrap it in
Maybe.The view gets trickier because there are now 4 ways to render the dropdown:
Open vs Closed each with the appropriate click handler and displaying the selected text or the help tip if nothing is selected.
To accomplish this, we extract a
dropdownHeadfunction that deals with selected vs no selection. Since we already know whether the dropdown is open or not, we just pass in theMsgtype we want for the click handler.Because we're now handling whether an item is selected or not, the closed view is no longer static.
This sets our selected item in response to a Msg sent when the user clicks on an item. This Msg is more complex than previous ones because it wraps a value.
This means we need to give it a value in the click handler. When reacting to the event, we unwrap the value as part of the case statement, just like we did for the
Maybe.
We already have an event that handles an item selection, now we just need to change the model's state to "closed".
Only display values in the dropdown that match the given query (case insensitive). Since all the original values are kept, we can re-filter them by a different query at any time.
When the user types in the text box, we set the query value on the model, thus filtering the dropdown.
Note that the new Msg value
SearchInputtakes a paremeter, we don't give it one inonInput. WhileonClicktakes a Msg as its argument,onInputtakes a functionString -> Msgas its argument.
SearchInputis such a function when not given a value.
onInputdoes this because we don't know the value ahead of time. Instead, it will put the whatever value the user has typed inside the Msg.
We introduced a bug in the previous commit occurred when:
- The results were filtered
- A value was selected (which auto-closes the dropdown)
- The user re-opens the dropdown
- The results are still filtered from last time
We fix this by setting the query to empty string whenever a selection is made, thus clearing the filter for next time.
This is a more complex program that allows interaction with the outside world with commands and subscriptions. Following the compiler errors, we update signatures and return values to match what is required from
Html.program.
This uses a combination of
Dom.focusandTask.attemptto create a Cmd that will focus the DOM node with the given id. Since all Cmds trigger a Msg when they are done but we just want to fire and forget, we create a Noop Msg to deal with the situation.
We want to style the dropdown so we embed the Elm app on an HTML page that has a
<style>element on the body. This means we can no longer just useelm reactor. We must now compile the app usingelm-make
This project is distributed under the BSD 3-clause license
This example project and walkthrough was developed for a workshop given at thoughtbot.
We love open source software! See our other projects or hire us to design, develop, and grow your product.