How to use Turbo.visit() and target a specific frame?

I’m trying to solve a problem where I’ve got a UI card, which is just a div, and I want the user to be able to click anywhere on the card and have a modal open up.

My modals are wired up as a turbo-frame. I’ve got the code to a point where, anywhere in my app, I can have a link open in the modal.

The challenge is, because the users are clicking on a card, they’re not clicking on a link! So I need to write a small Stimulus controller to catch the click, and do the right thing. Here’s what I’ve got so far:

import ApplicationController from './application_controller'
import { Turbo } from '@hotwired/turbo-rails'

export default class extends ApplicationController {

  openModal (event) {
    const destination = this.element.dataset.href
    const clickedElement = event.target
    
    // Treat clicks to links as normal clicks, but other clicks should open the modal
    if(clickedElement.localName != 'a' && clickedElement.parentElement.localName != 'a') {
      event.preventDefault()
      event.stopPropagation()
      Turbo.visit(destination)
    }
  }
}

In my markup, I’ve got the card like this:

<div class="card" data-controller="card-controller" data-action="click->card-controller#openModal" data-href="/card/destination">
  <!-- a bunch of junk in here, including some links -->
</div>

I’ve verified that, when the card is clicked, the openModal() is correctly called, and that openModal() is differentiating between normal links and other clicks correctly.

Here’s my trouble. I need that controller’s Turbo.visit() call to target the modal frame. Is there any way to do that? I’d rather not have a hidden secret link if I can… it seems like this should be possible without a hidden link, but I can’t see a way to make it happen.

Thanks for the help!

1 Like

You can navigate a <turbo-frame> by setting its src attribute. The change would like something like this (if you make your frame a target):

-      Turbo.visit(destination)
+      this.frameTarget.src = destination
1 Like

I thought that would work, but when I set the src, the new content doesn’t load. I’ve verified through console output that the src is set correctly. Any suggestions?

Make sure to call reload after setting the source.

let frame = querySelector(‘turbo-frame#your-frame’)
frame.src = ‘/my/new/path’
frame.reload()

1 Like

Correct me if I’m wrong, but I believe you only need to reload this if the src value is the same.

let frame = querySelector(‘turbo-frame#your-frame’)
frame.src = ‘/my/new/path’

or

let frame = querySelector(‘turbo-frame#your-frame’)
frame.src = ‘/my/new/path’ // => loads turbo frame
...
frame.src = ‘/my/new/path’ // same as previous src, doesn't change anything
frame.reload() // => refresh/reload turbo-frame with updated src