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

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
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
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
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
ryanwinchester
apply_graft/2 doesn’t rewrite an add_many sub-workflow’s deps on an add step. Grafted jobs cancel with “upstream job was deleted” Version...
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