1

I am using a Bootstrap subtheme, and would like to trigger an event when a modal is closed.

Reviewing the main theme's modal.jquery.ui.bridge.js file, I see that the bridge is intending to map jQuery UI events (beforeClose, close) to framework events (hide.bs.modal, hidden.bs.modal).

I'm struggling with figuring out how to properly declare the data-dialog-options attribute of the triggering link to pass this event through the bridge.

In the JS file, this.options.jQueryUiBridge is true when typeof options === 'object'. My guess is that my options are not typeof object.

My attributes array on the Url object is

  $attributes = [
    'class' => ['use-ajax'],
    'data-dialog-type' => 'modal',
    'data-dialog-options' => Json::encode([
      'dialogClass' => 'modal-content--special-class',
      'beforeClose' => 'function() {console.log("closed");}',
    ]),
  ];

which results in the rendered link

<a href="/my-content-route" class="use-ajax" data-dialog-type="modal" data-dialog-options="{&quot;dialogClass&quot;:&quot;modal-content--space-wizard&quot;,&quot;beforeClose&quot;:&quot;function() {console.log(\u0022closed\u0022);}&quot;}">My Link Text</a>

What am I missing?

2

The technical reason it's not doing anything is that the value is retrieved as a string and not as a Function object.

You shouldn't pass anonymous functions in data attributes.

This would require whatever implements the callback (jQuery UI/Bootstrap) property to use eval() or some other sort of method to evaluate the callback.

This, of course, has major security implications and why it doesn't work.

All callbacks should be added via JavaScript.

If you don't have control over specific implementation, you can always set a global listening event and then use some sort of unique identifier on the modal to trigger a custom callback.

Example:

(function ($){
  var $document = $(document);

  // Create a custom beforeClose handler.
  var customBeforeCloseHandler = function (e) {
    console.log('closed');

    // Optionally, you should be able to stop it from closing
    // if that is the desired outcome by using the following:
    // e.preventDefault();
  };

  // jQuery UI Dialog Events.
  // @see http://api.jqueryui.com/dialog/#events
  $document.on('dialogbeforeclose', '.ui-dialog', function(e) {
    var $dialog = $(e.target);

    // Check if this dialog is ours.
    if ($dialog.is('.modal-content--special-class')) {
      // Proxy this event to our custom handler.
      customBeforeCloseHandler(e, ui);
    }
  });

  // Bootstrap Modal Events.
  // @see https://getbootstrap.com/docs/3.4/javascript/#modals-events
  $document.on('hide.bs.modal', '.modal', function (e) {
    var $modal = $(e.target);

    // Check if this modal is ours.
    if ($modal.find('.modal-content').is('.modal-content--special-class')) {
      // Proxy this event to our custom handler.
      customBeforeCloseHandler(e);
    }
  });

})(jQuery);

Given that the Drupal Bootstrap base theme has the jQuery UI Dialog bridge, I would recommend binding using jQuery UI Dialog events so it will work even without Bootstrap. That being said, if you can't seem to get that to work... using Bootstrap's events should work just as well.

However, don't use both as that would likely trigger your custom handler for both events.

  • "All callbacks should be added via JavaScript." If I knew how to do that, I wouldn't be attempting to use the attributes-driven API. Could you expand on how I would add a callback function to an existing data-dialog-options attribute? – akalata Jun 3 at 15:05
  • I've added an example to my original post. – Mark Carver Jun 3 at 16:19
  • Thanks Mark. I was able to get the Bootstrap version firing, but had to remove the specific class check since .modal is not on the same element as .modal-content--special-class. In the jQuery version, .ui-dialog and the specific class are at the same level; to get jQuery working, we need to match the bridge and use ui-front as the selector instead. – akalata Jun 3 at 17:28
  • PS the ui variable is missing from $document.on('dialogbeforeclose', '.ui-dialog', function(e), but SO would not allow the edit since it's under 6 characters. – akalata Jun 3 at 17:35
  • This was just off the top of my head, I didn't actually execute these. The exact DOM tree, of course, would vary between the two implementations. I've updated the Bootstrap example to reflect how ui-dialog is mapped so it checks the correct element. I also purposefully left out the ui argument from the jQuery UI dialog callback because using it would mean you're reliant on it. This prevents portability should it be replaced with something else. – Mark Carver Jun 3 at 22:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.