8

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?

2
  • 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
    Commented Aug 5, 2018 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
    Commented Aug 5, 2018 at 13:40

4 Answers 4

13

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

0
8

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) {
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;
        }
    }
};
0

You should add context to the dom object, which will limit execution to a particular context.

Refer to the example below which will fetch data from a remote url.

Drupal.behaviors.fetchTermsAndConditions = {
    attach: function (context, settings) {
        $('#clickableElementID', context).on('click', function(){
                var remoteURL = 'https://remotejson.url';
                $.ajax({
                    type: 'GET',
                    url: remoteURL,
                    success: function (data) {
                      {
                        $('#targetElementID').append(data.value);
                      }
                    }
                  });
                }); 
                
            }
      }
    };

In addition if the function to be executed on global context uses the html or body context it is Drupals equivalent to $(document).ready( myInit() );

Read more on the Drupal documentation here https://www.drupal.org/docs/drupal-apis/javascript-api/javascript-api-overview

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.