How do I dynamically update views when objects change
Hello,
I have objects called lockouts. I have several views where the users lockouts are listed or displayed. I want to update those views without having to refresh the page. So if an attribute of the record changes, it should update, if it is deleted, it should disappear. If a new one is created, it should appear. Bonus would be a brief highlight or flash if something was new or updated. Is it done with actioncable? is there a lesson to watch for that?
Yeah, behind the scenes you'd use ActionCable, but Turbo morphing is what will make that easy. You'll add "broadcasts_refreshes" to the models to have them broadcast a page refresh with those updates automatically anytime the database records change. We don't have a lesson on that just yet but I'll make one for you!
I think i set it up correctly, but my view is not updating.
on the view i have <%= turbo_stream_from @lockouts %>
application.html.erb
<%= turbo_refreshes_with method: :morph, scroll: :preserve %>
lockout.rb
belongs_to :status
belongs_to :user, touch: true
has_one_attached :technician
has_many :tag_audits
has_one_attached :lockout_tag
broadcasts_refreshes
broadcasts_refreshes
actually broadcasts to a couple different streams:
<%= turbo_stream_from :lockouts %>
<%= turbo_stream_from @lockout %>
Seems like it is sending broadcasts. I tried both of the streams and none of them are updating.
Here is what the terminal is showing for the updates
17:20:05 web.1 | [ActiveJob] [Turbo::Streams::BroadcastStreamJob] [d9c68e70-38df-43f7-8333-1a0ec7911272] Performing Turbo::Streams::BroadcastStreamJob (Job ID: d9c68e70-38df-43f7-8333-1a0ec7911272) from Async(default) enqueued at 2025-02-15T22:20:05.251787000Z with arguments: "Z2lkOi8vbG9ja291dGlkcy9Vc2VyLzE", {:content=>""}
17:20:05 web.1 | [ActiveJob] [Turbo::Streams::BroadcastStreamJob] [d9c68e70-38df-43f7-8333-1a0ec7911272] [ActionCable] Broadcasting to Z2lkOi8vbG9ja291dGlkcy9Vc2VyLzE: ""
17:20:05 web.1 | [ActiveJob] [Turbo::Streams::BroadcastStreamJob] [c0094aca-09c3-42ef-9571-290e132ebaec] Performing Turbo::Streams::BroadcastStreamJob (Job ID: c0094aca-09c3-42ef-9571-290e132ebaec) from Async(default) enqueued at 2025-02-15T22:20:05.253297000Z with arguments: "Z2lkOi8vbG9ja291dGlkcy9Mb2Nrb3V0LzEwMw", {:content=>""}
17:20:05 web.1 | [ActiveJob] [Turbo::Streams::BroadcastStreamJob] [c0094aca-09c3-42ef-9571-290e132ebaec] [ActionCable] Broadcasting to Z2lkOi8vbG9ja291dGlkcy9Mb2Nrb3V0LzEwMw: ""
17:20:05 web.1 | [ActiveJob] [Turbo::Streams::BroadcastStreamJob] [d9c68e70-38df-43f7-8333-1a0ec7911272] Performed Turbo::Streams::BroadcastStreamJob (Job ID: d9c68e70-38df-43f7-8333-1a0ec7911272) from Async(default) in 1.47ms
17:20:05 web.1 | [ActiveJob] [Turbo::Streams::BroadcastStreamJob] [c0094aca-09c3-42ef-9571-290e132ebaec] Performed Turbo::Streams::BroadcastStreamJob (Job ID: c0094aca-09c3-42ef-9571-290e132ebaec) from Async(default) in 1.34ms
17:20:05 web.1 | [ActiveJob] [Turbo::Streams::BroadcastStreamJob] [c8caed49-4543-4f9c-9225-c7a948225609] Performing Turbo::Streams::BroadcastStreamJob (Job ID: c8caed49-4543-4f9c-9225-c7a948225609) from Async(default) enqueued at 2025-02-15T22:20:05.280683000Z with arguments: "Z2lkOi8vbG9ja291dGlkcy9Mb2Nrb3V0LzEwMw", {:content=>""}
17:20:05 web.1 | [ActiveJob] [Turbo::Streams::BroadcastStreamJob] [d6a16ce1-30af-4b32-a8c7-fc6ed8df440f] Performing Turbo::Streams::BroadcastStreamJob (Job ID: d6a16ce1-30af-4b32-a8c7-fc6ed8df440f) from Async(default) enqueued at 2025-02-15T22:20:05.281233000Z with arguments: "Z2lkOi8vbG9ja291dGlkcy9Vc2VyLzE", {:content=>""}
17:20:05 web.1 | [ActiveJob] [Turbo::Streams::BroadcastStreamJob] [c8caed49-4543-4f9c-9225-c7a948225609] [ActionCable] Broadcasting to Z2lkOi8vbG9ja291dGlkcy9Mb2Nrb3V0LzEwMw: ""
17:20:05 web.1 | [ActiveJob] [Turbo::Streams::BroadcastStreamJob] [d6a16ce1-30af-4b32-a8c7-fc6ed8df440f] [ActionCable] Broadcasting to Z2lkOi8vbG9ja291dGlkcy9Vc2VyLzE: ""
17:20:05 web.1 | [ActiveJob] [Turbo::Streams::BroadcastStreamJob] [c8caed49-4543-4f9c-9225-c7a948225609] Performed Turbo::Streams::BroadcastStreamJob (Job ID: c8caed49-4543-4f9c-9225-c7a948225609) from Async(default) in 0.88ms
17:20:05 web.1 | [ActiveJob] [Turbo::Streams::BroadcastStreamJob] [d6a16ce1-30af-4b32-a8c7-fc6ed8df440f] Performed Turbo::Streams::BroadcastStreamJob (Job ID: d6a16ce1-30af-4b32-a8c7-fc6ed8df440f) from Async(default) in 1.01ms
Here is one of the views
<!DOCTYPE html>
<%= turbo_stream_from :lockouts %>
Lockout Board
<br>
body {<br>
background-color: #A9A9A9; /* Medium gray background <em>/<br>
font-family: Arial, sans-serif;<br>
display: flex;<br>
flex-direction: column;<br>
height: 100vh;<br>
margin: 0;<br>
}<br>
.header {<br>
display: flex;<br>
align-items: center;<br>
justify-content: space-between; /</em> Space out elements equally <em>/<br>
padding: 10px;<br>
height: 10%;<br>
}<br>
.logo, .current-lockouts, .rectangles, .circle, .date-time {<br>
border: 2px solid #909090; /</em> Medium border <em>/<br>
}<br>
.logo {<br>
border-radius: 50%;<br>
width: 75px;<br>
height: 75px;<br>
display: flex;<br>
align-items: center;<br>
justify-content: center;<br>
background-color: #000; /</em> Placeholder for logo <em>/<br>
}<br>
.current-lockouts {<br>
background-color: #FFD700; /</em> Banana yellow <em>/<br>
color: #000;<br>
padding: 10px;<br>
margin-left: 10px;<br>
border-radius: 5px;<br>
}<br>
.rectangles, .circle, .date-time {<br>
background-color: #D3D3D3; /</em> Light gray <em>/<br>
padding: 10px;<br>
margin-left: 10px;<br>
border-radius: 5px;<br>
}<br>
.circle {<br>
border-radius: 50%;<br>
width: 50px;<br>
height: 50px;<br>
display: flex;<br>
align-items: center;<br>
justify-content: center;<br>
}<br>
.date-time {<br>
display: flex;<br>
flex-direction: column;<br>
align-items: flex-end;<br>
}<br>
.table-container {<br>
flex: 1;<br>
overflow: auto;<br>
}<br>
.onshift {<br>
color:#56c25e;<br>
}<br>
.offshift {<br>
color:#FA4B4D;<br>
}<br>
table {<br>
width: 100%;<br>
border-collapse: collapse;<br>
}<br>
th, td {<br>
padding: 10px;<br>
text-align: left;<br>
}<br>
th {<br>
background-color: #000;<br>
color: #EFC600; /</em> Yellow text <em>/<br>
}<br>
tr:nth-child(even) {<br>
background-color: #024879; /</em> Light blue <em>/<br>
color: #EFC600; /</em> Yellow text <em>/<br>
}<br>
tr:nth-child(odd) {<br>
background-color: #77808a; /</em> Light gray <em>/<br>
color: #EFC600; /</em> Yellow text */<br>
}<br>
.footer {<br>
text-align: center;<br>
height: 10%;<br>
display: flex;<br>
align-items: center;<br>
justify-content: center;<br>
}<br>
<!-- All Workscopes -->
<% @workscope_names.each do |ws| %>
<% end %>
<!--
<!--
<!-- Sample row -->
<% @lockouts.each do |loid| %>
<% if loid.status.display_text == "On Shift" %>
<% else %>
<% end %>
<% end %>
LOCKOUTID NAME WORKSCOPE COMPANY STATUS LAST UPDATED SOURCE
<% if loid.technician.attached? %>
<%= image_tag loid.technician, class: "mx-auto h-100 w-auto circle" %>
<% else %>
<%= image_tag 'LID-LOGIN.png', class: "mx-auto h-100 w-auto circle" %>
<% end %>
<%= loid.lid %> <%= loid.name %> <%= loid.workscope %> <%= loid.company %> <%= loid.status.display_text %> <%= loid.status.display_text %> <%= loid.time_since_last_status_change%> <%= loid.last_tag_audit_source %>
<script>
function updateDateTime() {
const now = new Date();
document.getElementById('current-date').textContent = now.toLocaleDateString();
document.getElementById('current-time').textContent = now.toLocaleTimeString();
}
setInterval(updateDateTime, 1000);
updateDateTime();
// Function to scroll the table
function scrollTable() {
const table = document.getElementById('lockout-table');
const firstRow = table.firstElementChild;
table.appendChild(firstRow.cloneNode(true));
table.removeChild(firstRow);
}
setInterval(scrollTable, 3000); // Adjust the interval as needed
</script>
anyone else have insight into this? Do I need to wrap my loop in a turbo_frame? Or having the stream_from enough. Also i have an array of the lockouts in the controller, do i need to reference that variable?
Is the problem that this is coming over as async, for some reason?
17:23:08 web.1 | [ActiveJob] [Turbo::Streams::BroadcastStreamJob] [2da37dda-001d-402d-9be8-89b8d779085f] Performed Turbo::Streams::BroadcastStreamJob (Job ID: 2da37dda-001d-402d-9be8-89b8d779085f) from Async(default) in 1.01ms
"from Async" means your background jobs are being processed by the default "async" adapter.
You'll want to change the ActiveJob queue adapter to Solid Queue or Sidekiq and you'll see the following:
[ActiveJob] [Turbo::Streams::BroadcastStreamJob] [e6bee483-4ebb-439e-a6df-69959947506a] Performed Turbo::Streams::BroadcastStreamJob (Job ID: e6bee483-4ebb-439e-a6df-69959947506a) from SolidQueue(default) in 37.44ms
I installed the solid_queue gem and ran the install. It did not create any db migrations and I had to do those manually.
09:52:02 web.1 | [ActiveJob] Enqueued Turbo::Streams::BroadcastStreamJob (Job ID: 9f022bc7-7ad1-4ba9-8adf-8a6f98187775) to SolidQueue(default) with arguments: "Z2lkOi8vbG9ja291dGlkcy9Mb2Nrb3V0LzExMw", {:content=>""}
I do see this in the console now, but my views are not updating.