LiveSelect - Dynamic selection input component for LiveView

Hi! :wave:

I would like to present LiveSelect, a little library that I wrote to easily add a dynamic selection input to your LV forms.

Github
Hexdocs

The idea is that the user can type some text, and the component presents a dropdown with content that is filled dynamically by your LV as the user types (LiveSelect sends your LV a message, and your LV replies with the new list of options). The user can then select an option or continue the search.

demo

Background

There are a few (mostly oldish) tutorials that explain how to build similar components, but to the best of my knowledge no component you can add to your LV as a library and just use.

While creating this component for one of my projects, I put considerable effort trying to get it right, especially handling all the tiny little details like navigation with the arrow keys, selection with mouse or enter key, resetting the selection and so on. Therefore, I decided to make it all available as an easy-to-use library so that hopefully the next poor developer won’t have to reeinvent this wheel :wink:

How to use

To use LiveSelect, you add it to your mix dependencies, import the JS hooks (1-2 lines) into your app.js, and add an extra line in your tailwind configurations. You’re now ready to add the LiveSelect input to your forms.

I would be very happy if at least some folks found this component useful, and I’ll be extremely grateful for any feedback :pray:

Thanks,
Max

48 Likes

This looks great! Thanks for releasing it.

Do you have any plans/thoughts about exposing it as a component when LiveView 0.18 hits? Link to new attr syntax (not sure if there’s better docs available as of now).

1 Like

This Looks great. Thanks for releasing it!

I am curious, what does this do?

1 Like

This is so great, I was hoping for such effort since a while now.
Does it handle multiselect already, or anyway you plan to implement it?

3 Likes

Thanks @zachallaun !

Interesting, I haven’t been following the development of the 0.18 version that much to be honest, so thank you for the pointer. Looks like LV will soon incorporate some features reminiscent of Surface. I was expecting that and I find it good :+1:

To answer your question: my initial idea was to provide an interface as similar as possible to Phoenix.HTML.Forms, because I beleive this is how most people write LV forms:

<%= live_select form, field, options %>

However, as I mentioned, I would like to make the rendering of the options in the dropdown customizable. The most natural way to do that seems to be using slots, and so I believe I’m gonna have to provide a second interface that uses function components. Let’s say you wanna display all the option labels in uppercase (silly example but just to make the point):

<.live_select form={form} field={field} options={options} let={{label, _value}}>
  <div><%= String.upcase(label) %></div>
</.live_select>

So the answer to your question is: “probably yes”.

I’m interested in any thoughts you might have

Hi @TwistingTwists and thanks.

The LiveSelect component renders 2 inputs: a visible one where the user enter text and that will contain the label of the option after selection, and a hidden one that will contain the actual value of the option after selection.

Let’s say you do this in your form:

<%= live_select form, :user_role %>

This will render 2 inputs: a visible text input called :user_role_text_input and a hidden one called :user_role

If now you pass the options: %{user: 1, admin: 2, guest: 3}, LiveSelect will render 3 labels in the dropdown: ["user", "admin", "guest"]. If you select one of them, the corresponding numeric value (1, 2 or 3) will be the value of the hidden input :user_role, whereas the selected option label (“user”, “admin” or “guest”) will be the value of the text input :user_role_text_input.

The label and the values of the options could also be the same of course (i.e. options = ["user", "admin", "guest"]), in which case both :user_role and :user_role_text_input will have the same value.

Hope this makes sense

2 Likes

Hi @ivanminutillo and thank you!

you raise a very good point, and I confess I had not given this too much thought until I read your question :slight_smile:

I think that a good way to handle multiple selects is to do what this jQuery library is doing:

https://harvesthq.github.io/chosen/

Basically adding removable tags to the input field as the user selects options.

What do you think?

1 Like

Right on this makes sense.

I’ve had very contrived examples of trying to do the same in Angular in past. This approach is relatively breeze.

Thanks for explaining this one!

1 Like

This is awesome !! Thank you.
A couple of suggestions if I may …

  1. commenting out the phx-change on the component (component.html.heex) allows for the event to fire. For some reason, I was not seeing the handle_event triggering (only the handle_info callback).
  2. a bit of a hack, but I needed to have three LiveSelect components on the same form with “intelligence” i.e. the search criteria and the options displayed were interdependent. Hence, I needed the values of all three components passed into handle_info (for determining the options to display) when any of them changed. This is similar to the “grouping” function of checkboxes and how the values are passed into liveview callbacks. I was able to accomplish this with a few tweaks … basically, am using the handle_event to collect the key value pairs as all form component values are passed to handle_event upon change. I then pass this back as an assign and onto the LiveSelect component (had to add a “values” attribute in ChangeMsg) which then shows up nicely in the handle_info callback. I can submit the PR on github if you think worthy …
  3. One side-effect, for handle_events to be generated reliably, I had to also remove the debounce (handle-info always fires).
  4. Do you think it is possible to adapt this so it can work within a live_component ? I am still learning …

