Why is it replacing the whole page and not just the turbo_frame_tag

I step outside of the world for a few days and things seem to break.

I can’t seem to find why the following isn’t replacing just the turbo_frame_tag on my page. The button_to is calling the method, but won’t work in the way I’m expecting. For some reason, if it works at all, it replaces the whole page and not just the content of the turbo_frame. And it doesn’t show start the video at the right time code. Any help you can provide would be greatly appreciated.

Button_to code:

<%= button_to changetime_video_path(time: key), method: :post, form: {data: {'turbo-frame': 'showvideo'}}, class: "text-sm text-left text-orange-500 hover:text-white" do %><%= value %><% end %>

changetime method

  def changetime
    # Designed to load the video at the timecode
    time = params[:time]
    @newtime = time.split(':').map(&:to_i).inject(0) { |a, b| a * 60 + b }
  end

changetime.html.erb

<%= turbo_frame_tag "showvideo" do %>
  <% if !@video.wis_slug.nil? %>
    <div class="wistia_embed wistia_async_<%= @video.wis_slug %>">&nbsp;</div>
  <% else %>
    <div class="aspect-w-16 aspect-h-9">
      <iframe src="https://www.youtube.com/embed/<%= @video.youtube_slug %>?start=<%= @newtime %>&enablejsapi=1&rel=0&autoplay=1&modestbranding=1&cc_load_policy=0" allow="autoplay; accelerometer; gyroscope" frameborder="0" allowfullscreen></iframe>
    </div>
  <% end %>
<% end %>

UPDATE: OK. Using Chrome, when I Inspect, it seems to replace the whole page, but with the correct video timecode, but for some reason it’s not auto playing. When I turn off the Inspect mode, it auto plays. HOWEVER … it still seems to be loading the whole page?? I thought Hotwire stopped that and only did the part it needed. Or do I keep needing to put layout: false in my methods?

Whatever is happening is stopping it from working on mobiles or iPads etc

I really don’t understand why it’s calling the method but returning the whole page when I’ve told it to only look at the “showvideo” section of the page.

I’ve checked the HTML it’s generating, and that seems to be fine.

Isn’t it supposed to just return the HTML in the changetime.html.erb file?

Thanks in advance. This is driving me to drink!

1 Like

Try changing your button_to data {“turbo-frame”} to

<%= button_to changetime_video_path(time: key), method: :post, form: {data: { turbo_frame: 'showvideo'}}, class: "text-sm text-left text-orange-500 hover:text-white" do %><%= value %><% end %>

The button_/link_to helpers convert the underscores to dashes for data attributes

@BryTai Thanks for your suggestion.

Sadly … no change to the operation. The whole page seems to be refreshing rather than just the turbo frame section I was hoping to change. This is really frustrating as I’m sure other sections of the site are working as they should.

Hmm, it may be because your changetime method isnt standard CRUD. Try adding a

respond_to do |format|
  format.html {}
end

to the method and see if that works.

To go a step further, you may need to change your changetime.html.erb to changetime.turbo_stream.erb and add the format.turbo_stream to your respond_to

I think your missing a target here, that will tell turbo which frame you want to replace: https://turbo.hotwire.dev/reference/frames#html-attributes

In my own application I have links like: <%= link_to t('.edit'), edit_category_path(@category), class: 'btn btn-primary', data: { 'turbo-frame': "edit_category_#{@category.id}" }, target: 'modal' %>

Hi again @BryTai

Still no change. The whole page is being rendered when I add the respond_to.

Could it be that the button_to is being called inside a different turbo_frame? Would this make a difference?

Hi @robbevp

Thanks for your suggestion. Doesn’t the form: {data: {‘turbo-frame’: ‘showvideo’}} tell the controller what the target is?

I made the change and it doesn’t affect anything. The whole page still renders.

It shouldn’t matter if the frame is inside a separate frame.
Is your changetime.html.erb a partial? Are you able post the code for the view that houses the button and changetime view?
Also, what does the controller that your changetime method is part of look like?

@BryTai

The changetime.html.erb file isn’t a partial.

There are two divs. One which holds a video. One which holds a list of timecodes which, when clicked should change the video being shown to play from the selected timecode.

This is the code in show.html.erb which is the view which uses the turbo_frame_tag … which is the same as changetime.html.erb.

