From 99af3458895441a598f0bda672baf092813a2359 Mon Sep 17 00:00:00 2001 From: Dave Long Date: Wed, 1 May 2024 16:39:19 +0100 Subject: [PATCH] Issue #3390549 by finnsky, nod_, smustgrave, larowlan, catch, bnjmnm: Get rid of jQuery in dialog events --- core/core.libraries.yml | 2 + core/misc/dialog/dialog-deprecation.js | 40 +++++++++ core/misc/dialog/dialog.ajax.js | 7 +- core/misc/dialog/dialog.js | 28 ++++-- core/misc/dialog/dialog.position.js | 48 +++++----- core/misc/dialog/off-canvas/js/off-canvas.js | 50 +++++++---- core/modules/ckeditor5/js/ckeditor5.js | 4 +- .../layout_builder/js/layout-builder.js | 6 +- .../media_library/js/media_library.ui.js | 4 +- .../modules/settings_tray/js/settings_tray.js | 34 ++++--- core/modules/toolbar/js/toolbar.js | 49 ++++++----- core/modules/views_ui/js/dialog.views.js | 3 +- core/themes/claro/js/media-library.ui.js | 88 +++++++++---------- 13 files changed, 221 insertions(+), 142 deletions(-) create mode 100644 core/misc/dialog/dialog-deprecation.js diff --git a/core/core.libraries.yml b/core/core.libraries.yml index 789e263629a..3ed1c10b667 100644 --- a/core/core.libraries.yml +++ b/core/core.libraries.yml @@ -549,6 +549,7 @@ drupal.debounce: drupal.dialog: version: VERSION js: + misc/dialog/dialog-deprecation.js: {} misc/dialog/dialog.js: {} misc/dialog/dialog.position.js: {} misc/dialog/dialog.jquery-ui.js: {} @@ -582,6 +583,7 @@ drupal.dialog: - core/drupal.debounce - core/drupal.displace - core/tabbable.jquery.shim + - core/once - core/drupal.jquery.position drupal.dialog.ajax: diff --git a/core/misc/dialog/dialog-deprecation.js b/core/misc/dialog/dialog-deprecation.js new file mode 100644 index 00000000000..6e2f960cf8d --- /dev/null +++ b/core/misc/dialog/dialog-deprecation.js @@ -0,0 +1,40 @@ +/** + * @file + * Maintains and deprecates Dialog jQuery events. + */ + +(function ($, Drupal, once) { + if (once('drupal-dialog-deprecation-listener', 'html').length) { + const eventSpecial = { + handle($event) { + const $element = $($event.target); + const event = $event.originalEvent; + const dialog = event.dialog; + const dialogArguments = [$event, dialog, $element, event?.settings]; + $event.handleObj.handler.apply(this, dialogArguments); + }, + }; + + $.event.special['dialog:beforecreate'] = eventSpecial; + $.event.special['dialog:aftercreate'] = eventSpecial; + $.event.special['dialog:beforeclose'] = eventSpecial; + $.event.special['dialog:afterclose'] = eventSpecial; + + const listenDialogEvent = (event) => { + const windowEvents = $._data(window, 'events'); + const isWindowHasDialogListener = windowEvents[event.type]; + if (isWindowHasDialogListener) { + Drupal.deprecationError({ + message: `jQuery event ${event.type} is deprecated in 10.3.0 and is removed from Drupal:12.0.0. See https://www.drupal.org/node/3422670`, + }); + } + }; + + [ + 'dialog:beforecreate', + 'dialog:aftercreate', + 'dialog:beforeclose', + 'dialog:afterclose', + ].forEach((e) => window.addEventListener(e, listenDialogEvent)); + } +})(jQuery, Drupal, once); diff --git a/core/misc/dialog/dialog.ajax.js b/core/misc/dialog/dialog.ajax.js index f8a1b5f90cf..6a839e4d8bc 100644 --- a/core/misc/dialog/dialog.ajax.js +++ b/core/misc/dialog/dialog.ajax.js @@ -270,7 +270,9 @@ * @param {object} [settings] * Dialog settings. */ - $(window).on('dialog:aftercreate', (e, dialog, $element, settings) => { + window.addEventListener('dialog:aftercreate', (event) => { + const $element = $(event.target); + const dialog = event.dialog; $element.on('click.dialog', '.dialog-cancel', (e) => { dialog.close('cancel'); e.preventDefault(); @@ -288,7 +290,8 @@ * @param {jQuery} $element * jQuery collection of the dialog element. */ - $(window).on('dialog:beforeclose', (e, dialog, $element) => { + window.addEventListener('dialog:beforeclose', (e) => { + const $element = $(e.target); $element.off('.dialog'); }); diff --git a/core/misc/dialog/dialog.js b/core/misc/dialog/dialog.js index 3ec4218282b..8cac24c3c00 100644 --- a/core/misc/dialog/dialog.js +++ b/core/misc/dialog/dialog.js @@ -5,6 +5,14 @@ * @see http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#the-dialog-element */ +class DrupalDialogEvent extends Event { + constructor(type, dialog, settings = null) { + super(`dialog:${type}`, { bubbles: true }); + this.dialog = dialog; + this.settings = settings; + } +} + (function ($, Drupal, drupalSettings, bodyScrollLock) { /** * Default dialog options. @@ -61,7 +69,10 @@ */ Drupal.dialog = function (element, options) { let undef; + const $element = $(element); + const domElement = $element.get(0); + const dialog = { open: false, returnValue: undef, @@ -70,28 +81,33 @@ function openDialog(settings) { settings = $.extend({}, drupalSettings.dialog, options, settings); // Trigger a global event to allow scripts to bind events to the dialog. - $(window).trigger('dialog:beforecreate', [dialog, $element, settings]); - $element.dialog(settings); + const event = new DrupalDialogEvent('beforecreate', dialog, settings); + domElement.dispatchEvent(event); + $element.dialog(event.settings); dialog.open = true; // Locks the body scroll only when it opens in modal. if (settings.modal) { // Locks the body when the dialog opens. - bodyScrollLock.lock($element.get(0)); + bodyScrollLock.lock(domElement); } - $(window).trigger('dialog:aftercreate', [dialog, $element, settings]); + domElement.dispatchEvent( + new DrupalDialogEvent('aftercreate', dialog, settings), + ); } function closeDialog(value) { - $(window).trigger('dialog:beforeclose', [dialog, $element]); + domElement.dispatchEvent(new DrupalDialogEvent('beforeclose', dialog)); + // Unlocks the body when the dialog closes. bodyScrollLock.clearBodyLocks(); $element.dialog('close'); dialog.returnValue = value; dialog.open = false; - $(window).trigger('dialog:afterclose', [dialog, $element]); + + domElement.dispatchEvent(new DrupalDialogEvent('afterclose', dialog)); } dialog.show = () => { diff --git a/core/misc/dialog/dialog.position.js b/core/misc/dialog/dialog.position.js index 8864496cb07..9c1060f3cac 100644 --- a/core/misc/dialog/dialog.position.js +++ b/core/misc/dialog/dialog.position.js @@ -113,28 +113,30 @@ .trigger('dialogContentResize'); } - $(window).on({ - 'dialog:aftercreate': function (event, dialog, $element, settings) { - const autoResize = debounce(resetSize, 20); - const eventData = { settings, $element }; - if (settings.autoResize === true || settings.autoResize === 'true') { - const uiDialog = $element - .dialog('option', { resizable: false, draggable: false }) - .dialog('widget'); - uiDialog[0].style.position = 'fixed'; - $(window) - .on('resize.dialogResize scroll.dialogResize', eventData, autoResize) - .trigger('resize.dialogResize'); - $(document).on( - 'drupalViewportOffsetChange.dialogResize', - eventData, - autoResize, - ); - } - }, - 'dialog:beforeclose': function (event, dialog, $element) { - $(window).off('.dialogResize'); - $(document).off('.dialogResize'); - }, + window.addEventListener('dialog:aftercreate', (e) => { + const autoResize = debounce(resetSize, 20); + const $element = $(e.target); + const { settings } = e; + const eventData = { settings, $element }; + + if (settings.autoResize === true || settings.autoResize === 'true') { + const uiDialog = $element + .dialog('option', { resizable: false, draggable: false }) + .dialog('widget'); + uiDialog[0].style.position = 'fixed'; + $(window) + .on('resize.dialogResize scroll.dialogResize', eventData, autoResize) + .trigger('resize.dialogResize'); + $(document).on( + 'drupalViewportOffsetChange.dialogResize', + eventData, + autoResize, + ); + } + }); + + window.addEventListener('dialog:beforeclose', () => { + $(window).off('.dialogResize'); + $(document).off('.dialogResize'); }); })(jQuery, Drupal, drupalSettings, Drupal.debounce, Drupal.displace); diff --git a/core/misc/dialog/off-canvas/js/off-canvas.js b/core/misc/dialog/off-canvas/js/off-canvas.js index a85f3522ab8..2517c3cd775 100644 --- a/core/misc/dialog/off-canvas/js/off-canvas.js +++ b/core/misc/dialog/off-canvas/js/off-canvas.js @@ -341,23 +341,39 @@ if (!once('off-canvas', 'html').length) { return; } - $(window).on({ - 'dialog:beforecreate': (event, dialog, $element, settings) => { - if (Drupal.offCanvas.isOffCanvas($element)) { - Drupal.offCanvas.beforeCreate({ dialog, $element, settings }); - } - }, - 'dialog:aftercreate': (event, dialog, $element, settings) => { - if (Drupal.offCanvas.isOffCanvas($element)) { - Drupal.offCanvas.render({ dialog, $element, settings }); - Drupal.offCanvas.afterCreate({ $element, settings }); - } - }, - 'dialog:beforeclose': (event, dialog, $element) => { - if (Drupal.offCanvas.isOffCanvas($element)) { - Drupal.offCanvas.beforeClose({ dialog, $element }); - } - }, + + window.addEventListener('dialog:beforecreate', (e) => { + const $element = $(e.target); + if (Drupal.offCanvas.isOffCanvas($element)) { + Drupal.offCanvas.beforeCreate({ + $element, + settings: e.settings, + }); + } + }); + + window.addEventListener('dialog:aftercreate', (e) => { + const $element = $(e.target); + if (Drupal.offCanvas.isOffCanvas($element)) { + Drupal.offCanvas.render({ + $element, + dialog: e.dialog, + settings: e.settings, + }); + Drupal.offCanvas.afterCreate({ + $element, + settings: e.settings, + }); + } + }); + + window.addEventListener('dialog:beforeclose', (e) => { + const $element = $(e.target); + if (Drupal.offCanvas.isOffCanvas($element)) { + Drupal.offCanvas.beforeClose({ + $element, + }); + } }); }, }; diff --git a/core/modules/ckeditor5/js/ckeditor5.js b/core/modules/ckeditor5/js/ckeditor5.js index 7bace8702c0..f2c69f41b48 100644 --- a/core/modules/ckeditor5/js/ckeditor5.js +++ b/core/modules/ckeditor5/js/ckeditor5.js @@ -650,7 +650,7 @@ ); // Respond to new dialogs that are opened by CKEditor, closing the AJAX loader. - $(window).on('dialog:beforecreate', () => { + window.addEventListener('dialog:beforecreate', () => { const dialogLoading = document.querySelector('.ckeditor5-dialog-loading'); if (dialogLoading) { @@ -673,7 +673,7 @@ }); // Respond to dialogs that are closed, removing the current save handler. - $(window).on('dialog:afterclose', () => { + window.addEventListener('dialog:afterclose', () => { if (Drupal.ckeditor5.saveCallback) { Drupal.ckeditor5.saveCallback = null; } diff --git a/core/modules/layout_builder/js/layout-builder.js b/core/modules/layout_builder/js/layout-builder.js index ef85ad8533e..bf5b24ac2a9 100644 --- a/core/modules/layout_builder/js/layout-builder.js +++ b/core/modules/layout_builder/js/layout-builder.js @@ -219,7 +219,8 @@ }; // After a dialog opens, highlight element that the dialog is acting on. - $(window).on('dialog:aftercreate', (event, dialog, $element) => { + window.addEventListener('dialog:aftercreate', (e) => { + const $element = $(e.target); if (Drupal.offCanvas.isOffCanvas($element)) { // Start by removing any existing highlighted elements. $('.is-layout-builder-highlighted').removeClass( @@ -309,7 +310,8 @@ }); } - $(window).on('dialog:afterclose', (event, dialog, $element) => { + window.addEventListener('dialog:afterclose', (e) => { + const $element = $(e.target); if (Drupal.offCanvas.isOffCanvas($element)) { // Remove the highlight from all elements. $('.is-layout-builder-highlighted').removeClass( diff --git a/core/modules/media_library/js/media_library.ui.js b/core/modules/media_library/js/media_library.ui.js index 07023660ad7..05429560f4e 100644 --- a/core/modules/media_library/js/media_library.ui.js +++ b/core/modules/media_library/js/media_library.ui.js @@ -382,7 +382,7 @@ if (!once('media-library-selection-info', 'html').length) { return; } - $(window).on('dialog:aftercreate', () => { + window.addEventListener('dialog:aftercreate', () => { // Since the dialog HTML is not part of the context, we can't use // context here. const $buttonPane = $( @@ -410,7 +410,7 @@ if (!once('media-library-clear-selection', 'html').length) { return; } - $(window).on('dialog:afterclose', () => { + window.addEventListener('dialog:afterclose', () => { Drupal.MediaLibrary.currentSelection = []; }); }, diff --git a/core/modules/settings_tray/js/settings_tray.js b/core/modules/settings_tray/js/settings_tray.js index 70e5df5a40a..5b627eafc47 100644 --- a/core/modules/settings_tray/js/settings_tray.js +++ b/core/modules/settings_tray/js/settings_tray.js @@ -237,24 +237,22 @@ }; // Manage Active editable class on opening and closing of the dialog. - $(window).on({ - 'dialog:beforecreate': (event, dialog, $element, settings) => { - if ($element[0].id === 'drupal-off-canvas') { - $('body .settings-tray-active-editable').removeClass( - 'settings-tray-active-editable', - ); - const $activeElement = $(`#${settings.settingsTrayActiveEditableId}`); - if ($activeElement.length) { - $activeElement.addClass('settings-tray-active-editable'); - } + window.addEventListener('dialog:beforecreate', (e) => { + if (e.target.id === 'drupal-off-canvas') { + $('body .settings-tray-active-editable').removeClass( + 'settings-tray-active-editable', + ); + const $activeElement = $(`#${e.settings.settingsTrayActiveEditableId}`); + if ($activeElement.length) { + $activeElement.addClass('settings-tray-active-editable'); } - }, - 'dialog:beforeclose': (event, dialog, $element) => { - if ($element[0].id === 'drupal-off-canvas') { - $('body .settings-tray-active-editable').removeClass( - 'settings-tray-active-editable', - ); - } - }, + } + }); + window.addEventListener('dialog:beforeclose', (e) => { + if (e.target.id === 'drupal-off-canvas') { + $('body .settings-tray-active-editable').removeClass( + 'settings-tray-active-editable', + ); + } }); })(jQuery, Drupal); diff --git a/core/modules/toolbar/js/toolbar.js b/core/modules/toolbar/js/toolbar.js index ece513c13f6..ed741a374b1 100644 --- a/core/modules/toolbar/js/toolbar.js +++ b/core/modules/toolbar/js/toolbar.js @@ -205,34 +205,35 @@ }); } - $(window).on({ - 'dialog:aftercreate': (event, dialog, $element, settings) => { - const toolbarBar = document.getElementById('toolbar-bar'); - if (toolbarBar) { - toolbarBar.style.marginTop = '0'; + window.addEventListener('dialog:aftercreate', (e) => { + const $element = $(e.target); + const { settings } = e; + const toolbarBar = document.getElementById('toolbar-bar'); + if (toolbarBar) { + toolbarBar.style.marginTop = '0'; - // When off-canvas is positioned in top, toolbar has to be moved down. - if (settings.drupalOffCanvasPosition === 'top') { - const height = Drupal.offCanvas + // When off-canvas is positioned in top, toolbar has to be moved down. + if (settings.drupalOffCanvasPosition === 'top') { + const height = Drupal.offCanvas + .getContainer($element) + .outerHeight(); + toolbarBar.style.marginTop = `${height}px`; + + $element.on('dialogContentResize.off-canvas', () => { + const newHeight = Drupal.offCanvas .getContainer($element) .outerHeight(); - toolbarBar.style.marginTop = `${height}px`; + toolbarBar.style.marginTop = `${newHeight}px`; + }); + } + } + }); - $element.on('dialogContentResize.off-canvas', () => { - const newHeight = Drupal.offCanvas - .getContainer($element) - .outerHeight(); - toolbarBar.style.marginTop = `${newHeight}px`; - }); - } - } - }, - 'dialog:beforeclose': () => { - const toolbarBar = document.getElementById('toolbar-bar'); - if (toolbarBar) { - toolbarBar.style.marginTop = '0'; - } - }, + window.addEventListener('dialog:beforeclose', () => { + const toolbarBar = document.getElementById('toolbar-bar'); + if (toolbarBar) { + toolbarBar.style.marginTop = '0'; + } }); }); diff --git a/core/modules/views_ui/js/dialog.views.js b/core/modules/views_ui/js/dialog.views.js index 48a0b8eb88c..4cc4c5e7c24 100644 --- a/core/modules/views_ui/js/dialog.views.js +++ b/core/modules/views_ui/js/dialog.views.js @@ -81,7 +81,8 @@ * @param {jQuery} $element * The jQuery collection of the dialog element. */ - $(window).on('dialog:aftercreate', (e, dialog, $element) => { + window.addEventListener('dialog:aftercreate', (e) => { + const $element = $(e.target); const $scroll = $element.find('.scroll'); if ($scroll.length) { bodyScrollLock.unlock($element.get(0)); diff --git a/core/themes/claro/js/media-library.ui.js b/core/themes/claro/js/media-library.ui.js index 8eee2ad1fe1..6767a3628cd 100644 --- a/core/themes/claro/js/media-library.ui.js +++ b/core/themes/claro/js/media-library.ui.js @@ -20,53 +20,51 @@ if (!once('media-library-selection-info-claro-event', 'html').length) { return; } - $(window).on( - 'dialog:aftercreate', - (event, dialog, $element, settings) => { - // Since the dialog HTML is not part of the context, we can't use - // context here. - const moveCounter = ($selectedCount, $buttonPane) => { - const $moveSelectedCount = $selectedCount.detach(); - $buttonPane.prepend($moveSelectedCount); - }; + window.addEventListener('dialog:aftercreate', (e) => { + const $element = $(e.target); + // Since the dialog HTML is not part of the context, we can't use + // context here. + const moveCounter = ($selectedCount, $buttonPane) => { + const $moveSelectedCount = $selectedCount.detach(); + $buttonPane.prepend($moveSelectedCount); + }; - const $buttonPane = $element - .closest('.media-library-widget-modal') - .find('.ui-dialog-buttonpane'); - if (!$buttonPane.length) { - return; - } - const $selectedCount = $buttonPane.find( - '.js-media-library-selected-count', - ); + const $buttonPane = $element + .closest('.media-library-widget-modal') + .find('.ui-dialog-buttonpane'); + if (!$buttonPane.length) { + return; + } + const $selectedCount = $buttonPane.find( + '.js-media-library-selected-count', + ); - // If the `selected` counter is already present, it can be moved from - // the end of the button pane to the beginning. - if ($selectedCount.length) { - moveCounter($selectedCount, $buttonPane); - } else { - // If the `selected` counter is not yet present, create a mutation - // observer that checks for items added to the button pane. As soon - // as the counter is added, move it from the end of the button pane - // to the beginning. - const selectedCountObserver = new MutationObserver(() => { - const $selectedCountFind = $buttonPane.find( - '.js-media-library-selected-count', - ); - if ($selectedCountFind.length) { - moveCounter($selectedCountFind, $buttonPane); - selectedCountObserver.disconnect(); - } - }); - selectedCountObserver.observe($buttonPane[0], { - attributes: false, - childList: true, - characterData: false, - subtree: true, - }); - } - }, - ); + // If the `selected` counter is already present, it can be moved from + // the end of the button pane to the beginning. + if ($selectedCount.length) { + moveCounter($selectedCount, $buttonPane); + } else { + // If the `selected` counter is not yet present, create a mutation + // observer that checks for items added to the button pane. As soon + // as the counter is added, move it from the end of the button pane + // to the beginning. + const selectedCountObserver = new MutationObserver(() => { + const $selectedCountFind = $buttonPane.find( + '.js-media-library-selected-count', + ); + if ($selectedCountFind.length) { + moveCounter($selectedCountFind, $buttonPane); + selectedCountObserver.disconnect(); + } + }); + selectedCountObserver.observe($buttonPane[0], { + attributes: false, + childList: true, + characterData: false, + subtree: true, + }); + } + }); }, }; })(jQuery, Drupal, window);