# `MishkaGervaz.Table.Web.Events.RelationFilterHandler`
[🔗](https://github.com/mishka-group/mishka_gervaz/blob/v0.0.1-alpha.3/lib/mishka_gervaz/table/web/events/relation_filter_handler.ex#L1)

Handles relation filter events for dynamic search, load more, and multi-select.

This module manages server-side state for relation filters that need:
- Async search with database queries
- Paginated option loading
- Multi-select with label resolution

## Events Handled

- `search` - Search options by term (debounced)
- `load_more` - Load next page of options
- `focus` - Open dropdown and load initial options
- `close_dropdown` - Close dropdown on click-away
- `toggle` - Toggle selection in multi-select mode

## Public Validation Functions

- `skip_relation_search_term?/2` - Check if a value should be skipped as a filter value
- `valid_relation_value?/1` - Check if a value is a valid relation filter value (UUID or __nil__)

## Client-Side Alternative

Users with custom JS comboboxes (Tom Select, Headless UI, etc.) can bypass
these events entirely and just use the standard `"filter"` event to submit
final selected values. These events are for server-managed UI state.

## Customization

You can create a custom RelationFilterHandler:

    defmodule MyApp.CustomRelationFilterHandler do
      use MishkaGervaz.Table.Web.Events.RelationFilterHandler

      def handle("toggle", params, state, socket) do
        MyApp.Analytics.track("relation_filter_toggle", params)
        super("toggle", params, state, socket)
      end
    end

Then configure it in your resource's DSL:

    mishka_gervaz do
      table do
        events do
          relation_filter MyApp.CustomRelationFilterHandler
        end
      end
    end

See `MishkaGervaz.Table.Web.Events`,
`MishkaGervaz.Table.Web.DataLoader.RelationLoader`,
`MishkaGervaz.Table.Types.Filter.Relation`,
and the sibling handlers `SanitizationHandler`, `RecordHandler`,
`SelectionHandler`, `BulkActionHandler`, `HookRunner`.

# `params`

```elixir
@type params() :: map()
```

# `socket`

```elixir
@type socket() :: Phoenix.LiveView.Socket.t()
```

# `state`

```elixir
@type state() :: MishkaGervaz.Table.Web.State.t()
```

# `handle`

```elixir
@callback handle(
  action :: String.t(),
  params :: params(),
  state :: state(),
  socket :: socket()
) ::
  {:noreply, socket()}
```

# `skip_relation_search_term?`

```elixir
@spec skip_relation_search_term?(map(), term()) :: boolean()
```

Checks if a relation filter value should be skipped (not treated as a filter value).

For relation filters with `:search` or `:search_multi` mode, this returns `true`
if the value is not valid for the filter's ID type - meaning it's likely a search
term that should not be used as the actual filter value.

The `id_type` is determined from the related resource's primary key type:
- `:uuid` - Validates as UUID
- `:uuid_v7` - Validates as UUID (same format)
- `:integer` - Validates as integer
- `:string` - Any non-empty string is valid

## Examples

    iex> skip_relation_search_term?(%{type: :relation, mode: :search, id_type: :uuid}, "some search text")
    true

    iex> skip_relation_search_term?(%{type: :relation, mode: :search, id_type: :uuid}, "550e8400-e29b-41d4-a716-446655440000")
    false

    iex> skip_relation_search_term?(%{type: :relation, mode: :search, id_type: :integer}, "123")
    false

    iex> skip_relation_search_term?(%{type: :text}, "any value")
    false

# `valid_relation_value?`

```elixir
@spec valid_relation_value?(term(), atom() | nil) :: boolean()
```

Checks if a value is a valid relation filter value for the given ID type.

## ID Types

- `:uuid` / `:uuid_v7` - Must be a valid UUID string
- `:integer` - Must be a valid integer string
- `:string` - Any non-empty string is valid

## Special Values

- `"__nil__"` - Always valid, indicates "no selection" or "null"

## Examples

    iex> valid_relation_value?("__nil__", :uuid)
    true

    iex> valid_relation_value?("550e8400-e29b-41d4-a716-446655440000", :uuid)
    true

    iex> valid_relation_value?("123", :integer)
    true

    iex> valid_relation_value?("search text", :uuid)
    false

    iex> valid_relation_value?("any-string", :string)
    true

---

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