Simplify supporting AJAX/BigPipe in Drupal.behaviors: improve DX in case the selector to search is not in the given tree, but the root

Created on 31 March 2022, over 3 years ago
Updated 15 February 2023, over 2 years ago

Problem/Motivation

When bigpipe is enabled and I'm logged in (as admin), the context in Drupal.behavior functions is not the same as when logged out, so certain $('selectors', context) in behavior functions that work fine when logged out, do not work when logged in.

Steps to reproduce

A simple Drupal.Behavior function

(function (Drupal, once) {
  'use strict';

  Drupal.behaviors.bpBlockTest = {
    attach: function(context, settings) {
      once('bp-block-testin', '.my-block', context).forEach(el => {
        var addOn = document.createElement('span');
        addOn.innerHTML = 'addOn';
        el.after(addOn)';
      });
    }
  }

}(Drupal, once));

This works fine when big-pipe is not enabled or when you are logged out because the context that gets passed in these cases seems to be the HTML document.

But, when big-pipe is enabled and you are logged-in, the context passed is the element returned by bigpipe and no longer the HTML document, in this case <div class="my-block">....</div>

So my behavior code doesn't execute anymore because it is looking for $('.my-block') INSIDE the context of <div class="my-block">....</div> instead of inside <html>...</html>

So, without bigpipe or when logged out it works because:

$('.my-block', context = <html>...</html>).once('bp-block-testing').each(function () {
// gets exectuted
}

with big-pipe enabled and logged-in,

$('.my-block', context = <div class="my-block">....</div>).once('bp-block-testing').each(function () {
// does not get executed because jQuery is no longer looking for .my-block INSIDE the html context but INSIDE the .my-block context
}

essentially doing $('.my-block').find('.my-block') in this situation and finding nothing

Maybe this this is intended/normal but it breaks Drupal.Behavior execution in examples like above and it took me a long time to figure out why my JS was not working consistently through these different situations.

The solution is simple, remove the context, but since a lot of examples online do pass in the context to look for DOM elements, and all the examples on https://www.drupal.org/docs/drupal-apis/javascript-api/javascript-api-ov... this changes the conditions if a DOM element can be found in the context passed to a behavior function depending if big-pipe is enabled or not (and wether you are logged-in or not if it is enabled).

Feature request
Status

Active

Version

10.1

Component
Javascript 

Last updated 1 day ago

Created by

🇧🇪Belgium Percept

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • 🇪🇸Spain rodrigoaguilera Barcelona

    Interesting.
    Someone on my team has been bitten by this.
    Personally I think is bad developer experience having to remember selectors to avoid (like .block-xxxx) because they might be the context itself or adding additional logic to check if the context is the element you are trying to match.

    Not sure of the way forward but adding the logic to check for the context matching the selector (with context.matches(selector)) would be ideal for me.

    Updated the IS to use Drupal once as jQuery Once is removed in Drupal 10. Also use vanila JS.

  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    But, when big-pipe is enabled and you are logged-in, the context passed is the element returned by bigpipe and no longer the HTML document, in this case

    ....

    That is how the Drupal Behaviors API works, it's not related to BigPipe.

    You can observe this in the AJAX API too — see Drupal.AjaxCommands.insert:

          // Attach all JavaScript behaviors to the new content, if it was
          // successfully added to the page, this if statement allows
          // `#ajax['wrapper']` to be optional.
          if ($newContent.parents('html').length) {
            // Attach behaviors to all element nodes.
            $newContent.each((index, element) => {
              if (element.nodeType === Node.ELEMENT_NODE) {
                Drupal.attachBehaviors(element, settings);
              }
            });
          }
    

    👆 this only attaches behaviors to the newly inserted content, i.e. with the context not being the full document, but only the relevant DOM subtree.

    So this is completely unrelated to BigPipe, but is just one of the several ways to surface this.

    I do see how this can be confusing! Rescoping.

  • 🇪🇸Spain rodrigoaguilera Barcelona

    Fixed the code in the IS

Production build 0.71.5 2024