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
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
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
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
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
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
apz
I’m new to elixir and just tried to install the elixirLS extension for VScode(ium) and it is throwing some errors that I would like help ...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
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
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
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

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews