Real-Time Views
This guide explains how to implement real-time interfaces with Live::View.
What is Live::View?
Live::View enables real-time, interactive web interfaces using WebSocket connections. It allows you to update the DOM in real-time without JavaScript, making it perfect for dynamic content that changes frequently.
When to use Live::View:
- Real-time dashboards and status displays.
 - Interactive forms with live validation.
 - Live updating content (clocks, counters, progress bars).
 - Simple games and interactive applications.
 
When NOT to use Live::View:
- Static content that doesn't change.
 - Complex client-side interactions requiring heavy JavaScript.
 
Setup
Import Maps Configuration
Use bin/importmap to install the required JavaScript packages:
> bin/importmap pin @socketry/live
Pinning "@socketry/live" to vendor/javascript/@socketry/live.js via download from https://ga.jspm.io/npm:@socketry/live@0.14.0/Live.js
Pinning "morphdom" to vendor/javascript/morphdom.js via download from https://ga.jspm.io/npm:morphdom@2.7.7/dist/morphdom-esm.js
JavaScript Setup
Create app/javascript/live.js to initialize Live::View:
import {Live} from "@socketry/live"
window.live = Live.start()
Pin your local live.js file in config/importmap.rb:
pin "live"
Key Points:
- Import maps handle the 
@socketry/livepackage automatically. - The 
live.jsfile starts theLive::Viewclient connection. pin "live"makes your locallive.jsfile importable in view templates.window.livemakes the Live connection globally available.
Basic Implementation: Live Clock
Live::View Class
Create a Live::View class that handles the real-time logic:
require "live"
class ClockTag < Live::View
	def initialize(...)
		super
	end
	
	def bind(page)
		@task ||= start_clock
	end
	
	def close
		if task = @task
			@task = nil
			task.stop
		end
	end
	
	def start_clock
		Async do
			while true
				sleep 1
				self.update!
			end
		end
	end
	
	def forward_event(name)
		"event.preventDefault(); live.forwardEvent(#{JSON.dump(@id)}, event, {name: #{name.inspect}})"
	end
	
	def render(builder)
		builder.tag(:div, class: "clock-container") do
			builder.tag(:h2) {builder.text("Live Clock")}
			builder.tag(:div, id: "clock", class: "clock-display") do
				builder.text(Time.now.strftime("%H:%M:%S"))
			end
		end
	end
end
Key Points:
- Inherit from 
Live::Viewto get real-time capabilities. - Use 
Asyncblocks for background tasks. - Use 
self.update!to queue a full re-render. - Define 
forward_eventmethod to handle user interactions. - The 
rendermethod defines the initial HTML structure. 
Controller
Create a controller to handle the Live::View connection:
require "async/websocket/adapters/rails"
class ClockController < ApplicationController
	RESOLVER = Live::Resolver.allow(ClockTag)
	
	def index
		@tag = ClockTag.new("clock")
	end
	
	skip_before_action :verify_authenticity_token, only: :live
	
	def live
		self.response = Async::WebSocket::Adapters::Rails.open(request) do |connection|
			Live::Page.new(RESOLVER).run(connection)
		end
	end
end
Key Points:
- Use 
Live::Resolver.allowto whitelist yourLive::Viewsub-classes. - Skip CSRF verification for the WebSocket endpoint.
 - Use 
Async::WebSocket::Adapters::Rails.openfor WebSocket handling. Live::Page.new(RESOLVER).run(connection)handles theLive::Viewprotocol.
View Template
Create a view template that renders the Live::View:
<h1>⏰ Live Clock Example</h1>
<p>This clock updates every second using Live::View.</p>
<%= javascript_import_module_tag "live" %>
<div class="clock-wrapper">
	<%= raw @tag.to_html %>
</div>
<style>
.clock-container {
	text-align: center;
	padding: 2rem;
	border: 2px solid #ddd;
	border-radius: 8px;
	margin: 2rem 0;
}
.clock-display {
	font-size: 3rem;
	font-family: 'Monaco', 'Consolas', monospace;
	color: #333;
	background: #f0f0f0;
	padding: 1rem;
	border-radius: 4px;
	margin-top: 1rem;
}
</style>
Key Points:
- Use 
<%= javascript_import_module_tag "live" %>to load Live::View on specific pages. - Use 
raw @tag.to_htmlto render the Live::View component. - The Live::View will automatically connect via WebSocket.
 - This loads Live::View only on pages that need it, not globally.
 
Routing Configuration
Add routes to your config/routes.rb:
Rails.application.routes.draw do
	# Live Clock Example:
	get "clock/index"
	connect "clock/live"
end
Key Points:
- Use the Rails 8 
connecthelper for WebSocket routes. - The 
liveaction handles the WebSocket connection. 
Live::View Class with User Interaction
class CounterTag < Live::View
	def initialize(count: 0)
		super
		
		# @data is persisted on the tag in `data-` attributes.
		@data["count"] = @data.fetch("count", count).to_i
	end
	
	def handle(event)
		case event[:type]
		when "click"
			case event.dig(:detail, :name)
			when "increment"
				@data["count"] += 1
			when "decrement"
				@data["count"] -= 1
			end
			
			update_counter
		end
	end
	
	def update_counter
		self.replace("#counter") do |builder|
			builder.tag(:div, id: "counter", class: "counter-display") do
				builder.text(@data["count"].to_s)
			end
		end
	end
	
	def forward_event(name)
		"event.preventDefault(); live.forwardEvent(#{JSON.dump(@id)}, event, {name: #{name.inspect}})"
	end
	
	def render(builder)
		builder.tag(:div, class: "counter-container") do
			builder.tag(:h2) {builder.text("Live Counter")}
			
			builder.tag(:div, id: "counter", class: "counter-display") do
				builder.text(@count.to_s)
			end
			
			builder.tag(:div, class: "counter-buttons") do
				builder.tag(:button, onclick: forward_event("decrement")) do
					builder.text("-")
				end
				builder.tag(:button, onclick: forward_event("increment")) do
					builder.text("+")
				end
			end
		end
	end
end
Key Points:
- Define 
forward_event(orforward_click, etc) methods to generate JavaScript for event handling. live.forwardEvent(...)on the client side invokeshandle(event)on the server side.- Update the DOM in response to user actions.
 - Maintain persistent state in 
@data.