TL;DR see this demo project for the final solution.
Please be aware that the examples here are provided with ES6 syntax. There are different ways to get ES6 to work in Rails. The demo project has an ES5 branch for reference.
If you are reading this I assume you are familiar with Ruby on Rails and SweetAlert2. With Rails before version 5.1, when rails-ujs was still jquery-ujs, there was an easy way to hook up SweetAlert (or SweetAlert2) with the Rails confirm functionality.
One way to achieve it was to overwrite the confirm handler of Rails:
// Override the default confirm dialog of Rails
$.rails.handleConfirm = link => {
if (link.data('confirm') === undefined){
return true
}
showSweetAlertConfirmationDialog(link)
return false
}
We had this solution in one of our apps and I wanted to use the new rails-ujs
. My first thought was, that it should be an easy task to adapt. Just change $.rails.
into Rails.
and we’re good:
// Override the default confirm dialog of Rails
Rails.handleConfirm = link => {
if (link.data('confirm') === undefined){
return true
}
showSweetAlertConfirmationDialog(link)
return false
}
As it turns out some things have changed.
Rails.handleConfirm
can be overwritten, but that will not override the event listener that is already attached since rails-ujs
was initialised. But no problem, let’s just write our own event handler and plug it into the new rails-ujs
way of doing things. If you have a look at the source code of the start part of rails-ujs
you see how event listeners are created. The code to add an event listener for our own method then looks like this:
const handleConfirm = link => {
// Do your thing
}
Rails.delegate(document, 'a[data-confirm-swal]', 'click', handleConfirm)
Alright, cool. It works for links with the attribute data-confirm-swal="Are you sure?"
now 🎉 …but wait, if you have a delete link, the confirm dialog never shows up because the method never gets called. 🤔 Turns out the event listener for method: :delete
is called earlier because it got initialised before our event listener for SweetAlert2. This is because the event listeners of rails-ujs
are hooked up directly when evaluating the code.
If we want to add our event listener by requiring our Javascript code before rails-ujs
, then we get into the Problem that Rails.delegate
is not defined yet. When you require rails-ujs
this is what’s happening:
- Define event handlers and
Rails.delegate
- Fire
rails:attachBindings
event on document - Attach event handlers (
Rails.start
)
So in order to get in between the definition of Rails.delegate
and the execution of Rails.start
we have to attach to the rails:attachBindings
event. (For that to happen we need to require our script before rails-ujs
!)
document.addEventListener('rails:attachBindings', element => {
Rails.delegate(document, 'a[data-confirm-swal]', 'click', handleConfirm)
})
🎉 Now everything works as expected 🎉
For the final solution have a look at this demo project (with ES5 and ES6 version) or see the code below (only ES6).
// This file has to be required before rails-ujs
// To use it change `data-confirm` of your links to `data-confirm-swal`
(function() {
const handleConfirm = function(element) {
if (!allowAction(this)) {
Rails.stopEverything(element)
}
}
const allowAction = element => {
if (element.getAttribute('data-confirm-swal') === null) {
return true
}
showConfirmationDialog(element)
return false
}
// Display the confirmation dialog
const showConfirmationDialog = element => {
const message = element.getAttribute('data-confirm-swal')
const text = element.getAttribute('data-text')
swal({
title: message || 'Are you sure?',
text: text || '',
type: 'warning',
showCancelButton: true,
confirmButtonText: 'Yes',
cancelButtonText: 'Cancel',
}).then(result => confirmed(element, result))
}
const confirmed = (element, result) => {
if (result.value) {
// User clicked confirm button
element.removeAttribute('data-confirm-swal')
element.click()
}
}
// Hook the event before the other rails events so it works togeter
// with `method: :delete`.
// See https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts/rails-ujs/start.coffee#L69
document.addEventListener('rails:attachBindings', element => {
Rails.delegate(document, 'a[data-confirm-swal]', 'click', handleConfirm)
})
}).call(this)
To find this all out it took me some hours as I wasn’t familiar with the codebase of rails-ujs
. But I learnt a lot along the way. Hopefully with this writeup I can help some other developers as well who want to use the latest version of Rails with SweetAlert2 and without jQuery.
If you use Webpacker, there is an easy way to get in between the rails-ujs
code and the start script (not tested yet):
import Rails from 'rails-ujs';
const handleConfirm = () => {
// Do your thing
}
// Add event listener before the other Rails event listeners like the one
// for `method: :delete`
Rails.delegate(document, 'a[data-confirm-swal]', 'click', handleConfirm)
Rails.start()