Class: Datastar::Dispatcher

Inherits:
Object
  • Object
show all
Defined in:
lib/datastar/dispatcher.rb

Overview

The Dispatcher encapsulates the logic of handling a request and building a response with streaming datastar messages. You’ll normally instantiate a Dispatcher in your controller action of Rack handler via Datastar.new.

Examples:


datastar = Datastar.new(request:, response:, view_context: self)

# One-off fragment response
datastar.merge_fragments(template)

# Streaming response with multiple messages
datastar.stream do |sse|
  sse.merge_fragments(template)
  10.times do |i|
    sleep 0.1
    sse.merge_signals(count: i)
  end
end

Constant Summary collapse

BLANK_BODY =
[].freeze
SSE_CONTENT_TYPE =
'text/event-stream'
HTTP_ACCEPT =
'HTTP_ACCEPT'
HTTP1 =
'HTTP/1.1'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(request:, response: nil, view_context: nil, executor: Datastar.config.executor, error_callback: Datastar.config.error_callback, finalize: Datastar.config.finalize) ⇒ Dispatcher

Returns a new instance of Dispatcher.

Parameters:

  • request (Hash)

    a customizable set of options

  • response (Hash) (defaults to: nil)

    a customizable set of options

  • view_context (Hash) (defaults to: nil)

    a customizable set of options

  • executor (Hash) (defaults to: Datastar.config.executor)

    a customizable set of options

  • error_callback (Hash) (defaults to: Datastar.config.error_callback)

    a customizable set of options

  • finalize (Hash) (defaults to: Datastar.config.finalize)

    a customizable set of options

Options Hash (request:):

  • the (Rack::Request)

    request object

Options Hash (response:):

  • the (Rack::Response, nil)

    response object

Options Hash (view_context:):

  • the (Object, nil)

    view context object, to use when rendering templates. Ie. a controller, or Sinatra app.

Options Hash (executor:):

  • the (Object)

    executor object to use for managing threads and queues

Options Hash (error_callback:):

  • the (Proc)

    callback to call when an error occurs

Options Hash (finalize:):

  • the (Proc)

    callback to call when the response is finalized



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/datastar/dispatcher.rb', line 38

def initialize(
  request:,
  response: nil,
  view_context: nil,
  executor: Datastar.config.executor,
  error_callback: Datastar.config.error_callback,
  finalize: Datastar.config.finalize
)
  @on_connect = []
  @on_client_disconnect = []
  @on_server_disconnect = []
  @on_error = [error_callback]
  @finalize = finalize
  @streamers = []
  @queue = nil
  @executor = executor
  @view_context = view_context
  @request = request
  @response = Rack::Response.new(BLANK_BODY, 200, response&.headers || {})
  @response.content_type = SSE_CONTENT_TYPE
  @response.headers['Cache-Control'] = 'no-cache'
  @response.headers['Connection'] = 'keep-alive' if @request.env['SERVER_PROTOCOL'] == HTTP1
  # Disable response buffering in NGinx and other proxies
  @response.headers['X-Accel-Buffering'] = 'no'
  @response.delete_header 'Content-Length'
  @executor.prepare(@response)
end

Instance Attribute Details

#requestObject (readonly)

Returns the value of attribute request.



30
31
32
# File 'lib/datastar/dispatcher.rb', line 30

def request
  @request
end

#responseObject (readonly)

Returns the value of attribute response.



30
31
32
# File 'lib/datastar/dispatcher.rb', line 30

def response
  @response
end

Instance Method Details

#execute_script(script, options = BLANK_OPTIONS) ⇒ Object

One-off execute script in the UI See data-star.dev/reference/sse_events#datastar-execute-script

Examples:


datastar.execute_scriprt(%(alert('Hello World!'))

Parameters:

  • script (String)

    the script to execute

  • options (Hash) (defaults to: BLANK_OPTIONS)

    the options to send with the message



182
183
184
185
186
# File 'lib/datastar/dispatcher.rb', line 182

def execute_script(script, options = BLANK_OPTIONS)
  stream do |sse|
    sse.execute_script(script, options)
  end
end

#merge_fragments(fragments, options = BLANK_OPTIONS) ⇒ Object

Examples:


datastar.merge_fragments(%(<div id="foo">\n<span>hello</span>\n</div>\n))
# or a Phlex view object
datastar.merge_fragments(UserComponet.new)

Parameters:

  • fragments (String, #call(view_context: Object) => Object)

    the HTML fragment or object

  • options (Hash) (defaults to: BLANK_OPTIONS)

    the options to send with the message



126
127
128
129
130
# File 'lib/datastar/dispatcher.rb', line 126

def merge_fragments(fragments, options = BLANK_OPTIONS)
  stream do |sse|
    sse.merge_fragments(fragments, options)
  end
end

#merge_signals(signals, options = BLANK_OPTIONS) ⇒ Object

One-off merge signals in the UI See data-star.dev/reference/sse_events#datastar-merge-signals

Examples:


datastar.merge_signals(count: 1, toggle: true)

Parameters:

  • signals (Hash)

    signals to merge

  • options (Hash) (defaults to: BLANK_OPTIONS)

    the options to send with the message



154
155
156
157
158
# File 'lib/datastar/dispatcher.rb', line 154

def merge_signals(signals, options = BLANK_OPTIONS)
  stream do |sse|
    sse.merge_signals(signals, options)
  end
end

#on_client_disconnect(callable = nil, &block) ⇒ self

Register a callback for client disconnection Ex. when the browser is closed mid-stream

Parameters:

  • callable (Proc, nil) (defaults to: nil)

    the callback to call

Returns:

  • (self)


86
87
88
89
# File 'lib/datastar/dispatcher.rb', line 86

def on_client_disconnect(callable = nil, &block)
  @on_client_disconnect << (callable || block)
  self
end

#on_connect(callable = nil) {|sse| ... } ⇒ self

Register an on-connect callback Triggered when the request is handled

Parameters:

  • callable (Proc, nil) (defaults to: nil)

    the callback to call

Yield Parameters:

Returns:

  • (self)


77
78
79
80
# File 'lib/datastar/dispatcher.rb', line 77

def on_connect(callable = nil, &block)
  @on_connect << (callable || block)
  self
end

#on_error(callable = nil, &block) ⇒ self

Register a callback server-side exceptions Ex. when one of the server threads raises an exception

Parameters:

  • callable (Proc, nil) (defaults to: nil)

    the callback to call

Returns:

  • (self)


104
105
106
107
# File 'lib/datastar/dispatcher.rb', line 104

def on_error(callable = nil, &block)
  @on_error << (callable || block)
  self
end

#on_server_disconnect(callable = nil, &block) ⇒ self

Register a callback for server disconnection Ex. when the server finishes serving the request

Parameters:

  • callable (Proc, nil) (defaults to: nil)

    the callback to call

Returns:

  • (self)


95
96
97
98
# File 'lib/datastar/dispatcher.rb', line 95

def on_server_disconnect(callable = nil, &block)
  @on_server_disconnect << (callable || block)
  self
end

#redirect(url) ⇒ Object

Send an execute_script event to change window.location

Parameters:

  • url (String)

    the URL or path to redirect to



192
193
194
195
196
# File 'lib/datastar/dispatcher.rb', line 192

def redirect(url)
  stream do |sse|
    sse.redirect(url)
  end
end

#remove_fragments(selector, options = BLANK_OPTIONS) ⇒ Object

One-off remove fragments from the UI See data-star.dev/reference/sse_events#datastar-remove-fragments

Examples:


datastar.remove_fragments('#users')

Parameters:

  • selector (String)

    a CSS selector for the fragment to remove

  • options (Hash) (defaults to: BLANK_OPTIONS)

    the options to send with the message



140
141
142
143
144
# File 'lib/datastar/dispatcher.rb', line 140

def remove_fragments(selector, options = BLANK_OPTIONS)
  stream do |sse|
    sse.remove_fragments(selector, options)
  end
end

#remove_signals(paths, options = BLANK_OPTIONS) ⇒ Object

One-off remove signals from the UI See data-star.dev/reference/sse_events#datastar-remove-signals

Examples:


datastar.remove_signals(['user.name', 'user.email'])

Parameters:

  • paths (Array<String>)

    object paths to the signals to remove

  • options (Hash) (defaults to: BLANK_OPTIONS)

    the options to send with the message



168
169
170
171
172
# File 'lib/datastar/dispatcher.rb', line 168

def remove_signals(paths, options = BLANK_OPTIONS)
  stream do |sse|
    sse.remove_signals(paths, options)
  end
end

#signalsHash

Parse and returns Datastar signals sent by the client. See data-star.dev/guide/getting_started#data-signals

Returns:

  • (Hash)


112
113
114
# File 'lib/datastar/dispatcher.rb', line 112

def signals
  @signals ||= parse_signals(request).freeze
end

#sse?Boolean

Check if the request accepts SSE responses

Returns:

  • (Boolean)


68
69
70
# File 'lib/datastar/dispatcher.rb', line 68

def sse?
  @request.get_header(HTTP_ACCEPT) == SSE_CONTENT_TYPE
end

#stream(streamer = nil) {|sse| ... } ⇒ Object

Start a streaming response A generator object is passed to the block The generator supports all the Datastar methods listed above (it’s the same type) But you can call them multiple times to send multiple messages down an open SSE connection. This methods also captures exceptions raised in the block and triggers any error callbacks. Client disconnection errors trigger the @on_client_disconnect callbacks. Finally, when the block is done streaming, the @on_server_disconnect callbacks are triggered.

When multiple streams are scheduled this way, this SDK will spawn each block in separate threads (or fibers, depending on executor) and linearize their writes to the connection socket As a last step, the finalize callback is called with the view context and the response This is so that different frameworks can setup their responses correctly. By default, the built-in Rack finalzer just returns the resposne Array which can be used by any Rack handler. On Rails, the Rails controller response is set to this objects streaming response.

Examples:


datastar.stream do |sse|
  total = 300
  sse.merge_fragments(%(<progress data-signal-progress="0" id="progress" max="#{total}" data-attr-value="$progress">0</progress>))
  total.times do |i|
    sse.merge_signals(progress: i)
  end
end

datastar.stream do |sse|
  # update things here
end

datastar.stream do |sse|
  # more concurrent updates here
end

Parameters:

Yield Parameters:

Returns:

  • (Object)

    depends on the finalize callback



237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/datastar/dispatcher.rb', line 237

def stream(streamer = nil, &block)
  streamer ||= block
  @streamers << streamer

  body = if @streamers.size == 1
    stream_one(streamer) 
  else
    stream_many(streamer) 
  end

  @response.body = body
  @finalize.call(@view_context, @response)
end