From 2c6f28bef9514e9e1ef54efab8ecd2f2689a25e8 Mon Sep 17 00:00:00 2001 From: webchick Date: Fri, 23 Sep 2016 10:40:36 -0700 Subject: [PATCH] Issue #2785589 by tedbow, nod_, droplet, drpal, alexpott, Wim Leers, xjm: Fix js and jsdoc of outside-in module --- core/modules/outside_in/js/outside_in.js | 151 +++++++++++------- .../outside_in/outside_in.libraries.yml | 1 + core/modules/outside_in/outside_in.module | 4 +- .../OutsideInBlockFormTest.php | 129 ++++++++++----- .../OutsideInJavascriptTestBase.php | 2 + 5 files changed, 188 insertions(+), 99 deletions(-) diff --git a/core/modules/outside_in/js/outside_in.js b/core/modules/outside_in/js/outside_in.js index b7bd32da0c4..a4f94aa90a1 100644 --- a/core/modules/outside_in/js/outside_in.js +++ b/core/modules/outside_in/js/outside_in.js @@ -7,32 +7,10 @@ 'use strict'; - $('.outside-in-editable') - // Bind an event listener to the .outside-in-editable div - // This listen for click events and stops default actions of those elements. - .on('click', '.js-outside-in-edit-mode', function (e) { - if (localStorage.getItem('Drupal.contextualToolbar.isViewing') === 'false') { - e.preventDefault(); - } - }) - // Bind an event listener to the .outside-in-editable div - // When a click occurs try and find the outside-in edit link - // and click it. - .not('div.contextual a, div.contextual button') - .on('click', function (e) { - if ($(e.target.offsetParent).hasClass('contextual')) { - return; - } - if (!localStorage.getItem('Drupal.contextualToolbar.isViewing')) { - return; - } - var editLink = $(e.target).find('a[data-dialog-renderer="offcanvas"]')[0]; - if (!editLink) { - var closest = $(e.target).closest('.outside-in-editable'); - editLink = closest.find('li a[data-dialog-renderer="offcanvas"]')[0]; - } - editLink.click(); - }); + var blockConfigureSelector = '[data-dialog-renderer="offcanvas"]'; + var toggleEditSelector = '[data-drupal-outsidein="toggle"]'; + var itemsToToggleSelector = '#main-canvas, #toolbar-bar, [data-drupal-outsidein="editable"] a, [data-drupal-outsidein="editable"] button'; + var contextualItemsSelector = '[data-contextual-id] a, [data-contextual-id] button'; /** * Reacts to contextual links being added. @@ -53,45 +31,111 @@ // 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. - data.$el.find('.outside-inblock-configure a').on('click', function () { - if (!isActiveMode()) { - $('div.contextual-toolbar-tab.toolbar-tab button').click(); - } - }); + data.$el.find(blockConfigureSelector) + .on('click.outsidein', function () { + if (!isInEditMode()) { + $(toggleEditSelector).trigger('click.outsidein'); + } + }); }); /** * Gets all items that should be toggled with class during edit mode. * - * @return {*} + * @return {jQuery} * Items that should be toggled. */ - var getItemsToToggle = function () { - return $('#main-canvas, #toolbar-bar, .outside-in-editable a, .outside-in-editable button') - .not('div.contextual a, div.contextual button'); - }; + function getItemsToToggle() { + return $(itemsToToggleSelector).not(contextualItemsSelector); + } - var isActiveMode = function () { + /** + * Helper to check the state of the outside-in mode. + * + * @todo don't use a class for this. + * + * @return {boolean} + * State of the outside-in edit mode. + */ + function isInEditMode() { return $('#toolbar-bar').hasClass('js-outside-in-edit-mode'); - }; + } - var setToggleActiveMode = function setToggleActiveMode(forceActive) { - forceActive = forceActive || false; - if (forceActive || !isActiveMode()) { - $('#toolbar-bar .contextual-toolbar-tab button').text(Drupal.t('Editing')); + /** + * Helper to toggle Edit mode. + */ + function toggleEditMode() { + setEditModeState(!isInEditMode()); + } + + /** + * Prevent default click events except contextual links. + * + * In edit mode the default action of click events is suppressed. + * + * @param {jQuery.Event} event + * The click event. + */ + function preventClick(event) { + // Do not prevent contextual links. + if ($(event.target).closest('.contextual-links').length) { + return; + } + event.preventDefault(); + } + + /** + * Helper to switch edit mode state. + * + * @param {boolean} editMode + * True enable edit mode, false disable edit mode. + */ + function setEditModeState(editMode) { + editMode = !!editMode; + var $editButton = $(toggleEditSelector); + var $editables; + // Turn on edit mode. + if (editMode) { + $editButton.text(Drupal.t('Editing')); // Close the Manage tray if open when entering edit mode. if ($('#toolbar-item-administration-tray').hasClass('is-active')) { - $('#toolbar-item-administration').click(); + $('#toolbar-item-administration').trigger('click'); + } + + $editables = $('[data-drupal-outsidein="editable"]').once('outsidein'); + if ($editables.length) { + // Use event capture to prevent clicks on links. + document.querySelector('#main-canvas').addEventListener('click', preventClick, true); + + // When a click occurs try and find the outside-in edit link + // and click it. + $editables + .not(contextualItemsSelector) + .on('click.outsidein', function (e) { + // Contextual links are allowed to function in Edit mode. + if ($(e.target).closest('.contextual').length || !localStorage.getItem('Drupal.contextualToolbar.isViewing')) { + return; + } + + $(e.currentTarget).find(blockConfigureSelector).trigger('click'); + }); } - getItemsToToggle().addClass('js-outside-in-edit-mode'); - $('.edit-mode-inactive').addClass('visually-hidden'); } + // Disable edit mode. else { - $('#toolbar-bar .contextual-toolbar-tab button').text(Drupal.t('Edit')); - getItemsToToggle().removeClass('js-outside-in-edit-mode'); - $('.edit-mode-inactive').removeClass('visually-hidden'); + $editables = $('[data-drupal-outsidein="editable"]').removeOnce('outsidein'); + if ($editables.length) { + document.querySelector('#main-canvas').removeEventListener('click', preventClick, true); + $editables.off('.outsidein'); + } + + $editButton.text(Drupal.t('Edit')); + // Close/remove offcanvas. + $('.ui-dialog-offcanvas .ui-dialog-titlebar-close').trigger('click'); } - }; + getItemsToToggle().toggleClass('js-outside-in-edit-mode', editMode); + $('.edit-mode-inactive').toggleClass('visually-hidden', editMode); + } /** * Attaches contextual's edit toolbar tab behavior. @@ -105,7 +149,7 @@ attach: function () { var editMode = localStorage.getItem('Drupal.contextualToolbar.isViewing') === 'false'; if (editMode) { - setToggleActiveMode(true); + setEditModeState(true); } } }; @@ -118,11 +162,10 @@ * @prop {Drupal~behaviorAttach} attach * Toggle the js-outside-edit-mode class. */ - Drupal.behaviors.toggleActiveMode = { + Drupal.behaviors.toggleEditMode = { attach: function () { - $('.contextual-toolbar-tab.toolbar-tab button').once('toggle-edit-mode').on('click', function () { - setToggleActiveMode(); - }); + + $(toggleEditSelector).once('outsidein').on('click.outsidein', toggleEditMode); var search = Drupal.ajax.WRAPPER_FORMAT + '=drupal_dialog'; var replace = Drupal.ajax.WRAPPER_FORMAT + '=drupal_dialog_offcanvas'; diff --git a/core/modules/outside_in/outside_in.libraries.yml b/core/modules/outside_in/outside_in.libraries.yml index 6fbcd1ecf6d..5eddea4ca6e 100644 --- a/core/modules/outside_in/outside_in.libraries.yml +++ b/core/modules/outside_in/outside_in.libraries.yml @@ -18,6 +18,7 @@ drupal.outside_in: dependencies: - core/jquery - core/drupal + - core/jquery.once - core/drupal.ajax drupal.off_canvas: version: VERSION diff --git a/core/modules/outside_in/outside_in.module b/core/modules/outside_in/outside_in.module index 95e5fbeea12..298b994c9ed 100644 --- a/core/modules/outside_in/outside_in.module +++ b/core/modules/outside_in/outside_in.module @@ -93,8 +93,9 @@ function outside_in_preprocess_block(&$variables) { // The main system block does not contain the block contextual links. $variables['#cache']['contexts'][] = 'outside_in_is_applied'; if ($variables['plugin_id'] !== 'system_main_block' && \Drupal::service('outside_in.manager')->isApplicable()) { - // Add class to all blocks to allow Javascript to target. + // Add class and attributes to all blocks to allow Javascript to target. $variables['attributes']['class'][] = 'outside-in-editable'; + $variables['attributes']['data-drupal-outsidein'] = 'editable'; } } @@ -108,6 +109,7 @@ function outside_in_toolbar_alter(&$items) { if (isset($items['contextual']['tab']) && \Drupal::service('outside_in.manager')->isApplicable()) { $items['contextual']['#weight'] = -1000; $items['contextual']['#attached']['library'][] = 'outside_in/drupal.outside_in'; + $items['contextual']['tab']['#attributes']['data-drupal-outsidein'] = 'toggle'; // Set a class on items to mark whether they should be active in edit mode. // @todo Create a dynamic method for modules to set their own items. diff --git a/core/modules/outside_in/tests/src/FunctionalJavascript/OutsideInBlockFormTest.php b/core/modules/outside_in/tests/src/FunctionalJavascript/OutsideInBlockFormTest.php index 4487e24d912..dd79fc2c448 100644 --- a/core/modules/outside_in/tests/src/FunctionalJavascript/OutsideInBlockFormTest.php +++ b/core/modules/outside_in/tests/src/FunctionalJavascript/OutsideInBlockFormTest.php @@ -12,7 +12,17 @@ class OutsideInBlockFormTest extends OutsideInJavascriptTestBase { /** * {@inheritdoc} */ - public static $modules = ['block', 'system', 'breakpoint', 'toolbar', 'contextual', 'outside_in']; + public static $modules = [ + 'node', + 'block', + 'system', + 'breakpoint', + 'toolbar', + 'contextual', + 'outside_in', + 'quickedit', + 'search', + ]; /** * {@inheritdoc} @@ -26,72 +36,92 @@ class OutsideInBlockFormTest extends OutsideInJavascriptTestBase { 'administer blocks', 'access contextual links', 'access toolbar', + 'administer nodes', + 'access in-place editing', + 'search content', ]); $this->drupalLogin($user); $this->placeBlock('system_powered_by_block', ['id' => 'powered']); $this->placeBlock('system_branding_block', ['id' => 'branding']); + $this->placeBlock('search_form_block', ['id' => 'search']); } /** - * Tests updating the "Powered by Drupal" block in the Off-Canvas tray. + * Tests opening Offcanvas tray by click blocks and elements in the blocks. */ - public function testPoweredByBlock() { - + public function testBlocks() { + $blocks = [ + [ + 'id' => 'block-powered', + 'new_page_text' => 'Can you imagine anyone showing the label on this block?', + 'element_selector' => '.content a', + 'button_text' => 'Save Powered by Drupal', + ], + [ + 'id' => 'block-branding', + 'new_page_text' => 'The site that will live a very short life.', + 'element_selector' => 'a[rel="home"]:nth-child(2)', + 'button_text' => 'Save Site branding', + ], + [ + 'id' => 'block-search', + 'element_selector' => '#edit-submit', + 'button_text' => 'Save Search form', + ], + ]; $page = $this->getSession()->getPage(); - $web_assert = $this->assertSession(); + foreach ($blocks as $block) { + $block_selector = '#' . $block['id']; + $this->drupalGet('user'); + $this->toggleEditingMode(); + $this->openBlockForm($block_selector); - $this->drupalGet('user'); - $this->enableEditingMode(); + switch ($block['id']) { + case 'block-powered': + // Fill out form, save the form. + $page->fillField('settings[label]', $block['new_page_text']); + $page->checkField('settings[label_display]'); + break; - // Open "Powered by Drupal" block form by clicking div. - $page->find('css', '#block-powered')->click(); - $this->waitForOffCanvasToOpen(); - $this->assertOffCanvasBlockFormIsValid(); + case 'block-branding': + // Fill out form, save the form. + $page->fillField('settings[site_information][site_name]', $block['new_page_text']); + break; + } - // Fill out form, save the form. - $new_label = 'Can you imagine anyone showing the label on this block?'; - $page->fillField('settings[label]', $new_label); - $page->checkField('settings[label_display]'); + if (isset($block['new_page_text'])) { + $page->pressButton($block['button_text']); + // Make sure the changes are present. + $this->getSession()->wait(500); + $web_assert = $this->assertSession(); + $web_assert->pageTextContains($block['new_page_text']); + } - // @todo Uncomment the following lines after GastonJS problem solved. - // https://www.drupal.org/node/2789381 - // $this->getTray()->pressButton('Save block'); - // Make sure the changes are present. - // $web_assert->pageTextContains($new_label); - } + $this->openBlockForm($block_selector); - /** - * Tests updating the System Branding block in the Off-Canvas tray. - * - * Also tests updating the site name. - */ - public function testBrandingBlock() { - $web_assert = $this->assertSession(); - $this->drupalGet('user'); - $page = $this->getSession()->getPage(); - $this->enableEditingMode(); + $this->toggleEditingMode(); + // Canvas should close when editing module is closed. + $this->waitForOffCanvasToClose(); - // Open branding block form by clicking div. - $page->find('css', '#block-branding')->click(); - $this->waitForOffCanvasToOpen(); - $this->assertOffCanvasBlockFormIsValid(); + // Go into Edit mode again. + $this->toggleEditingMode(); - // Fill out form, save the form. - $new_site_name = 'The site that will live a very short life.'; - $page->fillField('settings[site_information][site_name]', $new_site_name); + $element_selector = "$block_selector {$block['element_selector']}"; + // Open block form by clicking a element inside the block. + // This confirms that default action for links and form elements is + // suppressed. + $this->openBlockForm($element_selector); - // @todo Uncomment the following lines after GastonJS problem solved. - // https://www.drupal.org/node/2789381 - // $this->getTray()->pressButton('Save block'); - // Make sure the changes are present. - //$web_assert->pageTextContains($new_site_name); + // Exit edit mode. + $this->toggleEditingMode(); + } } /** * Enables Editing mode by pressing "Edit" button in the toolbar. */ - protected function enableEditingMode() { + protected function toggleEditingMode() { $this->waitForElement('div[data-contextual-id="block:block=powered:langcode=en|outside_in::langcode=en"] .contextual-links a'); $this->waitForElement('#toolbar-bar', 3000); @@ -114,4 +144,15 @@ class OutsideInBlockFormTest extends OutsideInJavascriptTestBase { $web_assert->elementNotExists('css', 'select[data-drupal-selector="edit-region"]'); } + /** + * Open block form by clicking the element found with a css selector. + * + * @param string $block_selector + * A css selector selects the block or an element within it. + */ + protected function openBlockForm($block_selector) { + $this->click($block_selector); + $this->waitForOffCanvasToOpen(); + $this->assertOffCanvasBlockFormIsValid(); + } } diff --git a/core/modules/outside_in/tests/src/FunctionalJavascript/OutsideInJavascriptTestBase.php b/core/modules/outside_in/tests/src/FunctionalJavascript/OutsideInJavascriptTestBase.php index f27fb1ae566..da0be1b2bab 100644 --- a/core/modules/outside_in/tests/src/FunctionalJavascript/OutsideInJavascriptTestBase.php +++ b/core/modules/outside_in/tests/src/FunctionalJavascript/OutsideInJavascriptTestBase.php @@ -27,6 +27,8 @@ abstract class OutsideInJavascriptTestBase extends JavascriptTestBase { * Waits for Off-canvas tray to open. */ protected function waitForOffCanvasToOpen() { + $web_assert = $this->assertSession(); + $web_assert->assertWaitOnAjaxRequest(); $this->waitForElement('#drupal-offcanvas'); }