Matt Glaman: Using the new add_suggestion Twig filter in Drupal 10

When building my wife's web store, I used a new feature in Drupal 10 when templating her product display pages. Drupal 10 introduced a new Twig filter called add_suggestion. Drupal's render array output is templated through Twig and provides a base template name and more refined alternatives for controlled templating. Here's a list of template suggestions for outputting an image field from a product entity, from the most generic to the most specific.

Specbee: Taming JavaScript in Drupal (Includes FAQs)

Taming JavaScript in Drupal (Includes FAQs) Sagar Chauhan 07 Feb, 2023 Subscribe to our Newsletter Now Subscribe Leave this field blank

Interactive web experiences provide a more engaging and enjoyable experience for users. It leads to increased user satisfaction and a positive perception of a website. For example, a form that provides instant feedback and validation, rather than making the user wait for a page refresh, can significantly improve the user experience.

JavaScript plays an important role in Drupal by providing the means to create dynamic and interactive experiences for users on the frontend of a Drupal website. It enables developers to modify the behavior of certain elements on a page, such as forms, links, or any other DOM elements, without having to refresh the entire page. Drupal Behaviors are JavaScript functions that get executed when specific events occur on a page. Behaviors make it easy for developers to maintain and upgrade a site as they don’t need to change any underlying HTML code. Find out all you wanted to know about Drupal Behaviors in the article.

Image removed.

What are Drupal Behaviors?

Drupal.behaviors is an object inside the Javascript structure in Drupal, which allows us to attach functions to be executed at certain times during the execution of the application. It is called when the DOM is fully loaded, but these behaviors can be called again. Drupal’s official JavaScript documentation suggests that modules should implement JavaScript by attaching logic to Drupal.behaviors.

Why do we need Drupal behaviors?

The advantage of Behaviors is that they are automatically re-applied to any content that is loaded through AJAX. They can be called at any time with a context that represents new additions or changes to the DOM. This is better than $(document).ready() or document DOMContentLoaded where the code is just run once.

When are Drupal behaviors unwanted?

Drupal behaviors are not always the perfect solution for writing Javascript in Drupal. In some cases, as stated below Drupal behaviors are not needed at all!

  • When we need to execute some code that does not affect the DOM. Eg. Initializing an external script like Google Analytics
  • When some JS operation needs to be performed on the DOM just once knowing that the element will be available when the page loads (This scenario is different from using Once).

When are Drupal Behaviors called?

  • After an administration overlay has been loaded into the page.
  • After the AJAX Form API has submitted a form.
  • When an AJAX request returns a command that modifies the HTML, such as ajax_command_replace().

Other times when Drupal Behaviors are invoked

  • CTools calls it after a modal has been loaded.
  • Media calls it after the media browser has been loaded.
  • Panels calls it after in-place editing has been completed.
  • Views calls it after loading a new page that uses AJAX.
  • Views Load More calls it after loading the next chunk of items.
  • JavaScript from custom modules may call Drupal.attachBehaviors() when they add or change parts of the page.

Writing code without Drupal behaviors

In this code, we are adding a click event listener to the .views-row class which calculates the number of times we are clicking on this row. But it is added only once to the elements which come in the DOM during the initial page load. After clicking on Load More and loading more items, the click listener does not work on the newly loaded items.

// 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; }); }); })(); Image removed.

How do we use Drupal Behaviors?

Answer: Using the attach method

Things to remember:

  • The new object needs to have at least an attach method. 
  • Anytime Drupal.attachBehaviors is called, it will iterate through all behavior objects and call their respective attach methods.

Adding Drupal behavior to our code

After adding Drupal Behaviors, the code looks something like this.

(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);

 But something odd appears in the top when we click on Load More:

Image removed.

 

This is because Drupal behavior is called a lot of times and subsequently we get some unintended behavior.

What is Context in “Drupal context”?

  • When calling the attach method for all behaviors, Drupal passes along a context parameter. 
  • The context parameter that is passed can often give a better idea of what DOM element is being processed.
  • During the initial page load this will be the complete HTML Document; during subsequent calls, this will be just the elements that are being added to the page or get modified.

How to add Context?

The previous problem can be solved by using the context parameter that is provided by Drupal Behaviors. In this case, the first time the page loads, we get the whole HTML Document as context and that’s when we attach the header. For further operations, it will be the part of the code which is affected by Drupal Behaviors and hence that part of the code is safely controlled.

(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);

Again there is some odd behavior when we click on Load More. The food items which were initially loaded work fine. But After clicking on Load More, the new items get the click listener and work normally. But the initially loaded items get the listener attached again and clicking on them calls the click event more than once!

Image removed.

 

When do Drupal Behaviors start misbehaving?

  • Writing all the event listeners inside Drupal behaviors without using Once and Context.
  • Declaring unwanted functions inside Drupal behaviors which leads to the redeclaration of functions every time the attach method is called.

