arizona_js (arizona v0.4.0)
View SourceBuilders for web (browser/JavaScript) client effects.
Each function returns an arizona_effect:cmd/0 tuple
{arizona_effect, [OpCode | Args]} -- the shared wire format every client
command module produces (op codes in include/arizona_effect.hrl), encoded by
arizona_effect:encode/1. Use them inside web event attributes like az-click
or as effects from handle_event/3.
Most of these are browser/DOM-only (toggle, the class ops, set_attr,
dispatch_event, ...): they address the DOM by CSS selector or touch
document/window state. push_event and navigate have portable counterparts
in the per-platform modules (e.g. arizona_android) for ?native views.
Example
{'button', [{az_click, [
arizona_js:push_event(~"inc"),
arizona_js:toggle(~"#modal")
]}], [~"Both"]}Two lifetimes -- attribute vs handler effect
The same builder means different things depending on where it is placed:
- In an event attribute (
az_click,az_submit, ...) it is markup. It renders at SSR and sits in the DOM, so a purely client-side command (toggle, the class ops,set/2,3) runs on its trigger with no round trip at all.push_eventandfetchare the ones that do reach the server. - Returned from a handler callback it is a command sent over the
WebSocket, so it cannot exist at SSR -- an SSR render is an HTTP response
with no client attached yet. Which callbacks can return effects, and why
mount/1cannot, is covered by "Resets and effects" inarizona_stateful.
Appearance is rendered, not commanded
A class or attribute derived from state belongs in the template, where the diff engine owns it:
{'div', [{id, ?get(id)}, {class, [~"app ", ?get(tint)]}], [...]}Driving that same class from a handler with add_class/2 paints the wrong
value on a cold load and only corrects itself once connected -- never, if no
event ever fires. Effects are for imperative one-shots the DOM cannot derive
from state: focus a field, set the title, start a transition, copy to the
clipboard. It is the same reasoning behind there being deliberately no
set_text/set_html -- writing content from an effect would fight the diff
engine, which is authoritative for what a view shows.
Summary
Functions
Adds a CSS class to all elements matching the selector.
Blurs (removes focus from) the first element matching the selector.
Closes the first <dialog> matching the selector via HTMLDialogElement.close.
A non-dialog match is a safe no-op.
Copies to the clipboard the text of the first element matching the selector: the
matched element's value (a form control) or, failing that, its textContent,
written via navigator.clipboard.writeText.
Dispatches a custom DOM event with a payload.
Closes the floating Picture-in-Picture window for ViewId, moving the view back
inline. No-op if the view isn't popped out.
Issues an HTTP request to Url via the browser fetch() API, without a page
reload, and applies the response. Unlike push_event (WebSocket, can't set
cookies) and a plain form POST (full reload), a fetch response can carry a real
Set-Cookie -- HttpOnly honored, applied natively by the browser -- while the
page stays put. Use it for flows that must rotate a session cookie yet keep the
typed form fields and show inline validation (password change, login, logout).
Focuses the first element matching the selector.
Hides all elements matching the CSS selector.
Triggers a SPA navigation to Path.
Triggers a SPA navigation to Path with options.
Wraps a command (or list of commands) so it only fires when a key
matching Key is pressed. Key can be an atom (e.g. enter), a
list of atoms (matches any), or a regex pattern as a binary.
Triggers an in-place SPA navigation (patch) to Path: keeps the current
live view mounted and re-renders it via handle_update/3 instead of
replacing it. The counterpart of navigate/1; use it between routes that
share a root view so live chrome survives the navigation.
Triggers an in-place SPA navigation (patch) to Path with options. Accepts the
same flash opt as navigate/2 (carried in-process to the patched route's flash
binding, stripped from the client effect) and its replace opt.
Pushes a named event to the server. The payload is auto-collected from the
triggering element itself, not its siblings: a form trigger contributes its
fields (a form submit passes the form), an input/select/textarea trigger its
value; any other trigger (e.g. a plain button inside a form) sends an empty
payload. Use push_event/2 to attach data explicitly.
Pushes a named event with an explicit payload, merged on top of any auto-collected form data.
Reloads the current page.
Removes an attribute from all elements matching the selector.
Removes a CSS class from all elements matching the selector.
Moves the view identified by ViewId into a floating Document Picture-in-Picture
window, kept live by the server diff stream. Use inside a web event attribute
(e.g. az-click) -- Document PiP requires a user gesture, so it won't open from
a server-pushed handler effect. The window opens at the browser's default size;
use request_pip/2 to choose a size.
Like request_pip/1 but with window options. width/height set the floating
window's initial size in pixels; when omitted the browser picks a default size.
Resets all forms matching the selector, clearing the fields the user typed
back to their initial values (HTMLFormElement.reset).
Scrolls smoothly to the first element matching the selector.
Scrolls to the first element matching the selector with scrollIntoView
options (behavior, block, inline).
Selects (highlights) the text of the first element matching the selector, an
<input> or <textarea>, via HTMLInputElement.select -- so a subsequent copy
or overtype acts on the whole field.
Sets a client-owned slot (?local) to Value in the closest view of the
triggering element, updating every slot with Key locally with no server
round-trip. Use inside web event attributes like az-click.
Like set/2 but targets the slot in the view identified by ViewId.
Like set/2 but updates the slot in every view on the page (document-wide).
Sets an attribute on all elements matching the selector.
Sets the document title.
Shows all elements matching the CSS selector.
Opens the first <dialog> matching the selector as a true modal via
HTMLDialogElement.showModal -- placed in the top layer, with a ::backdrop
and ESC-to-close -- unlike merely setting the open attribute (a non-modal
dialog). A non-dialog match is a safe no-op.
Toggles the visibility of all elements matching the CSS selector.
Toggles the presence of a bare boolean attribute on all elements matching the
selector: removes it if present, otherwise sets it (e.g. disabled, readonly,
hidden, open). For toggling between two values, use toggle_attr/4.
Toggles an attribute between two values on all elements matching the selector: sets
ValueB when the current value is ValueA, otherwise sets ValueA (so any
other current value resolves to ValueA). Useful for one-button toggles like a
password field's type between password and text.
Toggles a CSS class on all elements matching the selector.
Wraps a command (or list of commands) so the DOM change it causes plays inside a
view transition (document.startViewTransition). Works for a client-side effect
(e.g. toggle/1, animated immediately), a navigate/1,2 (the page swap
animates), or a push_event (the resulting server diff animates). A view
transition is not tied to navigation -- it wraps any DOM change. Equivalent to
transition/2 with no options.
Like transition/1 but with options. types is a list of view-transition type
names activated for the transition (matched by :active-view-transition-type(...)
CSS), letting one stylesheet pick a different animation per call.
Functions
-spec add_class(Selector, Class) -> arizona_effect:cmd() when Selector :: binary(), Class :: binary().
Adds a CSS class to all elements matching the selector.
-spec blur(Selector) -> arizona_effect:cmd() when Selector :: binary().
Blurs (removes focus from) the first element matching the selector.
-spec close_modal(Selector) -> arizona_effect:cmd() when Selector :: binary().
Closes the first <dialog> matching the selector via HTMLDialogElement.close.
A non-dialog match is a safe no-op.
Like focus/1/blur/1/scroll_to/1 (first match only). Usable both as a web
event command (e.g. {az_click, arizona_js:close_modal(~"#confirm")}) and as a
handler effect from handle_event/3.
-spec copy_to_clipboard(Selector) -> arizona_effect:cmd() when Selector :: binary().
Copies to the clipboard the text of the first element matching the selector: the
matched element's value (a form control) or, failing that, its textContent,
written via navigator.clipboard.writeText.
Like focus/1/blur/1/scroll_to/1 (first match only). The Clipboard API
requires a secure context and a user gesture, so this is meaningful as an
event command only (e.g. {az_click, arizona_js:copy_to_clipboard(~"#token")});
a server-pushed handler effect can't satisfy the gesture requirement. A
missing/blocked clipboard is a safe no-op.
-spec dispatch_event(Name, Payload) -> arizona_effect:cmd() when Name :: binary(), Payload :: map().
Dispatches a custom DOM event with a payload.
-spec exit_pip(ViewId) -> arizona_effect:cmd() when ViewId :: binary().
Closes the floating Picture-in-Picture window for ViewId, moving the view back
inline. No-op if the view isn't popped out.
-spec fetch(Url, Opts) -> arizona_effect:cmd() when Url :: binary(), Opts :: #{method => get | post | put | patch | delete, body => term(), headers => #{binary() => binary()}, credentials => same_origin | include | omit, on_error => arizona_effect:cmd() | arizona_effect:cmds(), keep_alive => boolean()}.
Issues an HTTP request to Url via the browser fetch() API, without a page
reload, and applies the response. Unlike push_event (WebSocket, can't set
cookies) and a plain form POST (full reload), a fetch response can carry a real
Set-Cookie -- HttpOnly honored, applied natively by the browser -- while the
page stays put. Use it for flows that must rotate a session cookie yet keep the
typed form fields and show inline validation (password change, login, logout).
Composes as a command: as an az_submit command the trigger form's fields are the
request body; as an az_click command on a non-form element, body is sent
instead.
The endpoint is a controller route (e.g. {post, ...}) returning the {"e": [...]}
effects wire payload -- arizona_controller:reply_effects/1 for the success leg
(200; a form with az-form-reset clears) or reply_effects/2 with a non-2xx
(e.g. 422) for a validation error (the typed fields survive). The effects are
applied on any status, against the submitting view's element. on_error (and
an arizona:fetch-error DOM event) runs only when there is no usable effects body
-- a non-JSON page, an empty non-2xx, or a network failure.
To re-render the live view, return an arizona_js:push_event in the response: the
client relays it over the existing WebSocket and the view re-renders through its
normal handle_event/3 (no subscription; it targets the submitting view and does
not echo the form fields). Use arizona_pubsub from the controller instead to reach
other views (broadcast). To send the user elsewhere, return arizona_js:navigate/1
(e.g. via reply_redirect/1), not an HTTP 3xx -- a fetch-followed redirect can't
drive a SPA navigation.
Identity changes need a reload. The WebSocket is not re-handshaked when the
cookie changes, so a fetch that changes who the user is (login/logout) must
arizona_js:reload/0 (or do a real navigation) for the socket to pick up the new
session -- push_event/navigate would re-render over the stale one. Rotating the
cookie for the same identity (e.g. password change) needs no reload.
Opts:
method-- HTTP method. Default: the trigger form'smethod, elsepost.body-- request body for a non-form trigger (JSON-encoded); it wins over a trigger form's fields. Default: the trigger form's fields asapplication/x-www-form-urlencoded. A GET/HEAD has no request body, so either payload goes in the query string instead, ahead of any#fragment: the form's fields as they are, abodymap one parameter per key (a list repeats its key,nullis left out, a nested map is JSON-encoded). Only the atomnullis left out: any other atom is sent as its name, soundefinedarrives as the text"undefined". A GETbodythat is not a map is not sent, with a console warning.headers-- extra request headers, merged on top.credentials-- cookie/credential mode. Defaultsame_origin.on_error-- a command (or list) run on a non-2xx with no effects body, or a network error.keep_alive-- whentrue, the request completes even if a navigation starts right after it (use case: a POST fired just before navigating away); maps to the browser'sfetch(url, { keepalive: true }). Absent orfalseis the current behaviour (the navigation cancels an in-flight fetch). The browser caps a keepalive request's inflight body at ~64KB.
To send the user elsewhere, return an arizona_js:navigate/1 effect from the
controller (e.g. via arizona_controller:reply_redirect/1) rather than an HTTP
3xx -- a fetch-followed redirect can't drive a SPA navigation.
-spec focus(Selector) -> arizona_effect:cmd() when Selector :: binary().
Focuses the first element matching the selector.
-spec hide(Selector) -> arizona_effect:cmd() when Selector :: binary().
Hides all elements matching the CSS selector.
-spec on_key(Key, Cmd) -> arizona_effect:cmd() when Key :: atom() | [atom()] | binary(), Cmd :: arizona_effect:cmd() | arizona_effect:cmds().
Wraps a command (or list of commands) so it only fires when a key
matching Key is pressed. Key can be an atom (e.g. enter), a
list of atoms (matches any), or a regex pattern as a binary.
Matching is case-insensitive: the client lowercases the browser's
event.key before comparing, so give keys and patterns in lowercase
(enter, escape, arrowdown). An uppercase name or pattern ('Enter',
~"^[A-Z]$") never matches.
-spec patch(Path) -> arizona_effect:cmd() when Path :: binary().
Triggers an in-place SPA navigation (patch) to Path: keeps the current
live view mounted and re-renders it via handle_update/3 instead of
replacing it. The counterpart of navigate/1; use it between routes that
share a root view so live chrome survives the navigation.
-spec patch(Path, Opts) -> arizona_effect:cmd() when Path :: binary(), Opts :: map().
Triggers an in-place SPA navigation (patch) to Path with options. Accepts the
same flash opt as navigate/2 (carried in-process to the patched route's flash
binding, stripped from the client effect) and its replace opt.
An http/https Path on another origin cannot be patched in place: the client
loads it as a page (location.assign, or location.replace with replace). Any
other scheme is dropped with a warning, as for navigate/2.
-spec push_event(Event) -> arizona_effect:cmd() when Event :: binary().
Pushes a named event to the server. The payload is auto-collected from the
triggering element itself, not its siblings: a form trigger contributes its
fields (a form submit passes the form), an input/select/textarea trigger its
value; any other trigger (e.g. a plain button inside a form) sends an empty
payload. Use push_event/2 to attach data explicitly.
-spec push_event(Event, Payload) -> arizona_effect:cmd() when Event :: binary(), Payload :: map().
Pushes a named event with an explicit payload, merged on top of any auto-collected form data.
-spec reload() -> arizona_effect:cmd().
Reloads the current page.
-spec remove_attr(Selector, Attr) -> arizona_effect:cmd() when Selector :: binary(), Attr :: binary().
Removes an attribute from all elements matching the selector.
-spec remove_class(Selector, Class) -> arizona_effect:cmd() when Selector :: binary(), Class :: binary().
Removes a CSS class from all elements matching the selector.
-spec request_pip(ViewId) -> arizona_effect:cmd() when ViewId :: binary().
Moves the view identified by ViewId into a floating Document Picture-in-Picture
window, kept live by the server diff stream. Use inside a web event attribute
(e.g. az-click) -- Document PiP requires a user gesture, so it won't open from
a server-pushed handler effect. The window opens at the browser's default size;
use request_pip/2 to choose a size.
-spec request_pip(ViewId, Opts) -> arizona_effect:cmd() when ViewId :: binary(), Opts :: #{width => pos_integer(), height => pos_integer()}.
Like request_pip/1 but with window options. width/height set the floating
window's initial size in pixels; when omitted the browser picks a default size.
-spec reset_form(Selector) -> arizona_effect:cmd() when Selector :: binary().
Resets all forms matching the selector, clearing the fields the user typed
back to their initial values (HTMLFormElement.reset).
Unlike focus/1/blur/1 (first match only), this broadcasts to every match,
like the DOM-mutating selector effects (toggle, the class ops, set_attr). A
match without a reset() method (a non-form element) is a safe no-op.
Where set_attr(value, ~"") only rewrites the value attribute (leaving the
user-typed value property untouched, and doing nothing at all for a
<textarea> that has no value attribute), this drives the native form reset, so
it clears text inputs and textareas alike. Unlike az-form-reset (which fires
only on a successful submit/fetch), it's imperative: usable both as a web event
command (e.g. {az_click, arizona_js:reset_form(~"#signup")}) and as a handler
effect from handle_event/3.
-spec scroll_to(Selector) -> arizona_effect:cmd() when Selector :: binary().
Scrolls smoothly to the first element matching the selector.
-spec scroll_to(Selector, Opts) -> arizona_effect:cmd() when Selector :: binary(), Opts :: map().
Scrolls to the first element matching the selector with scrollIntoView
options (behavior, block, inline).
Opts merges onto the #{behavior => smooth} default of scroll_to/1, so
tuning only the alignment keeps the smooth scroll; pass #{behavior => auto}
for an instant jump.
-spec select(Selector) -> arizona_effect:cmd() when Selector :: binary().
Selects (highlights) the text of the first element matching the selector, an
<input> or <textarea>, via HTMLInputElement.select -- so a subsequent copy
or overtype acts on the whole field.
Like focus/1/blur/1/scroll_to/1 (first match only). A match without a
select() method is a safe no-op. Usable both as a web event command
(e.g. {az_click, arizona_js:select(~"#token")}) and as a handler effect from
handle_event/3.
-spec set(Key, Value) -> arizona_effect:cmd() when Key :: binary() | atom(), Value :: binary() | boolean() | number().
Sets a client-owned slot (?local) to Value in the closest view of the
triggering element, updating every slot with Key locally with no server
round-trip. Use inside web event attributes like az-click.
-spec set(ViewId, Key, Value) -> arizona_effect:cmd() when ViewId :: binary(), Key :: binary() | atom(), Value :: binary() | boolean() | number().
Like set/2 but targets the slot in the view identified by ViewId.
-spec set_all(Key, Value) -> arizona_effect:cmd() when Key :: binary() | atom(), Value :: binary() | boolean() | number().
Like set/2 but updates the slot in every view on the page (document-wide).
-spec set_attr(Selector, Attr, Value) -> arizona_effect:cmd() when Selector :: binary(), Attr :: binary(), Value :: binary().
Sets an attribute on all elements matching the selector.
-spec set_title(Title) -> arizona_effect:cmd() when Title :: binary().
Sets the document title.
-spec show(Selector) -> arizona_effect:cmd() when Selector :: binary().
Shows all elements matching the CSS selector.
-spec show_modal(Selector) -> arizona_effect:cmd() when Selector :: binary().
Opens the first <dialog> matching the selector as a true modal via
HTMLDialogElement.showModal -- placed in the top layer, with a ::backdrop
and ESC-to-close -- unlike merely setting the open attribute (a non-modal
dialog). A non-dialog match is a safe no-op.
Like focus/1/blur/1/scroll_to/1 (first match only). Usable both as a web
event command (e.g. {az_click, arizona_js:show_modal(~"#confirm")}) and as a
handler effect from handle_event/3.
-spec toggle(Selector) -> arizona_effect:cmd() when Selector :: binary().
Toggles the visibility of all elements matching the CSS selector.
-spec toggle_attr(Selector, Attr) -> arizona_effect:cmd() when Selector :: binary(), Attr :: binary().
Toggles the presence of a bare boolean attribute on all elements matching the
selector: removes it if present, otherwise sets it (e.g. disabled, readonly,
hidden, open). For toggling between two values, use toggle_attr/4.
-spec toggle_attr(Selector, Attr, ValueA, ValueB) -> arizona_effect:cmd() when Selector :: binary(), Attr :: binary(), ValueA :: binary(), ValueB :: binary().
Toggles an attribute between two values on all elements matching the selector: sets
ValueB when the current value is ValueA, otherwise sets ValueA (so any
other current value resolves to ValueA). Useful for one-button toggles like a
password field's type between password and text.
-spec toggle_class(Selector, Class) -> arizona_effect:cmd() when Selector :: binary(), Class :: binary().
Toggles a CSS class on all elements matching the selector.
-spec transition(Cmd) -> arizona_effect:cmd() when Cmd :: arizona_effect:cmd() | arizona_effect:cmds().
Wraps a command (or list of commands) so the DOM change it causes plays inside a
view transition (document.startViewTransition). Works for a client-side effect
(e.g. toggle/1, animated immediately), a navigate/1,2 (the page swap
animates), or a push_event (the resulting server diff animates). A view
transition is not tied to navigation -- it wraps any DOM change. Equivalent to
transition/2 with no options.
-spec transition(Cmd, Opts) -> arizona_effect:cmd() when Cmd :: arizona_effect:cmd() | arizona_effect:cmds(), Opts :: #{types => [binary()]}.
Like transition/1 but with options. types is a list of view-transition type
names activated for the transition (matched by :active-view-transition-type(...)
CSS), letting one stylesheet pick a different animation per call.