Users benefit from more engaging and pleasurable interactive web experiences. It results in higher user happiness and favorable opinions of a website. The user experience can be greatly enhanced by a form that offers immediate feedback and validation rather than making the user wait for a page refresh.

JavaScript contributes significantly to Drupal by giving developers the tools to build dynamic and engaging user interfaces for Drupal websites. Without having to reload the entire page, it enables developers to change the behavior of certain DOM elements, such as forms, links, or any other DOM elements, on a page. JavaScript methods called Drupal Behaviors are carried out when particular page events take place. Because they don’t have to alter any underlying HTML code, behaviors make it simple for developers to maintain and improve a site. The page contains all the information you need to know about Drupal Behaviors.

Understanding Drupal Behaviors

It is called when the DOM has finished loading entirely, however these behaviors can be called again in the future. Drupal’s official JavaScript documentation suggests that modules should implement JavaScript by attaching logic to Drupal.behaviors. Drupal.behaviors is an object inside the Javascript structure in Drupal that allows us to attach functions to be executed at specific times during the execution of the application.

Importance Of Drupal behaviors

The benefit of Behaviors is that any content that is loaded via AJAX has them automatically reapplied. They can be invoked at any moment with a context that denotes new DOM components or modifications. This is preferable to the single execution of the code in $(document).ready() and document DOMContentLoaded.

Situations Where Drupal Behaviors are Unwanted

Not always are Drupal behaviors the best choice for writing Javascript in Drupal. As explained below, Drupal characteristics are not always required.

  • Sometimes we must run some function that has no impact on the DOM. For instance, starting a third-party script like Google Analytics
  • When only one instance of a JS operation on the DOM is required since it is known that the element will be there when the page loads (this situation differs from using Once).

When are Drupal Behaviors called? 

  • once the website has loaded with an administrator overlay.
  • once a form has been submitted using the AJAX Form API.
  • when an AJAX request delivers a command, such ajax_command_replace(), that affects the HTML.

Additional Instances of Drupal Behaviors Invocation

  • It is invoked by CTools after a modal has loaded.
  • After the media browser has loaded, media calls it.
  • After in-place editing is finished, Panels calls it.
  • Views calls it after a new AJAX-enabled page has loaded.
  • After loading the following section of items, Views Load More calls it.
  • Drupal may be called by JavaScript from custom modules.When they add or modify a page’s elements, they should call attachBehaviors().

Writing Code Without Using Drupal Behaviors

The .views-row class is given a click event listener in this code that counts the number of times we have clicked on this row. However, it is only added once to the elements that are loaded into the DOM at the beginning of the page. The click listener does not function on the newly loaded items after clicking Load More and loading more things.

// No Drupal Behaviors

(function () {

 let header = document.querySelector(“.food-list-header”);

 if (header) {

   let greatFoodSpan = document.createElement(“span”);

   greatFoodSpan.textContent = “Get ready for great food!!!!!!”;

   header.append(greatFoodSpan);

}

 // Add the event listener for each click on the food

 let foods = document.querySelectorAll(“.views-row”);

 foods.forEach((food) => {

   food.addEventListener(“click”, () => {

     let foodCounter = food.querySelector(“.food-click-counter”);

     let timesClicked = parseInt(foodCounter.textContent.trim());

     foodCounter.textContent = ++timesClicked;

   });

 });

})();

event listener

Source: Specbee

Utilizing Drupal Behaviors

Use the attach approach, is the response.

Things to keep in mind

  • At a minimum, the new object must have an attach method. 
  • Every time Drupal.attachBehaviors is invoked, all behavior objects are iterated over and their corresponding attach methods are called.

Incorporating Drupal Behavior into Your Code

This is what the code looks like after adding Drupal Behaviors.

