A collection of records that documents can show: products, posts, events, team members… (dynamic content).
A source tells the editor which fields its records have, and gives the
records to the blocks that need them: a Loop grid repeats its content for
each record of a source, and any prop of a block can be bound to a field of
the current record (see ElementorexCms.Dynamic).
From an Ecto schema
The quickest way: list your schema in the config. Its fields are read from
the schema (see ElementorexCms.Source.Ecto), so a table made with
mix phx.gen.live or by hand works as it is:
config :elementorex_cms, sources: [MyApp.Catalog.Product]Or a module, to choose the fields, restrict the records and give each one a
link (mix elementorex.gen.source Catalog.Product writes it):
defmodule MyAppWeb.Sources.Products do
use ElementorexCms.Source.Ecto,
schema: MyApp.Catalog.Product,
repo: MyApp.Repo,
path: "/products/:slug",
fields: [price: [defaults: %{"before" => "$", "format" => "number"}]]
@impl true
def scope(query, _context), do: where(query, [p], p.published)
endAny other data
Implement this behaviour: key/0, label/0, fields/0 and list/2 are
required; get/2, value/2, url/1, id/1, title/1, count/2,
values/2 and icon/0 are optional. list/2 gets the filters, search and
order of the loop and of the visitors' filters in its query ("custom"
filters are the source's own, see ElementorexCms.Source.Field).
defmodule MyAppWeb.Sources.Team do
@behaviour ElementorexCms.Source
alias ElementorexCms.Source.{Field, Query}
@impl true
def key, do: "team"
@impl true
def label, do: "Team"
@impl true
def fields, do: [Field.new(:name, role: :title), Field.new(:photo, type: :image)]
@impl true
def list(query, _context), do: Query.run(query, MyApp.Team.members(), &Map.get(&1, String.to_existing_atom(&2)))
endcontext is %{mode: :public | :editor, data: map}: where the document is
rendered and the data the host passed (the current user, a tenant…).
The functions of this module work on the %ElementorexCms.Source{} built by
new/1 (see ElementorexCms.Sources for the configured ones).
Summary
Callbacks
How many records match the filters and exclusions of the query (its limit and offset aside). Loops with numbered pages need it; without it they show Previous and Next only.
Fields of the records (ElementorexCms.Source.Field).
A record by its id (as returned by id/1), or nil.
Heroicons name shown next to the collection.
The id of a record (default: its id key).
Stable id of the source, stored in documents ("products").
Name of the collection in the editor ("Products").
The records a loop shows (see ElementorexCms.Source.Query).
A short name of a record, for the editor's preview picker.
The page of a record, if it has one (offered as Link to the item).
The value of a field of a record (default: map and struct keys, a.b paths).
The distinct values of a field, for the options of the visitors' filters:
values or {value, count} tuples. Without it they come from the first 500
records of list/2 (ElementorexCms.Source.Query.values/3).
Functions
How many records match query (see count/2), or nil when the source
cannot tell.
The field with key (@url included), or nil.
The fields of a source whose values can be shown. @url (Link to the item)
is added when the source gives its records a page (url/1); custom filters
(custom: true) are left out: they filter, they have no value.
The fields loops and the visitors' filters can filter by, custom filters included.
A record by id. Sources without get/2 are searched with list/3 (the first
200 records).
The id of a record.
The records of query (see list/2).
Builds a source from a module of this behaviour, an Ecto schema or an
{ElementorexCms.Source.Ecto, opts} tuple.
Reads path ("name", "category.name") from maps and structs, with
string or atom keys. Atoms are never created: a key whose atom does not exist
cannot be in the record anyway.
The field with a role (:title, :image, :description, :price), or
nil. Fields marked with role: win; otherwise it is guessed from the
names and types (name/title, the first image, description/summary…).
The Ecto schema of a source made with ElementorexCms.Source.Ecto, or nil.
The keys of the fields the visitors' text search looks in.
Whether module implements this behaviour (or uses ElementorexCms.Source.Ecto).
A short name of a record (its title field, or its id).
The page of a record, or nil.
Whether the records of the source have a page (url/1).
Whether a source key is valid ("products", "blog_posts").
The value of field key of a record (@url gives url/2).
The distinct values of a field as {value, count} (count may be nil), in
order: the options of a filter (see values/2).
Types
Callbacks
@callback count(ElementorexCms.Source.Query.t(), context()) :: non_neg_integer()
How many records match the filters and exclusions of the query (its limit and offset aside). Loops with numbered pages need it; without it they show Previous and Next only.
@callback fields() :: [ElementorexCms.Source.Field.t()]
Fields of the records (ElementorexCms.Source.Field).
A record by its id (as returned by id/1), or nil.
@callback icon() :: String.t()
Heroicons name shown next to the collection.
The id of a record (default: its id key).
@callback key() :: String.t()
Stable id of the source, stored in documents ("products").
@callback label() :: String.t()
Name of the collection in the editor ("Products").
@callback list(ElementorexCms.Source.Query.t(), context()) :: [record()]
The records a loop shows (see ElementorexCms.Source.Query).
A short name of a record, for the editor's preview picker.
The page of a record, if it has one (offered as Link to the item).
The value of a field of a record (default: map and struct keys, a.b paths).
@callback values(key :: String.t(), context()) :: [term() | {term(), non_neg_integer()}]
The distinct values of a field, for the options of the visitors' filters:
values or {value, count} tuples. Without it they come from the first 500
records of list/2 (ElementorexCms.Source.Query.values/3).
Functions
@spec count(t(), ElementorexCms.Source.Query.t(), context()) :: non_neg_integer() | nil
How many records match query (see count/2), or nil when the source
cannot tell.
@spec field(t(), String.t()) :: ElementorexCms.Source.Field.t() | nil
The field with key (@url included), or nil.
@spec fields(t()) :: [ElementorexCms.Source.Field.t()]
The fields of a source whose values can be shown. @url (Link to the item)
is added when the source gives its records a page (url/1); custom filters
(custom: true) are left out: they filter, they have no value.
@spec filter_fields(t()) :: [ElementorexCms.Source.Field.t()]
The fields loops and the visitors' filters can filter by, custom filters included.
A record by id. Sources without get/2 are searched with list/3 (the first
200 records).
The id of a record.
@spec list(t(), ElementorexCms.Source.Query.t(), context()) :: [record()]
The records of query (see list/2).
Builds a source from a module of this behaviour, an Ecto schema or an
{ElementorexCms.Source.Ecto, opts} tuple.
Reads path ("name", "category.name") from maps and structs, with
string or atom keys. Atoms are never created: a key whose atom does not exist
cannot be in the record anyway.
@spec role(t(), atom()) :: ElementorexCms.Source.Field.t() | nil
The field with a role (:title, :image, :description, :price), or
nil. Fields marked with role: win; otherwise it is guessed from the
names and types (name/title, the first image, description/summary…).
The Ecto schema of a source made with ElementorexCms.Source.Ecto, or nil.
The keys of the fields the visitors' text search looks in.
Whether module implements this behaviour (or uses ElementorexCms.Source.Ecto).
A short name of a record (its title field, or its id).
The page of a record, or nil.
Whether the records of the source have a page (url/1).
Whether a source key is valid ("products", "blog_posts").
The value of field key of a record (@url gives url/2).
@spec values(t(), String.t(), context()) :: [{term(), non_neg_integer() | nil}]
The distinct values of a field as {value, count} (count may be nil), in
order: the options of a filter (see values/2).