ElementorexCms.Source behaviour (ElementorexCms v0.2.0)

Copy Markdown View Source

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)
end

Any 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)))
end

context 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

context()

@type context() :: %{mode: :public | :editor, data: map()}

record()

@type record() :: term()

t()

@type t() :: %ElementorexCms.Source{
  fields: [ElementorexCms.Source.Field.t()],
  icon: String.t(),
  impl: module() | {module(), keyword()},
  key: String.t(),
  label: String.t()
}

Callbacks

count(t, context)

(optional)

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()

@callback fields() :: [ElementorexCms.Source.Field.t()]

Fields of the records (ElementorexCms.Source.Field).

get(id, context)

(optional)
@callback get(id :: String.t(), context()) :: record() | nil

A record by its id (as returned by id/1), or nil.

icon()

(optional)
@callback icon() :: String.t()

Heroicons name shown next to the collection.

id(record)

(optional)
@callback id(record()) :: term()

The id of a record (default: its id key).

key()

@callback key() :: String.t()

Stable id of the source, stored in documents ("products").

label()

@callback label() :: String.t()

Name of the collection in the editor ("Products").

list(t, context)

@callback list(ElementorexCms.Source.Query.t(), context()) :: [record()]

The records a loop shows (see ElementorexCms.Source.Query).

title(record)

(optional)
@callback title(record()) :: String.t()

A short name of a record, for the editor's preview picker.

url(record)

(optional)
@callback url(record()) :: String.t() | nil

The page of a record, if it has one (offered as Link to the item).

value(record, key)

(optional)
@callback value(record(), key :: String.t()) :: term()

The value of a field of a record (default: map and struct keys, a.b paths).

values(key, context)

(optional)
@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

count(source, query, context)

@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.

field(source, key)

@spec field(t(), String.t()) :: ElementorexCms.Source.Field.t() | nil

The field with key (@url included), or nil.

fields(source)

@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.

filter_fields(source)

@spec filter_fields(t()) :: [ElementorexCms.Source.Field.t()]

The fields loops and the visitors' filters can filter by, custom filters included.

get(source, id, context)

@spec get(t(), String.t(), context()) :: record() | nil

A record by id. Sources without get/2 are searched with list/3 (the first 200 records).

id(source, record)

@spec id(t(), record()) :: term()

The id of a record.

list(source, query, context)

@spec list(t(), ElementorexCms.Source.Query.t(), context()) :: [record()]

The records of query (see list/2).

new(source)

@spec new(module() | {module(), keyword()} | t()) :: t()

Builds a source from a module of this behaviour, an Ecto schema or an {ElementorexCms.Source.Ecto, opts} tuple.

read(record, path)

@spec read(term(), String.t()) :: term()

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.

role(source, role)

@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…).

schema(source)

@spec schema(t()) :: module() | nil

The Ecto schema of a source made with ElementorexCms.Source.Ecto, or nil.

searchable(source)

@spec searchable(t()) :: [String.t()]

The keys of the fields the visitors' text search looks in.

source_module?(module)

@spec source_module?(module()) :: boolean()

Whether module implements this behaviour (or uses ElementorexCms.Source.Ecto).

title(source, record)

@spec title(t(), record()) :: String.t()

A short name of a record (its title field, or its id).

url(source, record)

@spec url(t(), record()) :: String.t() | nil

The page of a record, or nil.

url?(source)

@spec url?(t()) :: boolean()

Whether the records of the source have a page (url/1).

valid_key?(key)

@spec valid_key?(term()) :: boolean()

Whether a source key is valid ("products", "blog_posts").

value(source, record, key)

@spec value(t(), record(), String.t()) :: term()

The value of field key of a record (@url gives url/2).

values(source, key, context)

@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).