A drop-in upgrade for Rails data-turbo-confirm.
bin/importmap pin @rolemodel/turbo-confirmor
yarn add @rolemodel/turbo-confirmor
npm install @rolemodel/turbo-confirmIn your application's JavaScript entry point file. (usually app/javascript/application.js)
import "@hotwired/turbo-rails"
import TC from "@rolemodel/turbo-confirm"
TC.start()Then add your custom dialog markup to your application's layout. See the Example Template below for a good starting point that works with the default configuration.
Important
@hotwired/turbo-rails must be imported prior to calling the start function. This is so Turbo-Confirm can coordinate with Turbo regarding confirmation handling. The start function is also where you may override default behavior by passing a configuration object. See the configuration table for available properties and their default values.
Turbo's confirmation interface is exercised most commonly via button_to (examples shown in slim templating syntax)
= button_to 'Delete ToDo', todo_path(todo),
class: 'btn btn--danger',
method: :delete,
data: { turbo_confirm: 'Are you sure?' }or link_to with a data-turbo-method attribute.
= link_to 'Delete ToDo', todo_path(todo),
class: 'btn btn--danger',
data: { \
turbo_method: :delete,
turbo_confirm: 'Are you sure?',
}By setting additional, optional data attributes on the confirmation trigger, Turbo-Confirm adapts dynamically in different contexts.
Out of the box, Turbo-Confirm ships with two optional ContentSlots:
- body activated by a
data-confirm-detailsattribute on the confirmation trigger. The attribute's value will be assigned to the element matching the selector#confirm-body - acceptText activated by a
data-confirm-buttonattribute on the confirmation trigger. The attribute's value will be assigned to the element matching the selector#confirm-accept
example in slim templating syntax:
= button_to 'Delete ToDo', todo_path(todo),
method: :delete,
data: { \
turbo_confirm: 'The following ToDo will be permanently deleted.',
confirm_details: simple_format(todo.content),
confirm_button: 'Delete ToDo',
}See also this discussion, for instructions on defining additional dynamic ContentSlots.
Tip
we recommend populating your dialog template with sensible default content and only triggering the dynamic ContentSlots where necessary. See the example template for reference.
Turbo-Confirm can also serve as a general replacement for the native window.confirm function. Though it returns a Promise instead of pausing execution.
e.g.
import { TurboConfirm } from "@rolemodel/turbo-confirm"
const tc = new TurboConfirm()
tc.confirm('Are you sure?').then(response => { response ? /* accepted */ : /* denied */ })The message itself is optional as well. Simply call confirm() with no arguments and your dialog's default content will be displayed un-altered. e.g.
import { TurboConfirm } from "@rolemodel/turbo-confirm"
const tc = new TurboConfirm({ /* Any Custom Configuration */ })
tc.confirm()Turbo-Confirm has one additional public method, confirmWithContent that expects a contentMap object where the keys are content slot selectors and the values are the content you want displayed in each selected element.
e.g.
import { TurboConfirm } from "@rolemodel/turbo-confirm"
const tc = new TurboConfirm()
tc.confirmWithContent({
'#confirm-title': 'Are you sure?',
'#confirm-accept': 'Do it!'
}).then(response => { response ? /* accepted */ : /* denied */ })Note
The TurboConfirm constructor creates a brand new instance that will not share configuration with the one Turbo-Rails is using. For that reason, a config object may be passed into the TurboConfirm constructor. See the configuration table for available properties and their default values.
While Turbo will invoke Turbo-Confirm for you in the case of a form submission (like button_to) or form link (like link_to with a data-turbo-method), in the case of a regular link or a button that does not submit a form, you're on your own. But Turbo-Confirm can help!
For those cases, a simple Stimulus wrapper around Turbo-Confirm is a good solution.
e.g.
import { Controller } from "@hotwired/stimulus"
import { TurboConfirm } from "@rolemodel/turbo-confirm"
export default class extends Controller {
#hasAccepted = false
connect() {
this.tc = new TurboConfirm({ /* Any Custom Configuration */ })
}
async perform(event) {
if (this.#hasAccepted) {
this.#hasAccepted = false
return
}
event.preventDefault()
event.stopImmediatePropagation()
if (await this.tc.confirm(event.params.message)) {
this.#hasAccepted = true
event.target.click()
}
}
}<a href="https://rolemodelsoftware.com" data-controller="confirm" data-confirm-message-param="Do you need custom software?" data-action="confirm#perform">Click me</a>| Property | Description | Default Value |
|---|---|---|
dialogSelector |
Global CSS selector used to locate your dialog HTML (an ID selector is recommended) | '#confirm' |
activeClass |
HTML class that causes your dialog element to become visible. (note: you're responsible for defining necessary style rules) | 'modal--active' |
acceptSelector |
CSS selector identifying the button within your dialog HTML which should trigger acceptance of a confirmation challenge | '#confirm-accept' |
denySelector |
CSS selector identifying the button(s) within your dialog HTML which should trigger rejection of a confirmation challenge | '.confirm-cancel' |
animationDuration |
approximate number of miliseconds Turbo-Confirm should wait for your dialog's CSS to transition to/from a visible state | 300 |
showConfirmCallback |
a function, called on show with 1 argument (the dialog). The default provides support for native dialog elements | see below |
hideConfirmCallback |
a function, called on accept or reject with 1 argument (the dialog). The default provides support for native dialog elements | see below |
messageSlotSelector |
CSS selector of the element within your dialog HTML where the value of data-turbo-confirm (or supplied message) should be rendered |
'#confirm-title' |
contentSlots |
an object describing additional customization points. See contentSlots for a more detailed description. | see below |
{
dialogSelector: '#confirm',
activeClass: 'modal--active',
acceptSelector: '#confirm-accept',
denySelector: '.confirm-cancel',
animationDuration: 300,
showConfirmCallback: element => element.showModal && element.showModal(),
hideConfirmCallback: element => element.close && element.close(),
messageSlotSelector: '#confirm-title',
contentSlots: {
body: {
contentAttribute: 'confirm-details',
slotSelector: '#confirm-body'
},
acceptText: {
contentAttribute: 'confirm-button',
slotSelector: '#confirm-accept'
}
}
}The inert attribute makes elements non-interactive. This is useful in preventing hidden elements from obscuring the user's intended click target, which is a common issue when you have multiple hidden dialogs on the page.
Turbo-Confirm will automatically add an inert attribute to your confirmation element when it becomes hidden and then remove it once it becomes visible again. We also recommend that you include this attribute in your template. See the example below for reference.
Based on the default configuration, the following template is suitable.
<!-- not visible until a 'modal--active' class is applied to the #confirm element -->
<div id="confirm" class="modal" inert>
<div class="modal__backdrop confirm-cancel"></div>
<div class="modal__content">
<h3 id="confirm-title">Replaced by `data-turbo-confirm` attribute</h3>
<div id="confirm-body">
<p>Default confirm message.</p>
<p>Optionally replaced by `data-confirm-details` attribute</p>
</div>
<div class="modal-actions">
<button class="confirm-cancel">Cancel</button>
<button id="confirm-accept">Yes, I'm Sure</button>
</div>
</div>
</div>If you're not already using a CSS or style component framework. I suggest checking out Optics. Alternatively, the native dialog element is fully supported by modern browsers and removes much of the styling burden that would otherwise be required to emulate such behavior with only a div.
Turbo-Confirm fully supports the native dialog element, including dismissal via esc key.
<dialog id="confirm" class="modal" inert>
<div class="modal__content">
<h3 id="confirm-title">Replaced by `data-turbo-confirm` attribute</h3>
<div id="confirm-body">
<p>Default confirm message.</p>
<p>Optionally replaced by `data-confirm-details` attribute</p>
</div>
<div class="modal-actions">
<button class="confirm-cancel">Cancel</button>
<button id="confirm-accept">Yes, I'm Sure</button>
</div>
</div>
</dialog>After cloning the repository, you'll need to install dependencies by running yarn install.
The test suite can be run with yarn test. Or open the Playwright GUI application with yarn test:ui
Finally, the test app's server can be run on PORT 3000 with yarn dev.
Each of these tasks is also accessible via Rake, if you prefer. Run rake -T for details.
Turbo-Confirm is MIT-licensed, open-source software from RoleModel Software.
RoleModel Software is a world-class, collaborative software development team dedicated to delivering the highest quality custom web and mobile software solutions while cultivating a work environment where community, family, learning, and mentoring flourish.
