Class: Tep::LiveView
- Inherits:
-
Object
- Object
- Tep::LiveView
- Defined in:
- lib/tep/live_view.rb
Class Method Summary collapse
-
.render_page(content_html, ws_path) ⇒ Object
Wrap ‘content_html` in a full HTML page with the client-side bootstrap.
Instance Method Summary collapse
-
#apply_presence_diff_json(json) ⇒ Object
Convenience: same shape as dispatch_event_json.
-
#broadcast_render ⇒ Object
Re-render + publish the result to self.topic via Tep::Broadcast.
-
#dispatch_event_json(json_msg, req) ⇒ Object
Imeth bridge from the WS-side JSON wire format to the subclass’s ‘handle_event`.
-
#handle_event(event, payload, req) ⇒ Object
Receive an event from the client.
-
#handle_presence_diff(diff_json) ⇒ Object
Receive a Tep::Presence diff event.
-
#initialize ⇒ LiveView
constructor
A new instance of LiveView.
-
#mount(req) ⇒ Object
Called when the view boots – once on the initial HTTP GET, once on WS open.
-
#render ⇒ Object
Render the view’s current state to HTML.
-
#topic ⇒ Object
Topic this view is bound to for broadcast fan-out.
Constructor Details
#initialize ⇒ LiveView
Returns a new instance of LiveView.
62 63 64 |
# File 'lib/tep/live_view.rb', line 62 def initialize 0 end |
Class Method Details
.render_page(content_html, ws_path) ⇒ Object
Wrap ‘content_html` in a full HTML page with the client-side bootstrap. The JS:
1. Opens a WS to `ws_path`.
2. On each incoming text frame: parses as HTML and assigns
to #tep-live-root's innerHTML.
3. Intercepts clicks on anything with [data-event] and
sends `{"event": <name>, "payload": <data-payload or "">}`
over the WS.
That’s all the client-side surface for v1. No morphdom, no form-data shipping, no key bindings – “click + re-render” is enough to demonstrate the pattern. Future chunks can swap in morphdom for diff-on-client.
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'lib/tep/live_view.rb', line 202 def self.render_page(content_html, ws_path) "<!doctype html>\n<html><head><meta charset='utf-8'></head><body>\n" + content_html + "\n" + "<script>(function(){\n" + "var ws=new WebSocket((location.protocol==='https:'?'wss://':'ws://')+location.host+'" + ws_path + "');\n" + "ws.onmessage=function(e){var r=document.getElementById('tep-live-root');if(r){r.outerHTML=e.data;}};\n" + "document.addEventListener('click',function(e){\n" + " var t=e.target;while(t&&!t.dataset.event){t=t.parentElement;}\n" + " if(!t)return;\n" + " e.preventDefault();\n" + " ws.send(JSON.stringify({event:t.dataset.event,payload:t.dataset.payload||''}));\n" + "});\n" + "})();</script>\n" + "</body></html>\n" end |
Instance Method Details
#apply_presence_diff_json(json) ⇒ Object
Convenience: same shape as dispatch_event_json. Used by apps wiring a presence-diff intercept loop – they feed received diff JSON strings to this method and it dispatches to handle_presence_diff on the subclass.
Note: automatic server-side intercept of diffs (a background fiber per WS that pulls from the presence diff topic and calls apply_presence_diff_json) is not provided – needs the WS DSL to bridge ‘req` into on_X handler bodies, tracked at OriPekelman/tep#54. Apps that need server-side reaction wire the intercept loop themselves; apps that only need client- side display can skip this and rely on Tep::Broadcast.subscribe_ws delivering the diff JSON straight to the WS for client-side rendering.
158 159 160 161 |
# File 'lib/tep/live_view.rb', line 158 def apply_presence_diff_json(json) handle_presence_diff(json) 0 end |
#broadcast_render ⇒ Object
Re-render + publish the result to self.topic via Tep::Broadcast. Apps call this from handle_event after mutating state; subscribed WSes – including the originating client – receive the new HTML and the client-side bootstrap outerHTML-swaps it into the DOM.
Returns the local-match count from Tep::Broadcast.publish (number of WSes that received the re-render on this worker). 0 here is ambiguous between “no topic configured” (skip) and “topic configured but no subscribers” – the empty-topic path short-circuits without calling publish, so callers that want to distinguish can check ‘topic.length == 0` themselves.
109 110 111 112 113 114 115 |
# File 'lib/tep/live_view.rb', line 109 def broadcast_render t = topic if t.length == 0 return 0 end Tep::Broadcast.publish(t, render) end |
#dispatch_event_json(json_msg, req) ⇒ Object
Imeth bridge from the WS-side JSON wire format to the subclass’s ‘handle_event`. Apps call this from their on_message block:
do |evt|
v.dispatch_event_json(evt.data, req)
ws.text(v.render)
end
Why an imeth and not a cmeth: spinel widens cmeth params to poly (sp_RbVal) when the cmeth has callers across multiple LiveView subclasses, but doesn’t auto-box concrete subclass pointers into the poly slot at the call site. An imeth on the base class dispatches through the typed slot of the subclass instance and avoids the box.
178 179 180 181 182 183 |
# File 'lib/tep/live_view.rb', line 178 def dispatch_event_json(json_msg, req) event = Tep::Json.get_str(json_msg, "event") payload = Tep::Json.get_str(json_msg, "payload") handle_event(event, payload, req) 0 end |
#handle_event(event, payload, req) ⇒ Object
Receive an event from the client. ‘event` and `payload` are strings (the client-side JS sends them as JSON). Subclasses mutate @ivars based on the event; the caller re-renders + sends the new HTML.
84 85 86 |
# File 'lib/tep/live_view.rb', line 84 def handle_event(event, payload, req) 0 end |
#handle_presence_diff(diff_json) ⇒ Object
Receive a Tep::Presence diff event. Subclasses override to update their state when someone joins / leaves / changes status on the bound topic. Default is a no-op so v1 views without presence-awareness compile cleanly.
The diff arrives as the same flat JSON Tep::Presence emits:
{ "kind": "join" | "leave" | "status",
"topic": <topic>,
"principal": <principal_id>,
"ekind": "human" | "agent_for",
"agent_id": <agent_id or empty>,
"fd": <session-id surrogate>,
"since": <unix ts>,
"state": "available" | "busy" | "blocked",
"note": <free text>,
"until_ts": <unix ts or 0> }
Subclasses typically pull a few keys via Tep::Json.get_str / Tep::Json.get_int + update an @presence ivar; then either call broadcast_render (to fan out the new HTML to every subscriber) or just mutate (if the LiveView is the only observer).
140 141 142 |
# File 'lib/tep/live_view.rb', line 140 def handle_presence_diff(diff_json) 0 end |
#mount(req) ⇒ Object
Called when the view boots – once on the initial HTTP GET, once on WS open. Subclasses override to seed @ivars from req.params / req.identity / etc.
69 70 71 |
# File 'lib/tep/live_view.rb', line 69 def mount(req) 0 end |
#render ⇒ Object
Render the view’s current state to HTML. Subclasses override. Wrap your real content in an element with ‘id=“tep-live-root”` so the client-side bootstrap can swap the innerHTML cleanly.
76 77 78 |
# File 'lib/tep/live_view.rb', line 76 def render "<div id='tep-live-root'></div>" end |
#topic ⇒ Object
Topic this view is bound to for broadcast fan-out. Default is the empty string – subclasses override to return a stable id (e.g. “room:” + @id.to_s). When non-empty, the broadcast_render helper publishes re-renders here so every subscribed WS sees the new HTML.
93 94 95 |
# File 'lib/tep/live_view.rb', line 93 def topic "" end |