Using schemaless changesets with dynamic forms

I am attempting to use schemaless changesets with dynamic forms to collect data via a input wizard of sorts, and am having trouble understanding how to have the form object and template interact predictably.

I have different types of wizard questions - free text, guided (bounded) text, and multiple choice. After each question, I fetch the next question, look at its type, and build a form to accept the input of that question’s data:

defp reset_form(question) do
    question_type = Map.get(question, "type")

    case question_type do
      "multiple_choice" ->
        types = %{choice: :string}

        {:ok,
         {%{}, types}
         |> Ecto.Changeset.cast(%{}, Map.keys(types))
         |> to_form(as: :question)}

      "free_text" ->
        types = %{response: :string}

        {:ok,
         {%{}, types}
         |> Ecto.Changeset.cast(%{}, Map.keys(types))
         |> to_form(as: :question)}

      "guided_text" ->
        types =
          question["variables"]
          |> Enum.map(&variable_type_from_question/1)
          |> Map.new()
          |> IO.inspect()

        {:ok,
         {%{}, types}
         |> Ecto.Changeset.cast(%{}, Map.keys(types))
         |> to_form(as: :bubba)}

      _ ->
        {:error, "Question type not found: #{question_type}"}
    end
  end

In the heex, it looks like the following:

Multiple Choice:

<.form for={@form} phx-submit="wizard_submit">
          <div class="my-4">
            <%= for c <- choices_lens(@question) do %>
              <%!-- <label for={id_lens(c)}>{text_lens(c)}</label> --%>
              <.input
                label={text_lens(c)}
                id={id_lens(c)}
                name="form[choice]"
                type="radio"
                value={id_lens(c)}
              />
            <% end %>
          </div>
          
          <.button>Next</.button>
        </.form>

Free text:

      <div>
        <.form for={@form} phx-submit="wizard_submit">
          <div class="my-4">
            <.input type="textarea" id="a" field={@form[:response]} />
          </div>
          
          <.button>Next</.button>
        </.form>
      </div>

    """
  end

On submit, these are the outputs:
M/C: %{"form" => %{"choice" => "a"}}
Free Text: %{"bubba" => %{"response" => "kjhg"}}

Why does the first output show “form” => … instead of “question”? Also - my free text form does not reset if I have two free text questions in a row. I suspect that’s because the value in the form I’m pushing in as an initial condition is not being associated with the input field. How do I connect these?

You are naming the input in your MC form name="form[choice]", so it is only logical that it is sent that way.
You also dont need a changeset as input for to_form, you can use a map with string-keys.

1 Like

That answers the question why the output is the way it is, but it doesn’t answer the question for why my “reset_form” function is not setting the input control state. Any thoughts/suggestions?

Where is the function called?