362 lines
11 KiB
Elixir
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
|