Skip to content

Instantly share code, notes, and snippets.

@ismasan
Last active March 6, 2025 21:55
Show Gist options
  • Save ismasan/5272b0caf91bea525924d3f1391387c6 to your computer and use it in GitHub Desktop.
Save ismasan/5272b0caf91bea525924d3f1391387c6 to your computer and use it in GitHub Desktop.
Demo progress bar using Ruby, Rack and Datastar
# frozen_string_literal: true
source "https://rubygems.org"
gem 'puma'
gem 'datastar'
require 'bundler/setup'
require 'datastar'
# This is a test Rack endpoint
# With a dynamic progress bar example using Datastar.
# To run:
#
# # install dependencies
# bundle install
# # run this endpoint with Puma server
# bundle exec puma ./progress.ru
#
# Then open http://localhost:9292
#
INDEX = <<~HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Datastar progress bar</title>
<style>
body {#{' '}
padding: 10em;
font-family: Arial, sans-serif;
font-size: 1.5em;
}
</style>
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/[email protected]/bundles/datastar.js"></script>
</head>
<body data-signals="{running: false}">
<button data-attr-disabled="$running" data-on-click="@get('/')">Start</button>
<p>
<span id="progress">waiting</span>
</p>
</body>
<html>
HTML
run do |env|
datastar = Datastar.from_rack_env(env)
.on_connect do |socket|
p ['connect', socket]
end.on_client_disconnect do |socket|
p ['client disconnect', socket]
end.on_error do |error|
p ['exception', error]
puts error.backtrace.join("\n")
end
if datastar.sse?
datastar.stream do |sse|
total = 200
# Replace progress element with progress bar
# bind a `progress` signal to its value
sse.merge_fragments <<~HTML
<progress#{' '}
data-signal-progress="0"#{' '}
id="progress"#{' '}
max="#{total}"#{' '}
data-attr-value="$progress"
>0</progress>
HTML
sse.merge_signals(running: true)
# Move progress bar forward
total.times do |i|
sleep 0.01
sse.merge_signals(progress: i)
end
# We're done!
sse.merge_fragments(%(<strong id="progress">done!</strong>))
sse.merge_signals(running: false, progress: 0)
end
else
[200, { 'content-type' => 'text/html' }, [INDEX]]
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment