MishkaCmsCore.Runtime.PageBuilder.Hooks (Mishka CMS Core v0.0.1)

Copy Markdown View Source

The runtime JS hooks an element can be attached to, and the two attributes that attach it.

Why a picker and not a code box

MishkaCmsCore.Runtime.PageBuilder.Interactions covers what a page usually wants — toggle a class, show something, hide something — with no code at any point. What it cannot do is anything else, and the answer to "anything else" is a hook: real JavaScript, written on the JS hooks page, compiled into the site's bundle and registered with LiveView.

Writing one stays there. This only selects from what is already in the bundle, which is the whole reason it needs no gate of its own: it grants nothing the actor did not already have, it removes the step where they hand-type phx-hook into Advanced and get the capitalisation wrong.

The two attributes

phx-hook names the hook, and LiveView requires a unique DOM id on any element carrying one — a hook on an element with no id silently never mounts, which is a bad afternoon. So attaching one mints an id when the element has none, and the picker says so rather than leaving it to be discovered.

The name written is JavaScriptCompiler.browser_name/2's, not the row's: a global hook called collapse registers as GlobalCollapse and a site's own as <Site>Collapse. Asking the compiler is what keeps the two from drifting.

Summary

Functions

Every hook this site can attach, its own and the globals, as %{name:, hook:, description:}.

Whether name is a hook the picker is entitled to write.

Every hook the picker offers: this site's, the globals, and whatever a plugin contributes through builder:js_hooks.

Functions

available(actor, site_id, site_name)

@spec available(map() | nil, String.t() | nil, String.t() | nil) :: [map()]

Every hook this site can attach, its own and the globals, as %{name:, hook:, description:}.

name is what goes in phx-hook; hook is the row's own name, which is what the JS hooks page calls it and therefore what a reader is looking for.

known?(actor, site_id, site_name, name)

@spec known?(map() | nil, String.t() | nil, String.t() | nil, String.t()) :: boolean()

Whether name is a hook the picker is entitled to write.

The picker writes the name into the document, so a stale option — a hook archived in another tab — must not be written as though it existed.

registry(actor, site_id, site_name)

@spec registry(map() | nil, String.t() | nil, String.t() | nil) :: [map()]

Every hook the picker offers: this site's, the globals, and whatever a plugin contributes through builder:js_hooks.

ONE LIST FOR THE PICKER AND FOR known?/4, which is the whole point of naming it — the same Interactions.registry/0 and Conditions.registry/0 are. The picker read the appended list and known?/4 read available/3, so every hook an extension contributed was offered and then refused on Apply.