/** * Verifies that the datepicker can be localized. * * @see locale_library_alter() */ public function testLibraryAlter() { $assets = new AttachedAssets(); $assets->setLibraries(['core/jquery.ui.datepicker']); $js_assets = $this->container->get('asset.resolver')->getJsAssets($assets, FALSE)[1]; $this->assertTrue(array_key_exists('core/modules/locale/locale.datepicker.js', $js_assets), 'locale.datepicker.js added to scripts.'); }
/** * Prepares the AJAX commands for sending back to the client. * * @param Request $request * The request object that the AJAX is responding to. * * @return array * An array of commands ready to be returned as JSON. */ protected function ajaxRender(Request $request) { $ajax_page_state = $request->request->get('ajax_page_state'); // Aggregate CSS/JS if necessary, but only during normal site operation. $config = \Drupal::config('system.performance'); $optimize_css = !defined('MAINTENANCE_MODE') && $config->get('css.preprocess'); $optimize_js = !defined('MAINTENANCE_MODE') && $config->get('js.preprocess'); // Resolve the attached libraries into asset collections. $assets = new AttachedAssets(); $assets->setLibraries(isset($this->attachments['library']) ? $this->attachments['library'] : [])->setAlreadyLoadedLibraries(isset($ajax_page_state) ? explode(',', $ajax_page_state['libraries']) : [])->setSettings(isset($this->attachments['drupalSettings']) ? $this->attachments['drupalSettings'] : []); $asset_resolver = \Drupal::service('asset.resolver'); $css_assets = $asset_resolver->getCssAssets($assets, $optimize_css); list($js_assets_header, $js_assets_footer) = $asset_resolver->getJsAssets($assets, $optimize_js); // Render the HTML to load these files, and add AJAX commands to insert this // HTML in the page. Settings are handled separately, afterwards. $settings = []; if (isset($js_assets_header['drupalSettings'])) { $settings = $js_assets_header['drupalSettings']['data']; unset($js_assets_header['drupalSettings']); } if (isset($js_assets_footer['drupalSettings'])) { $settings = $js_assets_footer['drupalSettings']['data']; unset($js_assets_footer['drupalSettings']); } // Prepend commands to add the assets, preserving their relative order. $resource_commands = array(); $renderer = $this->getRenderer(); if (!empty($css_assets)) { $css_render_array = \Drupal::service('asset.css.collection_renderer')->render($css_assets); $resource_commands[] = new AddCssCommand($renderer->render($css_render_array)); } if (!empty($js_assets_header)) { $js_header_render_array = \Drupal::service('asset.js.collection_renderer')->render($js_assets_header); $resource_commands[] = new PrependCommand('head', $renderer->render($js_header_render_array)); } if (!empty($js_assets_footer)) { $js_footer_render_array = \Drupal::service('asset.js.collection_renderer')->render($js_assets_footer); $resource_commands[] = new AppendCommand('body', $renderer->render($js_footer_render_array)); } foreach (array_reverse($resource_commands) as $resource_command) { $this->addCommand($resource_command, TRUE); } // Prepend a command to merge changes and additions to drupalSettings. if (!empty($settings)) { // 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. An Ajax request would update them // with values for the Ajax request and incorrectly override the page's // values. // @see system_js_settings_alter() unset($settings['path']); $this->addCommand(new SettingsCommand($settings, TRUE), TRUE); } $commands = $this->commands; \Drupal::moduleHandler()->alter('ajax_render', $commands); return $commands; }
/** * Tests that new JavaScript and CSS files are lazy-loaded on an AJAX request. */ public function testLazyLoad() { $asset_resolver = \Drupal::service('asset.resolver'); $css_collection_renderer = \Drupal::service('asset.css.collection_renderer'); $js_collection_renderer = \Drupal::service('asset.js.collection_renderer'); $renderer = \Drupal::service('renderer'); $expected = array('setting_name' => 'ajax_forms_test_lazy_load_form_submit', 'setting_value' => 'executed', 'library_1' => 'system/admin', 'library_2' => 'system/drupal.system'); // Get the base page. $this->drupalGet('ajax_forms_test_lazy_load_form'); $original_settings = $this->getDrupalSettings(); $original_libraries = explode(',', $original_settings['ajaxPageState']['libraries']); // Verify that the base page doesn't have the settings and files that are to // be lazy loaded as part of the next requests. $this->assertTrue(!isset($original_settings[$expected['setting_name']]), format_string('Page originally lacks the %setting, as expected.', array('%setting' => $expected['setting_name']))); $this->assertTrue(!in_array($expected['library_1'], $original_libraries), format_string('Page originally lacks the %library library, as expected.', array('%library' => $expected['library_1']))); $this->assertTrue(!in_array($expected['library_2'], $original_libraries), format_string('Page originally lacks the %library library, as expected.', array('%library' => $expected['library_2']))); // Calculate the expected CSS and JS. $assets = new AttachedAssets(); $assets->setLibraries([$expected['library_1']])->setAlreadyLoadedLibraries($original_libraries); $css_render_array = $css_collection_renderer->render($asset_resolver->getCssAssets($assets, FALSE)); $expected_css_html = $renderer->render($css_render_array); $assets->setLibraries([$expected['library_2']])->setAlreadyLoadedLibraries($original_libraries); $js_assets = $asset_resolver->getJsAssets($assets, FALSE)[1]; unset($js_assets['drupalSettings']); $js_render_array = $js_collection_renderer->render($js_assets); $expected_js_html = $renderer->render($js_render_array); // Submit the AJAX request without triggering files getting added. $commands = $this->drupalPostAjaxForm(NULL, array('add_files' => FALSE), array('op' => t('Submit'))); $new_settings = $this->getDrupalSettings(); $new_libraries = explode(',', $new_settings['ajaxPageState']['libraries']); // Verify the setting was not added when not expected. $this->assertTrue(!isset($new_settings[$expected['setting_name']]), format_string('Page still lacks the %setting, as expected.', array('%setting' => $expected['setting_name']))); $this->assertTrue(!in_array($expected['library_1'], $new_libraries), format_string('Page still lacks the %library library, as expected.', array('%library' => $expected['library_1']))); $this->assertTrue(!in_array($expected['library_2'], $new_libraries), format_string('Page still lacks the %library library, as expected.', array('%library' => $expected['library_2']))); // Verify a settings command does not add CSS or scripts to drupalSettings // and no command inserts the corresponding tags on the page. $found_settings_command = FALSE; $found_markup_command = FALSE; foreach ($commands as $command) { if ($command['command'] == 'settings' && (array_key_exists('css', $command['settings']['ajaxPageState']) || array_key_exists('js', $command['settings']['ajaxPageState']))) { $found_settings_command = TRUE; } if (isset($command['data']) && ($command['data'] == $expected_js_html || $command['data'] == $expected_css_html)) { $found_markup_command = TRUE; } } $this->assertFalse($found_settings_command, format_string('Page state still lacks the %library_1 and %library_2 libraries, as expected.', array('%library_1' => $expected['library_1'], '%library_2' => $expected['library_2']))); $this->assertFalse($found_markup_command, format_string('Page still lacks the %library_1 and %library_2 libraries, as expected.', array('%library_1' => $expected['library_1'], '%library_2' => $expected['library_2']))); // Submit the AJAX request and trigger adding files. $commands = $this->drupalPostAjaxForm(NULL, array('add_files' => TRUE), array('op' => t('Submit'))); $new_settings = $this->getDrupalSettings(); $new_libraries = explode(',', $new_settings['ajaxPageState']['libraries']); // Verify the expected setting was added, both to drupalSettings, and as // the first AJAX command. $this->assertIdentical($new_settings[$expected['setting_name']], $expected['setting_value'], format_string('Page now has the %setting.', array('%setting' => $expected['setting_name']))); $expected_command = new SettingsCommand(array($expected['setting_name'] => $expected['setting_value']), TRUE); $this->assertCommand(array_slice($commands, 0, 1), $expected_command->render(), format_string('The settings command was first.')); // Verify the expected CSS file was added, both to drupalSettings, and as // the second AJAX command for inclusion into the HTML. $this->assertTrue(in_array($expected['library_1'], $new_libraries), format_string('Page state now has the %library library.', array('%library' => $expected['library_1']))); $this->assertCommand(array_slice($commands, 1, 1), array('data' => $expected_css_html), format_string('Page now has the %library library.', array('%library' => $expected['library_1']))); // Verify the expected JS file was added, both to drupalSettings, and as // the third AJAX command for inclusion into the HTML. By testing for an // exact HTML string containing the SCRIPT tag, we also ensure that // unexpected JavaScript code, such as a jQuery.extend() that would // potentially clobber rather than properly merge settings, didn't // accidentally get added. $this->assertTrue(in_array($expected['library_2'], $new_libraries), format_string('Page state now has the %library library.', array('%library' => $expected['library_2']))); $this->assertCommand(array_slice($commands, 2, 1), array('data' => $expected_js_html), format_string('Page now has the %library library.', array('%library' => $expected['library_2']))); }
/** * Prepares the AJAX commands to attach assets. * * @param \Drupal\Core\Ajax\AjaxResponse $response * The AJAX response to update. * @param \Symfony\Component\HttpFoundation\Request $request * The request object that the AJAX is responding to. * * @return array * An array of commands ready to be returned as JSON. */ protected function buildAttachmentsCommands(AjaxResponse $response, Request $request) { $ajax_page_state = $request->request->get('ajax_page_state'); // Aggregate CSS/JS if necessary, but only during normal site operation. $optimize_css = !defined('MAINTENANCE_MODE') && $this->config->get('css.preprocess'); $optimize_js = !defined('MAINTENANCE_MODE') && $this->config->get('js.preprocess'); $attachments = $response->getAttachments(); // Resolve the attached libraries into asset collections. $assets = new AttachedAssets(); $assets->setLibraries(isset($attachments['library']) ? $attachments['library'] : [])->setAlreadyLoadedLibraries(isset($ajax_page_state['libraries']) ? explode(',', $ajax_page_state['libraries']) : [])->setSettings(isset($attachments['drupalSettings']) ? $attachments['drupalSettings'] : []); $css_assets = $this->assetResolver->getCssAssets($assets, $optimize_css); list($js_assets_header, $js_assets_footer) = $this->assetResolver->getJsAssets($assets, $optimize_js); // First, AttachedAssets::setLibraries() ensures duplicate libraries are // removed: it converts it to a set of libraries if necessary. Second, // AssetResolver::getJsSettings() ensures $assets contains the final set of // JavaScript settings. AttachmentsResponseProcessorInterface also mandates // that the response it processes contains the final attachment values, so // update both the 'library' and 'drupalSettings' attachments accordingly. $attachments['library'] = $assets->getLibraries(); $attachments['drupalSettings'] = $assets->getSettings(); $response->setAttachments($attachments); // Render the HTML to load these files, and add AJAX commands to insert this // HTML in the page. Settings are handled separately, afterwards. $settings = []; if (isset($js_assets_header['drupalSettings'])) { $settings = $js_assets_header['drupalSettings']['data']; unset($js_assets_header['drupalSettings']); } if (isset($js_assets_footer['drupalSettings'])) { $settings = $js_assets_footer['drupalSettings']['data']; unset($js_assets_footer['drupalSettings']); } // Prepend commands to add the assets, preserving their relative order. $resource_commands = array(); if ($css_assets) { $css_render_array = $this->cssCollectionRenderer->render($css_assets); $resource_commands[] = new AddCssCommand($this->renderer->renderPlain($css_render_array)); } if ($js_assets_header) { $js_header_render_array = $this->jsCollectionRenderer->render($js_assets_header); $resource_commands[] = new PrependCommand('head', $this->renderer->renderPlain($js_header_render_array)); } if ($js_assets_footer) { $js_footer_render_array = $this->jsCollectionRenderer->render($js_assets_footer); $resource_commands[] = new AppendCommand('body', $this->renderer->renderPlain($js_footer_render_array)); } foreach (array_reverse($resource_commands) as $resource_command) { $response->addCommand($resource_command, TRUE); } // Prepend a command to merge changes and additions to drupalSettings. if (!empty($settings)) { // 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. An Ajax request would update them // with values for the Ajax request and incorrectly override the page's // values. // @see system_js_settings_alter() unset($settings['path']); $response->addCommand(new SettingsCommand($settings, TRUE), TRUE); } $commands = $response->getCommands(); $this->moduleHandler->alter('ajax_render', $commands); return $commands; }