Problem/Motivation

As mentioned in the parent issue #3238306: [META] Where possible, refactor existing jQuery uses to vanillaJS to reduce jQuery footprint, we are working towards reducing our jQuery footprint. One of the ways to accomplish this is to reduce the number of jQuery features used in Drupal core. We have added eslint rules that identify specific features and fail tests when those features are in use.

There are (or will be) individual issues for each jQuery-use eslint rule. This one is specific to jquery/no-trigger, which targets the jQuery trigger function.

Steps to reproduce

Proposed resolution

Remaining tasks

  • In core/.eslintrc.jquery.json Change "jquery/no-trigger": 0, to "jquery/no-trigger": 2, to enable eslint checks for uses of jQuery .trigger(). With this change, you'll be able to see uses of the undesirable jQuery feature by running yarn lint:core-js-passing from the core directory
  • Add the following lines to core/scripts/dev/commit-code-check.sh so the DrupalCI testing script can catch this jQuery usage on all files, not just those which have changed
    # @todo Remove the next chunk of lines before committing. This script only lints
    #  JavaScript files that have changed, so we add this to check all files for
    #  jQuery-specific lint errors.
    cd "$TOP_LEVEL/core"
    node ./node_modules/eslint/bin/eslint.js --quiet --config=.eslintrc.passing.json .
    
    CORRECTJQS=$?
    if [ "$CORRECTJQS" -ne "0" ]; then
      # No need to write any output the node command will do this for us.
      printf "${red}FAILURE ${reset}: unsupported jQuery usage. See errors above."
      STATUS=1
      FINAL_STATUS=1
    fi
    cd $TOP_LEVEL
    # @todo end lines to remove

    Add the block about 10 lines before the end of the file, just before if [[ "$FINAL_STATUS" == "1" ]] && [[ "$DRUPALCI" == "1" ]]; then, then remove it once all the jQuery uses have been refactored.

  • If it's determined to be feasible, refactor those uses of jQuery .trigger() to use Vanilla (native) JavaScript instead.

Files to fix | Usage count

  • core/misc/ajax.js (5)
  • core/misc/details.js (1)
  • core/misc/dialog/dialog.ajax.js (5)
  • core/misc/dialog/dialog.jquery-ui.js (1)
  • core/misc/dialog/dialog.js (4)
  • core/misc/dialog/dialog.position.js (1)
  • core/misc/dialog/off-canvas/js/off-canvas.js (2)
  • core/misc/displace.js (1)
  • core/misc/form.js (3)
  • core/misc/machine-name.js (1)
  • core/misc/states.js (2)
  • core/misc/tabbingmanager.js (5)
  • core/misc/tabledrag.js (4)
  • core/misc/tableresponsive.js (2)
  • core/misc/tableselect.js (2)
  • core/misc/vertical-tabs.js (1)
  • core/modules/contextual/js/contextual.js (1)
  • core/modules/editor/js/editor.admin.js (3)
  • core/modules/editor/js/editor.dialog.js (1)
  • core/modules/editor/js/editor.js (1)
  • core/modules/field_ui/field_ui.js (3)
  • core/modules/file/file.js (3)
  • core/modules/filter/filter.js (1)
  • core/modules/media_library/js/media_library.click_to_select.js (1)
  • core/modules/media_library/js/media_library.ui.js (3)
  • core/modules/media_library/js/media_library.view.js (1)
  • core/modules/menu_ui/menu_ui.js (3)
  • core/modules/node/node.preview.js (1)
  • core/modules/settings_tray/js/settings_tray.js (5)
  • core/modules/system/js/system.date.js (1)
  • core/modules/system/js/system.js (1)
  • core/modules/text/text.js (1)
  • core/modules/toolbar/js/toolbar.js (3)
  • core/modules/toolbar/tests/src/Nightwatch/Tests/toolbarTest.js (4)
  • core/modules/tour/js/tour.js (1)
  • core/modules/views_ui/js/ajax.js (2)
  • core/modules/views_ui/js/dialog.views.js (1)
  • core/modules/views_ui/js/views-admin.js (6)
  • core/tests/Drupal/Nightwatch/Tests/tabbableShimTest.js (2)
  • core/themes/claro/js/details.js (1)
  • core/themes/claro/js/nav-tabs.js (1)

User interface changes

API changes

Data model changes

Release notes snippet

Issue fork drupal-3239127

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

hooroomoo created an issue. See original summary.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

cosmicdreams’s picture

So part of this issue is about having a linting rule put in place to ensure that new code added to Drupal does not implement jQuery's .trigger method AND the same rule provides a nice checklist of all the places where Drupal core is currently implementing that method. That's a good piece of foundational / platform support for ensuring we're reducing our jQuery footprint.

That's part of, but not all of, the main goal here. The main goal is to actually reduce jQuery's footprint by removing all the instances of the method. Once that's done, we can close the door on adding new code that uses that part of jQuery. Then we're done.

