lubien

lubien

tl;dr: I want to know the most phoenix way of letting users fill a {:array, :string} input via live view form component.

  defp deps do
    [
      {:argon2_elixir, "~> 2.0"},
      {:phoenix, "~> 1.6.0"},
      {:phoenix_ecto, "~> 4.4"},
      {:ecto_sql, "~> 3.6"},
      {:postgrex, ">= 0.0.0"},
      {:phoenix_html, "~> 3.0"},
      {:phoenix_live_reload, "~> 1.2", only: :dev},
      {:phoenix_live_view, "~> 0.16.0"},
    ]
  end

inputs

I’m currently experimenting with the current schema and the relevant prop is :synonyms with {:array, :string} type. The user should be able to write any amount of items there.

defmodule MyApp.MyContext.Tag do
  use Ecto.Schema
  import Ecto.Changeset

  schema "tags" do
    field :synonyms, {:array, :string}
    timestamps()
  end

  @doc false
  def changeset(tag, attrs) do
    tag
    |> cast(attrs, [:synonyms])
  end
end

I was able to make both edit and create work with some assignments called @synonyms and dynamically rendering inputs called name="tag[synonyms][]" using the code below:

defmodule MyAppWeb.TagLive.FormComponent do
  use MyAppWeb, :live_component

  alias MyApp.MyContext

  @impl true
  def update(%{tag: tag} = assigns, socket) do
    changeset = MyContext.change_tag(tag)

    {:ok,
     socket
     |> assign(assigns)
     |> assign(:changeset, changeset)
     |> assign(:synonyms, tag.synonyms)} # <---- relevant
  end

  @impl true
  def handle_event("validate", %{"tag" => tag_params}, socket) do  # <---- no changes needed
    # tag_params => %{ "synonyms: ["foo", "bar"], rest... }, which is ok :)
    changeset =
      socket.assigns.tag
      |> Jobs.change_tag(tag_params)
      |> Map.put(:action, :validate)

    {:noreply, assign(socket, :changeset, changeset)}
  end

  # unrelated ...
end
  <.form
    let={f}
    for={@changeset}
    id="tag-form"
    phx-target={@myself}
    phx-change="validate"
    phx-submit="save">
  
    <%= label f, :slug %>
    <%= text_input f, :slug %>
    <%= error_tag f, :slug %>
  
    <%= label f, :type %>
    <%= select f, :type, Ecto.Enum.values(MyApp.MyContext.Tag, :type), prompt: "Choose a value" %>
    <%= error_tag f, :type %>

    <%= for s <- @synonyms do %>  <---------------- here
      <%= label f, :slug %>
      <input name="tag[synonyms][]" value={s}>
      <%= error_tag f, :slug %>
    <% end %>  
  
    <div>
      <%= submit "Save", phx_disable_with: "Saving..." %>
    </div>
  </.form>

From there I should also make “add more” and “remove” inputs which is fine but I was left wondering if I’ve missed something obvious on the docs or somewhere else that I wasn’t able to find by my searches (as most results seem to deal with embedded schemas instead of an array on the very own schema)

Showing Posts 1 to 4

sukidhar

sukidhar

did u solve this

if so could u let me know

lubien

lubien OP

It’s been a while so I’m not certain but I think I just went ahead and used the things described on the original topic above

nilskj

nilskj

Hello! I had the exact same requirement and a lot of troubles but I got it to work now with the new versions of stuff. Idk if idiomatic, I’m new here :smiley:

So basically I have a similar Ecto schema with an array of strings as features that I want to edit in a form component:

schema "products" do
    ...
    field :features, {:array, :string}, default: [""]
  end

In my form live component I used the same trick as presented earlier here from couple years ago, with the product[features][] syntax in the name property for the input they get mapped correctly to this array.

<%= for {entry, index} <- Enum.with_index(Ecto.Changeset.get_field(@form.source, :features, [])) do %>
          <div class="flex items-center gap-2">
            <.input value={entry} name="product[features][]" type="text" label="Feature" />
            <div phx-click="remove_feature" phx-value-index={index} phx-target={@myself}>
              <.icon
                name="hero-x-mark"
                class="w-8 h-8 relative top-4 bg-red-500 hover:bg-red-700 hover:cursor-pointer"
              />
            </div>
          </div>
        <% end %>

It was a bit tricky getting the index so I wrapped it into an Enum.with_index to make the deletion mutation easier to manage.

Here are my working but a bit ugly? click handlers as well:

  def handle_event("add_feature", _, socket) do
    existing_features = Ecto.Changeset.get_field(socket.assigns.form.source, :features, [])

    append_feature =
      Catalog.change_product(socket.assigns.product, %{
        "features" => existing_features ++ [""]
      })

    {:noreply, assign(socket, form: to_form(append_feature))}
  end

  def handle_event("remove_feature", %{"index" => index}, socket) do
    updated_features =
      Ecto.Changeset.get_field(socket.assigns.form.source, :features, [])
      |> List.delete_at(String.to_integer(index))

    remove_feature =
      Catalog.change_product(socket.assigns.product, %{
        "features" => updated_features
      })

    {:noreply, assign(socket, form: to_form(remove_feature))}
  end

Is this maybe better to do in an embedded schema? Then I guess I could use inputs_for or something. Maybe that is the way!

kuzyn

kuzyn

Very insightful thread for me :slight_smile:

Is the name="map_name[list_name][]" behavior documented anywhere? It looks very strange to me since you could assume it’s a simple HTML name property without any template magic…

— All posts loaded —

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
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
Onor.io
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New
jaybe78
Hello, I’m developing a online persistent chat system (what’s app) like using elixir/dynamodb/aws for a mobile app(flutter). The diffic...
New
Trolleger
What approach to take when sending live updates to “random” users Hi! I have a question, I have a little chat app, and when I create a DM...
New
widianto
I think I’ve found a small improvement I could contribute to &lt;%= web_namespace %&gt;.CoreComponents (installer/templates/phx_web/compo...
New

Other Trending Topics Top

garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
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
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New
aseigo
ICal is a library for interacting with iCalendar data. It parses iCalendars into typed Elixir structs via ICal.from_ics, and can prepare ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews