maxmarcon / live_select

Dynamic (multi)selection field for LiveView
https://hex.pm/packages/live_select
Apache License 2.0
185 stars 35 forks source link

Allow programmatic reset of selections #8

Closed goblinJoel closed 1 year ago

goblinJoel commented 1 year ago

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.

maxmarcon commented 1 year ago

Hi @goblinJoel

No you aren't missing anything, this feature is currently not supported. This library doesn't have many users yet, so it's very useful for me to collect use cases I hadn't thought about, like this one.

Your workaround looks very good 👍

As you said, it's also kind of hacky.

I'm thinking a possible solution would be to add a function LiveSelect.reset(id). The function would use send_update/3 to signal to the live select component that it needs to reset its selection.

One problem with this idea: how is the user supposed to know the id of the component? Right now, the id is opaque to the user and set here.

So maybe we could have live_select/3 accept an id option that allows the user to explicitly set the id of the component so they can use it later when calling reset/1.

Your idea of having the component listen to an event from the UI is also reasonable, again the same problem arises: the user will need to know the id of the component so they can use it in the phx-target binding of whatever UI element is triggering the event.

Does this make sense? Let me know what you think and if you have other ideas.

goblinJoel commented 1 year ago

I like the idea of being able to pass in a custom id. Compare with update_options/2: right now, that gets id and module from the ChangeMsg, which is the only reason it has to be passed a message that the parent received from the component. Seems like that could also have a signature that accepts an id instead of ChangeMsg (and assumes the module is LiveSelect.Component) for advanced usage. A reset/1 could also take an id, since you would never have a change message to give it unless you saved a previous one.

Getting a little off topic, I also wanted to be able to restore selections that hadn't been submitted yet, like other forms seem to do via their changesets. (In my case, it's 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 without saving.) A function that lets you set initial values, or letting you pass those into live_select/3, would also be helpful. If the changeset is accessible via the @form, that might already be doable with a template edit.

I saw the user in the other issue was arguing for working with the component via assigns instead of helper functions. I like that the helper functions make it obvious what you can do and how, but I also like the increased control of directly sending updates (if it allowed more assigns) or changing assigns. So I don't have a strong opinion on which way resetting the selections works as long as it's obvious from docs.

maxmarcon commented 1 year ago

Getting a little off topic, I also wanted to be able to restore selections that hadn't been submitted yet, like other forms seem to do via their changesets

This is a very good point and something I also had thought about. LiveSelect should behave like any other form input, taking the current value from the form data using Phoenix.HTML.Form.input_value/2. Would you mind creating another issue about this? Just a very short description will do.

I saw the user in the other issue was arguing for working with the component via assigns instead of helper functions. I like that the helper functions make it obvious what you can do and how, but I also like the increased control of directly sending updates (if it allowed more assigns) or changing assigns. So I don't have a strong opinion on which way resetting the selections works as long as it's obvious from docs.

Yes I think the discussion in the other issue convinced me that this is the way to go: not having custom functions but instead working with assigns or send_update/3. I believe we should use this approach to implement the reset functionality. Giving the ability to control everything via assigns and allowing for custom ids to be set should give us all the flexibility we need for this and any other feature we might want to add.

goblinJoel commented 1 year ago

Created https://github.com/maxmarcon/live_select/issues/10

maxmarcon commented 1 year ago

Thanks a ton!

maxmarcon commented 1 year ago

You should now be able to clear the selection programmatically by sending clear: true update to the component.

send_update(LiveSelect.Component, id: component_id, clear: true)

And you can now specify a custom id when calling live_select/3:

<%= live_select form, field, id: my_custom_id %> 

It's in the main branch, let me know if it works for you

maxmarcon commented 1 year ago

@goblinJoel I renamed the parameter to clear cause I think it's... clearer :) sorry for the sneaky change

goblinJoel commented 1 year ago

Thanks! I'm not sure if I'll get a chance to try these before you put together the next release, but it does sound cleaner (and certainly clearer) than the JS hack I'm using.

maxmarcon commented 1 year ago

closed by a495d546ba733728d096b4a0b9ebc61e057ccd22