13

I've been tinkering with this for a long long time.

I would like to hijack the default JS confirm dialog with something I rolled myself. I'd like to use a completely custom layout (the bootstrap(from twitter) dialog panel).

What I have doesn't work. It show up nicely and I can click the buttons and it'll dissapear. The documentation says that you should return true in case of Ok and false in case of a Cancel. This is very cute and all but it doesn't work. It looks like I need a callback or a reference to the object that originally called the function. Even the latter isn't possible since $.rails.confirm only passes in the message.

(The first answer from this question is pretty interesting. I need a way to make it modal so it waits for the return of the custom dialog.)

So could somebody please point me in the right direction? I feel like I'm going to slap something. Hard!! jQuery UI is only an option of I can make my dialog look exactly like the one I currently have.

Here is what I have:

This is placed in my application.erb

<div id="modal-confirm" class="modal">
  <div class="modal-header">
    <h3>Are you sure?</h3>
    <a href="#" class="close">×</a>
  </div>
  <div class="modal-body">
    <p>{{VALUE}}</p>
  </div>
  <div class="modal-footer">
    <a id="modal-accept" href="#" class="btn primary">OK</a>
    <a id="modal-cancel" href="#" class="btn secondary">Cancel</a>
  </div>
</div>

javascript.js:

function bootStrapConfirmDialog(message) {
  // get a handle on the modal div (that's already present in the layout).
  d = $("#modal-confirm");
  // replace the message in the dialog with the current message variable.
  $("#modal-confirm div.modal-body p").html(message);
  // offset the dialog so it's nice and centered. we like that ;)
  // d.offset({ top: 400, left: (document.width - d.width) / 2 });
  d.center();

  // show the dialog.
  d.toggle(true);
  console.log("popped open");
}

$(document).ready(function(){
  // jquery support
  $.fn.extend({
    center: function () {
      return this.each(function() {
        var top = ($(window).height() - $(this).outerHeight()) / 2;
        var left = ($(window).width() - $(this).outerWidth()) / 2;
        $(this).css({position:'absolute', margin:0, top: (top > 0 ? top : 0)+'px', left: (left > 0 ? left : 0)+'px'});
      });
    }
  });

  // modal stuff
  $("#modal-confirm").toggle(false);

  // wire up cancel and x button.
  $("#modal-confirm #modal-cancel, #modal-confirm a.close").click(function (e) {
    d.toggle(false);
    console.log("clicked cancel");
    return false;
  });

  // wire up OK button.
  $("#modal-confirm #modal-accept").click(function (e) {
    d.toggle(false);
    console.log("clicked accept");
    return true;
  });

  // wire up our own custom confirm dialog.
  $.rails.confirm = function(message) { console.log("start intercept"); return bootStrapConfirmDialog(message); };
});

and then finally in my view:

<%= link_to 'delete customer', customer_path(@customer), :class => 'btn danger', :method => :delete, :confirm => "Are you sure you would like to delete '#{@customer.name}'?" %>

@ 23:46 GMT

Ok, I figured out a way ... and it isn't pretty. I basically extended jquery-rjs in a way that the actual element is passed along to the $.rails.confirm method. That way I at least know what should happen if the OK button is pressed in the modal. So here is the new snazzy code.

My new application.js. Works like a charm. But I'm a little disturbed at how many things I had to override. I probably broke something and I don't even know about it (rails.formSubmitSelector and/or rails.formInputClickSelector). So if you have a better solution ... give :D thx!

function bootStrapConfirmModal(message, element) {
  // get a handle on the modal div (that's already present in the layout).
  d = $("#modal-confirm");
  // replace the message in the dialog with the current message variable.
  $("#modal-confirm div.modal-body p").html(message);
  // offset the dialog so it's nice and centered. we like that ;)
  d.center();

  // wire up cancel and x button.
  $("#modal-confirm #modal-cancel, #modal-confirm a.close").click(function (e) {
    d.toggle(false);
    return false;
  });

  // wire up OK button.
  $("#modal-confirm #modal-accept").click(function (e) {
    d.toggle(false);

    // actually handle the element. This has to happen here since it isn't an *actual* modal dialog.
    // It uses the element to continue proper execution.
    $.rails.handleLink(element);

    return false;
  });

  // show the dialog.
  d.toggle(true);
};

$(document).ready(function(){
  // jquery support
  $.fn.extend({
    center: function () {
      return this.each(function() {
        var top = ($(window).height() - $(this).outerHeight()) / 2;
        var left = ($(window).width() - $(this).outerWidth()) / 2;
        $(this).css({position:'absolute', margin:0, top: (top > 0 ? top : 0)+'px', left: (left > 0 ? left : 0)+'px'});
      });
    }
  });

  // modal stuff
  $("#modal-confirm").toggle(false);



  // $.rails overrides.

  // wire up our own custom confirm dialog. Also extend the function to take an element.
  $.rails.confirm = function(message, element) { return bootStrapConfirmModal(message, element); };

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

    if ($.rails.fire(element, 'confirm')) {
      // le extension.
      answer = $.rails.confirm(message, element);
      callback = $.rails.fire(element, 'confirm:complete', [answer]);
    }
    return answer && callback;
  };

  $.rails.handleLink = function(link) {
    if (link.data('remote') !== undefined) {
      $.rails.handleRemote(link);
    } else if (link.data('method')) {
      $.rails.handleMethod(link);
    }
    return false;
  };

});
Community
  • 1
  • 1
SpoBo
  • 2,040
  • 2
  • 20
  • 28

4 Answers4

5

Here's a working demo for changing confirmation box in Rails: http://rors.org/demos/custom-confirm-in-rails

Examples for Boostrap, jQueryUI and Noty are provided.

Dejan Simic
  • 6,902
  • 2
  • 25
  • 15
  • To see the original content of the link: https://web.archive.org/web/20121230034912/http://rors.org/demos/custom-confirm-in-rails – JosephK Jun 10 '17 at 07:24
2

I wrote a sample for Rails 3.1 to change the alerts using this solution, but instead of replacing the native alert with the bootstrap one, it does the following:

  • Change the link/button text to 'Sure?' for 2 seconds
  • If the link/button is clicked within 2 seconds, the action is performed
  • Otherwise the link/button flips back to the old text and nothing happens.

Here is a gist: https://gist.github.com/2594409

This is following this discussion in the UX forum:

https://ux.stackexchange.com/questions/20741/action-confirmation-through-double-clicking

Community
  • 1
  • 1
Khash
  • 2,406
  • 3
  • 30
  • 55
1

there is pull request with this feature which seems to work fine https://github.com/rails/jquery-ujs/pull/196

stasl
  • 933
  • 7
  • 6
0

I am posting my working solution in case it helps anybody. This solution renders a Bootstrap Modal when clicking link or button matched via following selectors:

$('a.ajaxLink, button.ajaxButton')

HAML code

- genericConfirmationModalId = "genericConfirmationModal"
.modal.fade{:role => "dialog", :tabindex => "-1", id: genericConfirmationModalId }
  .modal-dialog{:role => "document"}
    .modal-content
      .modal-body
        %button.close{"aria-label" => "Close", "data-dismiss" => "modal", :type => "button"}
          %span{"aria-hidden" => "true"} ×

        %h3.text-center<
          Are you sure?

      .modal-footer
        .row<
          .col-md-6<
            = button_tag(type: 'button', class: "btn btn-primary btn-lg genericConfirmationModalNoBtn" ) { "No" }
          .col-md-6<
            = button_tag(type: 'button', class: "btn btn-danger btn-lg genericConfirmationModalYesBtn") { "Yes" }

JS Code

