Class: Phlex::Reactive::Reply
- Inherits:
-
Object
- Object
- Phlex::Reactive::Reply
- Defined in:
- lib/phlex/reactive/reply.rb
Overview
The ONE door for building an action's reply (issue #182). Component#reply returns one, so an action writes
reply.replace.flash(:error, msg)
There is no constant to qualify (reply is a method, resolved on the
component — so a namespaced component needs no Response = … alias) and no
self to thread (the component is bound).
Reply is NOT a Response and does NOT subclass one: each verb calls the
Response build_* class method, supplying the bound component as the
subject, and returns the real, frozen Response value the endpoint honors. So
immutability, chaining (.flash/.stream/.also/.js/.defer), and render_self?
come from the Response value object untouched — the chain is plain Response
all the way down.
Response remains the value object the endpoint reads; you never construct it directly (its former public class verbs raise a guided rewrite).
Instance Method Summary collapse
-
#append(model = UNSET_ARG, legacy_model = UNSET_ARG, to: UNSET_ARG, **row_kwargs) ⇒ Object
Reactive collections (issue #35) — add/remove a row in a declared reactive_collection, emitting the row stream + the count companion + the empty-state toggle as ONE Response.
-
#defer(component, placeholder: nil, morph: false) ⇒ Object
Defer an expensive segment (issue #165):
reply.defer(Totals.new(...)). -
#initialize(component) ⇒ Reply
constructor
A new instance of Reply.
-
#morph ⇒ Object
Re-render in place via Idiomorph (method="morph") — keeps focus + caret.
- #prepend(model = UNSET_ARG, legacy_model = UNSET_ARG, to: UNSET_ARG, **row_kwargs) ⇒ Object
-
#redirect(url) ⇒ Object
Client-side full navigation (Turbo.visit).
-
#remove(model = UNSET_ARG, legacy_model = UNSET_ARG, from: UNSET_ARG) ⇒ Object
Two forms, dispatched on the PRESENCE of
from:(issue #182 — no sentinel overload): barereply.removeremoves the bound component's own element;reply.remove(model, from: :items)removes a collection row + count + empty-state. -
#replace(morph: false) ⇒ Object
Re-render in place.
-
#streams(*strings) ⇒ Object
Self-targeting again: emit exactly these streams with a TOKEN-ONLY refresh (issue #30) — partial/per-field update, NO full-self replace, so the component's live inputs survive.
-
#update(morph: false) ⇒ Object
Update only inner HTML (preserves the root element + its token attr).
-
#with(*strings) ⇒ Object
Escape hatch / multi-stream root: zero or more raw turbo-stream strings.
Constructor Details
#initialize(component) ⇒ Reply
Returns a new instance of Reply.
24 25 26 |
# File 'lib/phlex/reactive/reply.rb', line 24 def initialize(component) @component = component end |
Instance Method Details
#append(model = UNSET_ARG, legacy_model = UNSET_ARG, to: UNSET_ARG, **row_kwargs) ⇒ Object
Reactive collections (issue #35) — add/remove a row in a declared
reactive_collection, emitting the row stream + the count companion +
the empty-state toggle as ONE Response. The bound component is the
container (it carries the declaration + size resolver). The collection is
named by the to:/from: keyword (issue #182 — reads as Ruby, and a
to: present unambiguously means "a collection row"):
def add_item(...) = (item = @list.items.create!(...); reply.append(item, to: :items))
def remove_item(id:) = (@list.items.find(id).destroy!; reply.remove(id, from: :items))
model is the row's record; remove also accepts the row's dom-id string.
Extra kwargs (issue #186) thread to the row component's new(model:, **row_kwargs)
— e.g. reply.append(item, to: :items, autofocus: true) → ItemRow.new(item:,
autofocus: true). Additive: a no-kwarg call is byte-identical to before.
62 63 64 |
# File 'lib/phlex/reactive/reply.rb', line 62 def append(model = UNSET_ARG, legacy_model = UNSET_ARG, to: UNSET_ARG, **row_kwargs) collection_build!(:build_collection_append, :append, model, legacy_model, to, row_kwargs) end |
#defer(component, placeholder: nil, morph: false) ⇒ Object
Defer an expensive segment (issue #165): reply.defer(Totals.new(...)).
With NO prior verb, the bound component's token is refreshed via a tiny
token-only stream (reply.streams), NOT a full self-replace: a full
replace would re-render the acting component SYNCHRONOUSLY on the request
thread — and if you defer your OWN subject (reply.defer(self)), that is
the very render you meant to move OFF the critical path, so the defer
would be silently defeated (a double render). The token-only refresh
rolls the signed identity forward without any synchronous render; the
deferred segment's directive rides alongside. Chain off a verb when you
DO want a synchronous piece too:
reply.streams(volume_cell).defer(SessionTotals.new(workout: @workout))
reply.replace.defer(Totals.new(order: @order)) # explicit self re-render
123 124 125 |
# File 'lib/phlex/reactive/reply.rb', line 123 def defer(component, placeholder: nil, morph: false) Response.build_streams(@component).defer(component, placeholder:, morph:) end |
#morph ⇒ Object
Re-render in place via Idiomorph (method="morph") — keeps focus + caret.
37 38 39 |
# File 'lib/phlex/reactive/reply.rb', line 37 def morph Response.build_morph(@component) end |
#prepend(model = UNSET_ARG, legacy_model = UNSET_ARG, to: UNSET_ARG, **row_kwargs) ⇒ Object
66 67 68 |
# File 'lib/phlex/reactive/reply.rb', line 66 def prepend(model = UNSET_ARG, legacy_model = UNSET_ARG, to: UNSET_ARG, **row_kwargs) collection_build!(:build_collection_prepend, :prepend, model, legacy_model, to, row_kwargs) end |
#redirect(url) ⇒ Object
Client-side full navigation (Turbo.visit). Pass a *_url.
93 94 95 |
# File 'lib/phlex/reactive/reply.rb', line 93 def redirect(url) Response.build_redirect(url) end |
#remove(model = UNSET_ARG, legacy_model = UNSET_ARG, from: UNSET_ARG) ⇒ Object
Two forms, dispatched on the PRESENCE of from: (issue #182 — no sentinel
overload): bare reply.remove removes the bound component's own element;
reply.remove(model, from: :items) removes a collection row + count +
empty-state. model may be the record or the row's dom-id string.
reply.remove # remove the bound component's element
reply.remove(model, from: :items) # remove a collection row
77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/phlex/reactive/reply.rb', line 77 def remove(model = UNSET_ARG, legacy_model = UNSET_ARG, from: UNSET_ARG) reject_legacy_form!(:remove, model, legacy_model) # Bare `reply.remove` (no positional, no from:) removes self. return Response.build_remove(@component) if from.equal?(UNSET_ARG) && model.equal?(UNSET_ARG) if from.equal?(UNSET_ARG) raise ArgumentError, "reply.remove(model, …) needs a source collection — reply.remove(model, from: :name)" end reject_symbol_first!(:remove, model, :from, from) Response.build_collection_remove(@component, from, resolve_row_arg(model)) end |
#replace(morph: false) ⇒ Object
Re-render in place. morph: true morphs the subtree (preserves the
focused input + caret) instead of an outerHTML swap — see #morph.
32 33 34 |
# File 'lib/phlex/reactive/reply.rb', line 32 def replace(morph: false) Response.build_replace(@component, morph:) end |
#streams(*strings) ⇒ Object
Self-targeting again: emit exactly these streams with a TOKEN-ONLY refresh (issue #30) — partial/per-field update, NO full-self replace, so the component's live inputs survive. The bound component supplies the token.
105 106 107 |
# File 'lib/phlex/reactive/reply.rb', line 105 def streams(*strings) Response.build_streams(@component, *strings) end |
#update(morph: false) ⇒ Object
Update only inner HTML (preserves the root element + its token attr).
morph: true morphs the inner HTML in place (issue #113) so a
cross-tab/per-field update keeps a focused input's caret.
44 45 46 |
# File 'lib/phlex/reactive/reply.rb', line 44 def update(morph: false) Response.build_update(@component, morph:) end |
#with(*strings) ⇒ Object
Escape hatch / multi-stream root: zero or more raw turbo-stream strings.
98 99 100 |
# File 'lib/phlex/reactive/reply.rb', line 98 def with(*strings) Response.build_with(*strings) end |