diff --git a/core/misc/ajax.es6.js b/core/misc/ajax.es6.js index 6248e46..e690611 100644 --- a/core/misc/ajax.es6.js +++ b/core/misc/ajax.es6.js @@ -46,26 +46,7 @@ function loadAjaxBehavior(base) { } } - // Bind Ajax behaviors to all items showing the class. - $('.use-ajax').once('ajax').each(function () { - const element_settings = {}; - // Clicked links look better with the throbber than the progress bar. - element_settings.progress = { type: 'throbber' }; - - // For anchor tags, these will go to the target of the anchor rather - // than the usual location. - const href = $(this).attr('href'); - if (href) { - element_settings.url = href; - element_settings.event = 'click'; - } - element_settings.dialogType = $(this).data('dialog-type'); - element_settings.dialogRenderer = $(this).data('dialog-renderer'); - element_settings.dialog = $(this).data('dialog-options'); - element_settings.base = $(this).attr('id'); - element_settings.element = this; - Drupal.ajax(element_settings); - }); + Drupal.ajax.bindAjaxLinks(document.body); // This class means to submit the form to the action using Ajax. $('.use-ajax-submit').once('ajax').each(function () { @@ -271,6 +252,39 @@ function loadAjaxBehavior(base) { }; /** + * Bind Ajax functionality to links that use the 'use-ajax' class. + * + * @param {HTMLElement} element + * Element to enable Ajax functionality for. + */ + Drupal.ajax.bindAjaxLinks = (element) => { + // Bind Ajax behaviors to all items showing the class. + $(element).find('.use-ajax').once('ajax').each((i, ajaxLink) => { + const $linkElement = $(ajaxLink); + + const elementSettings = { + // Clicked links look better with the throbber than the progress bar. + progress: { type: 'throbber' }, + dialogType: $linkElement.data('dialog-type'), + dialog: $linkElement.data('dialog-options'), + dialogRenderer: $linkElement.data('dialog-renderer'), + base: $linkElement.attr('id'), + element: ajaxLink, + }; + const href = $linkElement.attr('href'); + /** + * For anchor tags, these will go to the target of the anchor rather + * than the usual location. + */ + if (href) { + elementSettings.url = href; + elementSettings.event = 'click'; + } + Drupal.ajax(elementSettings); + }); + }; + + /** * Settings for an Ajax object. * * @typedef {object} Drupal.Ajax~element_settings @@ -1338,4 +1352,5 @@ else if (effect.showEffect !== 'show') { } }, }; + }(jQuery, window, Drupal, drupalSettings)); diff --git a/core/misc/ajax.js b/core/misc/ajax.js index 5ea5242..b6c0948 100644 --- a/core/misc/ajax.js +++ b/core/misc/ajax.js @@ -27,23 +27,7 @@ function loadAjaxBehavior(base) { } } - $('.use-ajax').once('ajax').each(function () { - var element_settings = {}; - - element_settings.progress = { type: 'throbber' }; - - var href = $(this).attr('href'); - if (href) { - element_settings.url = href; - element_settings.event = 'click'; - } - element_settings.dialogType = $(this).data('dialog-type'); - element_settings.dialogRenderer = $(this).data('dialog-renderer'); - element_settings.dialog = $(this).data('dialog-options'); - element_settings.base = $(this).attr('id'); - element_settings.element = this; - Drupal.ajax(element_settings); - }); + Drupal.ajax.bindAjaxLinks(document.body); $('.use-ajax-submit').once('ajax').each(function () { var element_settings = {}; @@ -139,6 +123,28 @@ function loadAjaxBehavior(base) { }); }; + Drupal.ajax.bindAjaxLinks = function (element) { + $(element).find('.use-ajax').once('ajax').each(function (i, ajaxLink) { + var $linkElement = $(ajaxLink); + + var elementSettings = { + progress: { type: 'throbber' }, + dialogType: $linkElement.data('dialog-type'), + dialog: $linkElement.data('dialog-options'), + dialogRenderer: $linkElement.data('dialog-renderer'), + base: $linkElement.attr('id'), + element: ajaxLink + }; + var href = $linkElement.attr('href'); + + if (href) { + elementSettings.url = href; + elementSettings.event = 'click'; + } + Drupal.ajax(elementSettings); + }); + }; + Drupal.Ajax = function (base, element, element_settings) { var defaults = { event: element ? 'mousedown' : null, diff --git a/core/modules/contextual/contextual.libraries.yml b/core/modules/contextual/contextual.libraries.yml index 51281af..ec90680 100644 --- a/core/modules/contextual/contextual.libraries.yml +++ b/core/modules/contextual/contextual.libraries.yml @@ -20,6 +20,7 @@ drupal.contextual-links: dependencies: - core/jquery - core/drupal + - core/drupal.ajax - core/drupalSettings - core/backbone - core/modernizr diff --git a/core/modules/contextual/js/contextual.es6.js b/core/modules/contextual/js/contextual.es6.js index 8c2b1ae..286e1ef 100644 --- a/core/modules/contextual/js/contextual.es6.js +++ b/core/modules/contextual/js/contextual.es6.js @@ -249,4 +249,19 @@ function adjustIfNestedAndOverlapping($contextual) { Drupal.theme.contextualTrigger = function () { return ''; }; + + /** + * Bind Ajax contextual links when added. + * + * @param {jQuery.Event} event + * The `drupalContextualLinkAdded` event. + * @param {object} data + * An object containing the data relevant to the event. + * + * @listens event:drupalContextualLinkAdded + */ + $(document).on('drupalContextualLinkAdded', (event, data) => { + Drupal.ajax.bindAjaxLinks(data.$el[0]); + }); + }(jQuery, Drupal, drupalSettings, _, Backbone, window.JSON, window.sessionStorage)); diff --git a/core/modules/contextual/js/contextual.js b/core/modules/contextual/js/contextual.js index a23ac66..ed210d0 100644 --- a/core/modules/contextual/js/contextual.js +++ b/core/modules/contextual/js/contextual.js @@ -144,4 +144,8 @@ function adjustIfNestedAndOverlapping($contextual) { Drupal.theme.contextualTrigger = function () { return ''; }; + + $(document).on('drupalContextualLinkAdded', function (event, data) { + Drupal.ajax.bindAjaxLinks(data.$el[0]); + }); })(jQuery, Drupal, drupalSettings, _, Backbone, window.JSON, window.sessionStorage); \ No newline at end of file diff --git a/core/modules/contextual/tests/modules/contextual_test/contextual_test.links.contextual.yml b/core/modules/contextual/tests/modules/contextual_test/contextual_test.links.contextual.yml index 35e03b7..1738756 100644 --- a/core/modules/contextual/tests/modules/contextual_test/contextual_test.links.contextual.yml +++ b/core/modules/contextual/tests/modules/contextual_test/contextual_test.links.contextual.yml @@ -2,3 +2,11 @@ contextual_test: title: 'Test Link' route_name: 'contextual_test' group: 'contextual_test' +contextual_test_ajax: + title: 'Test Link with Ajax' + route_name: 'contextual_test' + group: 'contextual_test' + options: + attributes: + class: ['use-ajax'] + data-dialog-type: 'modal' diff --git a/core/modules/contextual/tests/modules/contextual_test/contextual_test.module b/core/modules/contextual/tests/modules/contextual_test/contextual_test.module index b8f2e60..d57255c 100644 --- a/core/modules/contextual/tests/modules/contextual_test/contextual_test.module +++ b/core/modules/contextual/tests/modules/contextual_test/contextual_test.module @@ -15,3 +15,23 @@ function contextual_test_block_view_alter(array &$build, BlockPluginInterface $b 'route_parameters' => [], ]; } + +/** + * Implements hook_contextual_links_view_alter(). + * + * @todo Apparently this too late to attach the library? + * It won't work without contextual_test_page_attachments_alter() + * Is that a problem? Should the contextual module itself do the attaching? + */ +function contextual_test_contextual_links_view_alter(&$element, $items) { + if (isset($element['#links']['contextual-test-ajax'])) { + $element['#attached']['library'][] = 'core/drupal.dialog.ajax'; + } +} + +/** + * Implements hook_page_attachments_alter(). + */ +function contextual_test_page_attachments_alter(array &$attachments) { + $attachments['#attached']['library'][] = 'core/drupal.dialog.ajax'; +} diff --git a/core/modules/contextual/tests/src/FunctionalJavascript/ContextualLinksTest.php b/core/modules/contextual/tests/src/FunctionalJavascript/ContextualLinksTest.php index a24d2ba..de83692 100644 --- a/core/modules/contextual/tests/src/FunctionalJavascript/ContextualLinksTest.php +++ b/core/modules/contextual/tests/src/FunctionalJavascript/ContextualLinksTest.php @@ -69,6 +69,17 @@ public function testContextualLinksClick() { $this->clickContextualLink('#block-branding', 'Test Link'); $this->assertSession()->pageTextContains('Everything is contextual!'); + // Test click a contextual link that uses ajax. + $this->drupalGet('user'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $current_page_string = 'NOT_RELOADED_IF_ON_PAGE'; + $this->getSession()->executeScript('document.body.appendChild(document.createTextNode("' . $current_page_string . '"));'); + $this->clickContextualLink('#block-branding', 'Test Link with Ajax'); + $this->assertNotEmpty($this->assertSession()->waitForElementVisible('css', '#drupal-modal')); + $this->assertSession()->elementContains('css', '#drupal-modal', 'Everything is contextual!'); + // Check to make sure that page was not reloaded. + $this->assertSession()->pageTextContains($current_page_string); + // Test clicking contextual link with toolbar. $this->container->get('module_installer')->install(['toolbar']); $this->grantPermissions(Role::load(Role::AUTHENTICATED_ID), ['access toolbar']); diff --git a/core/modules/settings_tray/js/settings_tray.es6.js b/core/modules/settings_tray/js/settings_tray.es6.js index b4edd84..6850e74 100644 --- a/core/modules/settings_tray/js/settings_tray.es6.js +++ b/core/modules/settings_tray/js/settings_tray.es6.js @@ -149,6 +149,27 @@ function toggleEditMode() { } /** + * Prepares Ajax links to work with off-canvas and Settings Tray module. + */ + function prepareAjaxLinks() { + // Find all Ajax instances that use the 'off_canvas' renderer. + Drupal.ajax.instances + // If there is an element and the renderer is 'off_canvas' then we want + // to add our changes. + .filter(instance => instance && $(instance.element).attr('data-dialog-renderer') === 'off_canvas') + // Loop through all Ajax instances that use the 'off_canvas' renderer to + // set active editable ID. + .forEach((instance) => { + // Check to make sure existing dialogOptions aren't overridden. + if (!('dialogOptions' in instance.options.data)) { + instance.options.data.dialogOptions = {}; + } + instance.options.data.dialogOptions.settingsTrayActiveEditableId = $(instance.element).parents('.settings-tray-editable').attr('id'); + instance.progress = { type: 'fullscreen' }; + }); + } + + /** * Reacts to contextual links being added. * * @param {jQuery.Event} event @@ -160,6 +181,12 @@ function toggleEditMode() { */ $(document).on('drupalContextualLinkAdded', (event, data) => { + /** + * When contextual links are add we need to set extra properties on the + * instances in Drupal.ajax.instances for them to work with Edit Mode. + */ + prepareAjaxLinks(); + // When the first contextual link is added to the page set Edit Mode. $('body').once('settings_tray.edit_mode_init').each(() => { const editMode = localStorage.getItem('Drupal.contextualToolbar.isViewing') === 'false'; @@ -169,12 +196,6 @@ function toggleEditMode() { }); /** - * Bind Ajax behaviors to all items showing the class. - * @todo Fix contextual links to work with use-ajax links in - * https://www.drupal.org/node/2764931. - */ - Drupal.attachBehaviors(data.$el[0]); - /** * Bind a listener to all 'Quick edit' links for blocks. Click "Edit" button * in toolbar to force Contextual Edit which starts Settings Tray edit * mode also. @@ -212,21 +233,6 @@ function toggleEditMode() { Drupal.behaviors.toggleEditMode = { attach() { $(toggleEditSelector).once('settingstray').on('click.settingstray', toggleEditMode); - // Find all Ajax instances that use the 'off_canvas' renderer. - Drupal.ajax.instances - // If there is an element and the renderer is 'off_canvas' then we want - // to add our changes. - .filter(instance => instance && $(instance.element).attr('data-dialog-renderer') === 'off_canvas') - // Loop through all Ajax instances that use the 'off_canvas' renderer to - // set active editable ID. - .forEach((instance) => { - // Check to make sure existing dialogOptions aren't overridden. - if (!('dialogOptions' in instance.options.data)) { - instance.options.data.dialogOptions = {}; - } - instance.options.data.dialogOptions.settingsTrayActiveEditableId = $(instance.element).parents('.settings-tray-editable').attr('id'); - instance.progress = { type: 'fullscreen' }; - }); }, }; diff --git a/core/modules/settings_tray/js/settings_tray.js b/core/modules/settings_tray/js/settings_tray.js index 88cbb0e..78c5b60 100644 --- a/core/modules/settings_tray/js/settings_tray.js +++ b/core/modules/settings_tray/js/settings_tray.js @@ -93,7 +93,21 @@ function toggleEditMode() { setEditModeState(!isInEditMode()); } + function prepareAjaxLinks() { + Drupal.ajax.instances.filter(function (instance) { + return instance && $(instance.element).attr('data-dialog-renderer') === 'off_canvas'; + }).forEach(function (instance) { + if (!('dialogOptions' in instance.options.data)) { + instance.options.data.dialogOptions = {}; + } + instance.options.data.dialogOptions.settingsTrayActiveEditableId = $(instance.element).parents('.settings-tray-editable').attr('id'); + instance.progress = { type: 'fullscreen' }; + }); + } + $(document).on('drupalContextualLinkAdded', function (event, data) { + prepareAjaxLinks(); + $('body').once('settings_tray.edit_mode_init').each(function () { var editMode = localStorage.getItem('Drupal.contextualToolbar.isViewing') === 'false'; if (editMode) { @@ -101,8 +115,6 @@ function toggleEditMode() { } }); - Drupal.attachBehaviors(data.$el[0]); - data.$el.find(blockConfigureSelector).on('click.settingstray', function () { if (!isInEditMode()) { $(toggleEditSelector).trigger('click').trigger('click.settings_tray'); @@ -122,16 +134,6 @@ function toggleEditMode() { Drupal.behaviors.toggleEditMode = { attach: function attach() { $(toggleEditSelector).once('settingstray').on('click.settingstray', toggleEditMode); - - Drupal.ajax.instances.filter(function (instance) { - return instance && $(instance.element).attr('data-dialog-renderer') === 'off_canvas'; - }).forEach(function (instance) { - if (!('dialogOptions' in instance.options.data)) { - instance.options.data.dialogOptions = {}; - } - instance.options.data.dialogOptions.settingsTrayActiveEditableId = $(instance.element).parents('.settings-tray-editable').attr('id'); - instance.progress = { type: 'fullscreen' }; - }); } };