` element."
attr :opts, :list,
default: [],
doc: """
Keyword list with additional options (see `t:Flop.Phoenix.table_option/0`).
Note that the options passed to the function are deep merged into the
default options. Since these options will likely be the same for all the
tables in a project, it is recommended to define them once in a function or
set them in a wrapper function as described in the `Customization` section
of the module documentation.
"""
slot :col,
required: true,
doc: """
For each column to render, add one `<:col>` element.
```elixir
<:col :let={pet} label="Name" field={:name} col_style="width: 20%;">
<%= pet.name %>
```
Any additional assigns will be added as attributes to the `` elements.
""" do
attr :label, :string, doc: "The content for the header column."
attr :field, :atom, doc: "The field name for sorting."
attr :show, :boolean,
doc: "Boolean value to conditionally show the column. Defaults to `true`."
attr :hide, :boolean,
doc:
"Boolean value to conditionally hide the column. Defaults to `false`."
attr :col_style, :string,
doc: """
If set, a `` element is rendered and the value of the
`col_style` assign is set as `style` attribute for the `` element of
the respective column. You can set the `width`, `background` and `border`
of a column this way.
"""
attr :rest, :global,
doc: """
Any additional attributes to pass to the ``.
"""
end
slot :foot,
default: nil,
doc: """
You can optionally add a `foot`. The inner block will be rendered inside
a `tfoot` element.
<:foot>
| Total: <%= @total %> |
"""
def table(assigns) do
assigns = Table.init_assigns(assigns)
~H"""
<%= if @items == [] do %>
<%= @opts[:no_results_content] %>
<% else %>
<%= if @opts[:container] do %>
<% else %>
<% end %>
<% end %>
"""
end
@doc """
Renders all inputs for a filter form including the hidden inputs.
If you need more control, you can use `filter_input/1` and `filter_label/1`
directly.
## Example
<.form :let={f} for={@meta}>
<.filter_fields :let={entry} form={f} fields={[:email, :name]}>
<%= entry.label %>
<%= entry.input %>
## Field configuration
The fields can be passed as atoms or keywords with additional options.
fields={[:name, :email]}
Or
fields={[
name: [label: gettext("Name")],
email: [
label: gettext("Email"),
op: :ilike_and,
type: :email_input
]
]}
Options:
- `label`
- `op`
- `type`
- `default`
The value under the `:type` key matches the format used in `filter_input/1`.
Any additional options will be passed to the input function
(e.g. HTML classes or a list of options).
## Label and input opts
You can set default attributes for all labels and inputs:
<.filter_fields
:let={e}
form={f}
fields={[:name]}
input_opts={[class: "input", phx_debounce: 100]}
label_opts={[class: "label"]}
>
The additional options in the type configuration are merged into the input
opts. This means you can set a default class and override it for individual
fields.
<.filter_fields
:let={e}
form={f}
fields={[
:name,
:email,
role: [type: {:select, ["author", "editor"], class: "select"}]
]}
input_opts={[class: "input"]}
>
"""
@doc since: "0.12.0"
@doc section: :components
@spec filter_fields(map) :: Phoenix.LiveView.Rendered.t()
attr :form, Phoenix.HTML.Form, required: true
attr :fields, :list,
default: [],
doc: """
The list of fields and field options. Note that inputs will not be rendered
for fields that are not marked as filterable in the schema
(see `Flop.Schema`).
If `dynamic` is set to `false`, only fields in this list are rendered. If
`dynamic` is set to `true`, only fields for filters present in the given
`Flop.Meta` struct are rendered, and the fields are rendered even if they
are not passed in the `fields` list. In the latter case, `fields` is
optional, but you can still pass label and input configuration this way.
Note that in a dynamic form, it is not possible to configure a single field
multiple times.
"""
attr :dynamic, :boolean,
default: false,
doc: """
If `true`, fields are only rendered for filters that are present in the
`Flop.Meta` struct passed to the form. You can use this for rendering filter
forms that allow the user to add and remove filters dynamically. The
`fields` assign is only used for looking up the options in that case.
"""
attr :id, :string,
default: nil,
doc: "Overrides the ID for the nested filter inputs."
attr :input_opts, :list,
default: [],
doc: "Additional options passed to each input."
attr :label_opts, :list,
default: [],
doc: "Additional options passed to each label."
slot :inner_block,
doc: """
The generated labels and inputs are passed to the inner block instead of being
automatically rendered. This allows you to customize the markup.
<.filter_fields :let={e} form={f} fields={[:email, :name]}>
<%= e.label %>
<%= e.input %>
"""
def filter_fields(assigns) do
is_meta_form!(assigns.form)
fields = normalize_filter_fields(assigns[:fields] || [])
field_opts = match_field_opts(assigns, fields)
inputs_for_fields = if assigns[:dynamic], do: nil, else: fields
assigns =
assigns
|> assign(:fields, inputs_for_fields)
|> assign(:field_opts, field_opts)
~H"""
<%= filter_hidden_inputs_for(@form) %>
<%= for {ff, {field, field_opts}} <- inputs_for_filters(@form, @fields, @field_opts, @id) do %>
<%= render_slot(@inner_block, %{
label:
~H"<.filter_label form={ff} texts={[{field, field_opts[:label]}]} {@label_opts} />",
input:
~H"<.filter_input form={ff} types={[{field, field_opts[:type]}]} input_opts={@input_opts} />"
}) %>
<% end %>
"""
end
defp inputs_for_filters(form, fields, field_opts, id) do
form
|> inputs_for(:filters, fields: fields, id: id)
|> Enum.zip(field_opts)
end
defp normalize_filter_fields(fields) do
Enum.map(fields, fn
field when is_atom(field) ->
{field, []}
{field, opts} when is_atom(field) and is_list(opts) ->
{field, opts}
field ->
raise """
Invalid filter field config
Filters fields must be passed as a list of atoms or {atom, keyword} tuples.
Got:
#{inspect(field)}
"""
end)
end
defp match_field_opts(%{dynamic: true, form: form}, fields) do
Enum.map(form.data.filters, fn %Flop.Filter{field: field} ->
{field, fields[field] || []}
end)
end
defp match_field_opts(_, fields) do
fields
end
@doc """
Renders a label for the `:value` field of a filter.
This function must be used within the `Phoenix.HTML.Form.inputs_for/2`,
`Phoenix.HTML.Form.inputs_for/3` or `Phoenix.HTML.Form.inputs_for/4` block of
the filter form.
Note that `inputs_for` will not render inputs for fields that are not marked
as filterable in the schema, even if passed in the options.
## Example
<.form :let={f} for={@meta}>
<%= filter_hidden_inputs_for(f) %>
<%= for ff <- inputs_for(f, :filters, fields: [:email]) do %>
<.filter_label form={ff} />
<.filter_input form={ff} />
<% end %>
`Flop.Phoenix.filter_hidden_inputs_for/1` is necessary because
`Phoenix.HTML.Form.hidden_inputs_for/1` does not support lists in versions
<= 3.1.0.
## Label text
By default, the label text is inferred from the value of the `:field` key of
the filter. You can override the default type by passing a keyword list or a
function that maps fields to label texts.
<.filter_label form={ff} text={[
email: gettext("Email")
phone: gettext("Phone number")
]} />
Or
<.filter_label form={ff} text={
fn
:email -> gettext("Email")
:phone -> gettext("Phone number")
end
} />
"""
@doc since: "0.12.0"
@doc section: :components
@spec filter_label(map) :: Phoenix.LiveView.Rendered.t()
attr :form, Phoenix.HTML.Form, required: true
attr :texts, :any,
default: nil,
doc: """
Either a function or a keyword list for setting the label text depending on
the field.
"""
attr :rest, :global,
doc: "Additional attributes to be added to the ` | |