(function (Drupal) {

 Drupal.behaviors.exampleBehaviour1 = {

   attach: (context, settings) => {

     // Add a delicious text to the top of the document

     let header = document.querySelector(“.food-list-header”);

     // jQuery Equivalent

     // $(“.food-list-header”);

     if (header) {

       let greatFoodSpan = document.createElement(“span”);

       greatFoodSpan.textContent = “Get ready for great food!!!!!!”;

       header.append(greatFoodSpan);

     }

     // Add the event listener for each click on the food

     let foods = document.querySelectorAll(“.views-row”);

     foods.forEach((food) => {

       food.addEventListener(“click”, () => {

         let foodCounter = food.querySelector(“.food-click-counter”);

         let timesClicked = parseInt(foodCounter.textContent.trim());

         foodCounter.textContent = ++timesClicked;

       });

     });

   },

 };

})(Drupal);

behavior

Source: Specbee

However, when we click on Load More, an oddity emerges at the top:

This is due to Drupal behavior being called frequently, which causes some unwanted behavior.

Understanding Context in “Drupal Context”

  • For all behaviors, Drupal passes a context argument when invoking the attach method. 
  • The provided context parameter frequently provides a clearer picture of the DOM element being processed.
  • This is the entire HTML document when the page first loads; on future calls, it just contains the elements that are being added to or changed on the page.

Adding Context to Your Code

Using the context argument offered by Drupal Behaviors, the above issue can be fixed. In this instance, the header gets attached when the page loads for the first time and we receive the entire HTML document as context. The portion of the code that is impacted by Drupal Behaviors will be used for subsequent actions; as a result, that portion of the code is secure.

(function (Drupal) {

 Drupal.behaviors.exampleBehaviour2 = {

   attach: (context, settings) => {

     // Add a delicious text to the top of the document.

     // The context parameter now can be used for adding

     // certain functionality which removes unwanted repeatability

     let header = context.querySelector(“.food-list-header”);

     // jQuery Equivalent

     // $(“.food-list-header”, context);

     if (header) {

       let greatFoodSpan = document.createElement(“span”);

       greatFoodSpan.textContent = “Get ready for great food!!!!!!”;

       header.append(greatFoodSpan);

     }

     // Add the event listener for each click on the food

     let foods = context.querySelectorAll(“.views-row”);

     foods.forEach((food) => {

       food.addEventListener(“click”, () => {

         let foodCounter = food.querySelector(“.food-click-counter”);

         let timesClicked = parseInt(foodCounter.textContent.trim());

         foodCounter.textContent = ++timesClicked;

       });

     });

   },

 };

})(Drupal);

Source: Specbee

When we click Load More, there is some strange activity once more. The initial food supplies that were loaded are fine. However, the new items receive the click listener and begin functioning normally after pressing Load More. However, the initially loaded items have the listener attached once again and clicking on them repeatedly triggers the click event!

Load more

Source: Specbee

Identifying When Drupal Behaviors Misbehave

  • Without utilizing Once or Context, all event listeners must be written inside Drupal behaviors.
  • declaring unnecessary functions inside of Drupal behaviors, which causes the attach method to call each time a function is redeclared.

“Once” to the rescue

  • Once the code has been run, Once ensures that something is only handled once by adding a data-once property to a DOM element.
  • The element with the data-once property is skipped for further execution if the behavior is called again.
  • One instance of jQuery is called Once. Once controls all functionality exactly as we require it when combined with context (which is an effort to move away from jQuery).

To fix the event listeners in our code, add Once.

(function (Drupal, once) {

 Drupal.behaviors.exampleBehaviour3 = {

   attach: (context, settings) => {

     once(“food-header-initialized”, “.food-list-header”, context).forEach(

       (header) => {

         let greatFoodSpan = document.createElement(“span”);

         greatFoodSpan.textContent = “Get ready for great food!!!!!!”;

         header.append(greatFoodSpan);

       }

     );

     // jQuery Equivalent

     // $(“.food-list-header”, context).once(“food-header-initialized”, function (header) {

     //

     // });

     // Add the event listener for each click on the food

     once(“food-initialized”, “.views-row”, context).forEach((food) => {

       food.addEventListener(“click”, () => {

         let foodCounter = food.querySelector(“.food-click-counter”);

         let timesClicked = parseInt(foodCounter.textContent.trim());

         foodCounter.textContent = ++timesClicked;

       });

     });

   },

 };

})(Drupal, once);