(function ($) {
  // ================ STARTS - CUSTOM rails_ujs CONFIRMATION DIALOG RELATED JS

  blockUI = function() {
    $.blockUI();
  };

  unblockUI = function() {
    $.unblockUI();
  };    

  // Reference: http://thelazylog.com/custom-dialog-for-data-confirm-in-rails/
  $.rails.confirmed = function(element) {
    hideGenericConfirmationModal();
    element.attr('data-confirmation-answer', '1');
    element.trigger('click.rails');
  }

  // Overridden version
  // Reference: https://github.com/rails/jquery-ujs/blob/master/src/rails.js#L88
  $.rails.allowAction = function(element) {
    var message = element.data('confirm'),
        answer = false, callback;
    if (!message) { return true; }

    var executeCustomBehavior = !!element.attr('data-confirmation-answer');

    if (executeCustomBehavior) {
      answer = element.attr('data-confirmation-answer') == '1' ? true : false;
      callback = $.rails.fire(element, 'confirm:complete', [answer]);
    } else {
      if ($.rails.fire(element, 'confirm')) {
        try {
          answer = $.rails.confirm(message);
        } catch (e) {
          (console.error || console.log).call(console, e.stack || e);
        }
        callback = $.rails.fire(element, 'confirm:complete', [answer]);
      }
    }

    return answer && callback;
  };

  bindblockUIOnAjaxLinks = function() {
    $('a.ajaxLink, button.ajaxButton').off('click').on('click', function() {
      var linkTriggersConfirmationDialog = !!$(this).attr('data-confirm');

      if (linkTriggersConfirmationDialog) {
        // Reference: http://stackoverflow.com/questions/19516654/rails-ujs-confirm-box-cancel-button-callback
        $(this).on('confirm:complete', function(e, response) {
          if(response) {
            blockUI(); // Block and Unblock UI was applicable in my case. It is not necessary to use this solution.
          } else {
            unblockUI();
          }
        });

        bindShowEventOnGenericConfirmationModal($(this));
        showGenericConfirmationModal();
        return false;

      } else {
        blockUI();
      }
    });
  };

  isGenericConfirmationModalAnswerYes = function() {
    return genericConfirmationModalAnswerHiddenField().val() == '1';
  };

  genericConfirmationModal = function() {
    return $("#genericConfirmationModal");
  };

  genericConfirmationModalYesBtn = function() {
    return genericConfirmationModal().find('.genericConfirmationModalYesBtn');
  };

  bindClickEventOnGenericConfirmationYesBtn = function(elementTriggeredConfirmation) {
    genericConfirmationModalYesBtn().off("click").on("click", function(event) {
      // console.log("generic confirmation modal yes button event callback");
      $.rails.confirmed(elementTriggeredConfirmation);
    });
  };

  genericConfirmationModalNoBtn = function() {
    return genericConfirmationModal().find('.genericConfirmationModalNoBtn');
  };

  bindClickEventOnGenericConfirmationNoBtn = function() {
    genericConfirmationModalNoBtn().off("click").on("click", function(event) {
      //    console.log("generic confirmation modal no button event callback");
      hideGenericConfirmationModal();
      unblockUI();
    });
  };

  showGenericConfirmationModal = function() {
    genericConfirmationModal().modal('show');
  };

  bindShowEventOnGenericConfirmationModal = function(elementTriggeredConfirmation) {
    genericConfirmationModal().off('shown.bs.modal').on('shown.bs.modal', function (event) {
      // console.log("generic confirmation modal shown event callback");
      bindHideEventOnGenericConfirmationModal();
      bindClickEventOnGenericConfirmationYesBtn(elementTriggeredConfirmation);
      bindClickEventOnGenericConfirmationNoBtn();
    })
  };

  hideGenericConfirmationModal = function() {
    genericConfirmationModal().modal('hide');
  };

  bindHideEventOnGenericConfirmationModal = function() {
    genericConfirmationModal().off('hidden.bs.modal').on('hidden.bs.modal', function (event) {
      // console.log("generic confirmation modal hidden event callback");
      // Nothing to handle as of now
    })
  };

  // ================ ENDS - CUSTOM rails_ujs CONFIRMATION DIALOG RELATED JS

}) (jQuery);

var ready;

ready = function() {
  bindblockUIOnAjaxLinks();
};

// References:
//  http://stackoverflow.com/questions/18769109/rails-4-turbo-link-prevents-jquery-scripts-from-working
//  http://stackoverflow.com/questions/18770517/rails-4-how-to-use-document-ready-with-turbo-links
$(document).ready(ready);
$(document).on('page:load', ready);
Jignesh Gohel
  • 5,425
  • 4
  • 46
  • 82
  • TypeError: $.blockUI is not a function – JosephK Jun 10 '17 at 07:34
  • @JosephK Sorry to hear that. `blockUI` And `unblockUI` those functions internally use jQuery BlockUI plugin. It's available at http://malsup.com/jquery/block/ . It's not a necessity to include that plugin but if you include it the code should start working. Basically those functions are used to show and hide Ajax request in progress indicators. Hope that helps. Thanks. – Jignesh Gohel Jun 11 '17 at 07:42
  • Ok - will try it in a future-project. I ended up just doing it all manually: When they click 'submit' on the custom box, I catch that, manually hide the box, remove the 'confirm' property of the link, then vanilla-javascript to "click" on the link (due to some odd-issue with jQuery's click function on links). – JosephK Jun 13 '17 at 11:10
  • $.rails is undefined. – Ali Ammaar May 29 '20 at 16:33