# `MishkaGervaz.Table.Web.DataLoader.RelationLoader`
[🔗](https://github.com/mishka-group/mishka_gervaz/blob/v0.0.2/lib/mishka_gervaz/table/web/data_loader/relation_loader.ex#L1)

Handles paginated loading of relation filter options.

Provides support for different loading modes:
- `:static` - Load all options at once
- `:load_more` - Initial page with load more capability
- `:search` - Search with pagination (single select)
- `:search_multi` - Search with pagination (multi select)

## A fixed list instead of a resource

A `:relation` filter with no relationship or `resource` behind it that declares `options` — a
list, or a zero-arity function returning one — is served from that list: loaded whole, searched by
label or value in memory, and its selection read back from it. It is how a list the database does not hold as rows —
the languages a site publishes — gets the searchable multi-select:

    filter :language, :relation do
      mode :search_multi
      options fn -> [{"EN", "en"}, {"FA", "fa"}] end
    end

## Usage

    # Load initial options
    {:ok, result} = RelationLoader.load_options(filter, state, page: 1)

    # Search options
    {:ok, result} = RelationLoader.search_options(filter, state, "search term", page: 1)

## Override

    defmodule MyApp.Table.RelationLoader do
      use MishkaGervaz.Table.Web.DataLoader.RelationLoader

      def load_options(filter, state, opts) do
        # Custom loading logic
        super(filter, state, opts)
      end
    end

See `MishkaGervaz.Table.Web.DataLoader`,
`MishkaGervaz.Table.Web.DataLoader.Helpers`,
`MishkaGervaz.Table.Types.Filter.Relation`,
and the sibling sub-builders `QueryBuilder`, `FilterParser`,
`PaginationHandler`, `TenantResolver`, `HookRunner`.

# `filter`

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

# `load_result`

```elixir
@type load_result() :: %{
  options: [{String.t(), any()}],
  page: pos_integer(),
  has_more?: boolean(),
  total_count: pos_integer() | nil
}
```

# `state`

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

# `load_more_options`

```elixir
@callback load_more_options(filter(), state(), keyword()) ::
  {:ok, load_result()} | {:error, term()}
```

# `load_options`

```elixir
@callback load_options(filter(), state(), keyword()) ::
  {:ok, load_result()} | {:error, term()}
```

# `load_with_selected`

```elixir
@callback load_with_selected(filter(), state(), list(), keyword()) ::
  {:ok, load_result()} | {:error, term()}
```

# `resolve_selected`

```elixir
@callback resolve_selected(filter(), state(), list()) ::
  {:ok, [{String.t(), any()}]} | {:error, term()}
```

# `search_options`

```elixir
@callback search_options(filter(), state(), String.t(), keyword()) ::
  {:ok, load_result()} | {:error, term()}
```

# `static_options`

```elixir
@spec static_options(map(), map()) :: [{String.t(), any()}] | nil
```

The filter's own option list, normalized to `{label, value}` — or `nil` when a relationship or a
`resource` supplies its options. A relation filter's `options` alone says nothing: the table fills
it with the options it loaded from the relationship.

# `static_page`

```elixir
@spec static_page(map(), [{String.t(), any()}], String.t() | nil) :: map()
```

A page of a static option list: all of it, or the options whose label or value contains `term`,
ignoring case.

---

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