“Once” to the rescue

  • Once ensures that something is processed only once by adding a data-once attribute in a DOM element after the code has been executed.
  • If the behavior is called again, the element with the data-once attribute is skipped for further execution.
  • Once is a modern implementation of jQuery.once (which is an endeavor to move away from jQuery)
  • Once, in combination with context, controls the entire functionality perfectly as we need it.

Adding Once to fix the event listeners in our code

(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); Image removed.

 

Now everything works as intended. We get a data-once attribute to the elements where the event listeners are attached and newly loaded elements and previously loaded elements function properly.

The Need for Detach method

The Detach method acts like an anti-hero (not evil), removing whatever we did in the attach method. Any code in the detach method will be called whenever content is removed from the DOM. This helps us to clean up our application. For example, Detach method enables us to remove unwanted event listeners which consume resources like a continuous polling situation.

Examples of Detach

Assume that we have an ajax form to fill and we are using a timer to show the time elapsed. We use setTimeOut to manage the timer. We log this timer in the console for monitoring.

(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); Image removed.

 

On form submission, the timer on DOM gets removed but the console starts throwing an error. This is because the element on which the setTimeOut is acting has been removed from DOM:

Image removed.

 

To avoid this we can use the detach method like this:

(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);

This removes the timer on unload and as seen from the logger, the error does not occur.

Image removed.

Immediately Invoked Function Expressions (IIFE) - The wrapper for JS

We have been using IIFE to write our Drupal code. The initial opening parentheses define an anonymous function which helps prevent the function's scope from polluting the global scope of the entire application. You can pass arguments to your anonymous function by including them as arguments at the end of the function definition. 

This also helps us to namespace the parameters however we want them to be used.

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);

Final Thoughts

Implementing Drupal behaviors allows for dynamic interactivity, streamlined user interaction, improved user feedback, efficient development and overall enhanced user experience of your website. Drupal.behaviors are flexible and modular, in that they can be executed multiple times on a page, can override and extend existing behavior, and can be automatically re-applied to any content loaded through Ajax. 

Looking for a Drupal development agency to help you build interactive web experiences, making the best out of Drupal? We’d love to talk!

Author: Sagar Chauhan


Meet Sagar Chauhan, Lead Engineer, Acquia-certified Frontend Developer, soccer player, and a huge Arsenal fan. Sagar loves long train journeys and checking out latest gadget reviews. If you want to invite him for a meal (and you don’t know how to cook), Maggi would be perfect! :)

Email Address Subscribe Leave this field blank Drupal Drupal Development Drupal Planet CSS/JS

Leave us a Comment

 

Recent Blogs

Image Image removed.

Taming JavaScript in Drupal (Includes FAQs)

Image Image removed.

From Aspirations to Accomplishments - Malabya Tewari's Career Story

Image Image removed.

How to Create a Custom Module and add CSS Libraries in Drupal 9

Want to extract the maximum out of Drupal? TALK TO US

Featured Success Stories

Image removed.

A Drupal powered multi-site, multi-lingual platform to enable a unified user experience at SEMI.

Image removed.

Discover how our technology enabled UX Magazine to cater to their massive audience and launch outreach programs.

Image removed.

Discover how a Drupal powered internal portal encouraged the sellers at Flipkart to obtain the latest insights with respect to a particular domain.

VIEW ALL CASE STUDIES

Web Wash: Customize View Fields using Twig in Drupal

Views is a powerful module that allows you to create all sorts of components. It can be used to create something simple such as a list of articles, or complex such as a carousel or even an embedded map.

The Views UI can be intimidating if you’re new to Drupal, but as you use the module, you’ll find bits of functionality hidden deep in the interface.

One feature I want to discuss, which may not be evident at first, is the ability to add Twig code into view a fields.

Adding Twig code into a field allows you to change a field’s output dynamically. Which can be helpful under certain circumstances.

To add Twig code or some HTML into a field, click on the field, expand “Rewrite results”, check “Override the output of this field with custom text” and add your code into the text area.

Talking Drupal: Talking Drupal #385 - Off The Cuff: Drupal 10, Skills Rot, and Contrib

Today we are talking about Drupal 10, Skills Rot, and Contrib with our hosts.

For show notes visit: www.talkingDrupal.com/385

Topics
  • Contribution
    • Smart Date
    • Calendar View
    • Core
  • Keeping up to date
  • Using Drupal 10
  • Dependency issues for Drupal 8
Resources Hosts

Nic Laflin - www.nLighteneddevelopment.com @nicxvan John Picozzi - www.epam.com @johnpicozzi Katherine Druckman - katherinedruckman.com @katherined Martin Anderson-Clutz - @mandclu

MOTW Correspondent

