From 4ecc4ead39c67a3ea63bec6ce3510610cc4fde33 Mon Sep 17 00:00:00 2001 From: Nathaniel Catchpole <catch@35733.no-reply.drupal.org> Date: Thu, 4 Dec 2014 11:37:23 +0000 Subject: [PATCH] Issue #2382557 by Wim Leers: Change JS settings into a separate asset type --- core/core.libraries.yml | 13 +- core/includes/batch.inc | 19 +-- core/includes/common.inc | 136 +++++++----------- core/lib/Drupal/Core/Ajax/AjaxResponse.php | 18 ++- .../Core/Asset/JsCollectionRenderer.php | 2 +- .../Core/Asset/LibraryDiscoveryParser.php | 10 +- .../Core/Installer/Form/SiteConfigureForm.php | 3 +- .../Core/Render/Element/MachineName.php | 12 +- .../Core/Render/Element/RenderElement.php | 5 +- core/modules/block/src/BlockListBuilder.php | 5 +- .../block/src/Controller/BlockController.php | 19 ++- .../ckeditor/src/Plugin/Editor/CKEditor.php | 22 +-- core/modules/color/color.module | 19 +-- .../comment/src/CommentViewBuilder.php | 27 ++-- .../content_translation.admin.inc | 8 +- .../editor/src/Plugin/EditorManager.php | 5 +- .../editor/src/Tests/EditorManagerTest.php | 19 ++- core/modules/field_ui/src/OverviewBase.php | 5 +- .../Controller/FileWidgetAjaxController.php | 2 +- core/modules/file/src/Element/ManagedFile.php | 5 +- core/modules/history/history.module | 13 +- core/modules/locale/locale.module | 6 +- .../src/Form/SimpletestTestForm.php | 15 +- core/modules/simpletest/src/WebTestBase.php | 2 +- core/modules/statistics/statistics.module | 5 +- .../system/src/Tests/Ajax/CommandsTest.php | 6 +- .../src/Tests/Common/JavaScriptTest.php | 104 +++----------- .../src/Tests/Common/MergeAttachmentsTest.php | 131 +++++++++++------ .../system/src/Tests/Common/RenderTest.php | 117 +++++++-------- core/modules/system/system.module | 43 ++++++ .../ajax_forms_test/ajax_forms_test.module | 26 ---- .../src/Form/AjaxFormsTestCommandsForm.php | 12 -- .../src/Form/AjaxFormsTestLazyLoadForm.php | 12 +- .../tests/modules/ajax_test/ajax_test.module | 18 +-- .../modules/common_test/common_test.module | 36 +++-- .../test_page_test/test_page_test.module | 5 +- core/modules/system/theme.api.php | 27 +++- .../taxonomy/src/Form/OverviewTerms.php | 8 +- core/modules/toolbar/src/Element/Toolbar.php | 9 +- core/modules/toolbar/toolbar.module | 11 +- core/modules/user/user.module | 29 +--- .../views_test_data.views_execution.inc | 1 + core/modules/views/views.module | 3 +- core/modules/views_ui/src/ViewEditForm.php | 15 +- .../Core/Asset/LibraryDiscoveryParserTest.php | 3 +- .../css_js_settings.libraries.yml | 2 +- core/themes/bartik/color/color.inc | 3 +- 47 files changed, 428 insertions(+), 588 deletions(-) diff --git a/core/core.libraries.yml b/core/core.libraries.yml index 5f327210e158..0c212ce587bd 100644 --- a/core/core.libraries.yml +++ b/core/core.libraries.yml @@ -52,7 +52,18 @@ drupal: drupalSettings: version: VERSION - settings: {} + drupalSettings: + # These placeholder values will be set by system_js_settings_alter(). + path: + baseUrl: null + scriptPath: null + pathPrefix: null + currentPath: null + currentPathIsAdmin: null + isFront: null + currentLanguage: null + locale: + pluralDelimiter: null drupal.active-link: version: VERSION diff --git a/core/includes/batch.inc b/core/includes/batch.inc index 460c1afb4069..d1902e0d7d7b 100644 --- a/core/includes/batch.inc +++ b/core/includes/batch.inc @@ -179,18 +179,13 @@ function _batch_progress_page() { ), ), // Adds JavaScript code and settings for clients where JavaScript is enabled. - 'js' => array( - array( - 'type' => 'setting', - 'data' => array( - 'batch' => array( - 'errorMessage' => $current_set['error_message'] . '<br />' . $batch['error_message'], - 'initMessage' => $current_set['init_message'], - 'uri' => $url, - ), - ), - ), - ), + 'drupalSettings' => [ + 'batch' => [ + 'errorMessage' => $current_set['error_message'] . '<br />' . $batch['error_message'], + 'initMessage' => $current_set['init_message'], + 'uri' => $url, + ], + ], 'library' => array( 'core/drupal.batch', ), diff --git a/core/includes/common.inc b/core/includes/common.inc index 99a56bd2c940..612546161da0 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -1456,50 +1456,20 @@ function _drupal_add_js($data = NULL, $options = NULL) { switch ($options['type']) { case 'setting': // If the setting array doesn't exist, add defaults values. - if (!isset($javascript['settings'])) { - $javascript['settings'] = array( + if (!isset($javascript['drupalSettings'])) { + $javascript['drupalSettings'] = array( 'type' => 'setting', 'scope' => 'header', 'group' => JS_SETTING, 'every_page' => TRUE, 'weight' => 0, 'browsers' => array(), - ); - // url() generates the script and prefix using hook_url_outbound_alter(). - // Instead of running the hook_url_outbound_alter() again here, extract - // them from url(). - // @todo Make this less hacky: http://drupal.org/node/1547376. - $request = \Drupal::request(); - $scriptPath = $request->getScriptName(); - - $pathPrefix = ''; - $current_query = $request->query->all(); - _url('', array('script' => &$scriptPath, 'prefix' => &$pathPrefix)); - $current_path = \Drupal::routeMatch()->getRouteName() ? Url::fromRouteMatch(\Drupal::routeMatch())->getInternalPath() : ''; - $current_path_is_admin = \Drupal::service('router.admin_context')->isAdminRoute(); - $path = array( - 'baseUrl' => $request->getBaseUrl() . '/', - 'scriptPath' => $scriptPath, - 'pathPrefix' => $pathPrefix, - 'currentPath' => $current_path, - 'currentPathIsAdmin' => $current_path_is_admin, - 'isFront' => drupal_is_front_page(), - 'currentLanguage' => \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_URL)->getId(), - ); - if (!empty($current_query)) { - ksort($current_query); - $path['currentQuery'] = (object) $current_query; - } - $javascript['settings']['data'][] = array( - 'path' => $path, - 'locale' => array( - 'pluralDelimiter' => LOCALE_PLURAL_DELIMITER, - ), + 'data' => array(), ); } // All JavaScript settings are placed in the header of the page with // the library weight so that inline scripts appear afterwards. - $javascript['settings']['data'][] = $data; + $javascript['drupalSettings']['data'] = NestedArray::mergeDeepArray([$javascript['drupalSettings']['data'], $data], TRUE); break; case 'inline': @@ -1603,21 +1573,21 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS uasort($items, 'drupal_sort_css_js'); // Don't add settings if there is no other JavaScript on the page, unless // this is an AJAX request. - if (!empty($items['settings']) || $is_ajax) { + if (!empty($items['drupalSettings']) || $is_ajax) { $theme_key = \Drupal::theme()->getActiveTheme()->getName(); // Provide the page with information about the theme that's used, so that // a later AJAX request can be rendered using the same theme. // @see \Drupal\Core\Theme\AjaxBasePageNegotiator - $setting['ajaxPageState']['theme'] = $theme_key; + $ajaxPageState['theme'] = $theme_key; // Checks that the DB is available before filling theme_token. if (!defined('MAINTENANCE_MODE')) { - $setting['ajaxPageState']['theme_token'] = \Drupal::csrfToken()->get($theme_key); + $ajaxPageState['theme_token'] = \Drupal::csrfToken()->get($theme_key); } // Provide the page with information about the individual JavaScript files // used, information not otherwise available when aggregation is enabled. - $setting['ajaxPageState']['js'] = array_fill_keys(array_keys($javascript), 1); - unset($setting['ajaxPageState']['js']['settings']); + $ajaxPageState['js'] = array_fill_keys(array_keys($javascript), 1); + unset($ajaxPageState['js']['drupalSettings']); // Provide the page with information about the individual CSS files used, // information not otherwise available when CSS aggregation is enabled. @@ -1631,23 +1601,35 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS $css = _drupal_add_css(); if (!empty($css)) { // Cast the array to an object to be on the safe side even if not empty. - $setting['ajaxPageState']['css'] = (object) array_fill_keys(array_keys($css), 1); + $ajaxPageState['css'] = (object) array_fill_keys(array_keys($css), 1); } - _drupal_add_js($setting, 'setting'); + _drupal_add_js(['ajaxPageState' => $ajaxPageState], 'setting'); // If we're outputting the header scope, then this might be the final time // that drupal_get_js() is running, so add the settings to this output as well - // as to the _drupal_add_js() cache. If $items['settings'] doesn't exist, it's - // because drupal_get_js() was intentionally passed a $javascript argument - // stripped of settings, potentially in order to override how settings get - // output, so in this case, do not add the setting to this output. - if ($scope == 'header' && isset($items['settings'])) { - $items['settings']['data'][] = $setting; + // as to the _drupal_add_js() cache. If $items['drupalSettings'] doesn't + // exist, it's because drupal_get_js() was intentionally passed a + // $javascript argument stripped of settings, potentially in order to + // override how settings get output, so in this case, do not add the + // setting to this output. + if ($scope == 'header' && isset($items['drupalSettings'])) { + $items['drupalSettings']['data']['ajaxPageState'] = $ajaxPageState; } } } + // Process the 'drupalSettings' JavaScript asset, if any. + if (!empty($items['drupalSettings'])) { + $settings = $items['drupalSettings']['data']; + + // Allow modules and themes to alter the JavaScript settings. + \Drupal::moduleHandler()->alter('js_settings', $settings); + \Drupal::theme()->alter('js_settings', $settings); + + $items['drupalSettings']['data'] = $settings; + } + // Render the HTML needed to load the JavaScript. $elements = array( '#type' => 'scripts', @@ -1658,30 +1640,25 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS } /** - * Merges an array of settings arrays into a single settings array. + * Merges two #attached arrays. * - * This function merges the items in the same way that + * The values under the 'drupalSettings' key are merged in a special way, to + * match the behavior of * * @code * jQuery.extend(true, {}, $settings_items[0], $settings_items[1], ...) * @endcode * - * would. This means integer indices are preserved just like string indices are, + * This means integer indices are preserved just like string indices are, * rather than re-indexed as is common in PHP array merging. * * Example: * @code * function module1_page_attachments(&$page) { - * $page['#attached']['js'][] = array( - * 'type' => 'setting', - * 'data' => array('foo' => array('a', 'b', 'c')), - * ); + * $page['a']['#attached']['drupalSettings']['foo'] = ['a', 'b', 'c']; * } * function module2_page_attachments(&$page) { - * $page['#attached']['js'][] = array( - * 'type' => 'setting', - * 'data' => array('foo' => array('d')), - * ); + * $page['#attached']['drupalSettings']['foo'] = ['d']; * } * // When the page is rendered after the above code, and the browser runs the * // resulting <SCRIPT> tags, the value of drupalSettings.foo is @@ -1690,32 +1667,12 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS * * By following jQuery.extend() merge logic rather than common PHP array merge * logic, the following are ensured: - * - _drupal_add_js() is idempotent: calling it twice with the same parameters - * does not change the output sent to the browser. + * - Attaching JavaScript settings is idempotent: attaching the same settings + * twice does not change the output sent to the browser. * - If pieces of the page are rendered in separate PHP requests and the * returned settings are merged by JavaScript, the resulting settings are the * same as if rendered in one PHP request and merged by PHP. * - * @param $settings_items - * An array of settings arrays, as returned by: - * @code - * $js = _drupal_add_js(); - * $settings_items = $js['settings']['data']; - * @endcode - * - * @return - * A merged $settings array, suitable for JSON encoding and returning to the - * browser. - * - * @see _drupal_add_js() - */ -function drupal_merge_js_settings($settings_items) { - return NestedArray::mergeDeepArray($settings_items, TRUE); -} - -/** - * Merges two #attached arrays. - * * @param array $a * An #attached array. * @param array $b @@ -1725,6 +1682,12 @@ function drupal_merge_js_settings($settings_items) { * The merged #attached array. */ function drupal_merge_attached(array $a, array $b) { + // If both #attached arrays contain drupalSettings, then merge them correctly; + // adding the same settings multiple times needs to behave idempotently. + if (!empty($a['drupalSettings']) && !empty($b['drupalSettings'])) { + $a['drupalSettings'] = NestedArray::mergeDeepArray([$a['drupalSettings'], $b['drupalSettings']], TRUE); + unset($b['drupalSettings']); + } return NestedArray::mergeDeep($a, $b); } @@ -1820,6 +1783,13 @@ function drupal_process_attached($elements, $dependency_check = FALSE) { unset($elements['#attached'][$type]); } + // Convert every JavaScript settings asset into a regular JavaScript asset. + // @todo Clean this up in https://www.drupal.org/node/2382533 + if (!empty($elements['#attached']['drupalSettings'])) { + _drupal_add_js($elements['#attached']['drupalSettings'], ['type' => 'setting']); + unset($elements['#attached']['drupalSettings']); + } + // Add additional types of attachments specified in the render() structure. // Libraries, JavaScript and CSS have been added already, as they require // special handling. @@ -2027,6 +1997,9 @@ function _drupal_add_library($library_name, $every_page = NULL) { 'js' => $library['js'], 'css' => $library['css'], ); + if (isset($library['drupalSettings'])) { + $elements['#attached']['drupalSettings'] = $library['drupalSettings']; + } foreach (array('js', 'css') as $type) { foreach ($elements['#attached'][$type] as $data => $options) { // Set the every_page flag if one was passed. @@ -2186,7 +2159,7 @@ function drupal_attach_tabledrag(&$element, array $options) { // If a subgroup or source isn't set, assume it is the same as the group. $target = isset($options['subgroup']) ? $options['subgroup'] : $group; $source = isset($options['source']) ? $options['source'] : $target; - $settings['tableDrag'][$options['table_id']][$group][$tabledrag_id] = array( + $element['#attached']['drupalSettings'][$options['table_id']][$group][$tabledrag_id] = array( 'target' => $target, 'source' => $source, 'relationship' => $options['relationship'], @@ -2196,7 +2169,6 @@ function drupal_attach_tabledrag(&$element, array $options) { ); $element['#attached']['library'][] = 'core/drupal.tabledrag'; - $element['#attached']['js'][] = array('data' => $settings, 'type' => 'setting'); } /** diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponse.php b/core/lib/Drupal/Core/Ajax/AjaxResponse.php index 2ae18536c18e..f69af8329d48 100644 --- a/core/lib/Drupal/Core/Ajax/AjaxResponse.php +++ b/core/lib/Drupal/Core/Ajax/AjaxResponse.php @@ -129,8 +129,8 @@ protected function ajaxRender(Request $request) { // HTML in the page. We pass TRUE as the $skip_alter argument to prevent the // data from being altered again, as we already altered it above. Settings // are handled separately, afterwards. - if (isset($items['js']['settings'])) { - unset($items['js']['settings']); + if (isset($items['js']['drupalSettings'])) { + unset($items['js']['drupalSettings']); } $styles = drupal_get_css($items['css'], TRUE); $scripts_footer = drupal_get_js('footer', $items['js'], TRUE, TRUE); @@ -153,17 +153,15 @@ protected function ajaxRender(Request $request) { // Prepend a command to merge changes and additions to drupalSettings. $scripts = _drupal_add_js(); - if (!empty($scripts['settings'])) { - $settings = drupal_merge_js_settings($scripts['settings']['data']); + if (!empty($scripts['drupalSettings'])) { + $settings = $scripts['drupalSettings']['data']; // During Ajax requests basic path-specific settings are excluded from // new drupalSettings values. The original page where this request comes - // from already has the right values for the keys below. An Ajax request - // would update them with values for the Ajax request and incorrectly - // override the page's values. + // from already has the right values. An Ajax request would update them + // with values for the Ajax request and incorrectly override the page's + // values. // @see _drupal_add_js() - foreach (array('basePath', 'currentPath', 'scriptPath', 'pathPrefix') as $item) { - unset($settings[$item]); - } + unset($settings['path']); $this->addCommand(new SettingsCommand($settings, TRUE), TRUE); } diff --git a/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php b/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php index 807179f0d53b..36f012be9003 100644 --- a/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php +++ b/core/lib/Drupal/Core/Asset/JsCollectionRenderer.php @@ -68,7 +68,7 @@ public function render(array $js_assets) { switch ($js_asset['type']) { case 'setting': $element['#value_prefix'] = $embed_prefix; - $element['#value'] = 'var drupalSettings = ' . Json::encode(drupal_merge_js_settings($js_asset['data'])) . ";"; + $element['#value'] = 'var drupalSettings = ' . Json::encode($js_asset['data']) . ";"; $element['#value_suffix'] = $embed_suffix; break; diff --git a/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php b/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php index a28ca6e8369c..ec5f457ba40c 100644 --- a/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php +++ b/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php @@ -84,7 +84,7 @@ public function buildByExtension($extension) { } foreach ($libraries as $id => &$library) { - if (!isset($library['js']) && !isset($library['css']) && !isset($library['settings'])) { + if (!isset($library['js']) && !isset($library['css']) && !isset($library['drupalSettings'])) { throw new IncompleteLibraryDefinitionException(sprintf("Incomplete library definition for '%s' in %s", $id, $library_file)); } $library += array('dependencies' => array(), 'js' => array(), 'css' => array()); @@ -198,14 +198,6 @@ public function buildByExtension($extension) { } } - // @todo Introduce drupal_add_settings(). - if (isset($library['settings'])) { - $library['js'][] = array( - 'type' => 'setting', - 'data' => $library['settings'], - ); - unset($library['settings']); - } // @todo Convert all uses of #attached[library][]=array('provider','name') // into #attached[library][]='provider/name' and remove this. foreach ($library['dependencies'] as $i => $dependency) { diff --git a/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php b/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php index 167421155f30..750e96777124 100644 --- a/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php +++ b/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php @@ -123,8 +123,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { $form['#attached']['library'][] = 'core/drupal.timezone'; // We add these strings as settings because JavaScript translation does not // work during installation. - $js = array('copyFieldValue' => array('edit-site-mail' => array('edit-account-mail'))); - $form['#attached']['js'][] = array('data' => $js, 'type' => 'setting'); + $form['#attached']['drupalSettings']['copyFieldValue']['edit-site-mail'] = ['edit-account-mail']; // Cache a fully-built schema. This is necessary for any invocation of // index.php because: (1) setting cache table entries requires schema diff --git a/core/lib/Drupal/Core/Render/Element/MachineName.php b/core/lib/Drupal/Core/Render/Element/MachineName.php index f0c376853bea..e678b7bb9aab 100644 --- a/core/lib/Drupal/Core/Render/Element/MachineName.php +++ b/core/lib/Drupal/Core/Render/Element/MachineName.php @@ -163,17 +163,9 @@ public static function processMachineName(&$element, FormStateInterface $form_st NestedArray::setValue($form_state->getCompleteForm(), $parents, $source['#field_suffix']); } - $js_settings = array( - 'type' => 'setting', - 'data' => array( - 'machineName' => array( - '#' . $source['#id'] => $element['#machine_name'], - ), - 'langcode' => $language->getId(), - ), - ); $element['#attached']['library'][] = 'core/drupal.machine-name'; - $element['#attached']['js'][] = $js_settings; + $element['#attached']['drupalSettings']['machineName']['#' . $source['#id']] = $element['#machine_name']; + $element['#attached']['drupalSettings']['langcode'] = $language->getId(); return $element; } diff --git a/core/lib/Drupal/Core/Render/Element/RenderElement.php b/core/lib/Drupal/Core/Render/Element/RenderElement.php index bb8af732be6f..c0c982a79f60 100644 --- a/core/lib/Drupal/Core/Render/Element/RenderElement.php +++ b/core/lib/Drupal/Core/Render/Element/RenderElement.php @@ -296,10 +296,7 @@ public static function preRenderAjaxForm($element) { unset($settings['progress']['path']); } - $element['#attached']['js'][] = array( - 'type' => 'setting', - 'data' => array('ajax' => array($element['#id'] => $settings)), - ); + $element['#attached']['drupalSettings']['ajax'][$element['#id']] = $settings; // Indicate that Ajax processing was successful. $element['#ajax_processed'] = TRUE; diff --git a/core/modules/block/src/BlockListBuilder.php b/core/modules/block/src/BlockListBuilder.php index 1d11f9979a18..4f430044b833 100644 --- a/core/modules/block/src/BlockListBuilder.php +++ b/core/modules/block/src/BlockListBuilder.php @@ -139,10 +139,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { $placement = FALSE; if ($this->request->query->has('block-placement')) { $placement = $this->request->query->get('block-placement'); - $form['#attached']['js'][] = array( - 'type' => 'setting', - 'data' => array('blockPlacement' => $placement), - ); + $form['#attached']['drupalSettings']['blockPlacement'] = $placement; } $entities = $this->load(); $form['#theme'] = array('block_list'); diff --git a/core/modules/block/src/Controller/BlockController.php b/core/modules/block/src/Controller/BlockController.php index 958b3b2fd0e9..5ed1ce031550 100644 --- a/core/modules/block/src/Controller/BlockController.php +++ b/core/modules/block/src/Controller/BlockController.php @@ -59,17 +59,14 @@ public function demo($theme) { '#title' => $this->themeHandler->getName($theme), '#type' => 'page', '#attached' => array( - 'js' => array( - array( - // The block demonstration page is not marked as an administrative - // page by \Drupal::service('router.admin_context')->isAdminRoute() - // function in order to use the frontend theme. Since JavaScript - // relies on a proper separation of admin pages, it needs to know - // this is an actual administrative page. - 'data' => array('path' => array('currentPathIsAdmin' => TRUE)), - 'type' => 'setting', - ) - ), + 'drupalSettings' => [ + // The block demonstration page is not marked as an administrative + // page by \Drupal::service('router.admin_context')->isAdminRoute() + // function in order to use the frontend theme. Since JavaScript + // relies on a proper separation of admin pages, it needs to know this + // is an actual administrative page. + 'path' => ['currentPathIsAdmin' => TRUE], + ], 'library' => array( 'block/drupal.block.admin', ), diff --git a/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php b/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php index 41259d5bb3f3..c8cb9321c9e6 100644 --- a/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php +++ b/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php @@ -140,14 +140,11 @@ public function settingsForm(array $form, FormStateInterface $form_state, Editor '#type' => 'container', '#attached' => array( 'library' => array('ckeditor/drupal.ckeditor.admin'), - 'js' => array( - array( - 'type' => 'setting', - 'data' => array('ckeditor' => array( - 'toolbarAdmin' => drupal_render($ckeditor_settings_toolbar), - )), - ) - ), + 'drupalSettings' => [ + 'ckeditor' => [ + 'toolbarAdmin' => drupal_render($ckeditor_settings_toolbar), + ], + ], ), '#attributes' => array('class' => array('ckeditor-toolbar-configuration')), ); @@ -218,14 +215,7 @@ public function settingsForm(array $form, FormStateInterface $form_state, Editor $form['hidden_ckeditor'] = array( '#markup' => '<div id="ckeditor-hidden" class="hidden"></div>', '#attached' => array( - 'js' => array( - array( - 'type' => 'setting', - 'data' => array('ckeditor' => array( - 'hiddenCKEditorConfig' => $config, - )), - ), - ), + 'drupalSettings' => ['ckeditor' => ['hiddenCKEditorConfig' => $config]], ), ); diff --git a/core/modules/color/color.module b/core/modules/color/color.module index 5b008693b120..e9cc0a695098 100644 --- a/core/modules/color/color.module +++ b/core/modules/color/color.module @@ -213,18 +213,13 @@ function color_scheme_form($complete_form, FormStateInterface $form_state, $them 'color/admin', ), // Add custom JavaScript. - 'js' => array( - array( - 'data' => array( - 'color' => array( - 'reference' => color_get_palette($theme, TRUE), - 'schemes' => $schemes, - ), - 'gradients' => $info['gradients'], - ), - 'type' => 'setting', - ), - ), + 'drupalSettings' => [ + 'color' => [ + 'reference' => color_get_palette($theme, TRUE), + 'schemes' => $schemes, + ], + 'gradients' => $info['gradients'], + ], ), ); diff --git a/core/modules/comment/src/CommentViewBuilder.php b/core/modules/comment/src/CommentViewBuilder.php index 0e453aee3a10..b903250a92a6 100644 --- a/core/modules/comment/src/CommentViewBuilder.php +++ b/core/modules/comment/src/CommentViewBuilder.php @@ -364,23 +364,16 @@ public static function attachNewCommentsLinkMetadata(array $element, array $cont $query = $page_number ? array('page' => $page_number) : NULL; // Attach metadata. - $element['#attached']['js'][] = array( - 'type' => 'setting', - 'data' => array( - 'comment' => array( - 'newCommentsLinks' => array( - $context['entity_type'] => array( - $context['field_name'] => array( - $context['entity_id'] => array( - 'new_comment_count' => (int)$new, - 'first_new_comment_link' => \Drupal::urlGenerator()->generateFromPath('node/' . $entity->id(), array('query' => $query, 'fragment' => 'new')), - ) - ) - ), - ) - ), - ), - ); + $element['#attached']['drupalSettings']['comment']['newCommentsLinks'] = [ + $context['entity_type'] => [ + $context['field_name'] => [ + $context['entity_id'] => [ + 'new_comment_count' => (int)$new, + 'first_new_comment_link' => \Drupal::urlGenerator()->generateFromPath('node/' . $entity->id(), ['query' => $query, 'fragment' => 'new']), + ], + ], + ], + ]; return $element; } diff --git a/core/modules/content_translation/content_translation.admin.inc b/core/modules/content_translation/content_translation.admin.inc index b1d1a2a00684..3f08c9a8cbc6 100644 --- a/core/modules/content_translation/content_translation.admin.inc +++ b/core/modules/content_translation/content_translation.admin.inc @@ -52,9 +52,9 @@ function content_translation_field_sync_widget(FieldDefinitionInterface $field) 'library' => array( 'content_translation/drupal.content_translation.admin', ), - 'js' => array( - array('data' => array('contentTranslationDependentOptions' => $settings), 'type' => 'setting'), - ), + 'drupalSettings' => [ + 'contentTranslationDependentOptions' => $settings, + ], ), ); } @@ -126,7 +126,7 @@ function _content_translation_form_language_content_settings_form_alter(array &$ } $settings = array('dependent_selectors' => $dependent_options_settings); - $form['#attached']['js'][] = array('data' => array('contentTranslationDependentOptions' => $settings), 'type' => 'setting'); + $form['#attached']['drupalSettings']['contentTranslationDependentOptions'] = $settings; $form['#validate'][] = 'content_translation_form_language_content_settings_validate'; $form['#submit'][] = 'content_translation_form_language_content_settings_submit'; } diff --git a/core/modules/editor/src/Plugin/EditorManager.php b/core/modules/editor/src/Plugin/EditorManager.php index da0081e5aebc..6fc04d070d52 100644 --- a/core/modules/editor/src/Plugin/EditorManager.php +++ b/core/modules/editor/src/Plugin/EditorManager.php @@ -96,10 +96,7 @@ public function getAttachments(array $format_ids) { return array(); } - $attachments['js'][] = array( - 'type' => 'setting', - 'data' => $settings, - ); + $attachments['drupalSettings'] = $settings; return $attachments; } diff --git a/core/modules/editor/src/Tests/EditorManagerTest.php b/core/modules/editor/src/Tests/EditorManagerTest.php index 66804ef3b931..c28d99dc83d1 100644 --- a/core/modules/editor/src/Tests/EditorManagerTest.php +++ b/core/modules/editor/src/Tests/EditorManagerTest.php @@ -90,27 +90,26 @@ public function testManager() { 'library' => array( 0 => 'editor_test/unicorn', ), - 'js' => array( - 0 => array( - 'type' => 'setting', - 'data' => array('editor' => array('formats' => array( - 'full_html' => array( + 'drupalSettings' => [ + 'editor' => [ + 'formats' => [ + 'full_html' => [ 'format' => 'full_html', 'editor' => 'unicorn', 'editorSettings' => $unicorn_plugin->getJSSettings($editor), 'editorSupportsContentFiltering' => TRUE, 'isXssSafe' => FALSE, - ) - ))) - ) - ), + ], + ], + ], + ], ); $this->assertIdentical($expected, $this->editorManager->getAttachments(array('filtered_html', 'full_html')), 'Correct attachments when one text editor is enabled and retrieving attachments for multiple text formats.'); // Case 4: a text editor available associated, but now with its JS settings // being altered via hook_editor_js_settings_alter(). \Drupal::state()->set('editor_test_js_settings_alter_enabled', TRUE); - $expected['js'][0]['data']['editor']['formats']['full_html']['editorSettings']['ponyModeEnabled'] = FALSE; + $expected['drupalSettings']['editor']['formats']['full_html']['editorSettings']['ponyModeEnabled'] = FALSE; $this->assertIdentical($expected, $this->editorManager->getAttachments(array('filtered_html', 'full_html')), 'hook_editor_js_settings_alter() works correctly.'); } diff --git a/core/modules/field_ui/src/OverviewBase.php b/core/modules/field_ui/src/OverviewBase.php index 2de43eeb7535..93adaa8c3932 100644 --- a/core/modules/field_ui/src/OverviewBase.php +++ b/core/modules/field_ui/src/OverviewBase.php @@ -210,10 +210,7 @@ public function tablePreRender($elements) { $elements['#regions'][$region_name]['rows_order'] = array_reduce($trees[$region_name], array($this, 'reduceOrder')); } - $elements['#attached']['js'][] = array( - 'type' => 'setting', - 'data' => array('fieldUIRowsData' => $js_settings), - ); + $elements['#attached']['drupalSettings']['fieldUIRowsData'] = $js_settings; // If the custom #tabledrag is set and there is a HTML ID, add the table's // HTML ID to the options and attach the behavior. diff --git a/core/modules/file/src/Controller/FileWidgetAjaxController.php b/core/modules/file/src/Controller/FileWidgetAjaxController.php index 3187c3b7972e..3adc43a58d31 100644 --- a/core/modules/file/src/Controller/FileWidgetAjaxController.php +++ b/core/modules/file/src/Controller/FileWidgetAjaxController.php @@ -81,7 +81,7 @@ public function upload(Request $request) { $output = drupal_render($form); drupal_process_attached($form); $js = _drupal_add_js(); - $settings = drupal_merge_js_settings($js['settings']['data']); + $settings = $js['drupalSettings']['data']; $response = new AjaxResponse(); foreach ($commands as $command) { diff --git a/core/modules/file/src/Element/ManagedFile.php b/core/modules/file/src/Element/ManagedFile.php index 34c20e8cc921..40d0c3113014 100644 --- a/core/modules/file/src/Element/ManagedFile.php +++ b/core/modules/file/src/Element/ManagedFile.php @@ -256,10 +256,7 @@ public static function processManagedFile(&$element, FormStateInterface $form_st // Add the extension list to the page as JavaScript settings. if (isset($element['#upload_validators']['file_validate_extensions'][0])) { $extension_list = implode(',', array_filter(explode(' ', $element['#upload_validators']['file_validate_extensions'][0]))); - $element['upload']['#attached']['js'] = [[ - 'type' => 'setting', - 'data' => ['file' => ['elements' => ['#' . $element['#id'] => $extension_list]]], - ]]; + $element['upload']['#attached']['drupalSettings']['file']['elements']['#' . $element['#id']] = $extension_list; } // Prefix and suffix used for Ajax replacement. diff --git a/core/modules/history/history.module b/core/modules/history/history.module index f4c9742926b4..bd8ca71b7054 100644 --- a/core/modules/history/history.module +++ b/core/modules/history/history.module @@ -193,16 +193,7 @@ function history_user_delete($account) { * The updated $element. */ function history_attach_timestamp(array $element, array $context) { - $element['#attached']['js'][] = array( - 'type' => 'setting', - 'data' => array( - 'history' => array( - 'lastReadTimestamps' => array( - $context['node_id'] => (int) history_read($context['node_id']), - ) - ), - ), - ); - + $node_id = $context['node_id']; + $element['#attached']['drupalSettings']['history']['lastReadTimestamps'][$node_id] = (int) history_read($node_id); return $element; } diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index c5add1d21b05..72f0e23e5477 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -535,14 +535,10 @@ function locale_library_alter(array &$library, $name) { $library['dependencies'][] = 'locale/drupal.locale.datepicker'; $language_interface = \Drupal::languageManager()->getCurrentLanguage(); - $settings['jquery']['ui']['datepicker'] = array( + $library['drupalSettings']['jquery']['ui']['datepicker'] = array( 'isRTL' => $language_interface->getDirection() == LanguageInterface::DIRECTION_RTL, 'firstDay' => \Drupal::config('system.date')->get('first_day'), ); - $library['js'][] = array( - 'type' => 'setting', - 'data' => $settings, - ); } } diff --git a/core/modules/simpletest/src/Form/SimpletestTestForm.php b/core/modules/simpletest/src/Form/SimpletestTestForm.php index b55de4c33208..03f16f511713 100644 --- a/core/modules/simpletest/src/Form/SimpletestTestForm.php +++ b/core/modules/simpletest/src/Form/SimpletestTestForm.php @@ -98,17 +98,10 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#title' => $this->t('Collapse'), '#suffix' => '<a href="#" class="simpletest-collapse">(' . $this->t('Collapse') . ')</a>', ); - $form['tests']['#attached']['js'][] = array( - 'type' => 'setting', - 'data' => array( - 'simpleTest' => array( - 'images' => array( - drupal_render($image_collapsed), - drupal_render($image_extended), - ), - ), - ), - ); + $form['tests']['#attached']['drupalSettings']['simpleTest']['images'] = [ + drupal_render($image_collapsed), + drupal_render($image_extended), + ]; // Generate the list of tests arranged by group. $groups = simpletest_test_get_all(); diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php index 93a5047f1a89..44af34caad09 100644 --- a/core/modules/simpletest/src/WebTestBase.php +++ b/core/modules/simpletest/src/WebTestBase.php @@ -1844,7 +1844,7 @@ protected function drupalProcessAjaxResponse($content, array $ajax_response, arr } switch ($command['command']) { case 'settings': - $drupal_settings = drupal_merge_js_settings(array($drupal_settings, $command['settings'])); + $drupal_settings = NestedArray::mergeDeepArray([$drupal_settings, $command['settings']], TRUE); break; case 'insert': diff --git a/core/modules/statistics/statistics.module b/core/modules/statistics/statistics.module index 00ca78de2708..53dd91f4e198 100644 --- a/core/modules/statistics/statistics.module +++ b/core/modules/statistics/statistics.module @@ -40,10 +40,7 @@ function statistics_node_view(array &$build, EntityInterface $node, EntityViewDi if (!$node->isNew() && $view_mode == 'full' && node_is_page($node) && empty($node->in_preview)) { $build['statistics_content_counter']['#attached']['library'][] = 'statistics/drupal.statistics'; $settings = array('data' => array('nid' => $node->id()), 'url' => _url(drupal_get_path('module', 'statistics') . '/statistics.php')); - $build['statistics_content_counter']['#attached']['js'][] = array( - 'data' => array('statistics' => $settings), - 'type' => 'setting', - ); + $build['statistics_content_counter']['#attached']['drupalSettings']['statistics'] = $settings; } } diff --git a/core/modules/system/src/Tests/Ajax/CommandsTest.php b/core/modules/system/src/Tests/Ajax/CommandsTest.php index 945a763651b8..ec981a7efd69 100644 --- a/core/modules/system/src/Tests/Ajax/CommandsTest.php +++ b/core/modules/system/src/Tests/Ajax/CommandsTest.php @@ -118,10 +118,6 @@ function testAjaxCommands() { $commands = $this->drupalPostAjaxForm($form_path, $edit, array('op' => t("AJAX 'settings' command"))); $expected = new SettingsCommand(array('ajax_forms_test' => array('foo' => 42))); $this->assertCommand($commands, $expected->render(), "'settings' AJAX command issued with correct data."); - - // Test that the settings command merges settings properly. - $commands = $this->drupalPostAjaxForm($form_path, $edit, array('op' => t("AJAX 'settings' command with setting merging"))); - $expected = new SettingsCommand(array('ajax_forms_test' => array('foo' => 9001)), TRUE); - $this->assertCommand($commands, $expected->render(), "'settings' AJAX command with setting merging."); } + } diff --git a/core/modules/system/src/Tests/Common/JavaScriptTest.php b/core/modules/system/src/Tests/Common/JavaScriptTest.php index 2196de6dc0c1..a59d50effa6a 100644 --- a/core/modules/system/src/Tests/Common/JavaScriptTest.php +++ b/core/modules/system/src/Tests/Common/JavaScriptTest.php @@ -77,16 +77,14 @@ function testAddFile() { */ function testAddSetting() { // Add a file in order to test default settings. - $attached['#attached']['library'][] = 'core/drupalSettings'; - $this->render($attached); + $build['#attached']['library'][] = 'core/drupalSettings'; + drupal_process_attached($build); $javascript = _drupal_add_js(); - $last_settings = reset($javascript['settings']['data']); - $this->assertTrue(array_key_exists('currentPath', $last_settings['path']), 'The current path JavaScript setting is set correctly.'); + $this->assertTrue(array_key_exists('currentPath', $javascript['drupalSettings']['data']['path']), 'The current path JavaScript setting is set correctly.'); $javascript = _drupal_add_js(array('drupal' => 'rocks', 'dries' => 280342800), 'setting'); - $last_settings = end($javascript['settings']['data']); - $this->assertEqual(280342800, $last_settings['dries'], 'JavaScript setting is set correctly.'); - $this->assertEqual('rocks', $last_settings['drupal'], 'The other JavaScript setting is set correctly.'); + $this->assertEqual(280342800, $javascript['drupalSettings']['data']['dries'], 'JavaScript setting is set correctly.'); + $this->assertEqual('rocks', $javascript['drupalSettings']['data']['drupal'], 'The other JavaScript setting is set correctly.'); } /** @@ -156,81 +154,11 @@ function testAggregatedAttributes() { function testHeaderSetting() { $attached = array(); $attached['#attached']['library'][] = 'core/drupalSettings'; + // Nonsensical value to verify if it's possible to override path settings. + $attached['#attached']['drupalSettings']['path']['pathPrefix'] = 'yarhar'; $this->render($attached); $javascript = drupal_get_js('header'); - $this->assertTrue(strpos($javascript, 'baseUrl') > 0, 'Rendered JavaScript header returns baseUrl setting.'); - $this->assertTrue(strpos($javascript, 'scriptPath') > 0, 'Rendered JavaScript header returns scriptPath setting.'); - $this->assertTrue(strpos($javascript, 'pathPrefix') > 0, 'Rendered JavaScript header returns pathPrefix setting.'); - $this->assertTrue(strpos($javascript, 'currentPath') > 0, 'Rendered JavaScript header returns currentPath setting.'); - - // Only the second of these two entries should appear in drupalSettings. - $attached = array(); - $attached['#attached']['js'][] = array( - 'type' => 'setting', - 'data' => array('commonTest' => 'commonTestShouldNotAppear'), - ); - $attached['#attached']['js'][] = array( - 'type' => 'setting', - 'data' => array('commonTest' => 'commonTestShouldAppear'), - ); - // Only the second of these entries should appear in drupalSettings. - $attached['#attached']['js'][] = array( - 'type' => 'setting', - 'data' => array('commonTestJsArrayLiteral' => array('commonTestJsArrayLiteralOldValue')), - ); - $attached['#attached']['js'][] = array( - 'type' => 'setting', - 'data' => array('commonTestJsArrayLiteral' => array('commonTestJsArrayLiteralNewValue')), - ); - // Only the second of these two entries should appear in drupalSettings. - $attached['#attached']['js'][] = array( - 'type' => 'setting', - 'data' => array('commonTestJsObjectLiteral' => array('key' => 'commonTestJsObjectLiteralOldValue')), - ); - $attached['#attached']['js'][] = array( - 'type' => 'setting', - 'data' => array('commonTestJsObjectLiteral' => array('key' => 'commonTestJsObjectLiteralNewValue')), - ); - // Real world test case: multiple elements in a render array are adding the - // same (or nearly the same) JavaScript settings. When merged, they should - // contain all settings and not duplicate some settings. - $settings_one = array('moduleName' => array('ui' => array('button A', 'button B'), 'magical flag' => 3.14159265359)); - $attached['#attached']['js'][] = array( - 'type' => 'setting', - 'data' => array('commonTestRealWorldIdentical' => $settings_one), - ); - $attached['#attached']['js'][] = array( - 'type' => 'setting', - 'data' => array('commonTestRealWorldIdentical' => $settings_one), - ); - $settings_two = array('moduleName' => array('ui' => array('button A', 'button B'), 'magical flag' => 3.14159265359, 'thingiesOnPage' => array('id1' => array()))); - $attached['#attached']['js'][] = array( - 'type' => 'setting', - 'data' => array('commonTestRealWorldAlmostIdentical' => $settings_two), - ); - $settings_two = array('moduleName' => array('ui' => array('button C', 'button D'), 'magical flag' => 3.14, 'thingiesOnPage' => array('id2' => array()))); - $attached['#attached']['js'][] = array( - 'type' => 'setting', - 'data' => array('commonTestRealWorldAlmostIdentical' => $settings_two), - ); - - $this->render($attached); - $javascript = drupal_get_js('header'); - - // Test whether _drupal_add_js can be used to override a previous setting. - $this->assertTrue(strpos($javascript, 'commonTestShouldAppear') > 0, 'Rendered JavaScript header returns custom setting.'); - $this->assertTrue(strpos($javascript, 'commonTestShouldNotAppear') === FALSE, '_drupal_add_js() correctly overrides a custom setting.'); - - // Test whether _drupal_add_js can be used to add and override a JavaScript - // array literal (an indexed PHP array) values. - $array_override = strpos($javascript, 'commonTestJsArrayLiteralNewValue') > 0 && strpos($javascript, 'commonTestJsArrayLiteralOldValue') === FALSE; - $this->assertTrue($array_override, '_drupal_add_js() correctly overrides settings within an array literal (indexed array).'); - - // Test whether _drupal_add_js can be used to add and override a JavaScript - // object literal (an associate PHP array) values. - $associative_array_override = strpos($javascript, 'commonTestJsObjectLiteralNewValue') > 0 && strpos($javascript, 'commonTestJsObjectLiteralOldValue') === FALSE; - $this->assertTrue($associative_array_override, '_drupal_add_js() correctly overrides settings within an object literal (associative array).'); // Parse the generated drupalSettings <script> back to a PHP representation. $startToken = 'drupalSettings = '; @@ -240,10 +168,20 @@ function testHeaderSetting() { $json = Unicode::substr($javascript, $start, $end - $start + 1); $parsed_settings = Json::decode($json); - // Test whether the two real world cases are handled correctly. - $settings_two['moduleName']['thingiesOnPage']['id1'] = array(); - $this->assertIdentical($settings_one, $parsed_settings['commonTestRealWorldIdentical'], '_drupal_add_js handled real world test case 1 correctly.'); - $this->assertEqual($settings_two, $parsed_settings['commonTestRealWorldAlmostIdentical'], '_drupal_add_js handled real world test case 2 correctly.'); + // Test whether the settings for core/drupalSettings are available. + $this->assertTrue(isset($parsed_settings['path']['baseUrl']), 'drupalSettings.path.baseUrl is present.'); + $this->assertTrue(isset($parsed_settings['path']['scriptPath']), 'drupalSettings.path.scriptPath is present.'); + $this->assertIdentical($parsed_settings['path']['pathPrefix'], 'yarhar', 'drupalSettings.path.pathPrefix is present and has the correct (overridden) value.'); + $this->assertIdentical($parsed_settings['path']['currentPath'], '', 'drupalSettings.path.currentPath is present and has the correct value.'); + $this->assertIdentical($parsed_settings['path']['currentPathIsAdmin'], FALSE, 'drupalSettings.path.currentPathIsAdmin is present and has the correct value.'); + $this->assertIdentical($parsed_settings['path']['isFront'], FALSE, 'drupalSettings.path.isFront is present and has the correct value.'); + $this->assertIdentical($parsed_settings['path']['currentLanguage'], 'en', 'drupalSettings.path.currentLanguage is present and has the correct value.'); + + // Tests whether altering JavaScript settings via hook_js_settings_alter() + // is working as expected. + // @see common_test_js_settings_alter() + $this->assertIdentical($parsed_settings['locale']['pluralDelimiter'], '☃'); + $this->assertIdentical($parsed_settings['foo'], 'bar'); } /** diff --git a/core/modules/system/src/Tests/Common/MergeAttachmentsTest.php b/core/modules/system/src/Tests/Common/MergeAttachmentsTest.php index 7eebe329624b..51d7c6234b77 100644 --- a/core/modules/system/src/Tests/Common/MergeAttachmentsTest.php +++ b/core/modules/system/src/Tests/Common/MergeAttachmentsTest.php @@ -12,6 +12,8 @@ /** * Tests the merging of attachments. * + * @see drupal_merge_attached() + * * @group Common */ class MergeAttachmentsTest extends DrupalUnitTestBase { @@ -165,36 +167,30 @@ function testJsSettingMerging() { $a['#attached'] = array( 'js' => array( 'foo.js' => array(), - array( - 'type' => 'setting', - 'data' => array('foo' => array('d')), - ), 'bar.js' => array(), ), + 'drupalSettings' => [ + 'foo' => ['d'], + ], ); $b['#attached'] = array( 'js' => array( - 83 => array( - 'type' => 'setting', - 'data' => array('bar' => array('a', 'b', 'c')), - ), 'baz.js' => array(), ), + 'drupalSettings' => [ + 'bar' => ['a', 'b', 'c'], + ], ); $expected['#attached'] = array( 'js' => array( 'foo.js' => array(), - 0 => array( - 'type' => 'setting', - 'data' => array('foo' => array('d')), - ), 'bar.js' => array(), - 1 => array( - 'type' => 'setting', - 'data' => array('bar' => array('a', 'b', 'c')), - ), 'baz.js' => array(), ), + 'drupalSettings' => [ + 'foo' => ['d'], + 'bar' => ['a', 'b', 'c'], + ], ); $this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached']), 'Attachments merged correctly.'); @@ -202,48 +198,95 @@ function testJsSettingMerging() { // order. $expected['#attached'] = array( 'js' => array( - 0 => array( - 'type' => 'setting', - 'data' => array('bar' => array('a', 'b', 'c')), - ), 'baz.js' => array(), 'foo.js' => array(), - 1 => array( - 'type' => 'setting', - 'data' => array('foo' => array('d')), - ), 'bar.js' => array(), ), + 'drupalSettings' => [ + 'bar' => ['a', 'b', 'c'], + 'foo' => ['d'], + ], ); $this->assertIdentical($expected['#attached'], drupal_merge_attached($b['#attached'], $a['#attached']), 'Attachments merged correctly; opposite merging yields opposite order.'); - // Merging with duplicates: JavaScript setting duplicates are simply - // retained, it's up to the rest of the system (drupal_merge_js_settings()) - // to handle duplicates. - $b['#attached']['js'][] = array( - 'type' => 'setting', - 'data' => array('foo' => array('a', 'b', 'c')), - ); + // Merging with duplicates (simple case). + $b['#attached']['drupalSettings']['foo'] = ['a', 'b', 'c']; $expected['#attached'] = array( 'js' => array( 'foo.js' => array(), - 0 => array( - 'type' => 'setting', - 'data' => array('foo' => array('d')), - ), 'bar.js' => array(), - 1 => array( - 'type' => 'setting', - 'data' => array('bar' => array('a', 'b', 'c')), - ), 'baz.js' => array(), - 2 => array( - 'type' => 'setting', - 'data' => array('foo' => array('a', 'b', 'c')), - ), ), + 'drupalSettings' => [ + 'foo' => ['a', 'b', 'c'], + 'bar' => ['a', 'b', 'c'], + ], + ); + $this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached'])); + + // Merging with duplicates (simple case) in the opposite direction yields + // the opposite JS setting asset order, but also opposite overriding order. + $expected['#attached'] = array( + 'js' => array( + 'baz.js' => array(), + 'foo.js' => array(), + 'bar.js' => array(), + ), + 'drupalSettings' => [ + 'bar' => ['a', 'b', 'c'], + 'foo' => ['d', 'b', 'c'], + ], ); - $this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached']), 'Attachments merged correctly; JavaScript asset duplicates removed, JavaScript setting asset duplicates retained.'); + $this->assertIdentical($expected['#attached'], drupal_merge_attached($b['#attached'], $a['#attached'])); + + // Merging with duplicates: complex case. + // Only the second of these two entries should appear in drupalSettings. + $build = array(); + $build['a']['#attached']['drupalSettings']['commonTest'] = 'firstValue'; + $build['b']['#attached']['drupalSettings']['commonTest'] = 'secondValue'; + // Only the second of these entries should appear in drupalSettings. + $build['a']['#attached']['drupalSettings']['commonTestJsArrayLiteral'] = ['firstValue']; + $build['b']['#attached']['drupalSettings']['commonTestJsArrayLiteral'] = ['secondValue']; + // Only the second of these two entries should appear in drupalSettings. + $build['a']['#attached']['drupalSettings']['commonTestJsObjectLiteral'] = ['key' => 'firstValue']; + $build['b']['#attached']['drupalSettings']['commonTestJsObjectLiteral'] = ['key' => 'secondValue']; + // Real world test case: multiple elements in a render array are adding the + // same (or nearly the same) JavaScript settings. When merged, they should + // contain all settings and not duplicate some settings. + $settings_one = array('moduleName' => array('ui' => array('button A', 'button B'), 'magical flag' => 3.14159265359)); + $build['a']['#attached']['drupalSettings']['commonTestRealWorldIdentical'] = $settings_one; + $build['b']['#attached']['drupalSettings']['commonTestRealWorldIdentical'] = $settings_one; + $settings_two_a = array('moduleName' => array('ui' => array('button A', 'button B', 'button C'), 'magical flag' => 3.14159265359, 'thingiesOnPage' => array('id1' => array()))); + $build['a']['#attached']['drupalSettings']['commonTestRealWorldAlmostIdentical'] = $settings_two_a; + $settings_two_b = array('moduleName' => array('ui' => array('button D', 'button E'), 'magical flag' => 3.14, 'thingiesOnPage' => array('id2' => array()))); + $build['b']['#attached']['drupalSettings']['commonTestRealWorldAlmostIdentical'] = $settings_two_b; + + $merged = drupal_merge_attached($build['a']['#attached'], $build['b']['#attached']); + + // Test whether #attached can be used to override a previous setting. + $this->assertIdentical('secondValue', $merged['drupalSettings']['commonTest']); + + // Test whether #attached can be used to add and override a JavaScript + // array literal (an indexed PHP array) values. + $this->assertIdentical('secondValue', $merged['drupalSettings']['commonTestJsArrayLiteral'][0]); + + // Test whether #attached can be used to add and override a JavaScript + // object literal (an associate PHP array) values. + $this->assertIdentical('secondValue', $merged['drupalSettings']['commonTestJsObjectLiteral']['key']); + + // Test whether the two real world cases are handled correctly: the first + // adds the exact same settings twice and hence tests idempotency, the + // second adds *almost* the same settings twice: the second time, some + // values are altered, and some key-value pairs are added. + $settings_two['moduleName']['thingiesOnPage']['id1'] = array(); + $this->assertIdentical($settings_one, $merged['drupalSettings']['commonTestRealWorldIdentical']); + $expected_settings_two = $settings_two_a; + $expected_settings_two['moduleName']['ui'][0] = 'button D'; + $expected_settings_two['moduleName']['ui'][1] = 'button E'; + $expected_settings_two['moduleName']['ui'][2] = 'button C'; + $expected_settings_two['moduleName']['magical flag'] = 3.14; + $expected_settings_two['moduleName']['thingiesOnPage']['id2'] = []; + $this->assertIdentical($expected_settings_two, $merged['drupalSettings']['commonTestRealWorldAlmostIdentical']); } } diff --git a/core/modules/system/src/Tests/Common/RenderTest.php b/core/modules/system/src/Tests/Common/RenderTest.php index 8f801d88eea0..206f0a568a62 100644 --- a/core/modules/system/src/Tests/Common/RenderTest.php +++ b/core/modules/system/src/Tests/Common/RenderTest.php @@ -493,7 +493,7 @@ function testDrupalRenderPostRenderCache() { $context = array('foo' => $this->randomContextValue()); $test_element = array(); $test_element['#markup'] = ''; - $test_element['#attached']['js'][] = array('type' => 'setting', 'data' => array('foo' => 'bar')); + $test_element['#attached']['drupalSettings']['foo'] = 'bar'; $test_element['#post_render_cache']['common_test_post_render_cache'] = array( $context ); @@ -504,11 +504,11 @@ function testDrupalRenderPostRenderCache() { $output = drupal_render_root($element); $this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.'); $this->assertIdentical($element['#markup'], '<p>overridden</p>', '#markup is overridden.'); - $expected_js = [ - ['type' => 'setting', 'data' => ['foo' => 'bar']], - ['type' => 'setting', 'data' => ['common_test' => $context]], + $expected_js_settings = [ + 'foo' => 'bar', + 'common_test' => $context, ]; - $this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; both the original JavaScript setting and the one added by the #post_render_cache callback exist.'); + $this->assertIdentical($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the one added by the #post_render_cache callback exist.'); // The cache system is turned off for POST requests. $request_method = \Drupal::request()->getMethod(); @@ -522,11 +522,11 @@ function testDrupalRenderPostRenderCache() { $this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.'); $this->assertTrue(isset($element['#printed']), 'No cache hit'); $this->assertIdentical($element['#markup'], '<p>overridden</p>', '#markup is overridden.'); - $expected_js = [ - ['type' => 'setting', 'data' => ['foo' => 'bar']], - ['type' => 'setting', 'data' => ['common_test' => $context]], + $expected_js_settings = [ + 'foo' => 'bar', + 'common_test' => $context, ]; - $this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; both the original JavaScript setting and the one added by the #post_render_cache callback exist.'); + $this->assertIdentical($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the one added by the #post_render_cache callback exist.'); // GET request: validate cached data. $element = array('#cache' => array('cid' => 'post_render_cache_test_GET')); @@ -546,11 +546,11 @@ function testDrupalRenderPostRenderCache() { $this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.'); $this->assertFalse(isset($element['#printed']), 'Cache hit'); $this->assertIdentical($element['#markup'], '<p>overridden</p>', '#markup is overridden.'); - $expected_js = [ - ['type' => 'setting', 'data' => ['foo' => 'bar']], - ['type' => 'setting', 'data' => ['common_test' => $context]], + $expected_js_settings = [ + 'foo' => 'bar', + 'common_test' => $context, ]; - $this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; both the original JavaScript setting and the one added by the #post_render_cache callback exist.'); + $this->assertIdentical($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the one added by the #post_render_cache callback exist.'); // Verify behavior when handling a non-GET request, e.g. a POST request: // also in that case, #post_render_cache callbacks must be called. @@ -564,11 +564,11 @@ function testDrupalRenderPostRenderCache() { $this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.'); $this->assertTrue(isset($element['#printed']), 'No cache hit'); $this->assertIdentical($element['#markup'], '<p>overridden</p>', '#markup is overridden.'); - $expected_js = [ - ['type' => 'setting', 'data' => ['foo' => 'bar']], - ['type' => 'setting', 'data' => ['common_test' => $context]], + $expected_js_settings = [ + 'foo' => 'bar', + 'common_test' => $context, ]; - $this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; both the original JavaScript setting and the one added by the #post_render_cache callback exist.'); + $this->assertIdentical($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the one added by the #post_render_cache callback exist.'); // POST request: Ensure no data was cached. $element = array('#cache' => array('cid' => 'post_render_cache_test_POST')); @@ -604,9 +604,9 @@ function testDrupalRenderChildrenPostRenderCache() { ), '#title' => 'Parent', '#attached' => array( - 'js' => array( - array('type' => 'setting', 'data' => array('foo' => 'bar')) - ), + 'drupalSettings' => [ + 'foo' => 'bar', + ], ), ); $test_element['child'] = array( @@ -628,22 +628,20 @@ function testDrupalRenderChildrenPostRenderCache() { $this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.'); $this->assertTrue(isset($element['#printed']), 'No cache hit'); $this->assertIdentical($element['#markup'], '<p>overridden</p>', '#markup is overridden.'); - $expected_js = [ - ['type' => 'setting', 'data' => ['foo' => 'bar']], - ['type' => 'setting', 'data' => ['common_test' => $context_1 ]], - ['type' => 'setting', 'data' => ['common_test' => $context_2 ]], - ['type' => 'setting', 'data' => ['common_test' => $context_3 ]], + $expected_js_settings = [ + 'foo' => 'bar', + 'common_test' => $context_1 + $context_2 + $context_3, ]; - $this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; both the original JavaScript setting and the ones added by each #post_render_cache callback exist.'); + $this->assertIdentical($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the ones added by each #post_render_cache callback exist.'); // GET request: validate cached data. $element = array('#cache' => $element['#cache']); $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data; $expected_element = array( '#attached' => array( - 'js' => array( - array('type' => 'setting', 'data' => array('foo' => 'bar')) - ), + 'drupalSettings' => [ + 'foo' => 'bar', + ], 'library' => array( 'core/drupal.collapse', 'core/drupal.collapse', @@ -675,7 +673,7 @@ function testDrupalRenderChildrenPostRenderCache() { $output = drupal_render_root($element); $this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.'); $this->assertFalse(isset($element['#printed']), 'Cache hit'); - $this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; both the original JavaScript setting and the ones added by each #post_render_cache callback exist.'); + $this->assertIdentical($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the ones added by each #post_render_cache callback exist.'); // Test case 2. // Use the exact same element, but now unset #cache. @@ -684,7 +682,7 @@ function testDrupalRenderChildrenPostRenderCache() { $output = drupal_render_root($element); $this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.'); $this->assertIdentical($element['#markup'], '<p>overridden</p>', '#markup is overridden.'); - $this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; both the original JavaScript setting and the ones added by each #post_render_cache callback exist.'); + $this->assertIdentical($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the ones added by each #post_render_cache callback exist.'); // Test case 3. // Create an element with a child and subchild. Each element has the same @@ -701,7 +699,7 @@ function testDrupalRenderChildrenPostRenderCache() { $this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.'); $this->assertTrue(isset($element['#printed']), 'No cache hit'); $this->assertIdentical($element['#markup'], '<p>overridden</p>', '#markup is overridden.'); - $this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; both the original JavaScript setting and the ones added by each #post_render_cache callback exist.'); + $this->assertIdentical($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the ones added by each #post_render_cache callback exist.'); // GET request: validate cached data for both the parent and child. $element = $test_element; @@ -711,9 +709,9 @@ function testDrupalRenderChildrenPostRenderCache() { $cached_child_element = \Drupal::cache('render')->get(drupal_render_cid_create($element['child']))->data; $expected_parent_element = array( '#attached' => array( - 'js' => array( - array('type' => 'setting', 'data' => array('foo' => 'bar')) - ), + 'drupalSettings' => [ + 'foo' => 'bar', + ], 'library' => array( 'core/drupal.collapse', 'core/drupal.collapse', @@ -771,7 +769,7 @@ function testDrupalRenderChildrenPostRenderCache() { $output = drupal_render_root($element); $this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.'); $this->assertFalse(isset($element['#printed']), 'Cache hit'); - $this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; both the original JavaScript setting and the ones added by each #post_render_cache callback exist.'); + $this->assertIdentical($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the ones added by each #post_render_cache callback exist.'); // GET request: #cache enabled, cache hit, child element. $element = $test_element; @@ -780,11 +778,10 @@ function testDrupalRenderChildrenPostRenderCache() { $output = drupal_render_root($element); $this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.'); $this->assertFalse(isset($element['#printed']), 'Cache hit'); - $expected_js = [ - ['type' => 'setting', 'data' => ['common_test' => $context_2 ]], - ['type' => 'setting', 'data' => ['common_test' => $context_3 ]], + $expected_js_settings = [ + 'common_test' => $context_2 + $context_3, ]; - $this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; both the original JavaScript setting and the ones added by each #post_render_cache callback exist.'); + $this->assertIdentical($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the ones added by each #post_render_cache callback exist.'); // Restore the previous request method. \Drupal::request()->setMethod($request_method); @@ -821,10 +818,10 @@ function testDrupalRenderRenderCachePlaceholder() { $element = $test_element; $output = drupal_render_root($element); $this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output'); - $expected_js = [ - ['type' => 'setting', 'data' => ['common_test' => $context]], + $expected_js_settings = [ + 'common_test' => $context, ]; - $this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; JavaScript setting is added to page.'); + $this->assertIdentical($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; JavaScript setting is added to page.'); // The cache system is turned off for POST requests. $request_method = \Drupal::request()->getMethod(); @@ -838,7 +835,7 @@ function testDrupalRenderRenderCachePlaceholder() { $this->assertTrue(isset($element['#printed']), 'No cache hit'); $this->assertIdentical($element['#markup'], $expected_output, 'Placeholder was replaced in #markup.'); $this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output'); - $this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; JavaScript setting is added to page.'); + $this->assertIdentical($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; JavaScript setting is added to page.'); // GET request: validate cached data. $expected_token = $context['token']; @@ -874,7 +871,7 @@ function testDrupalRenderRenderCachePlaceholder() { $this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output'); $this->assertFalse(isset($element['#printed']), 'Cache hit'); $this->assertIdentical($element['#markup'], $expected_output, 'Placeholder was replaced in #markup.'); - $this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; JavaScript setting is added to page.'); + $this->assertIdentical($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; JavaScript setting is added to page.'); // Restore the previous request method. \Drupal::request()->setMethod($request_method); @@ -913,10 +910,10 @@ function testDrupalRenderChildElementRenderCachePlaceholder() { $element = $test_element; $output = drupal_render_root($element); $this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output'); - $expected_js = [ - ['type' => 'setting', 'data' => ['common_test' => $context]], + $expected_js_settings = [ + 'common_test' => $context, ]; - $this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; JavaScript setting is added to page.'); + $this->assertIdentical($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; JavaScript setting is added to page.'); // The cache system is turned off for POST requests. $request_method = \Drupal::request()->getMethod(); @@ -931,7 +928,7 @@ function testDrupalRenderChildElementRenderCachePlaceholder() { $this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output'); $this->assertTrue(isset($element['#printed']), 'No cache hit'); $this->assertIdentical($element['#markup'], $expected_output, 'Placeholder was replaced in #markup.'); - $this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; JavaScript setting is added to page.'); + $this->assertIdentical($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; JavaScript setting is added to page.'); // GET request: validate cached data for child element. $expected_token = $context['token']; @@ -1023,7 +1020,7 @@ function testDrupalRenderChildElementRenderCachePlaceholder() { $this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output'); $this->assertFalse(isset($element['#printed']), 'Cache hit'); $this->assertIdentical($element['#markup'], $expected_output, 'Placeholder was replaced in #markup.'); - $this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; JavaScript setting is added to page.'); + $this->assertIdentical($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; JavaScript setting is added to page.'); // Restore the previous request method. \Drupal::request()->setMethod($request_method); @@ -1048,10 +1045,10 @@ function testRecursivePostRenderCache() { $output = drupal_render_root($element); $this->assertEqual('<p>overridden</p>', $output, 'The output has been modified by the indirect, recursive #post_render_cache callback.'); $this->assertIdentical($element['#markup'], '<p>overridden</p>', '#markup is overridden by the indirect, recursive #post_render_cache callback.'); - $expected_js = [ - ['type' => 'setting', 'data' => ['common_test' => $context]], + $expected_js_settings = [ + 'common_test' => $context, ]; - $this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified by the indirect, recursive #post_render_cache callback.'); + $this->assertIdentical($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified by the indirect, recursive #post_render_cache callback.'); } /** @@ -1073,12 +1070,7 @@ public static function bubblingPreRender($elements) { ), 'child_asset' => array( '#attached' => array( - 'js' => array( - array( - 'type' => 'setting', - 'data' => array('foo' => 'bar'), - ) - ), + 'drupalSettings' => array('foo' => 'bar'), ), '#markup' => 'Asset!', ), @@ -1145,12 +1137,7 @@ function testDrupalRenderBubbling() { $this->assertEqual('Cache tag!Asset!Post-render cache!barquxNested!Cached nested!', trim($output), 'Expected HTML generated.'); $this->assertEqual(array('child:cache_tag'), $test_element['#cache']['tags'], 'Expected cache tags found.'); $expected_attached = array( - 'js' => array( - 0 => array( - 'type' => 'setting', - 'data' => array('foo' => 'bar'), - ), - ), + 'drupalSettings' => array('foo' => 'bar'), ); $this->assertEqual($expected_attached, $test_element['#attached'], 'Expected assets found.'); $this->assertEqual([], $test_element['#post_render_cache'], '#post_render_cache property is empty after rendering'); diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 4eaf7bb13b2c..b9ca4c80823a 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -629,6 +629,49 @@ function system_page_attachments(array &$page) { } } +/** + * Implements hook_js_settings_alter(). + * + * Generates the values for the core/drupalSettings library. + */ +function system_js_settings_alter(&$settings) { + // url() generates the script and prefix using hook_url_outbound_alter(). + // Instead of running the hook_url_outbound_alter() again here, extract + // them from url(). + // @todo Make this less hacky: http://drupal.org/node/1547376. + $request = \Drupal::request(); + $scriptPath = $request->getScriptName(); + + $pathPrefix = ''; + $current_query = $request->query->all(); + _url('', array('script' => &$scriptPath, 'prefix' => &$pathPrefix)); + $current_path = \Drupal::routeMatch()->getRouteName() ? Url::fromRouteMatch(\Drupal::routeMatch())->getInternalPath() : ''; + $current_path_is_admin = \Drupal::service('router.admin_context')->isAdminRoute(); + $path_settings = [ + 'baseUrl' => $request->getBaseUrl() . '/', + 'scriptPath' => $scriptPath, + 'pathPrefix' => $pathPrefix, + 'currentPath' => $current_path, + 'currentPathIsAdmin' => $current_path_is_admin, + 'isFront' => drupal_is_front_page(), + 'currentLanguage' => \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_URL)->getId(), + ]; + if (!empty($current_query)) { + ksort($current_query); + $path_settings['currentQuery'] = (object) $current_query; + } + + // Only set core/drupalSettings values that haven't been set already. + foreach ($path_settings as $key => $value) { + if (!isset($settings['path'][$key])) { + $settings['path'][$key] = $value; + } + } + if (!isset($settings['locale']['pluralDelimiter'])) { + $settings['locale']['pluralDelimiter'] = LOCALE_PLURAL_DELIMITER; + } +} + /** * Implements hook_form_FORM_ID_alter(). */ diff --git a/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.module b/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.module index 2ecbe2a0cb88..2665c30d9953 100644 --- a/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.module +++ b/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.module @@ -162,32 +162,6 @@ function ajax_forms_test_advanced_commands_add_css_callback($form, FormStateInte return $response; } -/** - * Ajax callback for 'settings' but with setting overrides. - */ -function ajax_forms_test_advanced_commands_settings_with_merging_callback($form, FormStateInterface $form_state) { - $attached = array( - '#attached' => array( - 'js' => array( - 0 => array( - 'type' => 'setting', - 'data' => array('ajax_forms_test' => array('foo' => 42)), - ), - '1' => array( - 'type' => 'setting', - 'data' => array('ajax_forms_test' => array('foo' => 9001)), - ), - ), - ), - ); - // @todo Why is this being tested via an explicit drupal_render() call? - drupal_render($attached); - drupal_process_attached($attached); - - $response = new AjaxResponse(); - return $response; -} - /** * Ajax form callback: Selects the 'drivertext' element of the validation form. */ diff --git a/core/modules/system/tests/modules/ajax_forms_test/src/Form/AjaxFormsTestCommandsForm.php b/core/modules/system/tests/modules/ajax_forms_test/src/Form/AjaxFormsTestCommandsForm.php index 46525e72082e..f0a3811f6517 100644 --- a/core/modules/system/tests/modules/ajax_forms_test/src/Form/AjaxFormsTestCommandsForm.php +++ b/core/modules/system/tests/modules/ajax_forms_test/src/Form/AjaxFormsTestCommandsForm.php @@ -194,18 +194,6 @@ public function buildForm(array $form, FormStateInterface $form_state) { ), ); - // Tests the 'settings' command with a callback which sets the same - // setting multiple times. This is used to check that settings are - // merged properly (e.g., array_merge_recursive() merges settings - // incorrectly, #1356170). - $form['settings_command_with_merging_example'] = array( - '#type' => 'submit', - '#value' => $this->t("AJAX 'settings' command with setting merging"), - '#ajax' => array( - 'callback' => 'ajax_forms_test_advanced_commands_settings_with_merging_callback', - ), - ); - $form['submit'] = array( '#type' => 'submit', '#value' => $this->t('Submit'), diff --git a/core/modules/system/tests/modules/ajax_forms_test/src/Form/AjaxFormsTestLazyLoadForm.php b/core/modules/system/tests/modules/ajax_forms_test/src/Form/AjaxFormsTestLazyLoadForm.php index 32b476b32c98..41e8499bd59a 100644 --- a/core/modules/system/tests/modules/ajax_forms_test/src/Form/AjaxFormsTestLazyLoadForm.php +++ b/core/modules/system/tests/modules/ajax_forms_test/src/Form/AjaxFormsTestLazyLoadForm.php @@ -30,10 +30,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { // commands will be a settings command. We can then check the settings // command to ensure that the 'currentPath' setting is not part // of the Ajax response. - $form['#attached']['js'][] = array( - 'type' => 'setting', - 'data' => array('test' => 'currentPathUpdate'), - ); + $form['#attached']['drupalSettings']['test'] = 'currentPathUpdate'; $form['add_files'] = array( '#title' => $this->t('Add files'), '#type' => 'checkbox', @@ -63,11 +60,8 @@ public function submitForm(array &$form, FormStateInterface $form_state) { 'system/admin', 'system/drupal.system', ], - 'js' => [ - 0 => [ - 'type' => 'setting', - 'data' => ['ajax_forms_test_lazy_load_form_submit' => 'executed'], - ], + 'drupalSettings' => [ + 'ajax_forms_test_lazy_load_form_submit' => 'executed', ], ], ]; diff --git a/core/modules/system/tests/modules/ajax_test/ajax_test.module b/core/modules/system/tests/modules/ajax_test/ajax_test.module index 870a1577b749..1f19aacbbe8c 100644 --- a/core/modules/system/tests/modules/ajax_test/ajax_test.module +++ b/core/modules/system/tests/modules/ajax_test/ajax_test.module @@ -26,11 +26,8 @@ function ajax_test_render() { $attached = array( '#attached' => array( - 'js' => array( - 0 => array( - 'type' => 'setting', - 'data' => array('ajax' => 'test'), - ), + 'drupalSettings' => array( + 'ajax' => 'test', ), ), ); @@ -64,17 +61,14 @@ function ajax_test_order() { // Add two JavaScript files (first to the footer, should appear last). $path . '/system.modules.js' => array('scope' => 'footer'), $path . '/system.js' => array(), - // Finally, add a JavaScript setting. - 0 => array( - 'type' => 'setting', - 'data' => array('ajax' => 'test'), - ), + ), + // Finally, add a JavaScript setting. + 'drupalSettings' => array( + 'ajax' => 'test', ), ), ); - // @todo Why is this being tested via an explicit drupal_render() call? - drupal_render($attached); drupal_process_attached($attached); return $response; diff --git a/core/modules/system/tests/modules/common_test/common_test.module b/core/modules/system/tests/modules/common_test/common_test.module index 24aa3da889ad..8dbf98a236b3 100644 --- a/core/modules/system/tests/modules/common_test/common_test.module +++ b/core/modules/system/tests/modules/common_test/common_test.module @@ -208,12 +208,10 @@ function common_test_post_render_cache(array $element, array $context) { $element['#markup'] = '<p>overridden</p>'; // Extend #attached. - $element['#attached']['js'][] = array( - 'type' => 'setting', - 'data' => array( - 'common_test' => $context - ), - ); + if (!isset($element['#attached']['drupalSettings']['common_test'])) { + $element['#attached']['drupalSettings']['common_test'] = []; + } + $element['#attached']['drupalSettings']['common_test'] += $context; // Set new property. $element['#context_test'] = $context; @@ -238,14 +236,9 @@ function common_test_post_render_cache_placeholder(array $element, array $contex $replace_element = array( '#markup' => '<bar>' . $context['bar'] . '</bar>', '#attached' => array( - 'js' => array( - array( - 'type' => 'setting', - 'data' => array( - 'common_test' => $context, - ), - ), - ), + 'drupalSettings' => [ + 'common_test' => $context, + ], ), ); $markup = drupal_render($replace_element); @@ -325,3 +318,18 @@ function common_test_page_attachments_alter(array &$page) { ]; } } + +/** + * Implements hook_js_settings_alter(). + * + * @see \Drupal\system\Tests\Common\JavaScriptTest::testHeaderSetting() + */ +function common_test_js_settings_alter(&$settings) { + // Modify an existing setting. + if (array_key_exists('pluralDelimiter', $settings['locale'])) { + $settings['locale']['pluralDelimiter'] = '☃'; + } + + // Add a setting. + $settings['foo'] = 'bar'; +} diff --git a/core/modules/system/tests/modules/test_page_test/test_page_test.module b/core/modules/system/tests/modules/test_page_test/test_page_test.module index 93c10c5f4186..f2967cfaf4da 100644 --- a/core/modules/system/tests/modules/test_page_test/test_page_test.module +++ b/core/modules/system/tests/modules/test_page_test/test_page_test.module @@ -6,10 +6,7 @@ * @deprecated Use \Drupal\test_page_test\Controller\TestPageTestController::testPage() */ function test_page_test_page() { - $attached['js'][] = array( - 'data' => array('test-setting' => 'azAZ09();.,\\\/-_{}'), - 'type' => 'setting', - ); + $attached['drupalSettings']['test-setting'] = 'azAZ09();.,\\\/-_{}'; return array( '#title' => t('Test page'), '#markup' => t('Test page text.'), diff --git a/core/modules/system/theme.api.php b/core/modules/system/theme.api.php index 5eeeeea4d867..a335e3868e07 100644 --- a/core/modules/system/theme.api.php +++ b/core/modules/system/theme.api.php @@ -717,6 +717,27 @@ function hook_js_alter(&$javascript) { $javascript['core/assets/vendor/jquery/jquery.js']['data'] = drupal_get_path('module', 'jquery_update') . '/jquery.js'; } +/** + * Perform necessary alterations to the JavaScript settings (drupalSettings). + * + * @param array &$settings + * An array of all JavaScript settings (drupalSettings) being presented on the + * page. + * + * @see _drupal_add_js() + * @see drupal_get_js() + * @see drupal_js_defaults() + */ +function hook_js_settings_alter(array &$settings) { + // Add settings. + $settings['user']['uid'] = \Drupal::currentUser(); + + // Manipulate settings. + if (isset($settings['dialog'])) { + $settings['dialog']['autoResize'] = FALSE; + } +} + /** * Alters the JavaScript/CSS library registry. * @@ -785,14 +806,10 @@ function hook_library_alter(array &$library, $name) { $library['dependencies'][] = 'locale/drupal.locale.datepicker'; $language_interface = \Drupal::languageManager()->getCurrentLanguage(); - $settings['jquery']['ui']['datepicker'] = array( + $library['drupalSettings']['jquery']['ui']['datepicker'] = array( 'isRTL' => $language_interface->getDirection() == LanguageInterface::DIRECTION_RTL, 'firstDay' => \Drupal::config('system.date')->get('first_day'), ); - $library['js'][] = array( - 'type' => 'setting', - 'data' => $settings, - ); } } diff --git a/core/modules/taxonomy/src/Form/OverviewTerms.php b/core/modules/taxonomy/src/Form/OverviewTerms.php index 65ed68ac7db0..e4c55048b735 100644 --- a/core/modules/taxonomy/src/Form/OverviewTerms.php +++ b/core/modules/taxonomy/src/Form/OverviewTerms.php @@ -333,10 +333,10 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular 'hidden' => FALSE, ); $form['terms']['#attached']['library'][] = 'taxonomy/drupal.taxonomy'; - $form['terms']['#attached']['js'][] = array( - 'data' => array('taxonomy' => array('backStep' => $back_step, 'forwardStep' => $forward_step)), - 'type' => 'setting', - ); + $form['terms']['#attached']['drupalSettings']['taxonomy'] = [ + 'backStep' => $back_step, + 'forwardStep' => $forward_step, + ]; } $form['terms']['#tabledrag'][] = array( 'action' => 'order', diff --git a/core/modules/toolbar/src/Element/Toolbar.php b/core/modules/toolbar/src/Element/Toolbar.php index 660ce323f04a..0db11666e966 100644 --- a/core/modules/toolbar/src/Element/Toolbar.php +++ b/core/modules/toolbar/src/Element/Toolbar.php @@ -78,14 +78,7 @@ public static function preRenderToolbar($element) { $media_queries[$id] = $breakpoint->getMediaQuery(); } - $element['#attached']['js'][] = array( - 'data' => array( - 'toolbar' => array( - 'breakpoints' => $media_queries, - ) - ), - 'type' => 'setting', - ); + $element['#attached']['drupalSettings']['toolbar']['breakpoints'] = $media_queries; } $module_handler = static::moduleHandler(); diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module index f95a890f3b16..7b75e8940719 100644 --- a/core/modules/toolbar/toolbar.module +++ b/core/modules/toolbar/toolbar.module @@ -149,13 +149,10 @@ function toolbar_toolbar() { // script here with the hash parameter that is needed for that route. // @see toolbar_subtrees_jsonp() $langcode = \Drupal::languageManager()->getCurrentLanguage()->getId(); - $subtrees_attached['js'][] = array( - 'type' => 'setting', - 'data' => array('toolbar' => array( - 'subtreesHash' => _toolbar_get_subtrees_hash($langcode), - 'langcode' => $langcode, - )), - ); + $subtrees_attached['drupalSettings']['toolbar'] = [ + 'subtreesHash' => _toolbar_get_subtrees_hash($langcode), + 'langcode' => $langcode, + ]; // The administration element has a link that is themed to correspond to // a toolbar tray. The tray contains the full administrative menu of the site. diff --git a/core/modules/user/user.module b/core/modules/user/user.module index 31d7054bbc35..82845c6b6334 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -108,27 +108,17 @@ function user_theme() { } /** - * Implements hook_js_alter(). + * Implements hook_js_settings_alter(). */ -function user_js_alter(&$javascript) { - // If >=1 JavaScript asset has declared a dependency on drupalSettings, the - // 'settings' key will exist. Thus when that key does not exist, return early. - if (!isset($javascript['settings'])) { - return; - } - +function user_js_settings_alter(&$settings) { // Provide the user ID in drupalSettings to allow JavaScript code to customize // the experience for the end user, rather than the server side, which would // break the render cache. // Similarly, provide a permissions hash, so that permission-dependent data // can be reliably cached on the client side. $user = \Drupal::currentUser(); - $javascript['settings']['data'][] = array( - 'user' => array( - 'uid' => $user->id(), - 'permissionsHash' => \Drupal::service('user.permissions_hash')->generate($user), - ), - ); + $settings['user']['uid'] = $user->id(); + $settings['user']['permissionsHash'] = \Drupal::service('user.permissions_hash')->generate($user); } /** @@ -1363,17 +1353,8 @@ function user_form_process_password_confirm($element) { ); } - $js_settings = array( - 'password' => $password_settings, - ); - $element['#attached']['library'][] = 'user/drupal.user'; - // Ensure settings are only added once per page. - static $already_added = FALSE; - if (!$already_added) { - $already_added = TRUE; - $element['#attached']['js'][] = array('data' => $js_settings, 'type' => 'setting'); - } + $element['#attached']['drupalSettings']['password'] = $password_settings; return $element; } diff --git a/core/modules/views/tests/modules/views_test_data/views_test_data.views_execution.inc b/core/modules/views/tests/modules/views_test_data/views_test_data.views_execution.inc index a72201eeb8be..8f0df189193e 100644 --- a/core/modules/views/tests/modules/views_test_data/views_test_data.views_execution.inc +++ b/core/modules/views/tests/modules/views_test_data/views_test_data.views_execution.inc @@ -49,6 +49,7 @@ function views_test_data_views_pre_render(ViewExecutable $view) { if (isset($view) && ($view->storage->id() == 'test_cache_header_storage')) { $path = drupal_get_path('module', 'views_test_data'); $view->element['#attached']['library'][] = 'views_test_data/test'; + $view->element['#attached']['drupalSettings']['foo'] = 'bar'; $view->element['#attached']['js'][] = "$path/views_cache.test.js"; $view->element['#attached']['css'][] = "$path/views_cache.test.css"; $view->element['#cache']['tags'][] = 'views_test_data:1'; diff --git a/core/modules/views/views.module b/core/modules/views/views.module index 1afd0e18245c..2efa2d92c864 100644 --- a/core/modules/views/views.module +++ b/core/modules/views/views.module @@ -54,7 +54,7 @@ function views_help($route_name, RouteMatchInterface $route_match) { function views_views_pre_render($view) { // If using AJAX, send identifying data about this view. if ($view->ajaxEnabled() && empty($view->is_attachment) && empty($view->live_preview)) { - $settings = array( + $view->element['#attached']['drupalSettings']['views'] = array( 'views' => array( 'ajax_path' => \Drupal::url('views.ajax'), 'ajaxViews' => array( @@ -72,7 +72,6 @@ function views_views_pre_render($view) { ), ), ); - $view->element['#attached']['js'][] = array('type' => 'setting', 'data' => $settings); $view->element['#attached']['library'][] = 'views/views.ajax'; } diff --git a/core/modules/views_ui/src/ViewEditForm.php b/core/modules/views_ui/src/ViewEditForm.php index 6ec6aac2d1b2..780058039657 100644 --- a/core/modules/views_ui/src/ViewEditForm.php +++ b/core/modules/views_ui/src/ViewEditForm.php @@ -109,15 +109,12 @@ public function form(array $form, FormStateInterface $form_state) { $form['#attached']['library'][] = 'views_ui/views_ui.admin'; $form['#attached']['library'][] = 'views_ui/admin.styling'; - $form['#attached']['js'][] = array( - 'data' => array('views' => array('ajax' => array( - 'id' => '#views-ajax-body', - 'title' => '#views-ajax-title', - 'popup' => '#views-ajax-popup', - 'defaultForm' => $view->getDefaultAJAXMessage(), - ))), - 'type' => 'setting', - ); + $form['#attached']['drupalSettings']['views']['ajax'] = [ + 'id' => '#views-ajax-body', + 'title' => '#views-ajax-title', + 'popup' => '#views-ajax-popup', + 'defaultForm' => $view->getDefaultAJAXMessage(), + ]; $form += array( '#prefix' => '', diff --git a/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryParserTest.php b/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryParserTest.php index 076b2cf994a9..141d43274fce 100644 --- a/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryParserTest.php +++ b/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryParserTest.php @@ -285,8 +285,7 @@ public function testLibraryWithCssJsSetting() { $this->assertEquals('file', $library['css'][0]['type']); $this->assertEquals($path . '/css/base.css', $library['css'][0]['data']); - $this->assertEquals('setting', $library['js'][1]['type']); - $this->assertEquals(array('key' => 'value'), $library['js'][1]['data']); + $this->assertEquals(array('key' => 'value'), $library['drupalSettings']); } /** diff --git a/core/tests/Drupal/Tests/Core/Asset/library_test_files/css_js_settings.libraries.yml b/core/tests/Drupal/Tests/Core/Asset/library_test_files/css_js_settings.libraries.yml index fdb479ff1e29..4b3970482d8f 100644 --- a/core/tests/Drupal/Tests/Core/Asset/library_test_files/css_js_settings.libraries.yml +++ b/core/tests/Drupal/Tests/Core/Asset/library_test_files/css_js_settings.libraries.yml @@ -4,5 +4,5 @@ example: css/base.css: {} js: js/example.js: {} - settings: + drupalSettings: key: value diff --git a/core/themes/bartik/color/color.inc b/core/themes/bartik/color/color.inc index 25e630d89e53..b2c854c7d9c2 100644 --- a/core/themes/bartik/color/color.inc +++ b/core/themes/bartik/color/color.inc @@ -6,8 +6,7 @@ */ // Put the logo path into JavaScript for the live preview. -$js = array('color' => array('logo' => theme_get_setting('logo.url', 'bartik'))); -$js_attached['#attached']['js'][] = array('data' => $js, 'type' => 'setting'); +$js_attached['#attached']['drupalSettings']['color']['logo'] = theme_get_setting('logo.url', 'bartik'); drupal_render($js_attached); $info = array( -- GitLab