Current section
Files
Jump to
Current section
Files
lib/ex_admin/index.ex
defmodule ExAdmin.Index do
@moduledoc """
Override the default index page for an ExAdmin resource
By default, ExAdmin renders the index table without any additional
configuration. It renders each column in the model, except the id,
inserted_at, and updated_at columns.
## Default Table Type
ExAdmin displays a selection checkbox column on the left with a batch
action control that enables when a checkbox is selected.
To customize the index page, use the `index` macro.
For example, the following will show on the id an name fields, as
well place a selection column and batch actions row on the page:
defmodule MyProject.ExAdmin.MyModel do
use ExAdmin.Register
register_resource MyProject.MyModel do
index do
selectable_column()
column :id
column :name
actions # display the default actions column
end
end
end
### Image fields
For image fields, use the `image: true` option. For example:
index do
column :name
column :image, [image: true, height: 100], &(ExAdminDemo.Image.url({&1.image, &1}, :thumb))
end
### Custom columns
Columns can be customized with column/2 where the second argument is
an anonymous function called with model. Here are a couple examples:
index do
column :id
column :name, fn(category) ->
Phoenix.HTML.Tag.content_tag :span, category.name,
"data-id": category.id, class: "category"
end
column "Created", fn(category) ->
category.created_at
end
end
### Override the Actions column
The Actions column can be customized by adding `column "Actions", fn(x) -> ...`
column "Actions", fn(r) ->
safe_concat link_to("Restore", "/admin/backuprestores/restore/#\{r.id}", "data-method": :put,
"data-confirm": "You are about to restore #\{r.file_name}. Are you sure?",
class: "member_link restore-link"),
link_to("Delete", "/admin/backuprestores/#\{r.id}", "data-method": :delete,
"data-confirm": "Are you sure you want to delete this?",
class: "member_link")
end
### Associations
By default, ExAdmin will attempt to render a belongs_to association with a
select control, using name field in the association. If you would like to
render an association with another field name, or would like to use more than
one field, use the :field option.
column :account, fields: [:username]
### Change the column label
Use the :label option to override the column name:
column :name, label: "Custom Name"
## As Grid
By providing option `as: :grid` to the `index` macro, a grid index page
is rendered.
### For Example:
index as: :grid, default: true do
cell fn(p) ->
markup do
div do
a href: admin_resource_path(p, :show) do
img(src: ExAdminDemo.Image.url({p.image_file_name, p}, :thumb), height: 100)
end
end
a truncate(p.title), href: admin_resource_path(p, :show)
end
end
end
"""
require Logger
require Integer
import ExAdmin.Utils
import ExAdmin.Helpers
import ExAdmin.Gettext
import Kernel, except: [div: 2, to_string: 1]
use Xain
# alias ExAdmin.Schema
@doc false
defmacro __using__(_) do
quote do
import unquote(__MODULE__)
end
end
@default_actions [:show, :edit, :delete]
@doc """
The index macro is used to customize the index page of a resource.
"""
defmacro index(opts \\ [], do: contents) do
quote location: :keep do
import ExAdmin.CSV, only: [csv: 1, csv: 2]
import ExAdmin.Register
import ExAdmin.Index
def index_view(var!(conn), page, scope_counts) do
import ExAdmin.Form, except: [actions: 1]
import ExAdmin.Register, except: [actions: 1]
import ExAdmin.ViewHelpers
var!(columns, ExAdmin.Show) = []
var!(selectable_column, ExAdmin.Index) = nil
var!(actions, ExAdmin.Index) = nil
var!(cell, ExAdmin.Index) = nil
opts = unquote(opts)
unquote(contents)
selectable = case Macro.expand(var!(selectable_column, ExAdmin.Index), __ENV__) do
nil -> false
other -> other
end
actions = ExAdmin.Index.get_index_actions(var!(conn).assigns.defn, var!(actions, ExAdmin.Index))
opts = Enum.into(opts, %{})
|> Map.put(:column_list, var!(columns, ExAdmin.Show) |> Enum.reverse)
|> Map.put(:selectable_column, selectable)
|> Map.put(:actions, actions)
markup safe: true do
ExAdmin.Index.render_index_pages(var!(conn), page, scope_counts, var!(cell, ExAdmin.Index), opts)
end
end
end
end
@doc false
def get_index_actions(defn, actions) do
actions = case actions do
[] -> @default_actions
nil -> @default_actions
false -> []
list -> list
end
actions -- (@default_actions -- defn.actions)
end
@doc """
Define which actions will be displayed in the index view.
## Examples
actions
actions [:new, :delete]
"""
defmacro actions(opts \\ []) do
if opts != nil and opts != false and (opts -- @default_actions) != [] do
raise ArgumentError, "Only #{inspect @default_actions} are allowed!"
end
quote do
var!(actions, ExAdmin.Index) = unquote(opts)
end
end
@doc """
Define a grid cell for grid view.
## Example
index as: :grid, default: true, columns: 6 do
import Kernel, except: [div: 2]
cell fn(p) ->
div ".box" do
div ".box-body" do
a href: admin_resource_path(p, :show) do
img(src: ExAdminDemo.Image.url({p.image_file_name, p}, :thumb), height: 100)
end
end
div ".box-footer" do
a truncate(p.title), href: admin_resource_path(p, :show)
end
end
end
end
"""
defmacro cell(fun) do
quote do
var!(cell, ExAdmin.Index) = unquote(fun)
end
end
@doc """
Add a column of selection check boxes
Allows users to select individual rows on the index page. Selecting
columns activates the batch actions button.
"""
defmacro selectable_column do
quote do
var!(selectable_column, ExAdmin.Index) = true
end
end
@doc false
def default_index_view(conn, page, scope_counts) do
case conn.assigns.defn do
nil ->
throw :invalid_route
%{__struct__: _} = defn ->
columns = case defn.index_filters do
[] -> []
[false] -> []
[_] ->
ExAdmin.Filter.fields(conn.assigns.defn)
|> Keyword.keys
end
|> case do
[] ->
defn.resource_model.__schema__(:fields)
|> Enum.filter(&(not &1 in [:inserted_at, :updated_at]))
other ->
other
end
|> Enum.map(&({translate_field(defn, &1), %{}}))
columns = if :id in defn.resource_model.__schema__(:fields) and Enum.any?(columns, (&(elem(&1, 0) == :id))) do
Keyword.put columns, :id, %{link: true}
else
columns
end
opts = %{}
|> Map.put(:column_list, columns)
|> Map.put(:selectable_column, true)
|> Map.put(:actions, get_index_actions(defn, []))
markup safe: true do
ExAdmin.Index.render_index_pages(var!(conn), page, scope_counts, nil, opts)
end
end
end
defp get_resource_fields([]), do: []
defp get_resource_fields([resource | _]), do: resource.__struct__.__schema__(:fields)
@doc false
def render_index_pages(conn, page, scope_counts, cell, page_opts) do
# require IEx
# IEx.pry
name = resource_model(conn) |> titleize |> Inflex.pluralize
defn = conn.assigns.defn
label = get_resource_label(conn) |> Inflex.pluralize
batch_actions = (not false in defn.batch_actions) and :delete in page_opts[:actions]
opts = %{
columns: Map.get(page_opts, :columns, 3),
column_list: Map.get(page_opts, :column_list),
count: page.total_entries,
name: name,
order: ExQueb.get_sort_order(conn.params["order"]),
href: admin_resource_path(conn, :index) <> "?order=",
defn: defn,
batch_actions: batch_actions,
scopes: defn.scopes,
label: label,
resource_model: conn.params["resource"],
page: page,
cell: cell,
scope_counts: scope_counts,
opts: page_opts,
resources: page.entries,
selectable_column: page_opts[:selectable_column],
actions: page_opts[:actions]
}
_render_index_page(conn, opts, page_opts)
end
defp _render_index_page(conn, opts, %{as: :grid}) do
Module.concat(conn.assigns.theme, Index).wrap_index_grid fn ->
Module.concat(conn.assigns.theme, Index).batch_action_form conn,
false, opts[:scopes], opts[:resource_model], opts[:scope_counts], fn ->
if opts[:count] == 0 do
Module.concat(conn.assigns.theme, Index).blank_slate_page(conn, opts)
else
Module.concat(conn.assigns.theme, Index).paginated_collection_grid(conn, opts)
end
end
end
end
defp _render_index_page(conn, opts, page_opts) do
page = opts[:page]
actions = opts[:actions]
opts = Map.put(opts, :fields, get_resource_fields page.entries)
columns = page_opts[:column_list]
custom_actions_column? = Enum.any? columns, &((elem &1, 0) == "Actions")
columns = if custom_actions_column? || Enum.empty?(actions) do
columns
else
columns ++ [{"Actions", %{fun: fn(resource) -> build_index_links(conn, resource, actions) end,
label: ExAdmin.Gettext.gettext("Actions") }}]
end
opts = Map.put opts, :column_list, columns
Module.concat(conn.assigns.theme, Index).wrap_index_grid fn ->
Module.concat(conn.assigns.theme, Index).batch_action_form conn,
opts[:batch_actions], opts[:scopes], opts[:resource_model], opts[:scope_counts], fn ->
if opts[:count] == 0 do
Module.concat(conn.assigns.theme, Index).blank_slate_page(conn, opts)
else
Module.concat(conn.assigns.theme, Index).paginated_collection_table(conn, opts)
end
end
end
end
@doc """
Build the scope link.
"""
def build_scope_href(href, nil), do: href
def build_scope_href(href, scope) do
String.replace(href, "?", "?scope=#{scope}&")
end
@doc """
Build the order link.
"""
def build_order_href(href, {name, sort}), do: href <> "#{name}_#{sort}"
def build_order_href(href, _), do: href
@doc """
Build the filter link.
"""
def build_filter_href(href, nil), do: href
def build_filter_href(href, q) do
q
|> Map.to_list
|> Enum.reduce(href, fn({name, value}, acc) ->
acc <> "&q%5B" <> name <> "%5D=" <> value
end)
end
@doc false
def download_links(conn, opts) do
div ".download_links " <> (gettext "Download:") <> " " do
a "CSV", href: build_csv_href(conn, opts)
end
end
@doc false
def build_csv_href(conn, opts) do
admin_resource_path(conn, :csv) <> "?order="
|> build_scope_href(conn.params["scope"])
|> build_order_href(opts[:order])
|> build_filter_href(conn.params["q"])
end
@doc false
def parameterize(name, seperator \\ "_")
def parameterize(atom, seperator) when is_atom(atom) do
Atom.to_string(atom)
|> parameterize(seperator)
end
def parameterize(string, seperator) do
Inflex.parameterize(string, seperator)
end
@doc false
def build_index_links(conn, resource, actions) do
resource_model = resource.__struct__
links = case actions do
nil -> []
other -> other
end
list = get_authorized_links(conn, links, resource_model) |> Enum.reverse
labels = conn.assigns.defn.action_labels
Module.concat(conn.assigns.theme, Index).handle_action_links(list, resource, labels)
end
@doc false
def get_authorized_links(conn, links, resource_model) do
Enum.reduce links, [], fn(item, acc) ->
if ExAdmin.Utils.authorized_action?(conn, item, resource_model), do: [item | acc], else: acc
end
end
end