jQuery UI Dialog instead of alert() for Rails 3 data-confirm attribute

15,133

Solution 1

I just added an external API to the Rails jquery-ujs for exactly this kind of customization. You can now make rails.js use a custom confirm dialog by plugging into (and re-writing 1 line of) the $.rails.allowAction function.

See my article, Rails jQuery UJS: Now Interactive, for a full explanation with examples.

EDIT: As of this commit, I moved the confirm dialog function to the $.rails object, so that it can be modified or swapped out even more easily now. E.g.

$.rails.confirm = function(message) { return myConfirmDialog(message); };

Solution 2

As others have mentioned, you cannot use a jQuery dialog box, as $.rails.confirm needs to block until it returns the users answer.

However, you can overwrite $.rails.allowAction in your application.js file like this:

$.rails.allowAction = function(element) {
        var message = element.data('confirm'),
        answer = false, callback;
        if (!message) { return true; }

        if ($.rails.fire(element, 'confirm')) {
                myCustomConfirmBox(message, function() {
                        callback = $.rails.fire(element,
                                'confirm:complete', [answer]);
                        if(callback) {
                                var oldAllowAction = $.rails.allowAction;
                                $.rails.allowAction = function() { return true; };
                                element.trigger('click');
                                $.rails.allowAction = oldAllowAction;
                        }
                });
        }
        return false;
}

function myCustomConfirmBox(message, callback) {
        // implement your own confirm box here
        // call callback() if the user says yes
}

It works by returning false immediately, thus effectively canceling the click event. However, your custom function can then call the callback to actually follow the link/submit the form.

Solution 3

I liked the answer from @Marc Schütz about overriding $.rails.allowAction the most of anything I found online - but I'm not a big fan of overriding the functionality in allowAction since it's used all throughout the jquery-ujs codebase (what if there are side effects? Or if the source for that method changes in a future update?).

By far, the best approach would be to make $.rails.confirm return a promise... But it doesn't look like that's going to happen anytime soon :(

So... I rolled my own method which I think is worth mentioning because it's lighter weight than the method outlined above. It doesn't hijack allowAction. Here it is:

# Nuke the default confirmation dialog. Always return true 
# since we don't want it blocking our custom modal.
$.rails.confirm = (message) -> true

# Hook into any data-confirm elements and pop a custom modal
$(document).on 'confirm', '[data-confirm]', ->
  if !$(this).data('confirmed')
    myCustomModal 'Are you sure?', $(this).data('confirm'), =>
      $(this).data('confirmed', true)
      $(this).trigger('click.rails')
    false
  else
    true

# myCustomModal is a function that takes (title, message, confirmCallback)

How does it work? Well, if you look at the source, you'll notice that the allowAction method halts if the confirm event returns a falsy value. So the flow is:

  1. User clicks link or button with data-confirm attribute. There is no data-confirmed present on the link or button, so we fall into the first if block, trigger our custom modal and return false, thereby stopping the action from continuing in the ujs click handler.
  2. User confirms in the custom modal, and the callback is triggered. We store state on the element via data('confirmed', true) and re-trigger the same event that was triggered previously (click.rails).
  3. This time the confirm event will fall into the else block (since data('confirmed') is truthy) and return true, causing the allowAction block to evaluate to true.

I'm sure I'm even missing other ways that might make this even simpler, but I think this is a really flexible approach to get a custom confirm modal without breaking core jquery-ujs functionality.

(Also, because we're using .on() this will bind to any data-confirm elements on the page at load time or in the future, similarly to how .delegate() works, in case you are wondering.)

Share:
15,133
Sai Perchard
Author by

Sai Perchard

Updated on June 19, 2022

Comments

  • Sai Perchard
    Sai Perchard almost 2 years

    In Rails 3, passing a :confirm parameter to link_to will populate the data-confirm attribute of the link. This will induce a JS alert() when the link is clicked.

    I am using the rails jQuery UJS adapter (https://github.com/rails/jquery-ujs). The relevant code from rails.js is:

    $('body').delegate('a[data-confirm], button[data-confirm], input[data-confirm]', 'click.rails', function () {
        var el = $(this);
        if (el.triggerAndReturn('confirm')) {
            if (!confirm(el.attr('data-confirm'))) {
                return false;
            }
        }
    });
    

    and

    triggerAndReturn: function (name, data) {
            var event = new $.Event(name);
            this.trigger(event, data);
    
            return event.result !== false;
        }
    

    I would like to know how this could be modified to instead yield a jQuery dialog (e.g. the jQuery UI Dialog) allowing the user to confirm or cancel.

    My knowledge of JavaScript isn't sufficient to achieve this elegantly. My current approach would be to simply rewrite the $('body').delegate() function to instead instantiate a lightbox. However I imagine that there is a more effective approach than this.