A drop-in upgrade for Rails data-turbo-confirm
.
npm install @rolemodel/turbo-confirm
or
yarn add @rolemodel/turbo-confirm
In your application's JavaScript entry point file. (usually app/javascript/application.js)
import "@hotwired/turbo-rails"
import TC from "@rolemodel/turbo-confirm"
TC.start()
note: @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 configuration docs for available options and their default values.
Turbo's confirmation interface is exercised most commonly via button_to
(example 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?',
}
Turbo-Confirm supports other custom content beyond a simple message, by setting additional data attributes on the confirmation trigger. Henceforth referred to as contentSlots, this feature is both infinitely configurable and completely optional. Out of the box Turbo-Confirm supports two:
- body activated by a
data-confirm-details
attribute on the confirmation trigger. The attribute's value will be assigned to the element matching the#confirm-body
selector. - acceptText activated by a
data-confirm-button
attribute on the confirmation trigger. The attribute's value will be assigned to the element matching the#confirm-accept
selector (same as the default value of theacceptSelector
configuration property)
example usage of default contentSlots via button_to
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',
}
note: It's recommended that you have sensible default content already populating each of your configured contentSlots within your app's confirmation dialog template. Such that every confirmation trigger is not required to supply custom content for every contentSlot. See our example template for a good starting point.
Though Turbo-Confirm was primarily designed to serve as turbo-rails' confirmation interface, it may also be invoked directly by application code. In almost the same manner as the native window.confirm
. While native confirm pauses execution until the user accepts or declines, Turbo-Confirm is Promise based.
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 an 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 configuration docs for available options 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>
Option | 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'
}
}
}
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">
<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.
<!-- not visible without an [open] attribute, which **Turbo-Confirm** will handle for you -->
<dialog id="confirm" class="modal">
<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.