0

I have some simple dom manipulation I want to make using javascript without jquery. I can't seem to figure out how to prevent this code from firing twice.

Almost all examples I find use jQuery, but I do not want to add jQuery just for this. I'm trying to use es6 with babel here.

((() => {
  Drupal.behaviors.node_page_filters_sales = {
    attach: (context, settings) => {

      const usaButton = document.querySelector('[data-sales-switcher="83"]');
      usaButton.style.border = '6px solid orange';
    }
  };
})());

This code works, but it is run twice. Once when there selector queried does not exist, giving me a typeError, and one when it does, executing the code as expected.

It seems like it's related to BigPipe. If i turn that off, things seem to work as normal, firing once. But I obviously want BigPipe turned on. I'm clearly missing something that needs to be done when dealing with it.

For a little more detail, this is in a custom module. The custom module creates a block plugin. The javascript (snippet above) is part of the custom module's library that's attached to the block template.


UPDATED CODE EXAMPLE

The inline style was just for testing purposes. I'm attempting to check multiple buttons and add/remove classes based on a variable. See below.

// Determine default region.
let defaultLocation = '83';

if (defaultLocation === '83') {
    document.querySelector('.tid-83').classList.add('active');
}

I also notice that the javascript works as expected and is only executed once if I'm not logged in. When logged in, it fires twice.

1
  • I think I get that BigPipe uses ajax or something to load the block in after the page has loaded. So it makes sense, maybe, that the js is run twice. I guess the question is, how to have my code wait until all elements are loaded - or something to that effect.
    – xpersonas
    Jun 24, 2019 at 22:52

1 Answer 1

2

I don't think you can force javascript to only run once. In addition to big pipe, any lazy loader, ajax form update (consider entity reference fields), or other ajax functionality on the page can trigger javascript to run again.

This is the issue that jQuery.once() addresses, and the reason it is used. It adds a class on the element, then executes on elements that do not have the class and adds the class. I expect you could reproduce this with something like the following.

const usaButton = document.querySelector('[data-sales-switcher="83"]');
if (!usaButton.classList.contains('node-page-filters-sales-once') {
  usaButton.style.border = '6px solid orange';
  usaButton.classList.add('node-page-filters-sales-once');
}
4
  • 1
    A couple other thoughts: 1: Using context instead of document for the query selector may help you with performance. 2: Adding a class and styling that class rather than adding inline styles will help you maintain your code in the future. Jun 24, 2019 at 23:13
  • Thank you for that. But what I was really trying to understand is, how do I prevent my javascript from being ran twice? What you have there would theoretically work, but it's just checking to see if the code has already added a class. See my updated code. That wouldn't really work there where it's multiple elements adding/removing classes based on a condition.
    – xpersonas
    Jun 25, 2019 at 13:33
  • 1
    Your javascript is going to be run when the window updates. Any future lazy loader, ajax form update, or other functionality can trigger javascript to run again. The method used by core is used because checking for a class on an element is a very light process. The class that you add for this purpose is only added to make sure that the internal js only executes once and should not be removed. So I've updated my answer to reflect this comment. Jun 25, 2019 at 16:40
  • Ah. Ok, That makes sense. So really when I was trying to avoid the typeerror (because the element did not exist yet), I just needed to be checking for the existence of that element on the page. For some reason it lead me down a path thinking my js was set up wrong and running twice. Thank you for helping me understand.
    – xpersonas
    Jun 25, 2019 at 20:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.