<%= turbo_frame_tag "showvideo" do %>
    <% if !@video.wis_slug.nil? %>
      <div class="wistia_embed wistia_async_<%= @video.wis_slug %>">&nbsp;</div>
        <% else %>
      <div class="aspect-w-16 aspect-h-9">
        <iframe src="https://www.youtube.com/embed/<%= @video.youtube_slug %>?enablejsapi=1&rel=0&autoplay=1&modestbranding=1&cc_load_policy=0" allow="autoplay; accelerometer; gyroscope" frameborder="0" allowfullscreen></iframe>
      </div>
    <% end %>
<% end %>

The other code which creates the button_to is also contained in show.html.erb a little further down:

<%= turbo_frame_tag "updatetimecode" do %>
    <%= render partial: 'showtimecodes' %>
<% end %>

This calls the partial showtimecodes where each timecode has the respective button_to link to run the changetime method and hence the reload of the showvideo tag.

<% @timecodes.each do |key, value| %>
  <div id="timecodes" class="flex flex-row">
    <div class="w-1/5">
      <div class="text-sm text-white"><%= key %></div>
    </div>
    <div class="flex flex-row justify-between w-4/5 px-2">
      <div>
        <%= button_to changetime_video_path(time: key), method: :post, form: {data: { turbo_frame: 'showvideo'}}, class: "text-sm text-left text-orange-500 hover:text-white" do %>
          <%= value %>
        <% end %>
      </div>
      <div>
        <% if current_user %>
          <% if current_user.admin? %>
            <%= button_to delete_timecode_video_path(time: key), method: :post, class: "text-sm text-orange-500 hover:text-white" do %>
              &nbsp;[x]
            <% end %>
          <% end %>
        <% end %>
      </div>
    </div>
  </div>
<% end %>

The controller (with a lot missed out) looks like:

require 'securerandom'
class VideosController < ApplicationController
  include ApplicationHelper
  before_action :authenticate_user!,
                only: %i[updateyoutubeslug update new create make_paid changetime make_video_courses addtimecode
                         update_video]
  before_action :set_video, only: %i[show update changetime addtimecode dislike like delete_timecode]
  
  def changetime
    # Designed to load the video at the timecode
    logger.warn("I am here trying to change the time ...")
    time = params[:time]
    @newtime = time.split(':').map(&:to_i).inject(0) { |a, b| a * 60 + b }
  end

  ...

  end

Thanks again for any help you can give.

OK cool, so if I’m understanding correctly, you have a show.html.erb and a changetime.html.erb file, both of which have the “showvideo” turbo frame.

If that is the case, you could rename your changetime.html.erb to changetime.turbo_stream.erb and in the code, change it to be

<%= turbo_stream.replace "showvideo" do %>
<%= turbo_frame_tag "showvideo" do %>
    <% if !@video.wis_slug.nil? %>
      <div class="wistia_embed wistia_async_<%= @video.wis_slug %>">&nbsp;</div>
        <% else %>
      <div class="aspect-w-16 aspect-h-9">
        <iframe src="https://www.youtube.com/embed/<%= @video.youtube_slug %>?enablejsapi=1&rel=0&autoplay=1&modestbranding=1&cc_load_policy=0" allow="autoplay; accelerometer; gyroscope" frameborder="0" allowfullscreen></iframe>
      </div>
    <% end %>
<% end %>
<% end %> 

emphasis on the turbo_stream.replace

in your controller, add

respond_to do |format|
  format.turbo_stream {}
end

at the end of your changetime method

When you user clicks the time code, the changetime method will do its thing and look for the changetime.turbo_stream.erb file.
That file will then issue the turbo_stream.replace on your “show video” frame, replacing it only in your show.html.erb.

I’m guessing the reason it doesn’t update the turbo_frame with a standard html format is because its a POST request. If you were doing a GET request such as changing a tab cont etc. you wouldn’t need the format.turbo_stream.

1 Like

@BryTai Thanks so much! That seems to be working … I would still dearly love to know why the other way wasn’t working … but … for now … I’ll just leave it alone!

I’m pretty sure its because you button_to is issuing a POST request and therefore not rendering a page directly like it would with a GET, instead it is doing a separate GET redirect after processing in you controller which is negating the turbo functionality and just loading a page refresh.

You shouldn’t need streams at all for this.

Do you show more than one video on the same page?

You should be doing:

<%= turbo_frame_tag dom_id(@video) do %>

To make sure you never have more than one frame with the same id.

If Turbo got multiple frames with that same id it would have no choice but to reload the page I think.