Greetings Everyone!!!
I have not been able to complete this task, so, I will show the code that is important for the task. Hopefully, you could help me out with the missing part.
When calling the form for new or edit employee, I get the Employee record or an Empty Employee record:
# .../employee_live/index.ex
defp apply_action(socket, :edit, %{"id" => id}) do
socket
|> assign(:page_title, "Edit Employee")
|> assign(:employee, Context.get_employee!(id))
end
defp apply_action(socket, :new, _params) do
socket
|> assign(:page_title, "New Employee")
|> assign(:employee, %Employee{})
end
# .../myapp/context.ex
def get_employee!(id) do
Employee
|> Repo.get!(id)
|> Repo.preload([:positions, :activities])
end
Then, about to render the form, the record is converted into a Form in the update function and I use a couple of functions to show the relationships Employee->Positions and Employee->Activities.
# .../lib/myapp/context.ex <- Called ahead
def change_employee(%Employee{} = employee, attrs \\ %{}) do
activities = list_activities_by_id(attrs["activity_ids"])
employee
|> Repo.preload([:positions, :activities])
|> Employee.changeset(attrs)
|> Ecto.Changeset.put_assoc(:activities, activities)
end
def list_activities_by_id(nil) do
[]
end
def list_activities_by_id(activity_ids) do
Repo.all(from a in Activity, where: a.id in ^activity_ids)
end
Then the app renders the form:
# .../employee_live/form_component.ex
@impl true
def update(%{employee: employee} = assigns, socket) do
{:ok,
socket
|> assign(assigns)
|> assign_new(:form, fn ->
to_form(Context.change_employee(employee))
end)}
end
@impl true
def render(assigns) do
~H"""
<div>
...
<.simple_form
for={@form}
id="employee-form"
phx-target={@myself}
phx-change="validate"
phx-submit="save"
>
<.input field={@form[:emp_first_name]} type="text" label="First Name" phx-debounce="blur"/>
...
<!-- :ERS: Here I am sending the @employee.emp_position from the socket -->
<.input field={@form[:emp_position]} type="select" label="Position" options={MyApp.Context.position_opts(@employee.emp_position)} prompt="Choose an option:" phx-change="validate_position"/>
<.input field={@form[:activity_ids]} type="select" label="Activities" multiple={true} options={MyApp.Context.activity_opts(@form.data)} phx-debounce="blur"/>
...
</div>
"""
end
I use this functions to create the list objects for the employee’s position and activities:
# .../lib/myapp/context.ex <--- Called above
def position_opts(position_id) do
for pst <- list_positions() do
[
key: pst.pos_descrip,
value: pst.id,
selected: pst.id == position_id
]
end
end
def activity_opts(data) do
activity_ids = Enum.map(data.activities, & &1.id )
for act <- list_activities() do
[
key: act.act_descrip,
value: act.id,
selected: act.id in activity_ids
]
end
end
Up to this moment, everything works great: The new record has no activities selected, and the edited record already has its activities selected, but … whenever the Position field is changed I call the validate_position event to set/replace the new activities, where I get the list of the activities for the new position:
# .../lib/myapp/context.ex <--- Called ahead
def get_position!(id) do Position
|> Repo.get!(id)
|> Repo.preload(:activities)
end
In here is where I have been running around for a couple of weeks now. I get the list of the new activities, but I don’t know how to have the app to “select” the new activities
@impl true
def handle_event("validate_position", %{"employee" => employee_params}, socket) do
%{"emp_position" => new_position_id} = employee_params
new_position_id = String.to_integer("0" <> new_position_id)
new_position = Context.get_position!( new_position_id )
changeset =
Context.change_employee(socket.assigns.employee, employee_params)
|> Ecto.Changeset.put_assoc(:positions, new_position)
|> Ecto.Changeset.cast_assoc(:activities, new_position.activities)
# ├──┤ Totally lost in here ├──────────────────────────────────────────────┤
{:noreply, assign(socket, form: to_form(changeset, action: :validate))}
end
I can set the the data into the changeset, but the app is either using the info from the socket (look for tag :ERS:) or from the Form. I should not be using the info in the Socket since it is the Original information, not the one being changed. Under different circumstances, I should be looking for the value in the changeset, the one being updated, but this form is not using the changeset.
I really hope you could help me to have the render function to see the new activities for the employee once the position is changed.
Any help will be more than welcome.