kccarter

kccarter

We’re trying to create a form using the .form component and a map but unable to figure out the right combination. We are not using Ecto. Our data is unstructured JSON stored in a NoSQL backend.

The documentation says to use to_form with a map, but we keep getting stuck with the error that the given map does not implement HTML.Safe.

<.form :let={f} for={@account} action={~p"/accounts/#{@tag["id"]}"}>
    # We know there's always a name, but in reality 
    # this is a for-loop over the account's keys.
    <.input field={f["name"]} />
</.form>
defmodule DemoWeb.AccountsController do
  def edit(conn, %{"id" => id}) do
    # This returns a Map of arbitrary key/value pairs.
    account = Accounts.find!(id) 
    
    # Is this what we're supposed to do?
    account_form = Phoenix.Component.to_form(account)
    
    render(conn, :edit, account: account_form)
  end
end

Our goal is to just render a form for a map, and then have the form fields posted back to the appropriate endpoint, where we’ll convert it back to a JSON blob and store it accordingly. Ecto, and Ecto changesets, are not used anywhere.

Showing Posts 1 to 10

sodapopcan

sodapopcan

Where is tag coming from? If account is a map, then this is the general idea:

account = %{"name" => "Account name"}
account_form = Phoenix.Component.to_form(account)
render(conn, :edit, account: account_form

then in the template:

<.form for={@account}>
  <.input field={@account[:name] />
</.form>
kccarter

kccarter OP

My bad. tag was meant to be account.

sodapopcan

sodapopcan

I’m assuming account is a struct since that would cause that error. You have to use either a bare map or a changeset with to_form. You can convert a struct to a map with Map.from_struct although you’ll get a warning that you should not be using atom keys with to_form. From there you could convert all the keys to strings but I would just use a changeset. Since you aren’t using changesets anywhere else, you won’t need to do anything else with them other than convert your struct to one just for use with the form:

account_form =
  account
  |> Ecto.Changeset.change()
  |> Phoenix.Component.to_form()

change just creates a changeset, no-questions-asked (no casting or anything) so it literally only needs to exist for the form.

kccarter

kccarter OP

Account was just a Map. The following still produces an error:

def edit(conn, _) do
  tag = Phoenix.Component.to_form(%{"name" => "Tim"})
  render(conn, :edit, tag: tag)
end

<.form :let={f} for={@tag}>
  <div>
    <label for="name">Name:</label>
    <.input field={f["name"]} />
  </div>
</.form>

Produces this error:

protocol Phoenix.HTML.Safe not implemented for %Phoenix.HTML.FormField{id: “id”, name: “id”, errors: , field: “id”, form: %Phoenix.HTML.Form{source: %{“name” => “Tim”}, impl: Phoenix.HTML.FormData.Map,…

That code above seems to be converting a normal Map using to_form as suggested by the documentation, but we’re obviously missing an important step.

sodapopcan

sodapopcan

Ohhhh crap, I’m sorry. It’s been a million years since I’ve used a controller with a form like that. AFAIR, you just use the bare map, no need for to_form. to_form is largely about change tracking which is a LiveView-specific thing. You can just do:

render(conn, :edit, account: %{"name" => "Name"})

<.form :let={f} for={@account} as="account">
  <.input field={f[:name]} />
</.form>

The as option will let you collect the params under an "account" key.

kccarter

kccarter OP

Awesome. Thank you.

We also noticed another issue that was causing a Safe HTML error but wasn’t related to the form, but rather a line further down in the heex file. Fridays..

sodapopcan

sodapopcan

That basically means you are trying to to_string some value into HTML. Most of the time it’s a struct, but it could be a tuple or fun or whatever doesn’t cast into a string (or HTML in this case, to be a bit more technical).

Sebb

Sebb

EDIT this anwser is OT, because this is about Controllers, not LV.

IF YOU’D BE USING LIVEVIEW you should not use :let here, see: Phoenix.Component — Phoenix LiveView v0.20.7

This works WITH LV:

def mount(_params, _session, socket) do
  form = to_form(%{"username" => "Kermit", "email" => "kermit@muppets.com"})
  {:ok, assign(socket, form: form)}
end
<.form for={@form} phx-change="validate" phx-submit="save">
  <.input type="text" field={@form[:username]} />
  <.input type="text" field={@form[:email]} />
  <.button>Save</.button>
</.form>
sodapopcan

sodapopcan

You’re making a similar mistake that I made—they are using controllers, not LiveViews. to_form just doesn’t work with controllers (from the bit of testing I did). And because of this it doesn’t really matter if they use :let or not (I also had the same thought, though).

kccarter

kccarter OP

Yeah, in a framework with relatively good documentation, we’ve found the narrative around forms to be a bit confusing and hard to follow. We’re not using LiveView, so the fact that .form is part of Phoneix.LiveView initially made us look past it.

There’s form_for in Phoenix.HTML but this blog post suggests that it’s now deprecated. And this older forum post has a post from Jose saying that .form is the correct way to go, but as noted above we initially thought that was just for LiveViews.

In core_components.ex there’s .simple_form which is used throughout the generated scaffolding code. In this case you can quickly see it just calls through to .form, but when you’re initially learning it’s an added layer of indirection.

In our case, where we’re just using a plain Map, the “secret” attribute to make it work was as:.

We’re editing JSON records with an arbitrary and unknown set of keys. All we really want a form component for is to have the proper _method and _csrf attributes injected along with proper field naming.

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews