View Source Form Data

Types provided by this library are intended to be used with nested form data that can be cast to ranges as a single unit. For example, a heex component could be written as follows:

defmodule Web.Components do
  use Phoenix.HTML

  import Phoenix.LiveView.Helpers

  def utc_date_time_range_field(assigns) do
    ~H"""
    <fieldset>
      <%= label @f, @field do %>
        <div is="grouped">
          <span><%= @title %></span>
          <%= error_tag @f, @field %>
        </div>
        <%= content_tag(:input, nil,
              type: "text"
              name: "#{form_name(@f)}[#{@field}][start_at]",
              value: utc_date_time_part(@f, @field, :start_at),
              id: "#{form.name}_#{field.name}_start_at") %>

        <%= content_tag(:input, nil,
              type: "text",
              name: "#{form_name(@f)}[#{@field}][end_at]",
              value: utc_date_time_part(@f, @field, :end_at),
              id: "#{form.name}_#{field.name}_end_at") %>

        <%= content_tag(:input, nil,
              type: "hidden",
              name: "#{form_name(@f)}[#{@field}][tz]",
              value: "Etc/UTC") %>
      <% end %>
    </fieldset>
    """
  end

  defp form_name(form), do: form.name

  defp utc_date_time_part(form, field, part) do
    form.source
    |> Ecto.Changeset.get_field(name)
    |> case do
      nil -> DateTime.utc_now()
      %Ecto.UTCDateTimeRange{} = range -> Map.get(range, field)
    end
  end
end

Components such as this can be used in a parent live view:

defmodule Web.MyLiveView do
  use Web, :live_view
  import Web.Components

  @impl Phoenix.LiveView
  def render(assigns) do
    ~H"""
    <.form let={f} for={@changeset} id="thing-form" phx-change="validate">
      <.utc_date_time_range_field id="performed-during" f={f} field={:performed_during} title="Performed During" />
      <%= submit("Save") %>
    </.form>
    """
  end

  @impl Phoenix.LiveView
  def mount(_params, _session, socket) do
    socket =
      socket
      |> assign(:changeset, Core.Thing.changeset(%{}))

    {:ok, socket}
  end

  @impl Phoenix.LiveView
  def handle_event("validate", %{"thing" => params}, socket) do
    socket = socket |> assign(changeset: Core.Things.changeset(params))
    {:noreply, socket}
  end

end

When this form is sent to the server via POST or in a LiveView's phx-change or phx-submit, the params will arrive in the following format, which matches that expected by Ecto.UTCDateTimeRange.cast/1:

%{
  "thing" => %{
    "performed_during" => %{
        "start_at" => "2022-06-22 01:00:00",
        "end_at" => "2022-06-22 01:00:00",
        "tz" => "Etc/UTC"
      }
  }
}