Giter Site home page Giter Site logo

maxmarcon / live_select Goto Github PK

View Code? Open in Web Editor NEW
164.0 3.0 27.0 8.08 MB

Dynamic (multi)selection field for LiveView

Home Page: https://hex.pm/packages/live_select

License: Apache License 2.0

Elixir 84.74% CSS 0.89% JavaScript 3.88% HTML 9.06% Dockerfile 1.37% Shell 0.04% Batchfile 0.02%
daisyui dropdown-menus liveview multiselect tailwindcss component elixir form-input web-forms

live_select's People

Contributors

abhishek-tmi avatar greglmcdonald avatar ivanminutillo avatar kianmeng avatar maxmarcon avatar munksgaard avatar sevab avatar shamanime avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

live_select's Issues

Case where :container_class and :container_extra_class are set is not handled

If you are using style: :none option and set both the :container_class and :container_extra_class (or any combination of _class and _extra_class) you get a match error at LiveSelect.Component.class/4

The following addition to the function resolves the error. I may be misunderstanding the usage, but happy to submit a PR if this looks right...

  defp class(_style, _element, class_override, class_extend) do
    class_override <> " #{class_extend}"
  end

Ran into overflow and bounds detection issue

I decided to test live_select but ran into two issues.

Select options overflow

This screenshot is from the demo app:

image

Any idea why this is overflowing? This happens on both Firefox and Chrome.

No bounds detection

Most JS-based select dropdowns have some sort of bounds detection. This makes it such that if there is not enough space to show the dropdown below the input field, the dropdown opens upwards. Live_select doesn't appear to support this:

image

You can look at Headless-UI or shadcn/ui to get a sense of what I am talking about:

image

Thank you so much for making this open source! :)

Tags list below the input...

Hi Max

Thanks for this great lib. I've got a requirement to move the tags list underneath the wrapper - so it ends up looking something like this:

image

Would you be interested in a PR for this functionality? If not, I'll just keep it to my own fork.

Cheers :)

(File.Error) could not stream "priv/static/assets/class.txt": no such file or directory

Encountered when running test:

git clone https://github.com/maxmarcon/live_select.git
cd live_select
mix deps.get
mix test
...
..11:54:52.833 [error] Task #PID<0.1379.0> started from #PID<0.1378.0> terminating
** (File.Error) could not stream "priv/static/assets/class.txt": no such file or directory
    (elixir 1.14.0) lib/file/stream.ex:47: Collectable.File.Stream.into/1
    (elixir 1.14.0) lib/enum.ex:1499: Enum.into_protocol/2
    (elixir 1.14.0) lib/task/supervised.ex:89: Task.Supervised.invoke_mfa/2
    (stdlib 4.0.1) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Function: #Function<6.25363132/0 in LiveSelectWeb.ShowcaseLive.spawn_save_classes_task/2>
    Args: []

Did I miss out any steps?

extending use case with slots.

image

This is a sample image / modal / dropdown that I want to display with live_select.

live_select has lots of 'keyboard' bindings and small things done right! I wish to support live_select with slots so that a custom slot can be passed for options / dropdown.

Would love you know your thoughts on this @maxmarcon .

preloading selected values from db

Hello there!

We've been using your library lately and firstly we must say: THANK YOU! This is a hard problem and we were so glad to not start from scratch.

I wanted to run past you a little problem we're having to see what you think.

We are using live select in tags mode and using a list of genres that are stored in a separate database table. To start, we search the db for the first 10 genres alphabetically. Imagine a list of genres like Active, Adventure, Animation, etc. If I had previously selected something outside of the first 10 results, like the "War" genre, it will display the db id instead of the label. This happens because the options we passed to live select did not include "War", so when it attempted to find the value and retrieve the label, it was not found.

This list of options could potentially balloon to a huge list. We wouldn't want to pass all options to the live select component because a) we don't know how large this data set is and b) we don't want the popup list to be a massive list of options that goes off the screen.

Thoughts?

Dark mode

In the demo, a change of the dark/light mode doesn't affect the color of the LiveSelect, which always stays white.

blur keeps wrong options around

How to reproduce

  1. write something in the live_select (see how options change)
  2. click outside of the input (options dropdown dismissed, input cleared)
  3. click on the input again
  4. notice how the old options are shown even though input is empty

What should happen

either the initial options should be restored or a possibility to detect the blur event so i can reset manually.


i'll make a minimal example if required. thought i'd pick your brain first.

Showing value instead of label on single search

If a field comes from the database, instead of showing the value from the label, it is showing from the value.

For example, when considering the map below:
%{label: "test", value: "1"}
In the search it would appear correctly as test,
When the data is saved and reload using the data from the db it shows as 1

The dropdown search continues working as it should.

Step 1

image

Step 2

image

Step 3

Save the record

Step 4

The issue appears
image

How can I handle input changes myself

Hi!

First of all, thanks a lot for your work, it's great!

I wanted to create a dynamic hierarchical LiveComponent wrapping LiveSelect. The problem I have is that when an option is selected, it's the Root LiveView (through the form's phx-change) who gets the even.

I tried wrapping the Heex in another form tag but that didn't work. I don't know if there is any other way of having more control over who gets the change event.

There might be an obvious way but I am new to LiveView, sorry.

Thanks in advance,
Rodrigo.

"Change" event is not triggered

Hi,

Just linking the previous issue I opened so that the code samples and the context are available.

#17

Issue is, the handle_info function is triggered when the user inputs characters, but once an option is clicked, the phx_change event is not triggered. Any idea why? Is this because I am using live_select inside a component?

Also as mentioned in the other issues, live_select's handle_info is received by the parent, so, I included the handle_event also in the parent

dropdown never closes - security policies may prevent inline styles

so i tried live_select again and the dropdown does not close indeed.

phoenix 1.7, live_view 0.18, but my empty test project had the same stuff and no problem there.

i downloaded the lib and connected the local version to my app to debug.

it seems when it comes down to it the @hide_dropdown variable is set correctly, but the display: none; doesn't end up in the HTML.

Here's an image where i print the var out. this is with initial options only, no interaction.

image

another interesting thing i noticed was that the initial HTML coming from the server does in fact have style="display: none;" included, but then the websocket gets two updates immediately and the second one has an empty class and style for this element. which is another confusing part, because the class does end up with appropriate classes so why doesn't style?

this is my thought process so far. i have no idea what it is in my app that is causing this. i thought perhaps you've got ideas where to look?

edit: i'm placing random values in places and it seems like none of the style attributes in the whole component get shown on page. not sure what this may or may not mean.

edit2: experimenting with raw style values it seems that it's not the LiveView websocket update that empties the style. so maybe javascript, but my project has barely any JS at all. new suspect: a library.

this.el.querySelector(...) is null

Hi,

I am executing

send_update(LiveSelect.Component, id: "workshop-tags", value: list_of_tags())

inside an update/2 callback of a live component, the live select updates correctly, but I got a

Uncaught TypeError: this.el.querySelector(...) is null

message, and the offending line is

this.el.querySelector(selector).dispatchEvent(new Event('input', {bubbles: true}))

This breaks my javascript script.

Am I doing something wrong?
Any idea how to solve?

Thank you

Compatibility with Bootstrap Icons

Hi there, thank you for creating this library!

I'm in a little tricky situation where I can't make use of tailwind and daisyui, so I have to handle styling based on the css my team uses, mainly bootstrap.

Without the usage of tailwind or daisyui, the X icon on tags appears as a rectangle, like this:
image
(it looks the same even when style is set to :none)

I have set bi bi-x (https://icons.getbootstrap.com/icons/x/) to clear_button_class, along with another custom class for background: transparent and border-style: none, which results in this:
image

This is what appears on inspect element of the x's
image

The svg is present regardless of the bootstrap class and default styling (as it still appears with style={:none}), it is an X when bi bi-x is used, and as a rectangle when nothing is used.

Google tells me that duplicates tend to happen when either bootstrap-icons is installed with another icon library, or when <i class="bi bi-x"/> is used instead of <i class="bi bi-x"></i>. I checked our packages and we don't appear to have another icon library installed.

Workarounds that I have tried and failed:

  • Setting content: '' on bi::before
    • The bootstrap x appears in bi::before, so it just resulted in the x disappearing entirely in both
  • Adding the bootstrap icon to the :tag slot and hiding the clear button
    • This didn't work because the hidden clear button leaves a transparent right "padding". This also doesn't work as the X icon itself isn't clickable, since it's not a button.

I'm rather new to LiveView (I'm also primarily a backend dev, not frontend), so this could also just be that I didn't import/use the bootstrap icon properly. Otherwise, it may be due to an incompatibility of using it directly in a button class?

Hopefully this makes sense!

Edit: I've found a workaround by making use of display: grid, having the tag contents all be in 1 row, then setting display: flex on the tag contents

Upgrading from v1.2.2 to v1.3.1 resets the input value

Hi Max,

I added 200 ms delay in handle_params so it is visible in the recording below.

The log is the same in both versions:

[debug] HANDLE EVENT "option_click" in LiveUIWeb.Admin.UserLive.Index
  Component: LiveSelect.Component
  Parameters: %{"idx" => "0"}
[debug] Replied in 118ยตs

[debug] HANDLE EVENT "users-update-filter" in LiveUIWeb.Admin.UserLive.Index
  Parameters: %{"_target" => ["company_id"], "company_id" => "1", "company_id_text_input" => "ACME INC", "filters" => %{"0" => %{"field" => "company_id"}, "1" => %{"field" => "email", "op" => "ilike", "value" => ""}}}
[debug] Replied in 173ยตs

[debug] HANDLE PARAMS in LiveUIWeb.Admin.UserLive.Index
  Parameters: %{"_target" => ["company_id"], "company_id" => "1", "company_id_text_input" => "ACME INC", "filters" => %{"0" => %{"field" => "company_id"}, "1" => %{"field" => "email", "op" => "ilike", "value" => ""}}}
[debug] QUERY OK source="users" db=0.3ms idle=1814.9ms
SELECT ...
[debug] QUERY OK source="users" db=0.2ms idle=1816.1ms
โ†ณ Flop.meta/3, at: lib/flop.ex:929
[debug] Replied in 2ms

It is triggered when the updated stream is added to the socket:

socket |> stream(:items, items, reset: true)

Looks like the whole component is re-rendered:

Screenshot 2024-01-28 at 11 32 33โ€ฏAM
live_select.mov

Options to keep the dropdown menu open

Right now, the menu closes when a selection is made, and also when there's a mouse click outside anywhere on the screen.

We need two new options:

close_menu_after_selection: Defaults to false. If true, the dropdown menu stays open after an option is selected.
Use case: You type something to filter the options, then want to select several from the filtered list in quick succession. Right now you have to do it one by one, which is very cumbersome.

persistent_menu: Defaults to false. If true, the menu stays open and needs to be closed programmatically.
Use case: Right now there's no way to inspect the HTML markup of the dropdown field options, because the menu automatically closes when using the Inspect Element tool in Chrome. So we can't write tests for components that use LiveSelect!

Add new option dynamically

Hi, this is a followup of the conversation in https://elixirforum.com/t/liveselect-dynamic-selection-input-component-for-liveview/49538/29?u=jaimeiniesta

The context is, I want to add a tagging editor, where the user can choose from previous tags but also add new ones, like here:

https://fullstackphoenix.com/tutorials/tagging-interface-with-phoenix-liveview-and-tailwind-tagging-part-2

tut_15_img_0

As mentioned in the Elixir Forum thread:

It would be something like a โ€œdynamic option modeโ€, where if you hit enter whatever you have typed will become a new selected option.

I have managed to make it more-or-less work, see video:

https://www.dropbox.com/s/a3ql5qs2zynggtj/live_select_2.mov?raw=1

But it won't work with the Enter key yet. What I did is:

<%= live_select f, :tag_search, mode: :tags, options: @live_select_options, placeholder: "Add tag" %>

and then listen to the ChangeMsg to replace the options:

  def handle_info(%LiveSelect.ChangeMsg{field: :tag_search, text: text}, socket) do
    new_live_select_options = [text | socket.assigns.user_tags]

    {:noreply, assign(socket, live_select_options: new_live_select_options)}
  end

Where user_tags is the original list of options from the existent user tags.

So for me what's left is being able to add new options with the "Enter" key, which should also clear the input field.

Have the option to populate a value for the input by default

When displaying the search in a search result page where we landed by url I would like to be able to populate the value of the input using the parameters I have in url. I have not found any way to set a default value. There is the option to set a default vaule for the dropdown if the input is empty, but no way to have the input not empty
Screenshot 2023-12-24 at 20 00 14
Is there a way I can populate the input value?

Global config for base classes?

Thank you for this awesome library
Was wondering if there's chance to support setting all the default classes in config.exs?

Something like this

config :live_select,
  app: :some_app, #opitional: for specific umbrella app?
  active_option_class: "text-white bg-red-800",
  ...

Thanks!

Tailwind classes are not extracted

Hi all,

I have added deps/live_select/lib/live_select/component.*ex to the content array in my tailwind config file,
but the classes are not extracted correctly (I don't find them in the compiled css).

If I change a line to something like active_option: ~W(text-white "bg-gray-600"), (note the quotes), tailwind extracts the class correctly.

This seems an upstream bug, but before I would like to make sure it's not only a problem of my setup.

Thank you

Allow html for options dropdown

I have a usecase for options to contain more than a text value. I want to be able to display an html I creating with label and sublabel.
Screenshot 2023-12-22 at 10 53 53

I was able to display my own html sending the options like this:

  s = """
         <span>
          #{city}</br>
          <small>#{state}</small>
        </span>
        """
        {raw(s), city}

but there is a problem when selecting the option from dropdown. It tries to serialize the label as json and I get a crash:

[error] GenServer #PID<0.1414.0> terminating
** (Protocol.UndefinedError) protocol Jason.Encoder not implemented for {:safe, "<span>\n  IaลŸi</br>\n  <small>IaลŸi</small>\n</span>\n"} of type Tuple, Jason.Encoder protocol must always be explicitly implemented. This protocol is implemented for the following type(s): Any, Atom, BitString, Date, DateTime, Decimal, Ecto.Association.NotLoaded, Ecto.Schema.Metadata, Float, Integer, Jason.Fragment, Jason.OrderedObject, List, Map, NaiveDateTime, Recenza.Models.Service, Recenza.Models.Speciality, Time
    (jason 1.4.1) lib/jason.ex:213: Jason.encode_to_iodata!/2

A live component's parent LiveView receives live_select's handle_info and handle_event messages

First of all thanks for this very nice piece of software! It's filling a gap that I previously tried to work around using Select2 and some custom code. I highly appreciate the effort you put into this project. When trying it out in a pet project of mine I found a potential bug:

To reproduce I've set up a minimal example app at: https://github.com/cblock/live_select_sample using Phoenix 1.7.0-rc.0. Then I added live_select 0.3.2 as per documentation.

For demonstration puposes let's assume that I want to use live_select to choose a person's location.

I implemented the handle_info callback in the live_component itself (not its parent liveviews which would be PersonLive.Index or PersonLive.Show respectievely depending on whether a user trigger's the form from the index or show view).

When I start the server, I see the following log output:

[debug] HANDLE EVENT
  Component: LiveSelect.Component
  View: LiveSelectSampleWeb.PersonLive.Index
  Event: "keyup"
  Parameters: %{"key" => "Control", "value" => "test"}
[debug] Replied in 146ยตs
[debug] warning: undefined handle_info in LiveSelectSampleWeb.PersonLive.Index. Unhandled message: %LiveSelect.ChangeMsg{id: "person_location_component", field: :location, text: "test", module: LiveSelect.Component}
[debug] HANDLE EVENT
  Component: LiveSelect.Component
  View: LiveSelectSampleWeb.PersonLive.Index
  Event: "blur"
  Parameters: %{"value" => "test"}
[debug] Replied in 49ยตs

To my understanding, live_select does not take into account the form's phx-target attribute. Instead it always sends messages to the live_component's parent live view.

IMHO this behavior is not desirable as it leads to code duplication: I'd have to implement callbacks both within Index and Show LiveView.

Multi-select support

It would be great if the support for multiple selected items is added. When enabled, the input field would display either N items selected if more than 1 is selected, or work like it does now, and the selected items would have a checkbox next to them that would change state on click.

Maybe some ideas could be taken from:

Says could not generate to HTML

cannot convert component LiveSelect.Component with id "test[review][0][name_review]_assignee_component" to HTML.
A component must always be returned directly as part of a LiveView template.
Any idea?

Crashes LiveView in function maybeHideFeedback

Using the latest lv v 0.18.18 there is no problem.
But when switching to LiveView's 'main' gh branch there is a crash on liveview's dom.js
For some reason it thinks liveselect input doesn't have a 'name' attribute and fails.

CleanShot 2023-05-28 at 10 38 29

[Question] Hide dropdown

Hi,

I just want to understand how @hide_dropdown is going to work when there are multiple live_select on a single page? For example, when multi_select is defined inside an inputs_for

I see in the code, you use the @hide_dropdown variable for this, but when there are more than one live_select, how does this work?

Add new CSS class for available_option

I'm trying to add a different CSS style for the selected options, so in the dropdown the cursor is changed to "none", but I can't see how to do this. This is my current setup:

<%= live_select f,
                  :tags,
                  mode: :tags,
                  options: @live_select_options,
                  update_min_len: 1,
                  placeholder: "Add tag",
                  style: :none,
                  container_class: "tag_editor_container",
                  text_input_class: "tag_editor_text_input",
                  dropdown_class: "tag_editor_dropdown",
                  option_class: "tag_editor_option",
                  active_option_class: "tag_editor_active_option",
                  selected_option_class: "tag_editor_selected_option" %>
@layer components {
  .tag_editor_container {
    @apply border my-4 p-4 rounded-md bg-gray-100;
  }

  .tag_editor_text_input {
    @apply  bg-gray-100 text-gray-900;
  }

  .tag_editor_text_input:focus {
    @apply outline-none;
  }

  .tag_editor_dropdown {
    @apply pt-4 pb-2 space-y-2;
  }

  .tag_editor_option {
    @apply cursor-pointer;
  }

  .tag_editor_option:hover {
    @apply font-bold text-blue-800;
  }

  .tag_editor_active_option {
    @apply font-bold text-blue-800;
  }

  .tag_editor_selected_option {
    @apply font-normal text-gray-500 cursor-default;
  }
}

I want to make it clearer that the options that have been already selected are no longer clickable, so that's why I'd like to avoid a pointer cursor in them.

Maybe we could have an "available_option" class, as the opposite and complementary class to "selected_option"? This way we could style the options depending on that condition, available or already selected.

Respect `:selected_option_class` when in `:single` mode

Per the docs, the :selected_option_class attr is only available when in :tags mode. However, I believe being able to style the current selection uniquely is valuable even in :single mode.

This is in fact how chrome's built-in select works (notice the check next to female, indicating that is the current selection):

Screenshot 2023-09-29 at 8 49 42 PM

If you're open to this change, I'd be happy to PR it. Just don't want work to go to waste.

Discussion: live_select LiveView component testing

First off I would like to say great job on the library, it is extremally useful.

I had a question around testing when being used within a LiveView component. While the directive of phx-target={@myself} works on the actual <.live_select /> element when being used in the live environment, when running this in a test environment the event messages are sent to the parent LiveView.

I in no way think this is a short coming of the library in anyway and understand this is something that still needs to be fleshed out in LiveView testing methodologies but was wondering if anyone has come up with a clever solution for this or if at the moment most are just not testing the live_select component in their views and are instead direct testing their components.

as an example:

Component:

defmodule MyAppWeb.PageLive.FormComponent do
  use MyAppWeb, :live_component

  def render(assigns) do
    ~H"""
      <.simple_form for={@form} id="page-form" phx-target={@myself} phx-change="validate" phx-submit="save">
        <.input field={@form[:name]} type="text" label="Name" />
        <.live_select field={@form[:cities]} phx-target={@myself} placeholder="Search Cities..." mode={:tags} />
        <:actions>
          <.button phx-disable-with="Saving...">Save Cities</.button>
        </:actions>
      <./simple_form>
    """
  end

  ...

  @impl true
  def handle_event("live_select_change", %{"field" => "cities_tags", "text" => text, "id" => live_select_id}, socket) do
    ...
    send_update(LiveSelect.Component, id: live_select_id, options: result)
    {:noreply, socket}
  end
end

Test:

  ... include the helper (with a few minor modifications to selectors) from this lib since it makes life much easier

  test "can save cities", %{conn: conn} do
    {:ok, new_live, _html} = live(conn, ~p"/cities/new")

    type(new_live, "ala",
        component: "#cities_live_select_component",
        field: "cities_tags"
      )

    select_nth_option(new_live, 1,
        method: :key,
        component: "#cities_live_select_component"
      )

    assert new_live
             |> form("#page-form",
               cause: %{
                 "name" => "A Fake Title Name"
               }
             )
             |> render_submit()
  end

This results in the following error which you won't get in when using this from the browser:
Assume MyAppWeb.PageLive is the LiveView which is hosting this in modal or something similar.

  ** (UndefinedFunctionError) function MyAppWeb.PageLive.handle_event/3 is undefined or private

If anyone has any ideas on how to test in the scenario or any pointers it would be very much appreciated.

no case clause matching after upgrading to v1.0.4

I upgraded to live_select 1.0.4 and LV 0.19 and suddenly many tests are failing for me.
I think this is related to live_select but I could be very much wrong (don't see live_select files in the stacktrace...)

Maybe you have an idea.

Here's one of the failing tests:

test "Adds an url", %{conn: conn, workshop: workshop} do
  {:ok, view, _} = live(conn, "/admin/workshops/#{workshop.id}/edit")

  x =
    view
    |> element("[phx-click='add-url']")
    |> render_click()
    |> assert_html("#workshop_urls_2_url", name: "workshop[urls][2][url]")
end

Here's the stacktrace, please note the error message is involving a live_select element:

  1) test Adds an url (ZdbWeb.Live.Workshops.UpdateTest)
     test/zdb_web/live/workshops/update_test.exs:198
     ** (EXIT from #PID<0.1082.0>) an exception was raised:
         ** (CaseClauseError) no case clause matching: [["select", %{id: "workshop-hallmarks", input_event: true, mode: :tags, selection: [%{key: "Steel Computer", label: "Steel Computer", value: "077388da-33bc-4653-9dfc-89bf88de460f"}, %{key: "Practical Granite Shoes", label: "Practical Granite Shoes", value: "0227eed3-6a50-4b7d-8137-812293aeb8fc"}, %{key: "Cotton Pants", label: "Cotton Pants", value: "7eb1fdcd-a7d8-4976-b831-2acdd4f7ab67"}]}], ["select", %{id: "workshop-tags", input_event: true, mode: :tags, selection: [%{key: "Small Concrete Computer", label: "Small Concrete Computer", value: "13a73bdc-08e7-49fb-b8c1-113129408ccf"}, %{key: "Fantastic Wooden Shirt", label: "Fantastic Wooden Shirt", value: "70a48d13-880d-4f76-86b7-de7c3c423ac4"}]}]]
             (phoenix_live_view 0.19.1) lib/phoenix_live_view/test/dom.ex:234: Phoenix.LiveViewTest.DOM.find_component/5
             (phoenix_live_view 0.19.1) lib/phoenix_live_view/test/dom.ex:211: anonymous fn/4 in Phoenix.LiveViewTest.DOM.merge_diff/2
             (stdlib 4.0.1) maps.erl:411: :maps.fold_1/3
             (phoenix_live_view 0.19.1) lib/phoenix_live_view/test/dom.ex:210: Phoenix.LiveViewTest.DOM.merge_diff/2
             (phoenix_live_view 0.19.1) lib/phoenix_live_view/test/client_proxy.ex:732: Phoenix.LiveViewTest.ClientProxy.merge_rendered/3
             (phoenix_live_view 0.19.1) lib/phoenix_live_view/test/client_proxy.ex:828: Phoenix.LiveViewTest.ClientProxy.handle_reply/2
             (phoenix_live_view 0.19.1) lib/phoenix_live_view/test/client_proxy.ex:452: Phoenix.LiveViewTest.ClientProxy.handle_info/2
             (stdlib 4.0.1) gen_server.erl:1120: :gen_server.try_dispatch/4
             (stdlib 4.0.1) gen_server.erl:1197: :gen_server.handle_msg/6
             (stdlib 4.0.1) proc_lib.erl:240: :proc_lib.init_p_do_apply/3

LiveSelect field should get initial values

I would like to be able to restore selections that hadn't been submitted yet, or that encountered an error on submission, like other forms seem to do via their changesets.

For example, I have a tabbed interface where switching tabs switches what info and form you're looking at. This live select is the only one that loses your work if you click away (within the same liveview) without saving.

It may be possible to simply take the current value from the form data using Phoenix.HTML.Form.input_value/2 in the template.

Blur event prevents option_click

I'm having trouble getting selections to work, and I've traced it to the blur event:

  def handle_event("blur", _params, socket) do
    {:noreply, assign(socket, :hide_dropdown, true)}
  end

For me, when I click on the option, the blur event fires, but the option_click and whatever is set on the form's phx-change never do. If I remove the :hide_dropdown assign, the option_click fires, and the form's change event happens.

I'm not entirely sure why it's blurring when I click an option, but it happens even if I use a live select with a minimal layout template and remove some other libraries from our app.js, so I don't think it's our own JS interfering.

I think this is probably a race condition where, if blur is processed before option click, the liveview updates with the hidden dropdown and drops the click event. Perhaps something could be done with send_update_after/4?

(Side note: I'm not sure the component template's phx_change: "change" on the text input itself goes anywhere.)

I'm thinking about what to do for a workaround in the meantime. This library seems very helpful to make a liveview-friendly select2 equivalent!

dependency live_view 0.19 fails

with LiveView set to 0.19, i get this error.

Because the lock depends on live_select 1.0.0 which depends on phoenix_live_view ~> 0.18.4, the lock requires phoenix_live_view ~> 0.18.4.
And because your app depends on the lock, phoenix_live_view ~> 0.18.4 is required.
So, because your app depends on phoenix_live_view ~> 0.19.0, version solving failed.
** (Mix) Hex dependency resolution failed

Limit component height attribute

Is it possible to have an attribute in the component to limit the number of displayed results if there are too many?

i.e. like the 'limit' attribute

 <%= live_select(f, :selected_countries,
                  mode: :tags,
                  placeholder: "Type to select countries",
                  limit:  10
                )
%>

Because one of the issues, except the aesthetic one is that the layout of the page is distorted if the selection list gets too big, by adding vertical scrollbars.

Not working on phoenix_live_view 0.20.4?

Hello!

With the update of LiveView 0.20.4, live_select seems to no longer work.

When clicking on the input there is this error in the console:
Uncaught TypeError: form is null

When I downgrade LiveView to 0.20.3, it's working.

error.mov

Possible bug in form data handling in 1.3

Hello and thanks for this project!

Setting options on initialization like

<.live_select :if={ @worksite.id != nil }
          field={@form[:project_id]}
          options={ [ {@worksite.project.name,@worksite.project.id} ] }
          phx-target={@myself} />

initially fills the select and works as expected but any change to other form fields (also LiveSelect) causes a "validate" and the component resets its displayed value to the .id part of the tuple. If selected it displays the .label part again. There are only LiveSelects in the form 1-3 of them and all reset to the .id.

I am new to Phoenix and LiveView hence the possible in the title. I made a demo with LiveSelect last week (1.2.2) and it worked as expected but after integration (now 1.3) it started to exhibit this strange behavior.

Any advice is welcome, thanks again.

Allow programmatic reset of selections

Currently, I can't see a good way to tell the component to clear its selected options in :tags mode. This is useful when you save the form via liveview and want to allow the user to make further selections without refreshing the page. If I'm just missing something, please let me know!

Perhaps allowing send_update/3 to change the :selection assign would be enough, or maybe it could listen for an event that triggers reset/1. I'd be willing to throw together a PR to that effect.

Workaround

My current workaround is pushing an event on save from the liveview that uses the component, then listening for it via JavaScript and simulating clicks to remove the selected options. (Compare https://hexdocs.pm/phoenix_live_view/js-interop.html#handling-server-pushed-events .)

<script>
  window.addEventListener('phx:reset-my-form', (e) => {
    let nodes = document.querySelectorAll("#my-form [phx-click='option_remove']");
    // start from last, otherwise we only end up clearing the early ones
    for (let i = nodes.length - 1; i >= 0; i--) {
      nodes[i].dispatchEvent(new MouseEvent('click', {bubbles: true, view: window}));
    }
  })
</script>

This seems to be working fine, but it feels a bit hacky, so a nicer way to do this in the future would be cool.

Discussion on styling

  • The above input is any old text input generated by Phoenix generators.
  • Bottom input is live_select.
  • Both are required.
  • Only top one shows error when trying to submit, even though the changeset has errors for both fields.

image

content disappears on click - add allow_clear setting to make single-mode option clearable

To reproduce

  1. select a value from dropdown
  2. deselect input
  3. click on the input
  4. notice the value disappeared
  5. unfocus input
  6. notice value is permanently gone

expected behaviour

once the user selects a value it should not disappear, only change to a new value if user selects it.

Demo video

https://imgur.com/VkZnTpL

Demo repo

i made a repo where this exists built on top of Phoenix 1.7 RC2 and it's LiveView generators.

https://github.com/KristerV/live_Select_bug

Multiple live selects get updated to same value

If you have two live selects in the same form, selecting a value for one updates both.

The form is set up as follows:

  <.form for={:unit_converter_form} let={f} phx-change="change" phx-submit="submit" class="w-1/2">
    <div class="flex gap-2 items-center">
      <%= LiveSelect.live_select(f, :unit_from, add_placeholder("Units From", @live_select_opts)) %>
      <%= LiveSelect.live_select(f, :unit_to, add_placeholder("Units To", @live_select_opts)) %>
    </div>
    <%= submit("Submit", class: "btn btn-primary") %>
  </.form>

It appears to be due to the js handleEvents firing for both instances. On my local copy I made a couple of changes to resolve this. I don't know if I am misunderstanding how to use multiple instances, but if these changes make sense, I'm happy to package them into a PR.

I patched the mounted in live_select.js to look like this:

    mounted() {
      this.handleEvent("reset", ({ id: id }) => {
        if (this.el.id == id) { // Note check on matching DOM id
          this.setSearchInputValue("")
          this.setHiddenInputValue("")
        }
      })
      this.handleEvent("selected", ({ selected: [label, selected], id: id }) => {
        if (this.el.id == id) {
          this.setSearchInputValue(label);
          this.setHiddenInputValue(selected)
        }
      })
      this.attachDomEventHandlers()
    },

and the functions that invoke the events in component.ex to look like this:

  defp select(socket, selected_position) do
    {label, selected} = Enum.at(socket.assigns.options, selected_position)
    id = socket.assigns.id # Pass in DOM id

    socket
    |> assign(
      options: [],
      current_focus: -1,
      search_term: label,
      selected: selected
    )
    |> push_event("selected", %{selected: [label, selected], id: id})
  end

  defp reset_input(socket) do
    id = socket.assigns.id

    socket
    |> assign(options: [], selected: nil, search_term: "")
    |> push_event("reset", %{id: id})
  end

Any known issues with nested forms?

This is more like a question but I wasn't able to get live_select input working in a form where the inputs are inside inputs_for block rather than top level of the form. I can't exclude that I did something incorrectly but the live_select_change event handling function was never called. If you could be so kind as to let me know whether this type of use case is fully supported and should work?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.