In my opinion, we are currently unblocked from making progress in that area. So i'm going to hack on this idea a bit.

cosmicdreams’s picture

Current output of yarn lint:core-js-passing when the jquery/no-trigger rule is enabled:

/var/www/html/web/core/misc/ajax.js
   584:15  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
   585:15  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
   587:17  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
   744:7   error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  1101:15  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/misc/details.js
  21:5  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/misc/dialog/dialog.ajax.js
   38:11  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
   42:9   error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  102:15  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  102:15  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  102:15  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/misc/dialog/dialog.jquery-ui.js
  72:7  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/misc/dialog/dialog.js
  73:7  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  76:7  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  80:7  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  84:7  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/misc/dialog/dialog.position.js
  125:9  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/misc/dialog/off-canvas/js/off-canvas.js
  142:7  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  244:7  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/misc/displace.js
  222:7  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/misc/form.js
  147:5   error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  292:7   error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  298:24  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/misc/machine-name.js
  190:11  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/misc/states.js
  735:7  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  746:9  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/misc/tabbingmanager.js
  181:9   error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  248:9   error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  367:11  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  382:11  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  396:11  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/misc/tabledrag.js
  395:5   error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  610:13  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  665:13  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  735:7   error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/misc/tableresponsive.js
   49:5   error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  186:11  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/misc/tableselect.js
  64:13  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  86:15  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/misc/vertical-tabs.js
  173:9  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/modules/contextual/js/contextual.js
  135:5  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/modules/editor/js/editor.admin.js
  30:7  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  46:7  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  65:7  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/modules/editor/js/editor.dialog.js
  32:5  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/modules/editor/js/editor.js
  305:9  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/modules/field_ui/field_ui.js
   50:15  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
   63:15  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  338:9   error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/modules/file/file.js
  198:7  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  214:7  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  278:7  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/modules/filter/filter.js
  28:7  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/modules/media_library/js/media_library.click_to_select.js
  29:9  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/modules/media_library/js/media_library.ui.js
   65:13  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  336:11  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  373:9   error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/modules/media_library/js/media_library.view.js
  30:13  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/modules/menu_ui/menu_ui.js
  80:11  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  81:11  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  90:13  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/modules/node/node.preview.js
  97:11  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/modules/settings_tray/js/settings_tray.js
   36:5   error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
   43:5   error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  100:13  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  209:9   error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  209:9   error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/modules/system/js/system.date.js
  60:7  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/modules/system/js/system.js
  79:7  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/modules/text/text.js
  68:11  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/modules/toolbar/js/toolbar.js
  177:13  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  180:13  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  183:13  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/modules/toolbar/tests/src/Nightwatch/Tests/toolbarTest.js
  241:9  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  254:9  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  304:9  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  308:9  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/modules/tour/js/tour.js
  39:13  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/modules/views_ui/js/ajax.js
  108:7   error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  177:13  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/modules/views_ui/js/dialog.views.js
  54:11  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/modules/views_ui/js/views-admin.js
   480:7   error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
   581:9   error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
   622:9   error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  1180:7   error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  1246:9   error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  1259:13  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/tests/Drupal/Nightwatch/Tests/tabbableShimTest.js
  180:22  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
  180:22  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/themes/claro/js/details.js
  24:13  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger

/var/www/html/web/core/themes/claro/js/nav-tabs.js
  71:5  error  Prefer dispatchEvent to $.trigger  jquery/no-trigger
cosmicdreams’s picture

Issue summary: View changes
cosmicdreams’s picture

To fix, this guidance from stack overflow seems important: https://stackoverflow.com/questions/5658849/whats-the-equivalent-of-jque...

In this article, they (as as the linting error response) recommend using JavaScript's dispatchEvent method on the targeted element. What the stack overflow article mentions is that we can avoid potential problems by being thoughtful about what kind of Event we dispatch. Considering the careful creation and use of JavaScript Events, we are likely to discover that each use of .trigger('specific_event') has a sensible default replacement.

Let's create a list of all the different kinds of events we're currently triggering and figure out a dispatchEvent equivalent.

cosmicdreams’s picture

Found .triggers (excluding 3rd party js)

  • focus
  • formUpdated.machineName
  • ajaxSuccess
  • ajaxComplete
  • ajaxStop
  • summaryUpdated
  • click
  • mousedown
  • mouseup
  • dialog:beforecreate
  • dialog:aftercreate
  • dialog:beforeclose
  • dialog:afterclose
  • dialogContentResize
  • resize.dialogResize
  • resize.off-canvas
  • dialogContentResize.off-canvas
  • drupalViewportOffsetChange
  • summaryUpdated
  • formUpdated
  • formFragmentLinkClickOrHashChange
  • change
  • drupalTabbingConstrained
  • DrupalTabbingContextReleased
  • DrupalTabbingContextActivated
  • DrupalTabbingContextDeactivated
  • blur
  • drupalEditorFeatureAdded
  • drupalEditorFeatureRemoved
  • drupalEditorFeatureModified
  • editor:dialogsave
  • fileUpload
  • change.filterGuidelines
  • submit
  • resize.tabs

