/** * {@inheritdoc} */ public function renderResponse(array $main_content, Request $request, RouteMatchInterface $route_match) { $response = new AjaxResponse(); if (isset($main_content['#type']) && $main_content['#type'] == 'ajax') { // Complex Ajax callbacks can return a result that contains an error // message or a specific set of commands to send to the browser. $main_content += $this->elementInfoManager->getInfo('ajax'); $error = $main_content['#error']; if (!empty($error)) { // Fall back to some default message otherwise use the specific one. if (!is_string($error)) { $error = 'An error occurred while handling the request: The server received invalid input.'; } $response->addCommand(new AlertCommand($error)); } } $html = $this->drupalRenderRoot($main_content); $response->setAttachments($main_content['#attached']); // The selector for the insert command is NULL as the new content will // replace the element making the Ajax call. The default 'replaceWith' // behavior can be changed with #ajax['method']. $response->addCommand(new InsertCommand(NULL, $html)); $status_messages = array('#type' => 'status_messages'); $output = $this->drupalRenderRoot($status_messages); if (!empty($output)) { $response->addCommand(new PrependCommand(NULL, $output)); } return $response; }
/** * Returns an AjaxResponse; settings command set last. * * Helps verifying AjaxResponse reorders commands to ensure correct execution. */ public function order() { $response = new AjaxResponse(); // HTML insertion command. $response->addCommand(new HtmlCommand('body', 'Hello, world!')); $build['#attached']['library'][] = 'ajax_test/order'; $response->setAttachments($build['#attached']); return $response; }
function er_browser_widget_search_content(array &$form, FormStateInterface $form_state) { $form = \Drupal::formBuilder()->getForm('Drupal\\er_browser_widget\\Form\\EntityReferenceBrowserWidgetForm'); $response = new AjaxResponse(); $title = $this->t('Entity Search and Reference.'); $form['#attached']['library'][] = 'core/drupal.dialog.ajax'; $response->setAttachments($form['#attached']); $content = views_embed_view('entity_reference_browser_widget'); $options = array('dialogClass' => 'test-dialog', 'width' => '75%'); $modal = new OpenModalDialogCommand($title, $form, $options); $response->addCommand($modal); return $response; }
/** * Processes AJAX file uploads and deletions. * * @param \Symfony\Component\HttpFoundation\Request $request * The current request object. * * @return \Drupal\Core\Ajax\AjaxResponse * An AjaxResponse object. */ public function upload(Request $request) { $form_parents = explode('/', $request->query->get('element_parents')); $form_build_id = $request->query->get('form_build_id'); $request_form_build_id = $request->request->get('form_build_id'); if (empty($request_form_build_id) || $form_build_id !== $request_form_build_id) { // Invalid request. drupal_set_message(t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@size) that this server supports.', array('@size' => format_size(file_upload_max_size()))), 'error'); $response = new AjaxResponse(); $status_messages = array('#type' => 'status_messages'); return $response->addCommand(new ReplaceCommand(NULL, $this->renderer->renderRoot($status_messages))); } try { /** @var $ajaxForm \Drupal\system\FileAjaxForm */ $ajaxForm = $this->getForm($request); $form = $ajaxForm->getForm(); $form_state = $ajaxForm->getFormState(); $commands = $ajaxForm->getCommands(); } catch (HttpExceptionInterface $e) { // Invalid form_build_id. drupal_set_message(t('An unrecoverable error occurred. Use of this form has expired. Try reloading the page and submitting again.'), 'error'); $response = new AjaxResponse(); $status_messages = array('#type' => 'status_messages'); return $response->addCommand(new ReplaceCommand(NULL, $this->renderer->renderRoot($status_messages))); } // Get the current element and count the number of files. $current_element = NestedArray::getValue($form, $form_parents); $current_file_count = isset($current_element['#file_upload_delta']) ? $current_element['#file_upload_delta'] : 0; // Process user input. $form and $form_state are modified in the process. $this->formBuilder->processForm($form['#form_id'], $form, $form_state); // Retrieve the element to be rendered. $form = NestedArray::getValue($form, $form_parents); // Add the special Ajax class if a new file was added. if (isset($form['#file_upload_delta']) && $current_file_count < $form['#file_upload_delta']) { $form[$current_file_count]['#attributes']['class'][] = 'ajax-new-content'; } else { $form['#suffix'] .= '<span class="ajax-new-content"></span>'; } $status_messages = array('#type' => 'status_messages'); $form['#prefix'] .= $this->renderer->renderRoot($status_messages); $output = $this->renderer->renderRoot($form); $response = new AjaxResponse(); $response->setAttachments($form['#attached']); foreach ($commands as $command) { $response->addCommand($command, TRUE); } return $response->addCommand(new ReplaceCommand(NULL, $output)); }
/** * {@inheritdoc} */ public function renderResponse(array $main_content, Request $request, RouteMatchInterface $route_match) { $response = new AjaxResponse(); // First render the main content, because it might provide a title. $content = $this->renderer->renderRoot($main_content); // Attach the library necessary for using the OpenOffCanvasDialogCommand and // set the attachments for this Ajax response. $main_content['#attached']['library'][] = 'outside_in/drupal.off_canvas'; $response->setAttachments($main_content['#attached']); // If the main content doesn't provide a title, use the title resolver. $title = isset($main_content['#title']) ? $main_content['#title'] : $this->titleResolver->getTitle($request, $route_match->getRouteObject()); // Determine the title: use the title provided by the main content if any, // otherwise get it from the routing information. $options = $request->request->get('dialogOptions', []); $response->addCommand(new OpenOffCanvasDialogCommand($title, $content, $options)); return $response; }
/** * {@inheritdoc} */ public function renderResponse(array $main_content, Request $request, RouteMatchInterface $route_match) { $response = new AjaxResponse(); // First render the main content, because it might provide a title. $content = drupal_render_root($main_content); // Attach the library necessary for using the OpenDialogCommand and set the // attachments for this Ajax response. $main_content['#attached']['library'][] = 'core/drupal.dialog.ajax'; $response->setAttachments($main_content['#attached']); // Determine the title: use the title provided by the main content if any, // otherwise get it from the routing information. $title = isset($main_content['#title']) ? $main_content['#title'] : $this->titleResolver->getTitle($request, $route_match->getRouteObject()); // Determine the dialog options and the target for the OpenDialogCommand. $options = $request->request->get('dialogOptions', array()); $target = $this->determineTargetSelector($options, $route_match); $response->addCommand(new OpenDialogCommand($target, $title, $content, $options)); return $response; }
/** * Wrapper for handling AJAX forms. * * Wrapper around \Drupal\Core\Form\FormBuilderInterface::buildForm() to * handle some AJAX stuff automatically. * This makes some assumptions about the client. * * @param \Drupal\Core\Form\FormInterface|string $form_class * The value must be one of the following: * - The name of a class that implements \Drupal\Core\Form\FormInterface. * - An instance of a class that implements \Drupal\Core\Form\FormInterface. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. * * @return \Drupal\Core\Ajax\AjaxResponse|string|array * Returns one of three possible values: * - A \Drupal\Core\Ajax\AjaxResponse object. * - The rendered form, as a string. * - A render array with the title in #title and the rendered form in the * #markup array. */ protected function ajaxFormWrapper($form_class, FormStateInterface &$form_state) { /** @var \Drupal\Core\Render\RendererInterface $renderer */ $renderer = \Drupal::service('renderer'); // This won't override settings already in. if (!$form_state->has('rerender')) { $form_state->set('rerender', FALSE); } $ajax = $form_state->get('ajax'); // Do not overwrite if the redirect has been disabled. if (!$form_state->isRedirectDisabled()) { $form_state->disableRedirect($ajax); } $form_state->disableCache(); // Builds the form in a render context in order to ensure that cacheable // metadata is bubbled up. $render_context = new RenderContext(); $callable = function () use($form_class, &$form_state) { return \Drupal::formBuilder()->buildForm($form_class, $form_state); }; $form = $renderer->executeInRenderContext($render_context, $callable); if (!$render_context->isEmpty()) { BubbleableMetadata::createFromRenderArray($form)->merge($render_context->pop())->applyTo($form); } $output = $renderer->renderRoot($form); drupal_process_attached($form); // These forms have the title built in, so set the title here: $title = $form_state->get('title') ?: ''; if ($ajax && (!$form_state->isExecuted() || $form_state->get('rerender'))) { // If the form didn't execute and we're using ajax, build up an // Ajax command list to execute. $response = new AjaxResponse(); // Attach the library necessary for using the OpenModalDialogCommand and // set the attachments for this Ajax response. $form['#attached']['library'][] = 'core/drupal.dialog.ajax'; $response->setAttachments($form['#attached']); $display = ''; $status_messages = array('#type' => 'status_messages'); if ($messages = $renderer->renderRoot($status_messages)) { $display = '<div class="views-messages">' . $messages . '</div>'; } $display .= $output; $options = array('dialogClass' => 'views-ui-dialog', 'width' => '75%'); $response->addCommand(new OpenModalDialogCommand($title, $display, $options)); if ($section = $form_state->get('#section')) { $response->addCommand(new Ajax\HighlightCommand('.' . Html::cleanCssIdentifier($section))); } return $response; } return $title ? ['#title' => $title, '#markup' => $output] : $output; }
/** * Sends BigPipe placeholders' replacements as embedded AJAX responses. * * @param array $placeholders * Associative array; the BigPipe placeholders. Keys are the BigPipe * placeholder IDs. * @param array $placeholder_order * Indexed array; the order in which the BigPipe placeholders must be sent. * Values are the BigPipe placeholder IDs. (These values correspond to keys * in $placeholders.) * @param \Drupal\Core\Asset\AttachedAssetsInterface $cumulative_assets * The cumulative assets sent so far; to be updated while rendering BigPipe * placeholders. * * @throws \Exception * If an exception is thrown during the rendering of a placeholder, it is * caught to allow the other placeholders to still be replaced. But when * error logging is configured to be verbose, the exception is rethrown to * simplify debugging. */ protected function sendPlaceholders(array $placeholders, array $placeholder_order, AttachedAssetsInterface $cumulative_assets) { // Return early if there are no BigPipe placeholders to send. if (empty($placeholders)) { return; } // Send the start signal. print "\n"; print static::START_SIGNAL; print "\n"; flush(); // A BigPipe response consists of a HTML response plus multiple embedded // AJAX responses. To process the attachments of those AJAX responses, we // need a fake request that is identical to the master request, but with // one change: it must have the right Accept header, otherwise the work- // around for a bug in IE9 will cause not JSON, but <textarea>-wrapped JSON // to be returned. // @see \Drupal\Core\EventSubscriber\AjaxResponseSubscriber::onResponse() $fake_request = $this->requestStack->getMasterRequest()->duplicate(); $fake_request->headers->set('Accept', 'application/vnd.drupal-ajax'); foreach ($placeholder_order as $placeholder_id) { if (!isset($placeholders[$placeholder_id])) { continue; } // Render the placeholder. $placeholder_render_array = $placeholders[$placeholder_id]; try { $elements = $this->renderPlaceholder($placeholder_id, $placeholder_render_array); } catch (\Exception $e) { if (\Drupal::config('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) { throw $e; } else { trigger_error($e, E_USER_ERROR); continue; } } // Create a new AjaxResponse. $ajax_response = new AjaxResponse(); // JavaScript's querySelector automatically decodes HTML entities in // attributes, so we must decode the entities of the current BigPipe // placeholder ID (which has HTML entities encoded since we use it to find // the placeholders). $big_pipe_js_placeholder_id = Html::decodeEntities($placeholder_id); $ajax_response->addCommand(new ReplaceCommand(sprintf('[data-big-pipe-placeholder-id="%s"]', $big_pipe_js_placeholder_id), $elements['#markup'])); $ajax_response->setAttachments($elements['#attached']); // Push a fake request with the asset libraries loaded so far and dispatch // KernelEvents::RESPONSE event. This results in the attachments for the // AJAX response being processed by AjaxResponseAttachmentsProcessor and // hence: // - the necessary AJAX commands to load the necessary missing asset // libraries and updated AJAX page state are added to the AJAX response // - the attachments associated with the response are finalized, which // allows us to track the total set of asset libraries sent in the // initial HTML response plus all embedded AJAX responses sent so far. $fake_request->request->set('ajax_page_state', ['libraries' => implode(',', $cumulative_assets->getAlreadyLoadedLibraries())] + $cumulative_assets->getSettings()['ajaxPageState']); try { $ajax_response = $this->filterEmbeddedResponse($fake_request, $ajax_response); } catch (\Exception $e) { if (\Drupal::config('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) { throw $e; } else { trigger_error($e, E_USER_ERROR); continue; } } // Send this embedded AJAX response. $json = $ajax_response->getContent(); $output = <<<EOF <script type="application/vnd.drupal-ajax" data-big-pipe-replacement-for-placeholder-with-id="{$placeholder_id}"> {$json} </script> EOF; print $output; flush(); // Another placeholder was rendered and sent, track the set of asset // libraries sent so far. Any new settings are already sent; we don't need // to track those. if (isset($ajax_response->getAttachments()['drupalSettings']['ajaxPageState']['libraries'])) { $cumulative_assets->setAlreadyLoadedLibraries(explode(',', $ajax_response->getAttachments()['drupalSettings']['ajaxPageState']['libraries'])); } } // Send the stop signal. print "\n"; print static::STOP_SIGNAL; print "\n"; flush(); }
/** * Returns a single field edit form as an Ajax response. * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity being edited. * @param string $field_name * The name of the field that is being edited. * @param string $langcode * The name of the language for which the field is being edited. * @param string $view_mode_id * The view mode the field should be rerendered in. * @param \Symfony\Component\HttpFoundation\Request $request * The current request object containing the search string. * * @return \Drupal\Core\Ajax\AjaxResponse * The Ajax response. */ public function fieldForm(EntityInterface $entity, $field_name, $langcode, $view_mode_id, Request $request) { $response = new AjaxResponse(); // Replace entity with PrivateTempStore copy if available and not resetting, // init PrivateTempStore copy otherwise. $tempstore_entity = $this->tempStoreFactory->get('quickedit')->get($entity->uuid()); if ($tempstore_entity && $request->request->get('reset') !== 'true') { $entity = $tempstore_entity; } else { $this->tempStoreFactory->get('quickedit')->set($entity->uuid(), $entity); } $form_state = (new FormState())->set('langcode', $langcode)->disableRedirect()->addBuildInfo('args', [$entity, $field_name]); $form = $this->formBuilder()->buildForm('Drupal\\quickedit\\Form\\QuickEditFieldForm', $form_state); if ($form_state->isExecuted()) { // The form submission saved the entity in PrivateTempStore. Return the // updated view of the field from the PrivateTempStore copy. $entity = $this->tempStoreFactory->get('quickedit')->get($entity->uuid()); // Closure to render the field given a view mode. $render_field_in_view_mode = function ($view_mode_id) use($entity, $field_name, $langcode) { return $this->renderField($entity, $field_name, $langcode, $view_mode_id); }; // Re-render the updated field. $output = $render_field_in_view_mode($view_mode_id); // Re-render the updated field for other view modes (i.e. for other // instances of the same logical field on the user's page). $other_view_mode_ids = $request->request->get('other_view_modes') ?: array(); $other_view_modes = array_map($render_field_in_view_mode, array_combine($other_view_mode_ids, $other_view_mode_ids)); $response->addCommand(new FieldFormSavedCommand($output, $other_view_modes)); } else { $output = $this->renderer->renderRoot($form); // When working with a hidden form, we don't want its CSS/JS to be loaded. if ($request->request->get('nocssjs') !== 'true') { $response->setAttachments($form['#attached']); } $response->addCommand(new FieldFormCommand($output)); $errors = $form_state->getErrors(); if (count($errors)) { $status_messages = array('#type' => 'status_messages'); $response->addCommand(new FieldFormValidationErrorsCommand($this->renderer->renderRoot($status_messages))); } } return $response; }
/** * Regression test: Settings command exists regardless of JS aggregation. */ public function testAttachedSettings() { $assert = function ($message) { $response = new AjaxResponse(); $response->setAttachments(['library' => ['core/drupalSettings'], 'drupalSettings' => ['foo' => 'bar']]); $ajax_response_attachments_processor = \Drupal::service('ajax_response.attachments_processor'); $subscriber = new AjaxResponseSubscriber($ajax_response_attachments_processor); $event = new FilterResponseEvent(\Drupal::service('http_kernel'), new Request(), HttpKernelInterface::MASTER_REQUEST, $response); $subscriber->onResponse($event); $expected = ['command' => 'settings']; $this->assertCommand($response->getCommands(), $expected, $message); }; $config = $this->config('system.performance'); $config->set('js.preprocess', FALSE)->save(); $assert('Settings command exists when JS aggregation is disabled.'); $config->set('js.preprocess', TRUE)->save(); $assert('Settings command exists when JS aggregation is enabled.'); }
/** * Renders form and status messages and returns an ajax response. * * Used for both submission buttons. * * @param array $form * The form. * * @return \Drupal\Core\Ajax\AjaxResponse * An ajax response to replace the form. */ protected function ajaxRenderFormAndMessages(array &$form) { $response = new AjaxResponse(); // Retrieve the element to be rendered. $status_messages = ['#type' => 'status_messages', '#weight' => -10]; // For some crazy reason, if we do this inline in the replace command, it // breaks ajax functionality entirely. $output = $this->renderer->renderRoot($form); $messages = $this->renderer->renderRoot($status_messages); $message_wrapper_id = '#' . self::MESSAGE_WRAPPER_ID; $response->setAttachments($form['#attached']); $response->addCommand(new ReplaceCommand(NULL, $output)); $response->addCommand(new HtmlCommand($message_wrapper_id, '')); $response->addCommand(new AppendCommand($message_wrapper_id, $messages)); return $response; }
/** * #ajax callback for managed_file upload forms. * * This ajax callback takes care of the following things: * - Ensures that broken requests due to too big files are caught. * - Adds a class to the response to be able to highlight in the UI, that a * new file got uploaded. * * @param array $form * The build form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The form state. * @param \Symfony\Component\HttpFoundation\Request $request * The current request. * * @return \Drupal\Core\Ajax\AjaxResponse * The ajax response of the ajax upload. */ public static function uploadAjaxCallback(&$form, FormStateInterface &$form_state, Request $request) { /** @var \Drupal\Core\Render\RendererInterface $renderer */ $renderer = \Drupal::service('renderer'); $form_parents = explode('/', $request->query->get('element_parents')); // Retrieve the element to be rendered. $form = NestedArray::getValue($form, $form_parents); // Add the special AJAX class if a new file was added. $current_file_count = $form_state->get('file_upload_delta_initial'); if (isset($form['#file_upload_delta']) && $current_file_count < $form['#file_upload_delta']) { $form[$current_file_count]['#attributes']['class'][] = 'ajax-new-content'; } else { $form['#suffix'] .= '<span class="ajax-new-content"></span>'; } $status_messages = ['#type' => 'status_messages']; $form['#prefix'] .= $renderer->renderRoot($status_messages); $output = $renderer->renderRoot($form); $response = new AjaxResponse(); $response->setAttachments($form['#attached']); return $response->addCommand(new ReplaceCommand(NULL, $output)); }
/** * Sends BigPipe placeholders' replacements as embedded AJAX responses. * * @param array $placeholders * Associative array; the BigPipe placeholders. Keys are the BigPipe * selectors. * @param array $placeholder_order * Indexed array; the order in which the BigPipe placeholders must be sent. * Values are the BigPipe selectors. (These values correspond to keys in * $placeholders.) * @param \Drupal\Core\Asset\AttachedAssetsInterface $cumulative_assets * The cumulative assets sent so far; to be updated while rendering BigPipe * placeholders. */ protected function sendPlaceholders(array $placeholders, array $placeholder_order, AttachedAssetsInterface $cumulative_assets) { // Return early if there are no BigPipe placeholders to send. if (empty($placeholders)) { return; } // Send a container and the start signal. print "\n"; print '<script type="application/json" data-big-pipe-event="start"></script>' . "\n"; flush(); // A BigPipe response consists of a HTML response plus multiple embedded // AJAX responses. To process the attachments of those AJAX responses, we // need a fake request that is identical to the master request, but with // one change: it must have the right Accept header, otherwise the work- // around for a bug in IE9 will cause not JSON, but <textarea>-wrapped JSON // to be returned. // @see \Drupal\Core\EventSubscriber\AjaxResponseSubscriber::onResponse() $fake_request = $this->requestStack->getMasterRequest()->duplicate(); $fake_request->headers->set('Accept', 'application/json'); foreach ($placeholder_order as $placeholder) { if (!isset($placeholders[$placeholder])) { continue; } // Render the placeholder. $placeholder_render_array = $placeholders[$placeholder]; $elements = $this->renderPlaceholder($placeholder, $placeholder_render_array); // Create a new AjaxResponse. $ajax_response = new AjaxResponse(); // JavaScript's querySelector automatically decodes HTML entities in // attributes, so we must decode the entities of the current BigPipe // placeholder (which has HTML entities encoded since we use it to find // the placeholders). $big_pipe_js_selector = Html::decodeEntities($placeholder); $ajax_response->addCommand(new ReplaceCommand(sprintf('[data-big-pipe-selector="%s"]', $big_pipe_js_selector), $elements['#markup'])); $ajax_response->setAttachments($elements['#attached']); // Push a fake request with the asset libraries loaded so far and dispatch // KernelEvents::RESPONSE event. This results in the attachments for the // AJAX response being processed by AjaxResponseAttachmentsProcessor and // hence: // - the necessary AJAX commands to load the necessary missing asset // libraries and updated AJAX page state are added to the AJAX response // - the attachments associated with the response are finalized, which // allows us to track the total set of asset libraries sent in the // initial HTML response plus all embedded AJAX responses sent so far. $fake_request->request->set('ajax_page_state', ['libraries' => implode(',', $cumulative_assets->getAlreadyLoadedLibraries())] + $cumulative_assets->getSettings()['ajaxPageState']); $this->requestStack->push($fake_request); $event = new FilterResponseEvent($this->httpKernel, $fake_request, HttpKernelInterface::SUB_REQUEST, $ajax_response); $this->eventDispatcher->dispatch(KernelEvents::RESPONSE, $event); $ajax_response = $event->getResponse(); $this->requestStack->pop(); // Send this embedded AJAX response. $json = $ajax_response->getContent(); $output = <<<EOF <script type="application/json" data-big-pipe-placeholder="{$placeholder}" data-drupal-ajax-processor="big_pipe"> {$json} </script> EOF; print $output; flush(); // Another placeholder was rendered and sent, track the set of asset // libraries sent so far. Any new settings are already sent; we don't need // to track those. if (isset($ajax_response->getAttachments()['drupalSettings']['ajaxPageState']['libraries'])) { $cumulative_assets->setAlreadyLoadedLibraries(explode(',', $ajax_response->getAttachments()['drupalSettings']['ajaxPageState']['libraries'])); } } // Send the stop signal. print '<script type="application/json" data-big-pipe-event="stop"></script>' . "\n"; flush(); }
/** * Regression test: Settings command exists regardless of JS aggregation. */ public function testAttachedSettings() { $assert = function ($message) { $response = new AjaxResponse(); $response->setAttachments(['library' => ['core/drupalSettings'], 'drupalSettings' => ['foo' => 'bar']]); $response->prepare(new Request()); $expected = ['command' => 'settings']; $this->assertCommand($response->getCommands(), $expected, $message); }; $config = $this->config('system.performance'); $config->set('js.preprocess', FALSE)->save(); $assert('Settings command exists when JS aggregation is disabled.'); $config->set('js.preprocess', TRUE)->save(); $assert('Settings command exists when JS aggregation is enabled.'); }
/** * 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; }
/** * Sends an ajax response. */ public function ajaxCallback(array $form, FormStateInterface $form_state) { $renderer = \Drupal::service('renderer'); $type = $form_state->getTriggeringElement()['#plugin_type']; $response = new AjaxResponse(); // Set URL hash so that the correct settings tab is open. if (isset($form[$type . '_configuration']['#id'])) { $hash = ltrim($form[$type . '_configuration']['#id'], '#'); $response->addCommand(new SetHashCommand($hash)); } // Update the forms. $plugin_settings = $renderer->renderRoot($form['plugin_settings']); $advanced_settings = $renderer->renderRoot($form[$type . '_wrapper']['advanced']); $response->addCommand(new ReplaceCommand('#feeds-ajax-form-wrapper', $plugin_settings)); $response->addCommand(new ReplaceCommand('#feeds-plugin-' . $type . '-advanced', $advanced_settings)); // Add attachments. $attachments = NestedArray::mergeDeep($form['plugin_settings']['#attached'], $form[$type . '_wrapper']['advanced']['#attached']); $response->setAttachments($attachments); // Display status messages. $status_messages = ['#type' => 'status_messages']; $output = $renderer->renderRoot($status_messages); if (!empty($output)) { $response->addCommand(new HtmlCommand('.region-messages', $output)); } return $response; }