data once

Source: Specbee

Everything now functions as intended. When event listeners are added to an element, the element receives a data-once attribute, and both newly loaded and previously loaded elements work as intended.

The Importance of the Detach Method

The Detach approach undoes what we accomplished with the Attach method, acting as an anti-hero (albeit one who is good). When content is removed from the DOM, the detach method’s logic will be executed. This aids in the application’s cleanup. Detach technique, for instance, helps us to get rid of unneeded event listeners that use resources continuously polling situations.

Examples of Detach

Let’s say we have an ajax form to fill out and we’re using a timer to display the amount of time that has passed. To control the timer, we utilize setTimeOut. This timer is recorded in the console for oversight.

(function (Drupal, once) {

 let counter = 0;

 Drupal.behaviors.exampleBehaviour4 = {

   attach: (context, settings) => {

     once(“timer-initalized”, “.contact-timer”, context).forEach((ele) => {

       const timer = context.querySelector(“.contact-timer-sec”);

       timer.textContent = counter;

       // Set the timer for user to see the time elapsed

       setInterval(() => {

         console.log(“This is logging”);

         const timer = document.querySelector(“.contact-timer-sec”);

         timer.textContent = ++counter;

       }, 1000);

     });

   },

 };

})(Drupal, once);

set timeout

Source: Specbee

The timer on the DOM is deleted upon form submission, but an error is now being thrown in the console. This is due to the DOM element on which setTimeOut was acting being deleted:

detach

Source: Specbee

To circumvent this, we can apply the detach method in the following manner:

(function (Drupal, once) {

 let counter = 0;

 let intervalStopper;

 Drupal.behaviors.exampleBehaviour4 = {

   attach: (context, settings) => {

     // Set the timer for user to see the time elapsed

     once(“timer-initialized”, “.contact-timer”, context).forEach((ele) => {

       const timer = context.querySelector(“.contact-timer-sec”);

       timer.textContent = counter;

       intervalStopper = setInterval(() => {

         const timer = document.querySelector(“.contact-timer-sec”);

         timer.textContent = ++counter;

         console.log(“This is logging”);

       }, 1000);

     });

   },

   // Clear the timer on confirmation

   detach: (context, settings, trigger) => {

     const timer = context.querySelector(“.contact-timer-sec”);

     if (trigger == “unload” && timer) {

       clearInterval(intervalStopper);

     }

   },

 };

})(Drupal, once);

Source: Specbee

When the timer is removed, the error is prevented, as can be seen from the logs.

JS Demo

Source: Specbee

The Role of Immediately Invoked Function Expressions (IIFE) in JavaScript

We have been writing our Drupal code using IIFE. The definition of an anonymous function in the initial opening parenthesis helps prevent the function’s scope from contaminating the application’s global scope. By adding them as arguments at the end of the function declaration, you can provide arguments to your anonymous function. This not only helps us to namespace the arguments, but it also allows us to use them however we want.

Example:

// Function name crisis!!!!

// The function is vulnearble to

// be replaced by some other function

function someFunction() {

 // Some code for this function

}

(function (Drupal) {

 // Function name crisis averted!

 function someFunction() {

   // Some code for this other function

 }

 Drupal.behaviors.exampleBehaviour6 = {

   attach: (context, settings) => {

     someFunction();

   },

 };

})(Drupal);

Source: Specbee

Conclusion

Utilizing Drupal behaviors improves the user experience of your website by enabling dynamic interactivity, streamlined user interaction, improved user feedback, and efficient development. Because they can be used numerous times on a page, can expand and replace current behavior, and can be immediately reapplied to any material loaded via Ajax, or Drupal. behaviors are adaptable and modular.

Moreover, if you are looking for a Drupal development company, then you should have a look at Appic Softwares. We have an experienced team of Drupal Developers who will help you with all of your technical requirements. You can even hire our developers on an hourly basis and make sure that your software runs smoothly.

So, what are you waiting for?

Contact us now!

This blog is inspired by <https://www.specbee.com/blogs/taming-javascript-in-drupal>