Bootstrap Popovers With Hotwire
·2 mins
Table of Contents
Attention! This article might be outdated, refer to latest documentation if solution does not work.
Wouldn’t it be nice to load Bootstrap popover only when user opens it?
Recently we implemented this feature on one of our Rails projects using Hotwire.
It allowed us to show a neat window with the information to make a desk booking in the office.
First of all we need to create a Stimulus controller.
/* app/javascript/controllers/desk_controller.js */
/* global bootstrap */
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
static targets = ["desk"]
static values = {
id: Number
}
connect () {
const pop = new bootstrap.Popover(this.deskTarget, {
html: true,
sanitize: false,
trigger: 'manual',
content: `<turbo-frame id="desk-${this.idValue}" src='/desks/${this.idValue}/book' loading="lazy"></turbo-frame>`
})
}
showPopover {
bootstrap.Popover.getInstance(this.deskTarget).show()
}
}
and connect it to the html element
<div data-controller="desk" data-desk-id-value="<%= desk.id %>">
<div
class="desk"
data-desk-target="desk"
data-action="click->desk#showPopup">
</div>
<div>
The idea behind the code is to manually create a popover instance with <turbo-frame>
inside the content.
id
- attribute is required later to update the turbo frame with response from Rails.src
- path where to load popover content from. Pay attention we can compose it dynamically.loading="lazy"
- allows us to trigger a request to/desks/:id/book
only when popover is opened.
And last, but not the least, we need to manually open popover via data-action="click->desk#showPopup"
.
On the Rails side we can now populate <turbo-frame>
with content
class DesksController < ApplicationController
def show
@desk = Desk.find(params[:id])
render turbo_stream: turbo_stream.append(
"desk-#{@desk.id}",
partial: "show",
locals: { desk: @desk }
)
end
end
Summary #
Here is what actually happens:
graph LR;
A[Stimulus connects
controller
to the element]-->B[Popover instance
is created]; B-->C[User clicks div] C-->D[Turbo loads partial
from Rails]
controller
to the element]-->B[Popover instance
is created]; B-->C[User clicks div] C-->D[Turbo loads partial
from Rails]
This way we receive SPA-like behaviour with all its perks.
Also using turbo_stream.replace
you can later replace <turbo-frame ...>
with desk booking result, error messages and etc. inside the popover.
You might need to fix popover positioning after it loads content
bootstrap.Popover.getInstance(this.deskTarget).update()
We are ready to provide expert's help with your product
or build a new one from scratch for you!