3

I created a custom theme (a sub-theme of Radix base theme).

I am loading a custom Javascript file (radix_sub.script.js) using the module libraries.yaml file like below:

style:
  version: VERSION
  css:
    theme:
      assets/css/radix_sub.style.css: {}
  js:
    '//cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js': { type: external }
    '//stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js': { type: external }
    assets/js/radix_sub.script.js: {}
  dependencies:
    - core/drupal
    - core/jquery
    - core/jquery.once
    - core/drupalSettings

In that file, I want to register some JavaScript events (Bootstrap modal).

(function ($, Drupal) {

  Drupal.behaviors.portfolio = {

    attach: function (context) {

      console.log('CONTEXT', context);

      $('#portfolioModal', context).once('bootstrap-modal-shown', console.log('*** REGISTER SHOWN.BS.MODAL')).each(function () {
        $(this).on('shown.bs.modal', function () {
          console.log('SHOWN BS MODAL EVENT TRIGGERED');
        });
      });

      $('#portfolioModal', context).once('bootstrap-modal-hidden', console.log('*** REGISTER HIDDEN.BS.MODAL')).each(function () {
        $(this).on('hidden.bs.modal', function () {
          console.log('HIDDEN BS MODAL EVENT TRIGGERED');
        });
      });
    }
  }
}(jQuery, Drupal));

It works. I see both SHOWN BS MODAL EVENT TRIGGERED and HIDDEN BS MODAL EVENT TRIGGERED logs when I show/hide the modal.

First, am I doing that the correct way?

This is what my developer console looks like when I load my page.

screenshot

This happens only when I am logged in (as admin). When I am anon, I see the REGISTER logs only once but here my code seems to be executed 11 times.

Is this normal behavior? And/or why is it doing that?

2
  • Is this normal behavior? prob not, can you replicate on fresh D8 install with Bartik theme?
    – No Sssweat
    Commented Mar 23, 2020 at 22:12
  • It ended up being "normal behavior" considering where I placed my console.log()s. jQuery once() is called on page load and on any AJAX load. All the scripts that can be seen in the screenshot (plainTextEditor.js, image.js, theme.js, etc.) are loaded on the page via AJAX, hence the 10 subsequent logs after the initial page load. The code within the jQuery.each() callbacks is executed only once.
    – MrUpsidown
    Commented Mar 24, 2020 at 11:23

1 Answer 1

5

The answer lies here:

$('#portfolioModal', context).once('bootstrap-modal-shown', console.log('*** REGISTER SHOWN.BS.MODAL'))

The code that comes after the call to $.once() will only be executed once. However, $.once() can and will be executed multiple times. That's why you are seeing your calls to the console being called multiple times. If you change this:

$('#portfolioModal', context).once('bootstrap-modal-shown', console.log('*** REGISTER SHOWN.BS.MODAL')).each(function () {
  // Do some stuff.
});

To this:

$('#portfolioModal', context).once('bootstrap-modal-shown').each(function () {
  console.log('*** REGISTER SHOWN.BS.MODAL');
  // Do some stuff.
});

You will see that your call to the console is only shown a single time. Note that you also will not use $.on() here, as the Drupal behaviors API replaces any necessity of it. Each time Drupal.behaviors are executed, a context argument is passed to it. This will be either the full DOM, or any HTML being newly inserted into the DOM. So where in normal jQuery you might do this:

$(".some_selector").on(someCallbackFunction);

In Drupal, you would do this:

Drupal.behaviors.someArbitraryKey = {
  attach: function(context) {
    $(context).find(".some_selector").once("some-arbitrary-key").each(someCallbackFunction);
  }
};

This is the Drupal way of achieving the same goals as using $.on() in normal jQuery, while tying in with the Drupal APIs.

---- Edit ----

Original answer below, which turned out to not answer the question. Left for posterity's sake, and because it actually adds on well to the end of the above answer.

Drupal behaviors are called on page load. It is also called upon AJAX load. You need to wrap code in $.once() if it should only be executed a single time:

function someElementListener(context) {
  // Call to $.once():
  $(context).find(".some_selector").once("arbitrary-key").each(function () {
    // Code here will only be executed a single time on
    // each .some_selector element found
    $(this).click(function() {alert("clicked");});
  });
}

Drupal.behaviors.someModule = {
  attach: function (context) {
    someElementListener(context);
  }
};

If you wrap your code in $.once(), your console calls will only be logged a single time.

4
  • 1
    he's already using .once()...
    – No Sssweat
    Commented Mar 23, 2020 at 23:50
  • I think that's what I am doing already and now I am thinking the problem only comes from where I placed my console.log() (in the once() function) which is called every time, including on AJAX calls as you mentioned. What's inside the each() callback though isn't called more than once.
    – MrUpsidown
    Commented Mar 24, 2020 at 1:35
  • Actually you are doing that. sorry I missed it, but I also see the answer to your question. I'll update my original answer.
    – Jaypan
    Commented Mar 24, 2020 at 2:40
  • Yes, thank you, that cleared up my doubts and I can confirm it works as expected!
    – MrUpsidown
    Commented Mar 24, 2020 at 10:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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