A lot of these seem to be custom events. jQuery enumerates it's events as:

  • blur
  • change
  • click
  • contextmenu
  • dblclick
  • error
  • focus
  • hover
  • keydown
  • keypress
  • keyup
  • load
  • mousedown
  • mouseenter
  • mouseleave
  • mousemove
  • mouseout
  • mouseover
  • mouseoff
  • mouseup
  • resize
  • scroll
  • select
  • submit
  • unload
cosmicdreams’s picture

When we use jQuery's list of events to reduce our list of found usages of .trigger we can reduce the list to:

  • custom events we create
  • focus
  • click
  • mousedown
  • mouseup
  • change
  • blur
  • submit
  • resize?

Tricky bit might come into play when we need to stop using .trigger for jQuery events that we are overriding / extending. Best place to see what I mean here is to look at ajaxSuccess (all the ajax* "Events")

from ajax.es6.js, line 1768

$.extend(true, $.event.special, {
    ajaxSuccess: {
      trigger(event, xhr, settings) {
        if (stopEvent(xhr, settings)) {
          return false;
        }
      },
    },
    ajaxComplete: {
      trigger(event, xhr, settings) {
        if (stopEvent(xhr, settings)) {
          // jQuery decrements its internal active ajax counter even when we
          // stop the ajaxComplete event, but we don't want that counter
          // decremented, because for our purposes this request is still active
          // while commands are executing. By incrementing it here, the net
          // effect is that it remains unchanged. By remaining above 0, the
          // ajaxStop event is also prevented.
          $.active++;
          return false;
        }
      },
    },
  });

Perhaps that's a good place to start. We can itemize which usages have simple fixes and which have complex fixes.

Also, from @bnjmnm in Slack:

Anything event related such as trigger may have a little bit of additional complexity as we need to account for event listeners in contrib modules that expect the [, extraParameters ] arg http://api.jquery.com/trigger/. I'm pretty sure there are reasonable ways to hack around it though, and there's only upside to getting started on this even if that part isn't figured out yet.

cosmicdreams’s picture

Scanning core for all instances of $.extend returns > 50 files. Perhaps that's not a good search at the moment. Maybe we should take a stab at fixing these "easy" replacements and see how much is left aftwards.

To that end, how can we map the current "easy" uses .trigger to non-jquery solutions. In order to understand better how to provide developers a good replacement for what they currently have available with jQuery's event system let's focus on Focus.

Possible deprecation code comment / guidance we could provide

.trigger('focus') => .dispatchEvent(new FocusEvent('focus'))

Using an event object instead of doing something like $element.focus() allows the event system to properly bubble / propagate the event through the system.

When we use an Event object, JavaScript's event dispatcher triggers the event, allows any event handlers to do their actions, and when they're all done, finally focuses on the targeted element.

When .focus() is used, the targeted element immediately receives focus then the event handlers get notified of the event and they do their actions.

cosmicdreams’s picture

More research also uncovered a possible solution for the CustomEvent concern. Regarding:

Anything event related such as trigger may have a little bit of additional complexity as we need to account for event listeners in contrib modules that expect the [, extraParameters ] arg http://api.jquery.com/trigger/. I'm pretty sure there are reasonable ways to hack around it though, and there's only upside to getting started on this even if that part isn't figured out yet.

Even when we're using only JavaScript to define events we'll need to allow those events to define custom data to consume. We can do so using an Event's detail property:

// Create a custom event with type 'myEvent' and include custom options
var customEvent = new CustomEvent('myEvent', {
  detail: {
    options: [
      {
        name: "Option 1",
        value: "1"
      },
      {
        name: "Option 2",
        value: "2"
      }
    ]
  }
});

// Dispatch the custom event
document.getElementById("myButton").dispatchEvent(customEvent);
cosmicdreams’s picture

Considering all of the above, it appears that we're unblocked here. All this groundwork is all the time I have today to explore this question. I'll see if I can get back to this tonight to start hacking on a MR for some of these easy bits.

eelkeblok’s picture

Maybe this is a silly question, but is this backwards compatible? How do jQuery events compare to events triggered through dispatchEvent()?

Sam Phillemon’s picture

Status: Active » Needs work

currently looking into this

Sam Phillemon changed the visibility of the branch 3239127-refactor-trigger to hidden.

Sam Phillemon’s picture

I am facing issues with the PHP unit tests. I have attempted to fix some of the issues, but many remain, and some solutions are causing additional problems. It would be great if someone else could have a look at it.