Struggling with a seemingly basic LiveView form / component

Background: I’ve literally spent the entire day (I think I’m up to 12 hours now…) trying to get a simple modal form working. My real problem is trying to manage a list in a form.

The form is trying to gather the following input:

  • A few simple text fields (e.g. name, description)
  • A set of key-value pairs (think label-value or tag-value pairs)

The thing that’s absolutely killing me is the last one. I want to present a list of the name/value pairs currently being staged in the form, along with an “X” on each one to remove it. Above or below those, I want a “+” to add a new name/value pair, which means there are also two input boxes awaiting the label and value text.

My first several attempts all revolved around having the list in a nested form, e.g. copying and gaining inspiration from some “todo list” controls. The core problem here was the form double-post where every time I click “add value” I also trigger the submit handler for the form. I spent hours of shenanigans trying to hack a way to do this to no avail.

Next I tried creating a pure component for the list where I use the changed event to copy the form fields that changed into two manual assigns like :new_key and :new_val. This didn’t work the way I wanted to either, because I couldn’t get the buttons to click “without a form” unless I physically moved the buttons outside the <.simple_form component declaration. I was able to get that “working” but the UI is horrible, the layout is counter-intuitive, and everything just feels super janky.

So, tl;dr - I want to create a live form that takes a few arbitrary fields like text inputs, but also manages a list of key-value pairs. When I finally hit submit, I need that list to either be in the form or in the socket assigns (I don’t particularly care where, I just need access to it on the server).

Hopefully someone can point me to a basic example of how to do this… because at this point after blowing so much time on it, I’m pretty close to the end of my rope, at which point I’ll end up abandoning LiveView for something awful like a Rust binary that self-hosts a Vue app or some other monstrosity like that.

Thanks in advance!

It’s not entirely trivial but it is possible. Here’s an example:

I’ve been using that as a reference. I can’t make enough sense of it to be able to figure out what to copy and what not do. I tried creating a form for each of my key-value line items like this sample does, but clicking on buttons in those forms always caused the double-submit and triggered the parent form, too.

I think another thing that makes it difficult to translate that sample is that in my UI I have text inputs sitting there waiting for input, while the todo creates a new persistent item and then lets you edit via click (I think)

2 Likes

This should help get you unblocked: GitHub - John-Elm/Nested-LiveView-Form-Example: Quick demo to show how you can create nested parent / child relationship forms with Phoenix LiveView

I hacked up a quick example. It’s not perfect – the child inputs will all clear if you remove one of them. I know that it’s got something to do with the embedded schema casting / how I’m building that changeset, but hopefully this will help you out a little bit

lib/nested/live/home_live.ex is where you’ll wanna look. Nested-LiveView-Form-Example/lib/nested_web/live/home_live.ex at master · John-Elm/Nested-LiveView-Form-Example · GitHub

2 Likes

Try this post from @LostKobrakai

3 Likes

These tutorials might also be helpful:

3 Likes

If I understood you correctly the form is submitting when you click a button, which is the intended element behaviour.

From MDN button types:

submit: The button submits the form data to the server. This is the default if the attribute is not specified for buttons associated with a <form>, or if the attribute is an empty or invalid value.

button: The button has no default behavior, and does nothing when pressed by default. It can have client-side scripts listen to the element’s events, which are triggered when the events occur.

So just pass type="button" to your buttons inside the form

<.button type="button" phx-click="my-event">Hi</.button>
2 Likes

A little clearer this way:

From MDN button types :

<button>
type attribute. Possible values:

  1. submit: The button submits the form data to the server. This is the default if the type attribute is not specified for buttons associated with a <form>, or if the type attribute is an empty or invalid value.

  2. button: With this type attribute, the button has no default behavior, and does nothing when pressed. However, the button can have client-side scripts listen to the element’s events, which are triggered when the events occur.