LiveSourceLivePage

class Page

Represents a connected client page with bound dynamic content areas.

Definitions

def initialize(resolver)

Signature

parameter resolver Live::Resolver

Used to resolve client-side elements to server-side element instances.

Implementation

def initialize(resolver)
	@resolver = resolver
	
	@elements = {}
	@attached = {}
	
	@updates = Async::Queue.new
end

def bind(element)

Bind a client-side element to a server side element.

Signature

parameter element Live::Element

The element to bind.

Implementation

def bind(element)
	@elements[element.id] = element
	
	element.bind(self)
end

def attach(element)

Attach a pre-existing element to the page, so that it may later be bound to this exact instance. You must later detach the element when it is no longer needed.

Implementation

def attach(element)
	@attached[element.id] = element
end

def resolve(id, data = {})

Resolve a client-side element to a server side instance.

Signature

parameter id String

The unique identifier within the page.

parameter data Hash

The data associated with the element, typically stored as data- attributes.

Implementation

def resolve(id, data = {})
	@attached.fetch(id) do
		@resolver.call(id, data)
	end
end

def handle(id, event)

Handle an event from the client. If the element could not be found, it is silently ignored.

Signature

parameter id String

The unique identifier of the element which forwarded the event.

parameter event String

The type of the event.

returns Object

The result of the element handler, if the element was found.

returns Nil

If the element could not be found.

Implementation

def handle(id, event)
	if element = @elements[id]
		return element.handle(event)
	else
		Console.warn(self, "Could not handle event:", id:, event:)
	end
	
	return nil
end

def process_message(message)

Process a single incoming message from the network.

Implementation

def process_message(message)
	case message[0]
	when "bind"
		# Bind a client-side element to a server-side element.
		if element = self.resolve(message[1], message[2])
			self.bind(element)
		else
			Console.warn(self, "Could not resolve element:", message)
			self.enqueue(["error", message[1], "Could not resolve element!"])
		end
	when "unbind"
		# Unbind a client-side element from a server-side element.
		if element = @elements.delete(message[1])
			element.close unless @attached.key?(message[1])
		else
			Console.warn(self, "Could not unbind element:", message)
			self.enqueue(["error", message[1], "Could not unbind element!"])
		end
	when "event"
		# Handle an event from the client.
		self.handle(message[1], message[2])
	else
		Console.warn(self, "Unhandled message:", message)
	end
end

def run(connection, keep_alive: 10)

Run the event handling loop with the given websocket connection.

Signature

parameter connection Async::WebSocket::Connection

Implementation

def run(connection, keep_alive: 10)
	Sync do |task|
		last_update = Async::Clock.now
		
		queue_task = task.async do
			while update = @updates.dequeue
				update.send(connection)
				
				# Flush the output if there are no more updates:
				if @updates.empty?
					connection.flush
				end
			end
		end
		
		keep_alive_task = task.async do
			while true
				sleep(keep_alive)
				
				duration = Async::Clock.now - last_update
				
				# We synchronize all writes to the update queue:
				if duration > keep_alive
					@updates.enqueue(::Protocol::WebSocket::PingMessage.new)
				end
			end
		end
		
		while message = connection.read
			last_update = Async::Clock.now
			process_message(message.parse)
		end
	ensure
		keep_alive_task&.stop
		
		self.close
		
		queue_task&.stop
	end
end