# `AshPhoenix.FilterForm`
[🔗](https://github.com/ash-project/ash_phoenix/blob/v2.3.21/lib/ash_phoenix/filter_form/filter_form.ex#L5)

A module to help you create complex forms that generate Ash filters.

```elixir
# Create a FilterForm
filter_form = AshPhoenix.FilterForm.new(MyApp.Payroll.Employee)
```

FilterForm's comprise 2 concepts, predicates and groups. Predicates are the simple boolean
expressions you can use to build a query (`name == "Joe"`), and groups can be used to group
predicates and more groups together. Groups can apply `and` or `or` operators to its nested
components.

```elixir
# Add a predicate to the root of the form (which is itself a group)
filter_form = AshPhoenix.FilterForm.add_predicate(filter_form, :some_field, :eq, "Some Value")

# Add a group and another predicate to that group
{filter_form, group_id} = AshPhoenix.FilterForm.add_group(filter_form, operator: :or, return_id?: true)
filter_form = AshPhoenix.FilterForm.add_predicate(filter_form, :another, :eq, "Other", to: group_id)
```

`validate/1` is used to merge the submitted form params into the filter form, and one of the
provided filter functions to apply the filter as a query, or generate an expression map,
depending on your requirements:

```elixir
filter_form = AshPhoenix.FilterForm.validate(socket.assigns.filter_form, params)

# Generate a query and pass it to the Domain
query = AshPhoenix.FilterForm.filter!(MyApp.Payroll.Employee, filter_form)
filtered_employees = MyApp.Payroll.read!(query)

# Or use one of the other filter functions
AshPhoenix.FilterForm.to_filter_expression(filter_form)
AshPhoenix.FilterForm.to_filter_map(filter_form)
```

## LiveView Example

You can build a form and handle adding and removing nested groups and predicates with the following:

```elixir
alias MyApp.Payroll.Employee

@impl true
def render(assigns) do
  ~H"""
  <.simple_form
    :let={filter_form}
    for={@filter_form}
    phx-change="filter_validate"
    phx-submit="filter_submit"
  >
    <.filter_form_component component={filter_form} />
    <:actions>
      <.button>Submit</.button>
    </:actions>
  </.simple_form>
  <.table id="employees" rows={@employees}>
    <:col :let={employee} label="Payroll ID"><%= employee.employee_id %></:col>
    <:col :let={employee} label="Name"><%= employee.name %></:col>
    <:col :let={employee} label="Position"><%= employee.position %></:col>
  </.table>
  """
end

attr :component, :map, required: true, doc: "Could be a FilterForm (group) or a Predicate"

defp filter_form_component(%{component: %{source: %AshPhoenix.FilterForm{}}} = assigns) do
  ~H"""
  <div class="border-gray-50 border-8 p-4 rounded-xl mt-4">
    <div class="flex flex-row justify-between">
      <div class="flex flex-row gap-2 items-center">Filter</div>
      <div class="flex flex-row gap-2 items-center">
        <.input type="select" field={@component[:operator]} options={["and", "or"]} />
        <.button phx-click="add_filter_group" phx-value-component-id={@component.source.id} type="button">
          Add Group
        </.button>
        <.button
          phx-click="add_filter_predicate"
          phx-value-component-id={@component.source.id}
          type="button"
        >
          Add Predicate
        </.button>
        <.button
          phx-click="remove_filter_component"
          phx-value-component-id={@component.source.id}
          type="button"
        >
          Remove Group
        </.button>
      </div>
    </div>
    <.inputs_for :let={component} field={@component[:components]}>
      <.filter_form_component component={component} />
    </.inputs_for>
  </div>
  """
end

defp filter_form_component(
       %{component: %{source: %AshPhoenix.FilterForm.Predicate{}}} = assigns
     ) do
  ~H"""
  <div class="flex flex-row gap-2 mt-4">
    <.input
      type="select"
      options={AshPhoenix.FilterForm.fields(Employee)}
      field={@component[:field]}
    />
    <.input
      type="select"
      options={AshPhoenix.FilterForm.predicates(Employee)}
      field={@component[:operator]}
    />
    <.input field={@component[:value]} />
    <.button
      phx-click="remove_filter_component"
      phx-value-component-id={@component.source.id}
      type="button"
    >
      Remove
    </.button>
  </div>
  """
end

@impl true
def mount(_params, _session, socket) do
  socket =
    socket
    |> assign(:filter_form, AshPhoenix.FilterForm.new(Employee))
    |> assign(:employees, Employee.read_all!())

  {:ok, socket}
end

@impl true
def handle_event("filter_validate", %{"filter" => params}, socket) do
  {:noreply,
   assign(socket,
     filter_form: AshPhoenix.FilterForm.validate(socket.assigns.filter_form, params)
   )}
end

def handle_event("filter_submit", %{"filter" => params}, socket) do
  filter_form = AshPhoenix.FilterForm.validate(socket.assigns.filter_form, params)

  case AshPhoenix.FilterForm.filter(Employee, filter_form) do
    {:ok, query} ->
      {:noreply,
       socket
       |> assign(:employees, Employee.read_all!(query: query))
       |> assign(:filter_form, filter_form)}

    {:error, filter_form} ->
      {:noreply, assign(socket, filter_form: filter_form)}
  end
end

def handle_event("remove_filter_component", %{"component-id" => component_id}, socket) do
  {:noreply,
   assign(socket,
     filter_form:
       AshPhoenix.FilterForm.remove_component(socket.assigns.filter_form, component_id)
   )}
end

def handle_event("add_filter_group", %{"component-id" => component_id}, socket) do
  {:noreply,
   assign(socket,
     filter_form: AshPhoenix.FilterForm.add_group(socket.assigns.filter_form, to: component_id)
   )}
end

def handle_event("add_filter_predicate", %{"component-id" => component_id}, socket) do
  {:noreply,
   assign(socket,
     filter_form:
       AshPhoenix.FilterForm.add_predicate(socket.assigns.filter_form, :name, :contains, nil,
         to: component_id
       )
   )}
end
```

# `add_group`

Add a group to the filter. A group can contain predicates and other groups,
allowing you to build quite complex nested filters.

Options:

* `:to` (`t:String.t/0`) - The nested group id to add the group to.

* `:operator` - The operator that the group should have internally. Valid values are :and, :or The default value is `:and`.

* `:return_id?` (`t:boolean/0`) - If set to `true`, the function returns `{form, predicate_id}` The default value is `false`.

# `add_predicate`

Add a predicate to the filter.

Options:

* `:to` (`t:String.t/0`) - The group id to add the predicate to. If not set, will be added to the top level group.

* `:return_id?` (`t:boolean/0`) - If set to `true`, the function returns `{form, predicate_id}` The default value is `false`.

* `:path` - The relationship path to apply the predicate to

# `errors`

Returns a flat list of all errors on all predicates in the filter, made safe for display in a form.

Only errors that implement the `AshPhoenix.FormData.Error` protocol are displayed.

# `fields`

Returns the list of available fields, which may be attributes, calculations, or aggregates.

# `filter`

Converts the form into a filter, and filters the provided query or resource with that filter.

# `filter!`

Same as `filter/2` but raises on errors.

# `new`

Create a new filter form.

Options:
* `:params` (`t:term/0`) - Initial parameters to create the form with The default value is `%{}`.

* `:as` (`t:String.t/0`) - Set the parameter name for the form. The default value is `"filter"`.

* `:transform_errors` (`t:term/0`) - Allows for manual manipulation and transformation of errors.  
  If possible, try to implement `AshPhoenix.FormData.Error` for the error (if it as a custom one, for example).
  If that isn't possible, you can provide this function which will get the predicate and the error, and should
  return a list of ash phoenix formatted errors, e.g `[{field :: atom, message :: String.t(), substituations :: Keyword.t()}]`

* `:warn_on_unhandled_errors?` (`t:boolean/0`) - Whether or not to emit warning log on unhandled form errors The default value is `true`.

* `:remove_empty_groups?` (`t:boolean/0`) - If true (the default), then any time a group would be made empty by removing a group or predicate, it is removed instead.  
  An empty form can still be added, this only affects a group if its last component is removed. The default value is `false`.

# `params_for_query`

Returns the minimal set of params (at the moment just strips ids) for use in a query string.

# `predicates`

Returns the list of available predicates for the given resource, which may be functions or operators.

# `raw_errors`

Returns a flat list of all errors on all predicates in the filter, without transforming.

# `remove_component`

Removes the group *or* predicate with the given id

# `remove_group`

Remove the group with the given id

# `remove_predicate`

Remove the predicate with the given id

# `to_filter!`

> This function is deprecated. Use to_filter_expression!/1 instead.

# `to_filter_expression`

Returns a filter expression that can be provided to Ash.Query.filter/2

To add this to a query, remember to use `^`, for example:
```elixir
filter = AshPhoenix.FilterForm.to_filter_expression(form)

Ash.Query.filter(MyApp.Post, ^filter)
```

Alternatively, you can use the shorthand: `filter/2` to apply the expression directly to a query.

# `to_filter_expression!`

Same as `to_filter_expression/1` but raises on errors.

# `to_filter_map`

Returns a filter map that can be provided to `Ash.Filter.parse`

This allows for things like saving a stored filter. Does not currently support parameterizing calculations or functions.

# `update_predicate`

Update the predicate with the given id

# `validate`

Updates the filter with the provided input and validates it.

At present, no validation actually occurs, but this will eventually be added.

Passing `reset_on_change?: false` into `opts` will prevent predicates to reset
the `value` and `operator` fields to `nil` if the predicate `field` changes.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
