MishkaGervaz.Table.Web.UrlSync (MishkaGervaz v0.0.2)

Copy Markdown View Source

URL state synchronization for bookmarkable table views.

Syncs table state (filters, sort, page, search) to URL query params, allowing users to bookmark or share specific table views.

Configuration

Enable URL sync in domain table config:

mishka_gervaz do
  table do
    url_sync do
      enabled true
      params [:filters, :sort, :page]
      prefix "table"
    end
  end
end

Usage in LiveView

In your parent LiveView, handle URL params:

def handle_params(params, _uri, socket) do
  url_state = MishkaGervaz.Table.Web.UrlSync.decode(params, "table")

  {:noreply,
    socket
    |> assign(:url_state, url_state)}
end

Pass to the component:

<.live_component
  module={MishkaGervaz.Table.Web.Live}
  id="posts-table"
  resource={MyApp.Post}
  url_state={@url_state}
/>

Param Format

  • Filters: ?table_filter_status=active&table_filter_category=1
  • Sort: ?table_sort=name:asc or ?table_sort=inserted_at:desc
  • Page: ?table_page=2
  • Search: ?table_search=hello
  • Template: ?table_template=grid

See MishkaGervaz.Table.Web.State, MishkaGervaz.Table.Web.State.UrlSync (the in-state sub-builder), MishkaGervaz.Table.Dsl.UrlSync (resource-level DSL section), MishkaGervaz.Table.Dsl.Defaults (domain-level defaults).

Summary

Functions

Apply URL state to existing table state.

Build URL path with encoded state params.

decode/3 with the resource's own url_sync configuration overridden.

Check if URL sync is enabled in config.

Check if url_state matches current table state.

Whether url_state was decoded from url: the same path and the same query params, in any order.

Types

decode_opts()

@type decode_opts() :: [
  allowed_params: [atom()],
  allowed_filters: [atom()],
  max_filter_length: non_neg_integer()
]

url_state()

@type url_state() :: %{
  filters: map(),
  sort: [{atom(), :asc | :desc}],
  page: integer(),
  page_size: pos_integer() | nil,
  search: String.t() | nil,
  template: atom() | nil,
  path: String.t() | nil,
  path_params: map(),
  preserved_params: map(),
  query: %{required(String.t()) => String.t()} | nil
}

Functions

apply_url_state(state, url_state)

@spec apply_url_state(map(), url_state()) :: map()

Apply URL state to existing table state.

Merges decoded URL state with default table state.

build_path(base_path, state, config)

@spec build_path(String.t(), map(), map()) :: String.t()

Build URL path with encoded state params.

Takes current path and state, returns path with query string.

decode(params, uri, resource, opts)

@spec decode(map(), String.t(), module(), decode_opts()) :: url_state() | nil

decode/3 with the resource's own url_sync configuration overridden.

Every option defaults to what the resource declared, so pass only what differs:

  • :allowed_params — which of :filters :sort :page :page_size :search :template to read
  • :allowed_filters — the filter names that may arrive from the URL
  • :max_filter_length — values longer than this are ignored
  • :prefix — the param prefix

Returns nil when the resource has URL sync disabled, exactly as decode/3 does.

UrlSync.decode(params, uri, MyApp.Blog.Post, allowed_filters: [:status])

enabled?(config)

@spec enabled?(map()) :: boolean()

Check if URL sync is enabled in config.

encode(state, config \\ %{enabled: false, params: [:filters, :sort, :page], prefix: nil})

@spec encode(map(), map()) :: map()

Encode table state to URL query params.

Examples

iex> state = %{filters: %{status: "active"}, sort: [{:name, :asc}], page: 2}
iex> UrlSync.encode(state, %{params: [:filters, :sort, :page], prefix: "t"})
%{"t_filter_status" => "active", "t_sort" => "name:asc", "t_page" => "2"}

matches_state?(url_state, state)

@spec matches_state?(url_state() | nil, map()) :: boolean()

Check if url_state matches current table state.

Used to detect if incoming url_state is from our own push_patch (to avoid duplicate data reload).

same_url?(arg1, url)

@spec same_url?(url_state() | nil, String.t()) :: boolean()

Whether url_state was decoded from url: the same path and the same query params, in any order.

UrlSync.same_url?(UrlSync.decode(params, "/posts?sort=name:asc", MyApp.Post), "/posts?sort=name%3Aasc")
#=> true

A url_state decoded without a URI has no :query, and is never the same.