Correct strategy for using datepicker attached to an input field with Liveview?

Hi folks, here is my latest contribution to this component: Flatpickr with support for an unused feature from Phoenix.


  attr :id, :string, required: true

  attr :field, Phoenix.HTML.FormField,
    doc: "a form field struct retrieved from the form, for example: @form[:email]"

  attr :label, :string, default: nil
  attr :class, :string, default: nil

  def datetime_picker(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
    errors = if Phoenix.Component.used_input?(field), do: field.errors, else: []
    assigns = assign(assigns, :errors, Enum.map(errors, &translate_error(&1)))

    ~H"""
    <div id={@id} class={["fieldset mb-2", not Enum.empty?(@errors) && "with-error"]}>
      <div>
        <label :if={@label} for={@field.id} class="label mb-1">{@label}</label>
        <div id={"#{@field.id}-picker"} class="relative" phx-hook=".Flatpickr" phx-update="ignore">
          <input
            type="datetime-local"
            name={@field.name}
            id={@field.id}
            placeholder="dd/mm/yyyy hh:mm"
            value={normalize_iso_datetime(@field.value)}
            class={[@class || "input w-full", @errors != [] && "input-error"]}
          />
        </div>
        <p :for={msg <- @errors} class="text-error mt-1.5 flex items-center gap-2 text-sm">
          <.icon name="hero-exclamation-circle" class="size-5" />
          {msg}
        </p>
      </div>
    </div>
    <script :type={Phoenix.LiveView.ColocatedHook} name=".Flatpickr">
      import flatpickr from "flatpickr";

      /**
      * @type {import("phoenix_live_view").ViewHook & { picker: flatpickr.Instance }}
      */
      export default {
        mounted() {
          const input = this.el.querySelector("input")
          const originalType = input.getAttribute("type")
          this.picker = flatpickr(input, {
            enableTime: true,
            altFormat: "d/m/Y H:i",
            dateFormat: "Z",
            minuteIncrement: 1,
            time_24hr: true,
            altInput: true,
            static: true,
          })
          input.setAttribute("type", originalType);
          input.setAttribute("style", "display: none;");
        },
        destroyed() {
          this.picker.destroy()
          this.picker = null
        },
      }
    </script>
    """
  end

This assumes that you are using Flatpickr with npm and the latest Phoenix/LiveView version.

2 Likes

My latest changes to this component are available here: LiveView DateTime Picker · GitHub

1 Like