Re-sort items in a form without submitting the form

Let's say I have a form to input a lit of to-do items, and want to give the user up/down buttons to reorder them based on priority. I can probably change the priority value via phx-click event, but I'm not sure what's the best way to display the items reordered based on the new priority values, without submitting the form. I'm guessing it should be done in the 'validate' handle_event, which currently only includes this.
form = AshPhoenix.Form.validate(socket.assigns.form, form_params)
form = AshPhoenix.Form.validate(socket.assigns.form, form_params)
14 Replies
ZachDaniel
ZachDaniel3y ago
It depends a bit on how you're doing it, but you ought to be able to sort the items in the assigned data on their priority are the todo items in a list of related things? using something like forms: [auto?: true]?
Jason
JasonOP3y ago
Yes it is. I was able to sort items as they are first added to the form (as shown below). However, I'm not clear how to update the reordered form data while in edit mode (ie. before submission)
form = AshPhoenix.Form.for_update(todo, :update,
forms: [
items: [
resource: Item,
data: todo.items,
...
form = AshPhoenix.Form.for_update(todo, :update,
forms: [
items: [
resource: Item,
data: todo.items,
...
Perhaps this has to involve some javascript magic rather than a fully data driven approach.
ZachDaniel
ZachDaniel3y ago
So when they press an up and down button, what are you doing?
Jason
JasonOP3y ago
I was thinking I would keep track of the order in an assigns list, and somehow patch the underlying data to the form.
ZachDaniel
ZachDaniel3y ago
What you can do is send an event to the server, then sort the value of form.forms[:relationship] and update the params with the index
Jason
JasonOP3y ago
and how do I feed the updated form data to Ash? I'm hoping to allow users to still discard any changes made so far including the reordering and other inputs. Maybe I should delay persisting the item orders until form submission, and handle DOM object reordering with javascript. That way, Ash doesn't need to know about the priority change until the form is submitted.
ZachDaniel
ZachDaniel3y ago
If you update the params of the form on the server side it will update the value in the client I can show an example when I’m at a computer 🙂
Jason
JasonOP3y ago
oh.. great. Thank you!
ZachDaniel
ZachDaniel3y ago
def handle_event("move_item", %{"name" => name, "index" => new_index}, socket) do
item_list = socket.assigns.form.forms[:items]
current_index = Enum.find_index(item_list, &(&1.name == name))
{form, item_list} = List.pop_at(item_list, current_index)
item_list =
item_list
|> List.insert_at(new_index, form)
|> Enum.with_index()
|> Enum.map(fn {form, index} ->
AshPhoenix.Form.validate(form, Map.put(form.params, "index", index))
end)
new_form = %{socket.assigns.form | forms: %{socket.assigns.forms | items: item_list}}

{:noreply, assign(socket, :form, new_form}
end
def handle_event("move_item", %{"name" => name, "index" => new_index}, socket) do
item_list = socket.assigns.form.forms[:items]
current_index = Enum.find_index(item_list, &(&1.name == name))
{form, item_list} = List.pop_at(item_list, current_index)
item_list =
item_list
|> List.insert_at(new_index, form)
|> Enum.with_index()
|> Enum.map(fn {form, index} ->
AshPhoenix.Form.validate(form, Map.put(form.params, "index", index))
end)
new_form = %{socket.assigns.form | forms: %{socket.assigns.forms | items: item_list}}

{:noreply, assign(socket, :form, new_form}
end
Something like that then you'd have an up arrow with something like phx-click="move_item" phx-value-name={item_form.name} phx-value-index={AshPhoenix.Form.value(item_form, :index) - 1}
Jason
JasonOP3y ago
Wow. you are truly amazing. Thanks a lot!! @Zach Daniel For some reason, the indexes changed using the above approach aren't persisted when the form is submitted. I wonder if the f.params should also be somehow changed, not just the sub-form's params (item_form.params). I noticed the new index is persisted if item name is also modified in the same form submission. When the item name is modified, f.params is automatically populated like the following, whereas up/down phx-click alone doesn't impact f.params.
%{"todo_form_name" => "My Todo",
"items" => %{
"0" => %{
"_form_type" => "update",
"_touched" => "index",
"id" => "fpwhf;skf;....",
"item_name" => "Task 1"
"index" => "0"
|,
...
%{"todo_form_name" => "My Todo",
"items" => %{
"0" => %{
"_form_type" => "update",
"_touched" => "index",
"id" => "fpwhf;skf;....",
"item_name" => "Task 1"
"index" => "0"
|,
...
ZachDaniel
ZachDaniel3y ago
Hmmm… It’s possible, yeah. Although not ideal Try doing this after you move an item Form.validate(form, Form.params(form))
Jason
JasonOP3y ago
Do you mean something like this?
item_list =
item_list
|> List.insert_at(new_index, form)
|> Enum.with_index()
|> Enum.map(fn {form, index} ->
form_param =
form.params
|> Map.put("index", Integer.to_string(index))

AshPhoenix.Form.validate(form, AshPhoenix.Form.params(form))
item_list =
item_list
|> List.insert_at(new_index, form)
|> Enum.with_index()
|> Enum.map(fn {form, index} ->
form_param =
form.params
|> Map.put("index", Integer.to_string(index))

AshPhoenix.Form.validate(form, AshPhoenix.Form.params(form))
It doesn't seem to make any difference. Is this meant to populate the params for the main form (f)?
ZachDaniel
ZachDaniel3y ago
Ah, no I mean do that for the entire form So after you alter the child form child forms And replace the form with the new list Then you recalibrate the whole thing
Jason
JasonOP3y ago
Okay. will try. thanks.

Did you find this page helpful?