Again, thank you.

1 Like

Thank you @milangupta this is excellent feedback! I’m currently traveling and unable to answer. Expect a proper response over the weekend :+1:

So as I dug deeper into the design, I realized I didn’t need to use handle_event triggered by phx-change at all, so 1 & 3 are not needed. The “grouping” equivalent can be accomplished over the top. Your widget is quite elegant and carefully designed. There is a lot of work that has gone in to make it behave precisely …

The one thing I struggled with is the interface/integration with the parent - I had to implement a new message to be able to get the “selection” event in the parent. I did not see a way for the parent to determine whether selections had been made.

For complex/tightly integrated interactions, is there an easier way to be able to get to the state of the component i.e. directly accessing variables ?

A quick answer from the road on this one. This is explained in the docs:

Whenever an option is selected, LiveSelect will trigger a standard phx-change event in the form. See the “Examples” section below for details on how to handle the event.

A live select input behaves like an ordinary select input, the change event is triggered when the user selects one of the options. No need to implement new messages. Let me know if this isn’t explained clearly enough in the docs or if you’re still struggling.

I assume you’re refering to this. This is intentional: you don’t want the change event in the form to fire whenever the user types something in the text input, but only when they select an option. Handling the ChangeMsg message is the proper way to be notified of and react to changes in the text input.

This is an interesting use case and, as you apparently already found out, is best handled in the parent LV by keeping track in the assigns of what is selected in each liveselect input and using this state to determine the options to send to a liveselect when the ChangeMsg message is received.

Interesting use case I didn’t think about. I never nest live components, but I can imagine how this could be desirable in some (perhaps rare) cases. The problem here is that messages can’t be handled in live components, only events can, and ChangeMsg is a message. Maybe one way would be to turn ChangeMsg into a custom event and route it to the parent component using phx-target. The parent component could communicate its “address” to live select using the @myself assign. Just an idea, will think about it. Let me know what you think.

Hello! I released a new version of LiveSelect (0.2.0), which finally supports built-in default styles for tailwindcss, without the need to use daisyui :tada:
So now the default is to use basic tailwindcss, with daisyui as an option. Of course, all the styles are still completely customizable. Thanks to everyone who gave feedback and special thanks to @mindok for finding a bug :bug:

You can use the included showcase app to configure LiveSelect on the fly and play around with all the options and styles to see what they look like. You can even see LV events and messages in flight:

Check it out, I think it’s pretty cool :slight_smile:

I hope that now more people will be interested in trying out this library. So: If you need a dynamic selection field for your LiveView, resist the temptation to RollYourOwn™ (trust me: it’s no fun) and take LiveSelect for a spin.

11 Likes

Great work @trisolaran !!! Totally agree with you that implementing a multiselect from scratch is no fun at all :)) i’m planning to use ti soon, also great to see that multiple options mode is in your todo <3

1 Like

thanks for the words of encouragement @ivanminutillo. Much appreciated! The multi-select option is the (last?) big thing missing. Hopefully I’ll be able to find some time to work on it soon.

2 Likes

Hey friend, the multiselect in your roadmap is EXACTLY what I have been looking for! Is there somewhere I can sponsor you or otherwise throw some money your way to support this?

3 Likes

dude, money isn’t a problem: time is :slight_smile:
I’m currently swamped with work + family-related stuff, but I’m confident I’ll find some time to spend on this in a month or so. Stay tuned.

3 Likes

Finally: version 0.3.0 with multiselect support is out

LiveSelect now accepts a new :mode option, which takes either :single or :tags as value. :single mode is the old single-selection mode you already know. :tags mode turns the component into a multiselect input with tags:

DEMO

I didn’t simply call it :multiple because I envision new multiselect modes in the future with different flavors, for example using checkboxes in the dropdown.

As usual, all new elements (these includes the single tags as well as the box containing them) can be styled by passing the appropriate options.

And as usual, you can experiment with all the options by running the showcase app locally.

I hope folks who were waiting on the multiselect feature will be satisfied! :rocket:

Looking forward to your feedback. Cheers! :beers:

9 Likes

Hey @trisolaran, nice work!

I’m curious if there is a way to set an initial selection of tags, for example when editing an existing record that comes from the database. I see there is an outstanding issue related to this feature. But maybe it’s already possible with some kind of workaround?

On another note: it would lower the barrier to play around with this library if you would deploy the demo application somewhere (fly.io comes to mind), to see it immediately in action, without having to start the application yourself.

1 Like