Writing a filterable dropdown menu in elm

At PhraseApp we got here to the realization that we needed to modify one thing about our frontend stack. After comparing a number of other frameworks we determined that we might check out elm.

For our software we would have liked a dropdown menu with a textfield, such that the person can clear out the displayed menu entries by means of typing in a question string. We typically use the javascript library selectize.js, and naturally we will have embedded this element in our elm software (the usage of ports). But we concept, that writing such a dropdown menu is in fact a fascinating downside, and we needed to look how we will be able to remedy it in elm.

You can to find our ultimate end result on github together with a small are living demo.

In this weblog publish, we love to percentage the answer we got here up with and particularly attempt to give some causes, why we did it in this manner. Also, we strive to provide an explanation for some implementation main points which could be fascinating, too.

What are the wanted options?

So let’s recap what our dropdown menu must be offering:

  • The opened menu must have a most top, and it must be scrollable if there are a lot of things to be displayed.
  • The person must be in a position to choose entries by means of clicking on them, and each and every access must get center of attention if we hover over it with the mouse.
  • Also, the person must be capable to center of attention entries the usage of the up and down keys and in fact choose them by means of urgent input.
  • When the menu is open, typing in one thing must clear out the checklist of proven entries
  • We additionally want to be in a position, to show dividers in the menu, which aren’t selectable.

What answers are in the market?

The elm group is small, however nonetheless there are some (superb) answers in the market! For instance, there’s the preferred bundle elm-autocomplete, which solves the issue of a filterable menu which may be navigatable with the keyboard in a very common method. And there are a number of dropdown programs one unearths when taking a look in the elm bundle database.

So why did we no longer use elm-autocomplete?

This bundle does no longer come with the textfield for coming into the question into the bundle. This was once executed for a superb reason why, specifically to offer a extra versatile resolution. The payoff is that one has so as to add world keydown-subscriptions to deal with keyboard navigation, since handiest html-elements which may have center of attention, can hearth keypress-events. In our use-case we at all times have an enter textfield, so the desire for world subscriptions appeared a bit unwieldy. (One may after all argue that having the extra subscription boilerplate isn’t an excessive amount of of an come up with the money for.)

We bet, that elm-autocomplete was once no longer designed with a scrollable menu in thoughts. When calling its view or replace purposes one has to offer a maxium collection of menu entries which must be displayed. In our case this could have at all times been the entire collection of entries which matched the filtering string. So this a part of the api didn’t appear to be a just right have compatibility for our downside.

And ultimate however no longer least, it’s by no means a unhealthy concept to make a recent get started and check out to get a hold of a some other option to an previous downside, later evaluating the effects with what’s already in the market.

And it seems, writing programs in elm is a in point of fact fulfilling procedure and also you at all times be informed one thing!

What must the Api seem like?

It is at all times a just right concept to spend a respectable period of time on fascinated by what your api must be taking a look like:

  • What a part of the state must are living in the appliance?
  • Which portions should be treated by means of the bundle?
  • How can the person customise the semblance or behaviour of the dropdown menu?

We determined that the (unfiltered) checklist of menu entries must be saved in the principle type. In our software we in fact have a number of dropdown menus which percentage a commonplace set of entries, and if the person selects an access in one among them, this access must disappear in the opposite menus.

Also the ultimately decided on access must no longer be saved in the state of the dropdown menu. We have so to alternate the choice inside our software and having setters and getters is typically a very unhealthy concept because it clashes with the “one source of truth” paradigma.

On the opposite hand, the open/closed state of the menu and the entered question for filtering the menu entries, must be controlled by means of our dropdown bundle. Also the present mouse/keyboard center of attention is on no account one thing we wish to care for at the software degree.

Another factor we needed to consider was once: How will we constitute the menu entries? We determined that our dropdown menu must be generic over the access sort. So the appliance has to give you the dropdown menu with a List (Entry a). Here a stands for the (selectable) menu entries and the api supplies constructors

one for the true selectable entries, and one for the dividers. This method, the person of the bundle has complete flexibility on how menu entries will also be modeled: Maybe it may be simply a checklist of strings, however possibly it needs to be one thing extra sophisticated, like a document which shops a name, some description and optionally a picture.

Going this extra generic direction, we want to inform the dropdown menu the way it can render a person access, i.e. offering a serve as like a -> Html msg, and the way it can clear out the entries with a given question, which boiles right down to a serve as String -> List a -> List a.

We didn’t do one thing like

for prodiving the likelihood so as to add non selectable dividers, as we did
no longer wish to introduce this additional nesting layer.

We will have additionally given the dividers a generic sort, however for now we simply want them to make captions for structuring the checklist of menu entries. And it’s relatively simple to increase the implementation.

