legendary-doc-site/apps/admin/kaffy/lib/kaffy/resource_form.ex
2020-07-27 20:28:41 +00:00

362 lines
11 KiB
Elixir

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(<div class="card ml-3" style="padding:15px;">)},
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, "</div>"}
]
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(<div class="custom-control custom-checkbox">)},
checkbox(form, field, checkbox_opts),
label(form, field, label_opts),
{:safe, "</div>"}
]
:boolean_switch ->
checkbox_opts = Keyword.put(opts, :class, "custom-control-input")
label_opts = Keyword.put(opts, :class, "custom-control-label")
[
{:safe, ~s(<div class="custom-control custom-switch">)},
checkbox(form, field, checkbox_opts),
label(form, field, label_opts),
{:safe, "</div>"}
]
: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(
<div class="input-group mb-2 flatpickr #{fp_class}">
<div class="input-group-prepend">
<div class="input-group-text" data-clear>❌</div>
</div>
<div class="input-group-prepend">
<div class="input-group-text" data-toggle>#{icon}</div>
</div>
)},
text_input(form, field, opts),
{:safe, "</div>"}
]
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