Martin Anderson-Clutz - @mandclu Pathauto Automatically generates SEO-friendly URLs (path aliases) for various kinds of content (nodes, taxonomy terms, users) without requiring the user to manually provide one.

MidCamp - Midwest Drupal Camp: 🎟️ 2023 Tickets Available Now!

🎟️ 2023 Tickets Available Now!

Early-bird tickets end February 14, 2023.

Get your ticket now!

MidCamp's pricing is meant to lower the financial barrier to entry for attendees. Every Wednesday/Thursday ticket offers the same experience:

  • 30 high-quality sessions over two days,
  • coffee, lunch, and maybe even snacks, 
  • conversations with 100+ awesome Drupal-folks, and
  • evening socials that are as accessible and inclusive as we can make them.

With that said, we know many folks come to camp from different places, and we want to make sure MidCamp is accessible to anyone who wants to join us.

  • Early Bird ($50) ends February 14, 2023 (with an extension for anyone who submitted a session that is not accepted).

  • Regular Admission ($100) ends April 5, three weeks before camp, so we can get our catering numbers finalized.

  • Late/Corporate Admission ($200) is available online until camp starts and then at the door. It’s priced for attendees who have generous professional development budgets and helps makes up for the impact to our catering costs for folks who buy at the last minute.

  • Students ($25) whether you’re in college, in a bootcamp, or otherwise furthering your education, we want you to join us and learn about the magic that is the Drupal community. We welcome you at this price-point, no questions asked.

  • Sponsored ($0) tickets are available for those who don’t fit in any of the labels above. MidCamp is committed to being accessible to anyone and everyone who is interested in learning and participating in our community. We’ll teach you, we’ll feed you, and we’ll welcome you with open arms. Get your sponsored ticket here.

As always, Contribution Day is free, but we've split that out as a separate (free) ticket so we can gauge attendance.

Whomever you are, wherever you come from, we look forward to seeing you in March 2023.

Ticket Schedule

That's all a lot to digest, so here's the summary:

  Price Ends Early Bird $50 February 14, 2023 Regular $100 April 5, 2023 Corporate/Late $200 at camp! Student $25 at camp! Sponsored $0 at camp!

Palantir: Tessa's DrupalEasy Fellowship Experience: Collaborative, supportive, and agile

Internships and Fellowships

In this second part of a four-part series, Tessa talks about her background, the projects she's worked on, and the vision for her professional future

There is no one way to change a career path. Palantir.net’s four most recent fellows - Paak, Tessa, Travis, and Yang - all joined us through the DrupalEasy program. With their different professional backgrounds and experiences, each offers a unique perspective into what interested them in Drupal and their journey to becoming integral members of Palantir.net.

In each of their written entries they share, among other insights, how they have each adjusted to a fully-remote workplace, how their own skills supported their success as a Fellow, and the importance of Palantir.net’s culture which encourages asking questions, remaining curious, and reaching out for help.

This is Tessa's story.
 

Where I Started

I’ve always enjoyed learning about tech, mostly focused in hardware; building/maintaining computers, networking, etc. Initially, I didn't really see myself learning programming or coding beyond what was required to make the hardware work; over time, however, I started to figure out that most of the fun with technology was in software, so I began to work in this space. I was working in IT Support before I was fortunate enough to hear about Palantir.net’s Fellowship opportunity to attend DrupalEasy.

My previous experience with networking architecture and command line for computers gave me a bit of a head start with learning Drupal at DrupalEasy, but much of it was new to me. What encouraged me was how Mike Anello, the lead instructor and curriculum developer at DrupalEasy, was so thorough in not only covering the basics, but in encouraging and being available for any and all questions I may have. 

Although there is only so much that can be learned about any given subject in the space of only a few months, I was able to take what I learned and build on it in a meaningful way and continue to grow. I felt comfortable in my professional development during my Plantir.net Fellowship, never feeling I was out of my depth or beyond my scope of ability. 

As front-end or back-end are the easiest entry points, I opted for back-end and have been working on learning and growing as an engineer. Figuring out how code works is a challenge but quite rewarding once things click into place.

The supportive atmosphere and agile way of thinking and working that Palantir.net provided is unlike anything I’ve experienced before within a work environment. I am glad I received this opportunity and look forward to where it will lead.

Where I Am Now

An amazing number of Palantiri have contributed to my success and inspired me to grow. I’ve found myself torn in many potential directions of interest and growth that I’m now stuck with the happy challenge of what direction I would like to take in my professional future. Working with tech of any flavor, whether for work or as a hobby, is always an opportunity for learning, and I am glad to see so much potential for my own development and growth. 

Having been with Palantir.net for a while now, I’ve found that the entire team has been eager to provide opportunities and support when I’ve shown interest in growing and expanding my current knowledge base. I have already branched out into Dev-Ops and am now looking at some project ownership down the road, as well as more coding in general. 

Community Culture Drupal People