What is once() in Drupal and do you still need it?

JavaScript plays a crucial role in modern web development, enhancing the interactivity and user experience of websites and web applications. In the context of Drupal, a powerful and flexible content management system, JavaScript is utilized to create dynamic and responsive interfaces, making websites more engaging and user-friendly.

Back in the day web development was quite different from what it is right now. We had a lot of issues with making things work with different browsers and if you are old enough, you may remember Internet Explorer 6, which consumed modern web with only polyfills. The most used library was jQuery because it provided an API layer for Javascript that worked the same in all browsers.

jQuery provided a wrapper where developers put their main code and it was executed when the page was loaded, so the developer had all the elements available in DOM to manipulate. There were a lot of names for this wrapper, like document ready, jQuery ready, and so on. It looks (still) like this:

$(document).ready(function () { // Your code here });

So then AJAX became more popular to enhance user experience on the page. This meant basically that there is no real "ready" event anymore - yes it still worked, but it did not work with content loaded later, with AJAX. There was an ajaxComplete event if you used jQuery to create AJAX requests.

As I started working with Drupal I frequently saw how things broke when someone enabled AJAX in View - things worked just fine when the page was loaded but stopped if an AJAX request was made (some filter or sort applied, page changed). Drupal had a solution for that - behaviors and it looks something like this:

Drupal.behaviors.myFunctionality = { attach: function (context, settings) { // Your code. } }

Now, things were applied if you wrapped your code inside behavior, but it usually caused another issue - things were applied multiple times and caused fun things to happen on the page like collapse elements opening and closing multiple times, interesting to see, but bad on the UX side. To overcome that, Drupal also had a jQuery plugin called jQuery.once(). It was really easy to use and it kept track of things just fine, so things that must apply, will apply only one time and it is still used a lot in Drupal.

Things have evolved, and new possibilities and best practices have emerged the question is - how relevant the once() still is, and what could be used instead of it?

drupal/once library

Previously known as jQuery.once(), but now ported to a plain Javascript library.

Available on Drupal by default, but can be installed separately with npm. The library is simple, easy to use, written in plain Javascript. To get most of it, something should call it again, if something changes on the page, Drupal has behaviors for this, elsewhere you need to handle it yourself.

You can use only the elements, that support data attributes, window and document objects cannot be used which could be a problem in some cases.

There is already good Javascript API documentation at Drupal.org available. The example usage is something like this:

Drupal.behaviors.myFunctionality = { attach: function (context, settings) { // Call once on all elements that has class js-clickable-element. once('myFunctionality', '.js-clickable-element').forEach(function (element) { // Add click event listener. element.addEventListener('click', e => { // Toggle class on the target element. e.target.classList.toggle('js-toggle-class'); }); }) } }

Event delegation

Event delegation works by listening to events on a higher level of DOM element, like a document or body, and inside of it, you check the target element or elements to determine if the event was triggered on the element you wanted. This works in cases where new elements appear (unless you switch out the element you added that event listener) and you don't have to bind events to them.

This works in all places where Javascript is available, no extra libraries are needed. In Drupal, if using behaviors, you could combine them with the once function so it is triggered only one time, which could defeat the purpose of event delegation, so most of the time you could use it outside of behaviors.

Simple usage of event delegation:

document.addEventListener('click', e => { // Check if it is the element we target or not. if (!e.target.classList.contains('js-my-class') { // Skip further execution, if it is not the element we need. return; } // Toggle class on the target element. e.target.classList.toggle('js-toggle-class'); })

Javascript frameworks

This could be a bit out of the main topic, but I think it still fits here, because it may give an alternative angle to build your functionality.

There are a lot of Javascript frameworks available, each has its purpose and philosophy. When the dynamic functionality on the page is complicated and massive, then I would recommend choosing some framework to do the heavy lifting. Vue.js and React are good to start with. They give better control of things and you don't need to worry about details, or how DOM should be manipulated and can concentrate on the main functionality instead. An additional benefit is that you can load everything onto the page and dynamic queries are not needed anymore, which could be useful if you want to create static sites with Tome.

Choosing this is a major decision but still an alternative to Drupal Javascript API and still can be used in Drupal by either loading all data to a page or creating some endpoint to pull data.

In conclusion

The once function is still useful, but there are other ways to achieve the same results. It is good that the original implementation was modernized and ported away from jQuery, but event delegation is a good alternative and would be recommended over the once, but it will have still problems if you want to use Drupal behaviors. 

I would recommend using it when the functionality is easy and small. If things get more complicated, build some javascript app with Vue.js or React

Buy Me a Coffee at ko-fi.com

Add new comment