# `ElementorexCms.Document`
[🔗](https://github.com/rocket4ce/elementorex_cms/blob/v0.1.0/lib/elementorex_cms/document.ex#L1)

The document: a tree of nodes stored as plain JSON (a `jsonb` column).

    %{
      "version" => 1,
      "root" => %{
        "id" => "Wq0m2Lx9Ka",
        "type" => "root",
        "props" => %{},
        "style" => %{},
        "attrs" => %{"css" => "selector { padding: 8px }"},
        "children" => [
          %{
            "id" => "b3Rt9Lm1Qe",
            "type" => "heading",
            "props" => %{"text" => "Hello", "level" => "h2"},
            "style" => %{"desktop" => %{"color" => "var(--color-primary)"}, "mobile" => %{"font-size" => "28px"}},
            "attrs" => %{"class" => "tracking-tight", "hide_on" => ["mobile"]},
            "children" => []
          }
        ]
      }
    }

Every node has:

  * `"id"` - stable id (`ElementorexCms.Id`)
  * `"type"` - the block type (`ElementorexCms.Registry`)
  * `"props"` - content of the block, as declared by its props
  * `"style"` - declarations per state: `"desktop"`, `"tablet"`, `"mobile"`
    and `"hover"` (see `ElementorexCms.Style`)
  * `"attrs"` - *Advanced* settings: `"id"`, `"class"`, `"hide_on"`, `"css"`,
    `"animation"`, `"animation_delay"` and `"label"` (name in the tree)
  * `"children"` - child nodes (containers only)

The functions here are pure: they take a document and return a new one.
Tree operations validate what they are asked (a node cannot go inside itself,
only containers take children, the root cannot be removed) and return
`{:error, reason}` otherwise.

# `t`

```elixir
@type t() :: map()
```

# `tree_node`

```elixir
@type tree_node() :: map()
```

# `animations`

Entrance animations of the *Advanced* tab.

# `clear_style`

```elixir
@spec clear_style(t(), String.t()) :: {:ok, t()} | {:error, :not_found}
```

Removes every style of a node (all states).

# `count`

```elixir
@spec count(t() | tree_node()) :: non_neg_integer()
```

Number of nodes, the root included.

# `devices`

Devices of the responsive styles.

# `duplicate`

```elixir
@spec duplicate(t(), String.t(), ElementorexCms.Registry.t() | nil) ::
  {:ok, t(), String.t()} | {:error, atom()}
```

Copies the node `id` (with new ids for it and its children) right after it.
Returns the document and the id of the copy.

# `empty?`

```elixir
@spec empty?(t() | nil) :: boolean()
```

Whether the document has no content.

# `fetch`

```elixir
@spec fetch(t(), String.t() | nil) :: {:ok, tree_node()} | {:error, :not_found}
```

Like `get/2`, as `{:ok, node}` or `{:error, :not_found}`.

# `from_json`

```elixir
@spec from_json(String.t(), ElementorexCms.Registry.t() | nil) :: t()
```

Decodes and normalizes a JSON document (an invalid one gives `new/0`).

# `get`

```elixir
@spec get(t(), String.t() | nil) :: tree_node() | nil
```

The node with `id`, or `nil`.

# `insert`

```elixir
@spec insert(
  t(),
  tree_node(),
  String.t(),
  integer(),
  ElementorexCms.Registry.t() | nil
) ::
  {:ok, t()} | {:error, atom()}
```

Inserts `node` in the container `parent_id` at `index` (clamped to the
children list). Checks that the parent takes that kind of child and the
document limits.

# `limits`

Limits applied by `normalize/2`: `%{nodes: 2000, depth: 24}`.

# `move`

```elixir
@spec move(t(), String.t(), String.t(), integer(), ElementorexCms.Registry.t() | nil) ::
  {:ok, t()} | {:error, atom()}
```

Moves the node `id` into `parent_id` at `index`. The index is counted in the
target's current children (the node itself included when it already is
there), which is what the editor's drop indicator shows.

# `new`

```elixir
@spec new([tree_node()]) :: t()
```

An empty document, optionally with root children.

# `node`

```elixir
@spec node(String.t(), map(), keyword()) :: tree_node()
```

A new node of `type`. `props` are stored as given (string keys); pass
`registry:` to fill in the defaults of the block and its default children.

Options: `:children`, `:style`, `:attrs`, `:id`, `:registry`.

# `nodes`

```elixir
@spec nodes(t() | tree_node()) :: [tree_node()]
```

Every node, depth first, the root included.

# `normalize`

```elixir
@spec normalize(term(), ElementorexCms.Registry.t() | nil) :: t()
```

Makes any map a valid document for `registry`: fills missing keys, casts and
sanitizes props with the definitions of each block, keeps only valid styles
and *Advanced* attributes, drops children of non-containers, gives new ids to
invalid or repeated ones and enforces the limits (`limits/0`).

Nodes of unknown types are kept as they are (they render nothing), so a
document never loses content because a block was removed from the registry.

Run it on everything that comes from outside: stored JSON, imports, the
editor's events.

# `normalize_attrs`

```elixir
@spec normalize_attrs(term()) :: map()
```

Keeps only valid *Advanced* attributes.

# `normalize_style`

```elixir
@spec normalize_style(term()) :: map()
```

Keeps only valid states and declarations of a style map.

# `parent`

```elixir
@spec parent(t(), String.t()) :: {:ok, tree_node(), non_neg_integer()} | :error
```

The parent of the node with `id` and the node's index in it.

# `path`

```elixir
@spec path(t(), String.t()) :: [tree_node()]
```

Nodes from the root down to the node with `id` (both included), or `[]` if
it does not exist.

# `put_attr`

```elixir
@spec put_attr(t(), String.t(), String.t(), term()) ::
  {:ok, t()} | {:error, :not_found}
```

Sets an *Advanced* attribute of a node (validated with `normalize_attrs/1`;
an invalid value removes the attribute).

# `put_prop`

```elixir
@spec put_prop(t(), String.t(), String.t(), term()) ::
  {:ok, t()} | {:error, :not_found}
```

Sets a prop of a node (the value is stored as given; cast it first).

# `put_style`

```elixir
@spec put_style(t(), String.t(), String.t(), String.t(), term()) ::
  {:ok, t()} | {:error, :not_found | :invalid}
```

Sets (or with `nil`/`""` removes) a style declaration of a node for a state
(`"desktop"`, `"tablet"`, `"mobile"` or `"hover"`). Invalid values are
rejected with `{:error, :invalid}`.

# `regenerate_ids`

```elixir
@spec regenerate_ids(t() | tree_node()) :: t() | tree_node()
```

A copy of a node (or of a whole document) with new ids everywhere.

# `remove`

```elixir
@spec remove(t(), String.t()) :: {:ok, t()} | {:error, atom()}
```

Removes the node `id` and its children. The root cannot be removed.

# `root`

```elixir
@spec root(t()) :: tree_node()
```

The root node.

# `states`

Style states a node can have (`desktop`, `tablet`, `mobile`, `hover`).

# `to_json`

```elixir
@spec to_json(t(), keyword()) :: String.t()
```

Encodes a document as JSON.

# `update`

```elixir
@spec update(t(), String.t(), (tree_node() -&gt; tree_node())) ::
  {:ok, t()} | {:error, :not_found}
```

Applies `fun` to the node with `id`. The id and type cannot change.

# `version`

Current version of the document format.

# `within?`

```elixir
@spec within?(t(), String.t(), String.t()) :: boolean()
```

Whether `id` is `ancestor_id` or one of its descendants.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
