CessK
Datatables like data entry with LiveView
Hello Everyone,
I have a question about table like data entry. And to make it as smooth as possible for the end user to add, edit and remove rows.
In previous projects i have done i have used Jquery data tables with a C# backed.
This worked very well. However i am unable to find something similar.
I have googled around and the best i could find was a 2 year old library:
I think Live View is the way to go here to implement something similar.
The use case for this would be something like a Purchase Order.
I have the main Purchase Order information and a table for the items. I would like to be able to add, update or remove items from this table.
And then able to submit a form to the _live.ex to save them as 1 object.
If there is something like this I would love to hear it as google has failed me on this.
Thanks for any information in advance!
Marked As Solved
mmmrrr
So I just bootstrapped a new project called Example and generated a new resource: mix phx.gen.live Billing Order orders title:string positions:array:map based on the assumption, that you want an embedded list of positions inside of your data schema.
Next you need to adjust the data model. This should always be your guiding principle: Model first! ![]()
# This is the generated model
defmodule Example.Billing.Order do
use Ecto.Schema
import Ecto.Changeset
schema "orders" do
# This has been changed from the generated schema
embeds_many :positions, Example.Billing.Position, on_replace: :delete
field :title, :string
timestamps(type: :utc_datetime)
end
@doc false
def changeset(order, attrs) do
order
|> cast(attrs, [:title])
# This is important for ecto to know how to cast the embed
|> cast_embed(:positions, with: &Example.Billing.Position.changeset/2)
|> validate_required([:title])
end
end
# This is something I added. This should contain the fields of your "table row".
defmodule Example.Billing.Position do
use Ecto.Schema
import Ecto.Changeset
embedded_schema do
field :product_name, :string
field :quantity, :integer
end
# The embedded schema can have a separate changeset definition
# This is cool, since it enables you to still get the error highlights
# inside the live view form
def changeset(schema, attrs) do
schema
|> cast(attrs, [:product_name, :quantity])
|> validate_required([:product_name, :quantity])
|> validate_number(:quantity, greater_than: 0)
end
end
Then you’ll need to adjust the generated form component (of course this doesn’t have to be in a modal, I simply kept it that way for ease of implementation:
defmodule ExampleWeb.OrderLive.FormComponent do
use ExampleWeb, :live_component
alias Example.Billing
@impl true
def render(assigns) do
~H"""
<div>
<.header>
<%= @title %>
<:subtitle>Use this form to manage order records in your database.</:subtitle>
</.header>
<.simple_form
for={@form}
id="order-form"
phx-target={@myself}
phx-change="validate"
phx-submit="save"
>
<.input field={@form[:title]} type="text" label="Title" />
<%!-- This is the important bit --%>
<fieldset class="flex flex-col gap-2">
<legend class="font-bold">Order positions</legend>
<%!-- Now we loop over the embedded positions. --%>
<.inputs_for :let={f_line} field={@form[:positions]}>
<.order_position f_line={f_line} />
</.inputs_for>
<.button class="mt-2" type="button" phx-click="add-position" phx-target={@myself}>
Add
</.button>
</fieldset>
<:actions>
<.button phx-disable-with="Saving...">Save Order</.button>
</:actions>
</.simple_form>
</div>
"""
end
def order_position(assigns) do
~H"""
<div>
<div class="flex gap-5 items-end">
<div class="grow">
<.input class="mt-0" field={@f_line[:product_name]} label="Product name" />
</div>
<div class="grow">
<.input class="mt-0" field={@f_line[:quantity]} type="number" label="Quantity" />
</div>
</div>
</div>
"""
end
@impl true
def update(%{order: order} = assigns, socket) do
{:ok,
socket
|> assign(assigns)
|> assign_new(:form, fn ->
to_form(Billing.change_order(order))
end)}
end
@impl true
def handle_event("validate", %{"order" => order_params}, socket) do
changeset = Billing.change_order(socket.assigns.order, order_params)
{:noreply, assign(socket, form: to_form(changeset, action: :validate))}
end
def handle_event("save", %{"order" => order_params}, socket) do
save_order(socket, socket.assigns.action, order_params)
end
#
# Add the event handler to update the form with the embedded
#
@impl true
def handle_event("add-position", _, socket) do
socket =
update(socket, :form, fn %{source: changeset} ->
existing = Ecto.Changeset.get_embed(changeset, :positions)
changeset = Ecto.Changeset.put_embed(changeset, :positions, existing ++ [%{}])
to_form(changeset)
end)
{:noreply, socket}
end
defp save_order(socket, :edit, order_params) do
case Billing.update_order(socket.assigns.order, order_params) do
{:ok, order} ->
notify_parent({:saved, order})
{:noreply,
socket
|> put_flash(:info, "Order updated successfully")
|> push_patch(to: socket.assigns.patch)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, form: to_form(changeset))}
end
end
defp save_order(socket, :new, order_params) do
case Billing.create_order(order_params) do
{:ok, order} ->
notify_parent({:saved, order})
{:noreply,
socket
|> put_flash(:info, "Order created successfully")
|> push_patch(to: socket.assigns.patch)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, form: to_form(changeset))}
end
end
defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
end
I hope this helps you to grok the connection between schema, changeset and form a little better.
Also Liked
CessK
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance









