Cartograph.Component (Cartograph v0.2.0)
View SourceThis module provides functions for relative query patching and navigation event dispatching in templates.
Examples
Linkable Breadcrumbs
use Phoenix.Component
import Cartograph.Component, only: [parse_patch: 2]
attr :trail, :list, required: true
attr :curr_uri, :string, required: true
attr :id, :string, required: true
def breadcrumbs(assigns) do
~H"""
<div id={@id}>
<.link patch={
parse_patch(@curr_uri, query: [remove: Enum.map(@trail, fn {p, _} -> p end)])
}>
Top
</.link>
<span> ></span>
<%= for {{query_param, display_label}, idx} <- Enum.with_index(@trail) do %>
<span>
<.link patch={
parse_patch(@curr_uri,
query: [
remove: Enum.slice(@trail, (idx + 1)..-1//1) |> Enum.map(fn {p, _} -> p end)
]
)
}>
{display_label}
</.link>
<span> ></span>
</span>
<% end %>
</div>
"""
endOld-School Pagination Widget
use Phoenix.Component
import Cartograph.Component, only: [cartograph_patch: 1]
defp compute_prev_page(page_no, page_count) do
if page_no == 1 do
page_count
else
page_no - 1
end
end
defp compute_next_page(page_no, page_count) do
if page_no == page_count do
1
else
page_no + 1
end
end
attr :page_no, :integer, required: true
attr :page_count, :integer, required: true
attr :id, :string, required: true
def pagination(assigns) do
~H"""
<div id={@id}>
<div>
<button phx-click={
cartograph_patch(
query: [merge: %{page_no: compute_prev_page(@page_no, @page_count)}]
)
}>
Prev
</button>
<p>Page {@page_no} of {@page_count}</p>
<button phx-click={
cartograph_patch(
query: [merge: %{page_no: compute_next_page(@page_no, @page_count)}]
)
}>
Next
</button>
</div>
<div>
<p>Jump to:</p>
<input
type="text"
pattern="+"
value={@page_no}
phx-keydown={cartograph_patch(query: [merge: %{page_no: :phx_value}])}
phx-key="Enter"
/>
</div>
</div>
"""
endSimple Stateful Selectable
use Phoenix.Component
import Cartograph.Component, only: [cartograph_patch: 1]
attr :display_label, :string, required: true
attr :choices, :list, required: true
attr :selected, :string, required: true
attr :query_param, :string, required: true
attr :id, :string, required: true
def generic_select(assigns) do
~H"""
<label for={@id}>{@display_label}</label>
<select id={@id}>
<option value="" phx-click={cartograph_patch(query: [remove: [@query_param]])}>
No Selection
</option>
<%= for {value, display_text} <- @choices do %>
<option
value={value}
selected={@selected == value}
phx-click={cartograph_patch(query: [merge: %{@query_param => value}])}
>
{display_text}
</option>
<% end %>
</select>
"""
endStateful Column Sort Button
Cycles through no sort > ascending > descending > no sort
use Phoenix.Component
import Cartograph.Component, only: [cartograph_patch: 1]
defp sort_toggle(field_name, :asc = _curr_sort_order) do
ops = [merge: %{"sort[]" => %{field_name <> "-asc" => field_name <> "-desc"}}]
cartograph_patch(query: ops)
end
defp sort_toggle(field_name, :desc = _curr_sort_order) do
cartograph_patch(query: [remove: %{"sort[]" => field_name <> "-desc"}])
end
defp sort_toggle(field_name, nil = _curr_sort_order) do
cartograph_patch(query: [add: %{"sort[]" => field_name <> "-asc"}])
end
attr :display_text, :string, required: true
attr :field_name, :string, required: true
attr :sort_order, :atom, default: nil
attr :sort_idx, :integer, default: nil
def sort_button(assigns) do
~H"""
<button class="common-btn" phx-click={sort_toggle(@field_name, @sort_order)}>
<span>{@display_text}</span>
<Heroicons.icon :if={@sort_order == :desc} name="arrow-down" class="inline-icon" />
<Heroicons.icon :if={@sort_order == :asc} name="arrow-up" class="inline-icon" />
<span if={@sort_order != nil} class="text-sm">{@sort_idx}</span>
</button>
"""
end
Summary
Types
A keyword list representing the query patching operations to apply to an existing URI.
Functions
Push a live navigation event to the server with the href computed by relative query parsing.
Push a live patch event to the server with the href computed by relative query parsing.
Parses a new URI suitable for use in a live navigation operation from the provided uri and opts.
Parses a new path suitable for use in a live patch operation from the provided uri and opts.
Types
@type query_opts() :: Keyword.t()
A keyword list representing the query patching operations to apply to an existing URI.
Unless otherwise specified in the specific option section, the values of the keyword items should be maps. Keys and values of the maps can be atoms or strings. Values can also be lists of atoms or strings in which case, the resulting query string will have one ocurrence of that key for each value in the list.
The operations are applied cumulatively in the order the keywords are provided, so the behavior of any arbitrary combination of operations is well-defined.
Providing a list as a value in the map will set multiple values for that key in the resulting query string.
For example, given the map %{selected_role: [:admin, :member]}, this would be parsed out to: ?selected_role=admin&selected_role=member in the resulting query string. The exact semantics for how these values get applied depends on the operation being used.
Valid Options
:set- replaces the whole query relative to the current document with the key-value pairs provided.- Example with single-values:
- base query:
?foo=bar - operation:
query: [set: %{bar: :baz}] - result:
?bar=baz
- base query:
- Example with multi-values:
- base query:
?foo=bar - operation:
query: [set: %{bar: [:baz, :qux]}] - result:
?bar=baz&bar=qux
- base query:
- Example with single-values:
:add- appends the provided key-value pairs into the query string without checking for existing keys. This can create duplicates, which is needed for array-valued params.- Example with single-values:
- base query:
?foo=bar - operation:
query: [add: %{foo: :baz, bar: :baz}] - result:
?foo=bar&foo=baz&bar=baz
- base query:
- Example with multi-values:
- base query:
?foo=bar - operation:
query: [add: %{foo: [:baz, :qux], bar: :baz}] - result:
?foo=bar&foo=baz&foo=qux&bar=baz
- base query:
- Example with single-values:
:merge- the same as:addbut replaces existing keys instead of appending duplicates.This option allows specifying a map as the value of a key in the top-level key-value map. If a regular scalar or list is provided as the value of the key, then all ocurrences of that key are removed and the provided values are added at the end of the query string. If a map is provided as the value for a key, it is used to match values to replace in-place for the corresponding key, so only params with matching values are removed and order of params is preserved.
- Example with single-values:
- base query:
?foo=bar - operation:
query: [merge: %{foo: :baz, bar: :baz}] - result:
?foo=baz&bar=baz
- base query:
- Example with multi-values:
- base query:
?foo=bar&bar=baz - operation:
query: [merge: %{foo: [:baz, :qux], lorem: :ipsum}] - result:
?bar=baz&foo=baz&foo=qux&lorem=ipsum
- base query:
- Example with nested map-values:
- base query:
?foo=bar&foo=baz&foo=spam&bar=baz - operation:
query: [merge: %{foo: %{"spam" => :eggs, baz: "qux"}}] - result:
?foo=bar&foo=qux&foo=eggs&bar=baz
- base query:
- Example with single-values:
:remove- The opposite of:add. Removes matching keys.This option allows passing a list of keys instead of a map of key-value pairs. If a list of keys is provided, all ocurrences of the matched keys will be removed from the query string regardless of their value. If a map is provided, only the ocurrences of each key that have a matching value will be removed.
- Example with keys only:
- base query:
?foo=bar&foo=baz&bar=baz - operation:
query: [remove: [:foo]] - result:
?bar=baz
- base query:
- Example with map single-values:
- base query:
?foo=bar&foo=baz&bar=baz - operation:
query: [remove: %{foo: :baz}] - result:
?foo=bar&bar=baz
- base query:
- Example with map multi-values:
- base query:
?foo=bar&foo=baz&foo=qux&bar=baz - operation:
query: [remove: %{foo: [:baz, :qux]}] - result:
?foo=bar&bar=baz
- base query:
- Example with keys only:
:toggle- toggles the provided key-value pairs into or out of the query string.This operation has the semantics of
:addfor any key-value pairs not in the current query and:removefor any that are in the current query.- Example with single-values:
- base query:
?foo=bar - operation:
query: [toggle: %{foo: :bar, bar: :baz}] - result:
?bar=baz
- base query:
- Example with multi-values:
- base query:
?foo=bar - operation:
query: [toggle: %{foo: [:bar, :baz], bar: :baz}] - result:
?foo=baz&bar=baz
- base query:
- Example with single-values:
Functions
Push a live patch event to the server with the href computed by relative query parsing.
Options
:query- the query operations to apply, see:query_opts/0The special placeholder value
:phx_valuewill be replaced by the current value of the input sending the event.- Example:
- template:
<input type="number" phx-key="Enter" phx-keydown={cartograph_patch(query: [merge: %{"page_no" => :phx_value}])} /> - current uri:
/users?page_no=1 - user input: 4
- resulting patch uri:
/users?page_no=4
- template:
- Example:
:loading- passed through toPhoenix.LiveView.JS.push/2as-is.:page_loading- passed through toPhoenix.LiveView.JS.push/2as-is.
Parses a new path suitable for use in a live patch operation from the provided uri and opts.
uri can be a URI.t/0 struct or a string:
parse_patch(@cartograph_uri, query: [merge: %{page_no: 1}])parse_patch(~p"/users", query: [merge: %{page_no: 1}])
Options
:query- the query operations to apply, see:query_opts/0:phx_value- the value of this option will replace any ocurrences of:phx_valuein the:queryoperations.- Example:
- starting uri:
/users?page_no=1 - opts:
query: [merge: %{"page_no" => :phx_value}], phx_value: 2 - result:
/users?page_no=2
- starting uri:
- Example: