# `MishkaGervaz.Table.Web.State.Helpers`
[🔗](https://github.com/mishka-group/mishka_gervaz/blob/v0.0.2/lib/mishka_gervaz/table/web/state/_helpers.ex#L1)

Helper functions for `MishkaGervaz.Table.Web.State`.

Extracted from the `__using__` macro so user overrides can reuse the
same primitives the default state implementation uses.

## Example

    defmodule MyApp.Table.State do
      use MishkaGervaz.Table.Web.State
      alias MishkaGervaz.Table.Web.State.Helpers, as: StateHelpers

      def hydrate_relation_filter_labels(state) do
        StateHelpers.hydrate_filter(filter, acc, state)
      end
    end

See `MishkaGervaz.Table.Web.State`,
`MishkaGervaz.Table.Web.State.ColumnBuilder`,
`MishkaGervaz.Table.Web.State.FilterBuilder`,
`MishkaGervaz.Table.Web.State.ActionBuilder`,
`MishkaGervaz.Table.Web.State.Presentation`,
`MishkaGervaz.Table.Web.State.UrlSync`,
`MishkaGervaz.Table.Web.State.Access`.

# `build_sort_field_map`

```elixir
@spec build_sort_field_map([map()]) :: %{required(atom()) =&gt; [atom()]}
```

Maps each sortable column to the database fields it sorts by.

A column falls back to its own name unless it declared `sort_field`, which is what lets a
`static true` column sort by something it does not display.

    iex> build_sort_field_map([%{name: :site_datetime, sortable: true, sort_field: [:inserted_at]}])
    %{site_datetime: [:inserted_at]}

# `extract_selected_ids`

```elixir
@spec extract_selected_ids(map(), atom()) :: [String.t()]
```

The selected ids for one filter, as strings, with blanks removed.

Accepts a single value or a list, so a `:search` and a `:search_multi` filter read the same.

    iex> extract_selected_ids(%{site_id: ["a", "", nil]}, :site_id)
    ["a"]

# `generate_stream_name`

```elixir
@spec generate_stream_name(module()) :: atom()
```

The stream name derived from a resource module when `identity do stream_name … end` is
omitted.

    iex> generate_stream_name(MyApp.Blog.Post)
    :post_stream

# `get_archive_visible`

```elixir
@spec get_archive_visible(map()) :: boolean()
```

The archive block's `visible` flag, defaulting to `true` when it is not a plain boolean —
a function predicate is evaluated later, against live state.

# `get_default_sort`

```elixir
@spec get_default_sort(map(), [map()]) :: [{atom(), :asc | :desc}]
```

The sort a table opens with.

The resource's `default_sort` when it declared a non-empty list; otherwise newest-first if
there is a sortable `:inserted_at` column; otherwise no sort at all.

    iex> get_default_sort(%{columns: %{default_sort: [featured: :desc]}}, [])
    [featured: :desc]

# `get_features`

```elixir
@spec get_features(map(), module()) :: [atom()]
```

The feature list this table runs with.

An explicit `features [...]` is taken as given. `:all` (the default) means the union of what
the current template and every switchable template support, so switching views never asks
for a feature the new template cannot draw. `:paginate` is dropped when the resource has no
pagination configured.

    iex> get_features(%{presentation: %{features: [:sort, :filter]}}, MyTemplate)
    [:sort, :filter]

# `get_filter_groups`

```elixir
@spec get_filter_groups(map()) :: [map()]
```

The `filter_groups` declared on the resource, or `[]`.

# `get_filter_mode`

```elixir
@spec get_filter_mode(map()) :: atom()
```

Where the filter bar renders — `:inline` (the default), `:sidebar`, `:modal` or `:drawer`.

# `get_layout_footer`

```elixir
@spec get_layout_footer(map()) :: map() | nil
```

The table's `layout do footer … end` map, or `nil` when the resource declares none.

# `get_layout_header`

```elixir
@spec get_layout_header(map()) :: map() | nil
```

The table's `layout do header … end` map, or `nil` when the resource declares none.

# `get_layout_notices`

```elixir
@spec get_layout_notices(map()) :: [map()]
```

Every `layout do notice … end` declared on the resource, in declaration order.

# `get_max_page_size`

```elixir
@spec get_max_page_size(map()) :: pos_integer() | nil
```

The ceiling a page size is clamped to — the guard against an oversized `?t_page_size=` in
the URL.

# `get_page_size`

```elixir
@spec get_page_size(map()) :: pos_integer() | nil
```

The configured page size, or `nil` when pagination is off.

# `get_page_size_options`

```elixir
@spec get_page_size_options(map()) :: [pos_integer()] | nil
```

The page-size choices offered in the UI, or `nil` when no dropdown should be drawn.

# `get_pagination_ui`

```elixir
@spec get_pagination_ui(map()) :: struct()
```

The pagination labels as a `MishkaGervaz.Table.Entities.Pagination.Ui` struct.

Always a struct: a resource that configured nothing gets one with the defaults, so a
template can read `ui.load_more_label` without checking for `nil` first.

# `get_sortable_columns`

```elixir
@spec get_sortable_columns([map()]) :: [atom()]
```

The names of the columns declared `sortable true`.

    iex> get_sortable_columns([%{name: :title, sortable: true}, %{name: :body, sortable: false}])
    [:title]

# `get_supports_archive`

```elixir
@spec get_supports_archive(map(), boolean()) :: boolean()
```

Whether this reader may see the archive toggle.

`false` unless the resource enables archive; an `archive do restricted true end` narrows it
further to master users only.

# `hydrate_filter`

```elixir
@spec hydrate_filter(map(), map(), MishkaGervaz.Table.Web.State.t()) :: map()
```

Resolves the labels for one relation filter's currently selected ids and stores them under
the filter's name.

Returns `acc` unchanged when the filter has no selection, which is what makes it safe to
fold over every filter.

    Enum.reduce(state.static.filters, %{}, &StateHelpers.hydrate_filter(&1, &2, state))

# `resolve_access`

```elixir
@spec resolve_access(module() | nil, module()) :: module()
```

The access module for a resource — its `state do access … end` override, or the default
passed in.

# `resolve_and_store_labels`

```elixir
@spec resolve_and_store_labels(
  map(),
  map(),
  [String.t()],
  MishkaGervaz.Table.Web.State.t()
) :: map()
```

Asks the resource's relation loader for the records behind `selected_ids` and writes them
into `acc` as that filter's `:selected_options`.

The entry is shaped like a loaded page with an empty option list — the dropdown fills the
options itself on first open; only the labels of what is already selected are needed here.

# `resolve_relation_loader`

```elixir
@spec resolve_relation_loader(module()) :: module()
```

The relation loader a resource uses — its `data_loader do relation … end` override, or
`MishkaGervaz.Table.Web.DataLoader.RelationLoader.Default`.

    loader = resolve_relation_loader(MyApp.Blog.Post)
    loader.resolve_selected(filter_map, state, ["1", "2"])

# `resolve_url_sync`

```elixir
@spec resolve_url_sync(module() | nil, module()) :: module()
```

The URL-sync module for a resource — its `state do url_sync … end` override, or the default
passed in.

---

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