defmodule Exzeitable do
@moduledoc """
# Exzeitable. Check README for usage instructions.
"""
@doc "Expands into the gigantic monstrosity that is Exzeitable"
defmacro __using__(opts) do
alias Exzeitable.{Database, Parameters}
search_string =
opts
|> Parameters.set_fields()
|> Database.tsvector_string()
# coveralls-ignore-stop
quote do
use Phoenix.LiveView
use Phoenix.HTML
import Ecto.Query
alias Phoenix.LiveView.Helpers
alias Exzeitable.{Database, Filter, Format, HTML, Parameters, Validation}
@callback render(map) :: {:ok, iolist}
@type socket :: Phoenix.LiveView.Socket.t()
@doc """
Convenience helper so LiveView doesn't have to be called directly
## Example
```
<%= YourAppWeb.Live.Site.live_table(@conn, query: @query) %>
```
"""
defdelegate build_table(assigns), to: HTML, as: :build
@spec live_table(Plug.Conn.t(), keyword) :: {:safe, iolist}
def live_table(conn, opts \\ []) do
Helpers.live_render(conn, __MODULE__,
# Live component ID
id: Keyword.get(unquote(opts), :id, 1),
session: Parameters.process(opts, unquote(opts), __MODULE__)
)
end
###########################
######## CALLBACKS ########
###########################
@doc "Initial setup on page load"
@spec mount(atom, map, socket) :: {:ok, socket}
def mount(:not_mounted_at_router, assigns, socket) do
assigns = Map.new(assigns, fn {k, v} -> {String.to_atom(k), v} end)
socket =
socket
|> assign(assigns)
|> maybe_get_records()
|> maybe_set_refresh()
{:ok, socket}
end
@doc "Clicking the hide button hides the column"
@spec handle_event(String.t(), map, socket) :: {:noreply, socket}
def handle_event("hide_column", %{"column" => column}, socket) do
%{assigns: %{fields: fields}} = socket
fields = Kernel.put_in(fields, [String.to_existing_atom(column), :hidden], true)
{:noreply, assign(socket, :fields, fields)}
end
@doc "Clicking the show button shows the column"
def handle_event("show_column", %{"column" => column}, socket) do
%{assigns: %{fields: fields}} = socket
fields = Kernel.put_in(fields, [String.to_existing_atom(column), :hidden], false)
{:noreply, assign(socket, :fields, fields)}
end
@doc "Hide all the show buttons"
def handle_event("hide_buttons", _, socket) do
{:noreply, assign(socket, :show_field_buttons, false)}
end
@doc "Show all the show buttons"
def handle_event("show_buttons", _, socket) do
{:noreply, assign(socket, :show_field_buttons, true)}
end
@doc "Changes page when pagination buttons are clicked"
def handle_event("change_page", %{"page" => page}, %{assigns: assigns} = socket) do
new_value = %{page: String.to_integer(page)}
socket =
socket
|> assign(new_value)
|> assign(:list, Database.get_records(Map.merge(assigns, new_value)))
{:noreply, socket}
end
@doc "Clicking the sort button sorts the column"
def handle_event("sort_column", %{"column" => column}, %{assigns: assigns} = socket) do
column = String.to_existing_atom(column)
new_value =
case assigns.order do
[asc: ^column] -> %{order: [desc: column], page: 1}
_ -> %{order: [asc: column], page: 1}
end
socket =
socket
|> assign(new_value)
|> assign(:list, Database.get_records(Map.merge(assigns, new_value)))
{:noreply, socket}
end
@doc "Typing into the search box... searches. Crazy, right?"
def handle_event("search", %{"search" => %{"search" => search}}, socket) do
socket =
socket
|> assign(%{search: search, page: 1})
|> maybe_get_records()
{:noreply, socket}
end
@doc "Refresh periodically grabs new records from the database"
def handle_info(:refresh, socket) do
{:noreply, maybe_get_records(socket)}
end
defp maybe_get_records(socket) do
%{assigns: assigns} = socket
if connected?(socket) do
socket
|> assign(:list, Database.get_records(assigns))
|> assign(:count, Database.get_record_count(assigns))
else
socket
|> assign(:list, [])
|> assign(:count, 0)
end
end
defp maybe_set_refresh(%{socket: %{assigns: %{refresh: refresh}}} = socket)
when is_integer(refresh) do
with true <- connected?(socket),
{:ok, _tref} <- :timer.send_interval(refresh, self(), :refresh) do
socket
else
_ -> socket
end
end
defp maybe_set_refresh(socket) do
socket
end
# Need to unquote the search string because string interpolation is not allowed.
@spec do_search(Ecto.Query.t(), String.t()) :: Ecto.Query.t()
def do_search(query, search) do
where(
query,
fragment(
unquote(search_string),
^Database.prefix_search(search)
)
)
end
end
end
end