So, except the opaque state sort, sort State a, and an opaque message sort, sort Msg a, the dropdown exposes handiest a view and an replace serve as, with the next signatures:

Here, type is the type form of our primary software and msg is the
primary message sort. If you evaluate this to different programs like for instance elm-sortable-table, you could surprise why we don’t give you the State a in the view and replace serve as. And additionally, how does view and replace know what the menu entries are and what the present variety is?

We determined that as a substitute of given view and replace a number of extra arguments, which might most definitely be extra the TEA method, we simply supply ViewConfig a type and UpdateConfig a msg type which come with purposes for retrieving this information from the present type. Namely, those configurations are created in the next method:

So, for instance if our primary type is given by means of

We would create the shared configuration by the use of

The replace configuration handiest provides the ideas, how the dropdown menu can ask the principle software to modify the choice. This may seem like a setter serve as, however it’s conceptually other:

We made the verdict that the choice state must are living in the principle software type and no longer in the dropdown menu state. But the dropdown menu must have a method to regulate the choice. (After all that’s the complete objective of the dropdown menu.) We will have modified the dropdowns replace serve as to go back a tuple which contains a Maybe a, indicating that we ask the appliance to modify the choice. But then we (because the person of the bundle) must ensure that this new variety is saved in the principle type. But what if we put out of your mind to try this? Or what if a alternate of the choice additionally calls for another industry common sense to be carried out?

The level is: from the viewpoint of the dropdown menu, converting (or higher requesting a alternate of) the choice is a facet impact. So our dropdown replace higher returns a Maybe msg, since messages are tips on how to keep up a correspondence results in elm.

It additionally provides the person the danger to split the replace boilerplate of the dropdown menu from the choice alternate common sense. This is just right for the reason that first one in point of fact is simply the vital boilerplate one has to put in writing, the second is a part of the true industry common sense.

Implementing style-independent scrolling

We need our dropdown menu to be navigatable the usage of up- and down arrow keys. Since the menu additionally makes use of overflow-y: scroll, we need to scroll the menu when the following access lies outdoor of the present menu viewport. There is already a bundle for issuing scrolling instructions. Awesome!

But wait! If we wish to scroll correctly we want to know the peak of all menu entries, and the elm structure does no longer supply an evident method of fetching knowledge from the DOM. So what are our probabilities?

One method is tagging each and every access with an identification and putting in some ports

at the javascript facet. The listener of fetchHeight seems to be up the DOM-element with the supplied identification and sends its top to the peak port. This is definitely a just right method to do it, however it calls for some Javascript common sense to be arrange and we needed our bundle to be elm handiest.

Luckily there’s differently to reach this! There is a position in the elm structure the place one can in fact analize the rendered DOM tree, specifically when deciphering Json parties. So, on every occasion you might be in the location that

  • you want details about the rendered DOM,
  • you want that data after some tournament was once fired,
  • the detail, this tournament was once connected to, is with regards to the DOM detail you want
    the ideas of,

you’ll be able to use this DOM deciphering trick, to retrieve for instance the width and top of a few detail.

So what we did was once attaching a customized decoder onto the point of interest tournament of the textfield, which additionally fetches the heights of all menu entries. To do that correctly, we simply need to ensure that the menu already exists in the DOM tree earlier than the textfield will get targeted. We achive this by means of at all times rendering the menu container, however hiding it with place: absolute whether it is closed. Note that the dad or mum node then wishes place: relative and overflow: hidden.

Then, once we deal with the up- and down-keys, we additionally fetch the present scroll place of the menu and use the prior to now fetched access heights to compute the place we wish to scroll our menu to.

We simply need to ensure that those heights are recomputed when the filtering is modified, in order that the checklist of menu heights and the checklist of filtered entries at all times is in sync.


We are in point of fact satisfied how this bundle grew to become out, and one can not rigidity sufficient that doing this in elm was once a primary reason why for this. Elm simply looks after all of the tense portions of programming and allows you to center of attention on fixing the true downside.

Still there are a few things which will also be stepped forward:

  • We attempted to make the dropdown menu (particularly the scrolling) as speedy as imaginable, however there’s most definitely nonetheless some area left for optimization. One reason why was once, that we didn’t see a easy method of benchmarking the view serve as.
  • You nonetheless have to offer each and every dropdown menu a globally distinctive identification, which isn’t splendid. Perhaps, it will be a just right factor should you had a particular sort sort Node which represents a rendered dom node, along with a method of deciphering those nodes from JSON values, i.e. one thing like goalDecoder : Decoder Node. The scrolling api then may seem like toY : Node -> Float -> Task Error ().It can be fascinating to understand if having one thing like this, is a just right concept and in fact imaginable!

Also printed on Medium.


Source link