MishkaCmsCore.Runtime.PageState (Mishka CMS Core v0.0.1)

Copy Markdown View Source

A database page's starting state: the assigns it declares in Page.state, put on the socket by the LiveView that serves the page.

A page module is not a LiveView — MishkaCmsWeb.RuntimeLive owns mount/3 and handle_params/3 — so until a page could declare these, its template could not read @count before an event had assigned it. The ways around it were a default in every expression (assigns[:count] || 0, wrong for false) or a helper handed assigns, and LiveView cannot track what a function does with assigns: every update re-sent each such expression.

What the compiler emits

MishkaCmsCore.Runtime.Compilers.LiveViewCompiler.build_page_state/1 turns

state: [%{name: "count", code: "0"}, %{name: "roles", code: "roles()"}]

into

def __page_state__, do: %{count: 0, roles: roles()}
def __page_state_keys__, do: [:count, :roles]

__page_state__/0 runs once per visitor, when the page is mounted — the expressions may call the page's helpers, and DateTime.utc_now/0 is fresh for each visitor. __page_state_keys__/0 is a literal, so the runtime can tell which keys a page owns without running its code.

Where the runtime calls this

  • assign_initial/2 — mount/3, once the page module is known.
  • switch/3 — handle_params/3. Only when the address now names ANOTHER page in the same process: that page's state is assigned and the keys only the previous page declared become nil, so nothing travels from one page to the next. The same page — a query string that changed, or the call right after mount/3 — keeps the visitor's state untouched.
  • fill_missing/2 — when the page is edited while it is open: the keys the new version declares and the socket does not have yet are added, so the template's new @key does not raise; the visitor's own values stay.

Names

name must match ^[a-z_][a-zA-Z0-9_]*$, must not start with __, must be unique in the list and must not be one of reserved/0 — the assigns the runtime and LiveView already own. The atoms are created when the page compiles, from names that passed this validation, never at request time.

Summary

Functions

Puts the page's starting state on the socket. A page that declares none is untouched.

Adds the keys module declares that the socket does not have yet, and leaves every other value as the visitor left it.

The keys module declares, without running its code.

The page's starting state, evaluated now — %{} for a module compiled before state existed or for a page that declares none.

The assign names a page's state may not take.

Moves the socket from previous's state to module's, when they are different pages.

Whether name may name a state entry, before uniqueness.

Functions

assign_initial(socket, module)

Puts the page's starting state on the socket. A page that declares none is untouched.

fill_missing(socket, module)

Adds the keys module declares that the socket does not have yet, and leaves every other value as the visitor left it.

keys(module)

@spec keys(module() | nil) :: [atom()]

The keys module declares, without running its code.

of(module)

@spec of(module() | nil) :: map()

The page's starting state, evaluated now — %{} for a module compiled before state existed or for a page that declares none.

reserved()

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

The assign names a page's state may not take.

switch(socket, module, module)

Moves the socket from previous's state to module's, when they are different pages.

The keys only previous declared become nil; module's are assigned from scratch. The same module answers the socket untouched.

valid_name?(name)

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

Whether name may name a state entry, before uniqueness.