In part 1 we built an admin panel and used Turbo Streams to make revenue dynamic. In this post we’ll continue building out the admin panel. We’ll implement tabs, using Turbo Frames, then finish up by making ‘real time orders’ dynamic.
Turbo Frame tabs
First, we’ll update the existing logic for displaying revenue. We’ll wrap it in a turbo_frame_tag
block and call it "tab_data"
. This tag is what Turbo will use to replace the content when changing tabs. All we need to do is render a new "tab_data"
frame, from a controller.
Also, we’ll update the partial to make it more generic:
index.html.erb
1
2
3
4
5
6
<%= turbo_frame_tag "tab_data" do %>
<%= turbo_stream_from "revenue" %>
<div id="data">
<%= render partial: 'metrics/financial_data', locals: { data: @revenue } %>
</div>
<% end %>
metrics/_financial_data.html.erb
1
2
3
<div id="data">
<%= number_to_currency(data) %>
</div>
In part 1 we created stub buttons to control the tabs. To get them working we need to add new routes:
1
2
3
4
5
6
resource :dashboard do
member do
get :revenue
get :orders
end
end
With the routes created we can update the button links:
1
2
<%= button_to "Real time revenue", revenue_dashboard_path, method: :get %>
<%= button_to "Real time orders", orders_dashboard_path, method: :get %>
These links need a controller, we can generate one with an action for ‘revenue’ and ‘orders’:
1
bin/rails g controller DashboardsController revenue orders
In the controller actions we ultimately want to render a "tab_data"
Turbo Frame, just like the one on the index page. We’ll place the Turbo Frame code inside a partial and render that from the controller. We’ll also use turbo_frame_request?
to make sure this partial is only rendered for Turbo Frame requests.
The rendering logic for each tab is similar but different enough to justify two different partials. The partials will contain the Turbo Frame and Turbo Stream code. The controller just needs to pass in the data to display:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def revenue
if turbo_frame_request?
respond_to do |format|
format.html { render partial: 'dashboards/revenue_tab',
locals: { data: Sale.total }}
end
else
# do something else
end
end
def orders
if turbo_frame_request?
respond_to do |format|
format.html { render partial: 'dashboards/orders_tab',
locals: { data: Sale.count }}
end
else
# do something else
end
end
Next we need to build the partials. The outer block creates a Turbo Frame tag, which provides the tab behaviour. The inner block defines a Turbo Stream subscription, which allows the metrics to be updated in realtime:
revenue_tab.html.erb
1
2
3
4
5
6
<%= turbo_frame_tag "tab_data" do %>
<%= turbo_stream_from "revenue" %>
<div id="revenue">
<%= number_to_currency(data) %>
</div>
<% end %>
orders_tab.html.erb
1
2
3
4
5
6
<%= turbo_frame_tag "tab_data" do %>
<%= turbo_stream_from "orders" %>
<div id="orders">
<%= data %>
</div>
<% end %>
The Turbo Frames implementation is now working end to end. Turbo will handle all the magic of updating the tabs.
Getting real time orders working
There’s one last change required to complete the dynamic dashboard. In part 1 we got ‘revenue’ working but didn’t complete ‘orders’. To fix it, we need to add an additional after_commit
that broadcasts order updates. We’ll use Sale.count
to represent orders:
sale.rb
1
after_commit -> { broadcast_update_to "orders", partial: "metrics/data", locals: { data: Sale.count }, target: "orders" }
Lastly, we need to add a small partial containing the code block we’re broadcasting:
metrics/_data.html.erb
1
2
3
<div id="data">
<%= data %>
</div>
That’s it! Thank you for reading. We hope you enjoying this 2 part Hotwire series, covering Turbo Streams and Turbo Frames.