Class: Phlex::Reactive::Reply
- Inherits:
-
Object
- Object
- Phlex::Reactive::Reply
- Defined in:
- lib/phlex/reactive/reply.rb
Overview
A component-bound facade over Response — the surface an action body uses to control its reply. Component#reply returns one, so an action writes
reply.replace.flash(:error, msg)
instead of
Phlex::Reactive::Response.replace(self).flash(:error, msg)
Two warts disappear: 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 forwards to the Response class method, supplying the bound component as the subject, and returns the real, frozen Response value the endpoint already honors. So immutability, chaining (.flash/.stream/.also_update/.also_replace), and render_self? are inherited untouched — and there is no "verb after a chained Response" asymmetry, because the chain is plain Response all the way down.
Response remains the public value object (it's what the endpoint reads); it is simply an internal detail you rarely name directly now.
Instance Method Summary collapse
-
#append(name, model) ⇒ 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(name, model) ⇒ Object
-
#redirect(url) ⇒ Object
Client-side full navigation (Turbo.visit).
-
#remove(name = UNSET, model = UNSET) ⇒ Object
Two forms: reply.remove # remove the bound component's own element reply.remove(:items, model) # remove a collection row + count + empty.
-
#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.
34 35 36 |
# File 'lib/phlex/reactive/reply.rb', line 34 def initialize(component) @component = component end |
Instance Method Details
#append(name, model) ⇒ 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).
def add_item(...) = (item = @list.items.create!(...); reply.append(:items, item))
def remove_item(id:) = (@list.items.find(id).destroy!; reply.remove(:items, id))
model is the row's record; remove also accepts the row's dom-id string.
67 68 69 |
# File 'lib/phlex/reactive/reply.rb', line 67 def append(name, model) Response.collection_append(@component, name, model) 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 (Response.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
117 118 119 |
# File 'lib/phlex/reactive/reply.rb', line 117 def defer(component, placeholder: nil, morph: false) Response.streams(@component).defer(component, placeholder:, morph:) end |
#morph ⇒ Object
Re-render in place via Idiomorph (method="morph") — keeps focus + caret.
47 48 49 |
# File 'lib/phlex/reactive/reply.rb', line 47 def morph Response.morph(@component) end |
#prepend(name, model) ⇒ Object
71 72 73 |
# File 'lib/phlex/reactive/reply.rb', line 71 def prepend(name, model) Response.collection_prepend(@component, name, model) end |
#redirect(url) ⇒ Object
Client-side full navigation (Turbo.visit). Pass a *_url.
87 88 89 |
# File 'lib/phlex/reactive/reply.rb', line 87 def redirect(url) Response.redirect(url) end |
#remove(name = UNSET, model = UNSET) ⇒ Object
Two forms:
reply.remove # remove the bound component's own element
reply.remove(:items, model) # remove a collection row + count + empty
78 79 80 81 82 |
# File 'lib/phlex/reactive/reply.rb', line 78 def remove(name = UNSET, model = UNSET) return Response.remove(@component) if name.equal?(UNSET) Response.collection_remove(@component, name, 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.
42 43 44 |
# File 'lib/phlex/reactive/reply.rb', line 42 def replace(morph: false) Response.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.
99 100 101 |
# File 'lib/phlex/reactive/reply.rb', line 99 def streams(*strings) Response.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.
54 55 56 |
# File 'lib/phlex/reactive/reply.rb', line 54 def update(morph: false) Response.update(@component, morph:) end |