I am new to LiveView, Phoenix and Elixir, and I am struggling to populate the UI from the form content. Here's a simplified version of what I have in my code: I have defined a struct with some simple fields:
defmodule MyApp.MyStruct do
defstruct some_integer: 3,
some_string: "",
some_bool: true
end
Then I have a context:
defmodule MyApp.Context do
alias MyApp.MyStruct
def current_struct() do
%MyStruct{}
end
end
It simply returns the newly created struct with default values. I will implement modifying later.
Now, I need to display a form populated with these values + allow modifying them.
This is my _live.ex
file:
defmodule MyAppWeb.ViewLive do
alias MyApp.MyStruct
use MyAppWeb, :live_view
def mount(_params, _session, socket) do
{:ok, assign(socket, form: to_form(Map.from_struct(Context.current_struct())))}
end
# Here I have handle_event methods, but they don't do anything at the moment.
end
Finally, this is the .heex
file:
<div class="container mx-auto my-4">
<.form for={@form} phx-change="change_form" phx-submit="submit_form">
<!-- Number picker -->
<div class="mb-4">
<.label>I want to pick a number here:</.label>
</div>
<div class="flex items-center space-x-4 mb-4">
<label class="cursor-pointer">
<input type="radio" name="some_integer" value="3" class="hidden" checked={Context.current_struct().some_integer == 3}
/>
<span class={"px-4 py-2 rounded-md #{if Context.current_struct().some_integer == 3, do: "bg-gray-400", else: "bg-gray-200"} hover:bg-gray-400"}>
3 of something
</span>
</label>
<!-- More of these integer options -->
<!-- Checkbox -->
<div class="mb-4">
<.input
label="I'd like this to be true or false:"
type="checkbox"
value={Context.current_struct().some_bool}
field={@form[:some_bool]}
/>
</div>
<.button>Submit</.button>
</.form>
</div>
The code above kind of works now for initial values, but it stops working when I select 7 or 10 instead of 3.
Note how to get the value or compute background colours I use Context.my_struct()
for calculations.
I assume, that if I implement handle_event("change_form"...)
and modify my current_struct()
on each change, then the change would also be applied on UI. But I would like to modify the actual struct only on submit, and in the meantime to get the data from the @form
.
I tried to do that, but failed.
When I try to do something like
value={@form[:some_bool]}
then it crashes. When I try to use input_value()
, then I always get nil, even if inspect(form)
shows that both some_integer
and some_bool
have the values that I expect them to have.
Please advise me how I can achieve this.
In
mount()
, you can extract the values from your struct and store the values in the socket, leaving the struct unchanged, then those values can be used to populate the form inrender()
. After the user changes something in the form,handle_event()
will be called, andhandle_event()
will receive the values that were entered into the form as the second argument.In addition, inside
mount()
you can save your original struct in the socket, then inhandle_event()
you will be able to access any values from the struct that you need. After the user hits Submit,handle_event()
will receive the final values entered into the form, which you can use to update the original struct.Here is an example:
Here is what the LiveView page looks like:
This function definition:
pattern matches on the second argument to extract the values entered into the form. Pattern matching a map works like this:
You could also do the pattern match like this:
This code:
uses a special syntax for updating a map or struct (which looks like the syntax used for lists). The special syntax ensures that the keys are already in the map so that mispellings don't add new keys to a map.
On the web page, watch the values above the form change as you type in the input fields. Then hit Submit and examine the output in the terminal window.
The only way I can get
nil
when I call:is if the key does not exist in
my_form
, e.g.:For instance, I can add the following code to the "save"
handle_event()
function:then when I hit Submit, I see the following output in the terminal window:
Notice that the value returned was the value which was used to create the form in
mount()
.When I try writing:
it crashes for me too:
====
I just read the following in the docs (Form Bindings)
I think that is saying that instead of dynamically changing the value attribute of an input element, you should use the entered values in the form to create a new form with
to_form()
, then assign the new form to the socket. Like this: