4

I'm creating some accordion functionality for fields and paragraphs using a combination of twig templates and a custom JavaScript file.

Everything prints as expected and the basic accordion functionality works; however, the click function script is running multiple times, causing an "expand-collapse-expand-collapse..." behavior. See image:

browser and console view that shows logging of each function iteration on click

Here is the script file:

(function($, Drupal) {

/* Add span to wysiwyg button classes for alignment
------------------------------------ */
Drupal.behaviors.accordionFaqFunction = {
  attach: function (context, settings) {
        $('.toggle').click(function(e) {

                e.preventDefault();
                var $this = $(this);

                if ($this.next().hasClass('show')) {
                        $this.next().removeClass('show');
                        $this.next().slideUp(350);
                        console.log("hiding");
                } else {
                        $this.parent().parent().find('li .inner').removeClass('show');
                        $this.parent().parent().find('li .inner').slideUp(350);
                        console.log("showing");
                        $this.next().toggleClass('show');
                        $this.next().slideToggle(350);
                }
        });


  }
};

})(jQuery, Drupal);

Why is the script running multiple times?

  • 1
    High level guess - because Drupal.behaviors.attach is executing multiple times, and you’re not making use of once to ensure the event handlers are only bound once – Clive Aug 5 '18 at 13:26
  • 1
    @Clive that may well be the case - would you please explain in an answer how to best do this for Drupal.Behaviors? – lane Aug 5 '18 at 13:40
10

Most importantly you have to use the JQuery selector context. Otherwise you run the entire DOM through your js code anytime when Drupal behaviors passes you only context to process.

If your function still runs multiple times, add once() with a unique identifier:

$('input.myCustom', context).once('mySecondBehavior').each(function () {

Don't forget to add the library core/jquery.once to mytheme.libraries.yml, see https://www.drupal.org/docs/8/api/javascript-api/javascript-api-overview

| improve this answer | |
4

I just came across this post while working something out in our project. We have multiple events bound to $('body', context) since flexibility in the platform allows for widgets and such to be placed almost anywhere within the body.

Using $('body', context).on('click', '.modal-trigger', function (e) {}); was causing the click event to be bound on body 2x.

Simply using $('body', context).once().on('click', '.modal-trigger', function (e) {}); was binding only the last attached/loaded script to the body, resulting in no other script events being attached.

Adding .once() with a unique identifier for each script solved this issue beautifully.

Ex. 1st modal script

  Drupal.behaviors.SigmaModal = {

    attach: function (context, settings) {

      // Trigger sigma dialog on click.
      $('body', context).once('SigmaModal').on('click', '.sigma-modal-trigger', function (e) {

2nd modal script

  Drupal.behaviors.DisplayModal = {

    attach: function (context, settings) {

      // Trigger generic dialogs on click.
      $('body', context).once('DisplayModal').on('click', '.modal-trigger', function (e) {
| improve this answer | |
1

You can try this one on Drupal 8:

Drupal.behaviors.custom = {
    attach: function(context, settings) {
        if(!Drupal.behaviors.custom.click_set){
            //Your code.
            Drupal.behaviors.custom.click_set = true;
        }
    }
};
| improve this answer | |

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.