defmodule Kaffy.ResourceForm do use Phoenix.HTML def form_label_string({field, options}), do: Map.get(options, :label, field) def form_label_string(field) when is_atom(field), do: field def form_label(form, field) do label_text = form_label_string(field) label(form, label_text) end def form_help_text({_field, options}), do: Map.get(options, :help_text, nil) def form_help_text(field) when is_atom(field), do: nil def bare_form_field(resource, form, {field, options}) do options = options || %{} type = Map.get(options, :type, Kaffy.ResourceSchema.field_type(resource[:schema], field)) permission = Map.get(options, :permission, :write) choices = Map.get(options, :choices) cond do !is_nil(choices) -> select(form, field, choices, class: "custom-select") permission == :read -> content_tag( :div, label(form, field, Kaffy.ResourceSchema.kaffy_field_value(resource[:schema], field)) ) true -> build_html_input(resource[:schema], form, field, type, []) end end def form_field(changeset, form, field, opts \\ []) def form_field(changeset, form, {field, options}, opts) do options = options || %{} type = Map.get(options, :type, Kaffy.ResourceSchema.field_type(changeset.data.__struct__, field)) opts = if type == :textarea do rows = Map.get(options, :rows, 5) Keyword.put(opts, :rows, rows) else opts end permission = case is_nil(changeset.data.id) do true -> Map.get(options, :create, :editable) false -> Map.get(options, :update, :editable) end choices = Map.get(options, :choices) cond do !is_nil(choices) -> select(form, field, choices, class: "custom-select") true -> build_html_input(changeset.data, form, field, type, opts, permission == :readonly) end end def form_field(changeset, form, field, opts) do type = Kaffy.ResourceSchema.field_type(changeset.data.__struct__, field) build_html_input(changeset.data, form, field, type, opts) end defp build_html_input(schema, form, field, type, opts, readonly \\ false) do data = schema {conn, opts} = Keyword.pop(opts, :conn) opts = Keyword.put(opts, :readonly, readonly) schema = schema.__struct__ case type do {:embed, _} -> embed = Kaffy.ResourceSchema.embed_struct(schema, field) embed_fields = Kaffy.ResourceSchema.fields(embed) embed_changeset = Ecto.Changeset.change(Map.get(data, field) || embed.__struct__) inputs_for(form, field, fn fp -> [ {:safe, ~s(
)}, Enum.reduce(embed_fields, [], fn f, all -> content_tag :div, class: "form-group" do [ [ form_label(fp, f), form_field(embed_changeset, fp, f, class: "form-control") ] | all ] end end), {:safe, "
"} ] end) :id -> case Kaffy.ResourceSchema.primary_key(schema) == [field] do true -> text_input(form, field, opts) false -> text_or_assoc(conn, schema, form, field, opts) end :string -> text_input(form, field, opts) :richtext -> opts = Keyword.put(opts, :class, "kaffy-editor") textarea(form, field, opts) :textarea -> textarea(form, field, opts) :integer -> number_input(form, field, opts) :float -> text_input(form, field, opts) :decimal -> text_input(form, field, opts) t when t in [:boolean, :boolean_checkbox] -> checkbox_opts = Keyword.put(opts, :class, "custom-control-input") label_opts = Keyword.put(opts, :class, "custom-control-label") [ {:safe, ~s(
)}, checkbox(form, field, checkbox_opts), label(form, field, label_opts), {:safe, "
"} ] :boolean_switch -> checkbox_opts = Keyword.put(opts, :class, "custom-control-input") label_opts = Keyword.put(opts, :class, "custom-control-label") [ {:safe, ~s(
)}, checkbox(form, field, checkbox_opts), label(form, field, label_opts), {:safe, "
"} ] :map -> value = Map.get(data, field, "") value = cond do is_map(value) -> Kaffy.Utils.json().encode!(value, escape: :html_safe, pretty: true) true -> value end textarea(form, field, [value: value, rows: 4, placeholder: "JSON Content"] ++ opts) {:array, _} -> value = data |> Map.get(field, "") |> Kaffy.Utils.json().encode!(escape: :html_safe, pretty: true) textarea(form, field, [value: value, rows: 4, placeholder: "JSON Content"] ++ opts) :file -> file_input(form, field, opts) :select -> select(form, field, opts) :date -> flatpickr_date(form, field, opts) :time -> flatpickr_time(form, field, opts) :naive_datetime -> flatpickr_datetime(form, field, opts) :naive_datetime_usec -> flatpickr_datetime_usec(form, field, opts) :utc_datetime -> flatpickr_datetime(form, field, opts) :utc_datetime_usec -> flatpickr_datetime_usec(form, field, opts) _ -> text_input(form, field, opts) end end defp flatpickr_time(form, field, opts) do flatpickr_generic(form, field, opts, "Select Date...", "flatpickr-wrap-time", "🕒") end defp flatpickr_date(form, field, opts) do flatpickr_generic(form, field, opts, "Select Date...", "flatpickr-wrap-date", "🗓️") end defp flatpickr_datetime(form, field, opts) do flatpickr_generic(form, field, opts, "Select Datetime...", "flatpickr-wrap-datetime") end defp flatpickr_datetime_usec(form, field, opts) do flatpickr_generic(form, field, opts, "Select Datetime...", "flatpickr-wrap-datetime-usec") end defp flatpickr_generic(form, field, opts, placeholder, fp_class, icon \\ "📅") do opts = Keyword.put(opts, :class, "flatpickr-input") opts = Keyword.put(opts, :class, "form-control") opts = Keyword.put(opts, :id, "inlineFormInputGroup") opts = Keyword.put(opts, :placeholder, placeholder) opts = Keyword.put(opts, :"data-input", "") [ {:safe, ~s(
#{icon}
)}, text_input(form, field, opts), {:safe, "
"} ] end defp text_or_assoc(conn, schema, form, field, opts) do actual_assoc = Enum.filter(Kaffy.ResourceSchema.associations(schema), fn a -> Kaffy.ResourceSchema.association(schema, a).owner_key == field end) |> Enum.at(0) field_no_id = case actual_assoc do nil -> field _ -> Kaffy.ResourceSchema.association(schema, actual_assoc).field end case field_no_id in Kaffy.ResourceSchema.associations(schema) do true -> assoc = Kaffy.ResourceSchema.association_schema(schema, field_no_id) option_count = Kaffy.ResourceQuery.cached_total_count(assoc, true, assoc) case option_count > 100 do true -> target_context = Kaffy.Utils.get_context_for_schema(conn, assoc) target_resource = Kaffy.Utils.get_schema_key(conn, target_context, assoc) content_tag :div, class: "input-group" do [ number_input(form, field, class: "form-control", id: field, aria_describedby: field ), content_tag :div, class: "input-group-append" do content_tag :span, class: "input-group-text", id: field do link(content_tag(:i, "", class: "fas fa-search"), to: Kaffy.Utils.router().kaffy_resource_path( conn, :index, target_context, target_resource, c: conn.params["context"], r: conn.params["resource"], pick: field ), id: "pick-raw-resource" ) end end ] end false -> options = Kaffy.Utils.repo().all(assoc) fields = Kaffy.ResourceSchema.fields(assoc) string_fields = Enum.filter(fields, fn {_f, options} -> options.type == :string end) popular_strings = string_fields |> Enum.filter(fn {f, _} -> f in [:name, :title] end) |> Enum.at(0) string_field = case is_nil(popular_strings) do true -> Enum.at(string_fields, 0) |> elem(0) false -> elem(popular_strings, 0) end select( form, field, Enum.map(options, fn o -> {Map.get(o, string_field, "Resource ##{o.id}"), o.id} end), class: "custom-select" ) end false -> number_input(form, field, opts) end end def get_field_error(form, field) do case Keyword.get_values(form.errors, field) do [{msg, _}] -> error_msg = Kaffy.ResourceAdmin.humanize_term(field) <> " " <> msg <> "!" {error_msg, "is-invalid"} _ -> {nil, ""} end end def kaffy_input(conn, changeset, form, field, options) do ft = Kaffy.ResourceSchema.field_type(changeset.data.__struct__, field) case Kaffy.Utils.is_module(ft) && Keyword.has_key?(ft.__info__(:functions), :render_form) do true -> ft.render_form(conn, changeset, form, field, options) false -> {error_msg, error_class} = get_field_error(form, field) help_text = form_help_text({field, options}) content_tag :div, class: "form-group #{error_class}" do label_tag = if ft != :boolean, do: form_label(form, {field, options}), else: "" field_tag = form_field(changeset, form, {field, options}, class: "form-control #{error_class}", conn: conn ) field_feeback = [ content_tag :div, class: "invalid-feedback" do error_msg end, content_tag :p, class: "help_text" do help_text end ] [label_tag, field_tag, field_feeback] end end end end