diff --git a/modules/webform_ajax/config/schema/webform_ajax.schema.yml b/modules/webform_ajax/config/schema/webform_ajax.schema.yml new file mode 100644 index 0000000..6fc72f5 --- /dev/null +++ b/modules/webform_ajax/config/schema/webform_ajax.schema.yml @@ -0,0 +1,7 @@ +webform.settings.third_party.webform_ajax: + type: mapping + label: 'Webform Ajax' + mapping: + enabled: + type: boolean + label: 'AJAX submission' diff --git a/modules/webform_ajax/js/webform_ajax.js b/modules/webform_ajax/js/webform_ajax.js new file mode 100644 index 0000000..4a11133 --- /dev/null +++ b/modules/webform_ajax/js/webform_ajax.js @@ -0,0 +1,23 @@ +(function($) { + Drupal.behaviors.webform_ajax = { + attach : function(context, settings) { + var wrapper_id = settings.webform_ajax.wrapper_id; + var ajax_settings = { + url: settings.webform_ajax.url, + event: 'click', + progress: { + type: 'throbber' + } + }; + + // Bind Ajax behaviors to Webform confirmation screen's "Go back to form" + // link. + $(context) + .find('.webform-confirmation__back a') + .once('webform_ajax') + .each(function() { + Drupal.Ajax[wrapper_id] = new Drupal.Ajax(wrapper_id, this, ajax_settings); + }); + } + }; +}(jQuery)); diff --git a/modules/webform_ajax/src/Controller/WebformAjaxController.php b/modules/webform_ajax/src/Controller/WebformAjaxController.php new file mode 100644 index 0000000..9988b85 --- /dev/null +++ b/modules/webform_ajax/src/Controller/WebformAjaxController.php @@ -0,0 +1,49 @@ +addCommand(new RemoveCommand("#$wrapper_id-status-messages")); + // Display the webform only if it is enabled. Otherwise, show messages. + if ($webform->isOpen()) { + $form = Webform::load($webform->get('id')); + $output = $this->entityTypeManager() + ->getViewBuilder('webform') + ->view($form); + } else { + $output = [ + '#type' => 'markup', + '#markup' => $this->t('The webform cannot be displayed.'), + ]; + } + + $response->addCommand(new ReplaceCommand('#' . $wrapper_id, $output)); + + return $response; + } +} \ No newline at end of file diff --git a/modules/webform_ajax/tests/src/FunctionalJavascript/WebformAjaxTest.php b/modules/webform_ajax/tests/src/FunctionalJavascript/WebformAjaxTest.php new file mode 100644 index 0000000..5d962b4 --- /dev/null +++ b/modules/webform_ajax/tests/src/FunctionalJavascript/WebformAjaxTest.php @@ -0,0 +1,386 @@ +provider = new WebformAjaxProvider(); + } + + /** + * Test AJAX webform submission storage. + * + * @see \Drupal\webform\Tests\WebformSubmissionStorageTest::testSubmissionStorage() + */ + public function testAjaxSubmissionStorage() { + $webform = $this->provider->webformWithTextfield(); + $webform->setSetting('confirmation_type', 'message'); + $webform->save(); + $assert = $this->assertSession(); + + $this->drupalPostForm($webform->toUrl(), ['textfield' => 'test value'], t('Submit')); + $assert->waitForElement('css', '.messages--status'); + /** @var \Drupal\webform\WebformSubmissionStorageInterface $storage */ + $storage = \Drupal::entityTypeManager()->getStorage('webform_submission'); + + $this->assertEquals($storage->getTotal($webform), 1); + } + + /** + * Test server-side validation of required fields. + * + * @todo Look into why client-side validation doesn't seem to kick in. + */ + public function testRequiredTextField() { + $webform = $this->provider->webformWithTextfield(['#required' => TRUE]); + $webform->setSetting('confirmation_type', 'message'); + $webform->save(); + $assert = $this->assertSession(); + + $this->drupalPostForm($webform->toUrl(), ['textfield' => ''], t('Submit')); + $element = $assert->waitForElement('css', '.messages--error'); + + $this->assertNotNull($element, "An AJAX submission triggers server-side validation errors."); + $assert->pageTextContains('Textfield field is required.'); + } + + /** + * Test an AJAX submission with a page confirmation. + */ + public function testAjaxSubmissionPageConfirmation() { + $webform = $this->provider + ->webformWithTextfield() + ->setSetting('confirmation_type', 'page'); + $webform->save(); + $assert = $this->assertSession(); + $expected_path = Url::fromRoute('entity.webform.confirmation', ['webform' => $webform->id()]); + + $this->drupalPostForm($webform->toUrl(), ['textfield' => 'test value'], t('Submit')); + $confirmation = $assert->waitForElement('css', '.webform-confirmation'); + $actual_path = parse_url($this->getUrl(), PHP_URL_PATH); + + $this->assertNotNull($confirmation, "An AJAX submission redirects to the confirmation page."); + $assert->pageTextContains('New submission added to Test webform'); + $assert->pageTextContains('test value'); + $this->assertEquals($actual_path, $expected_path->toString()); + + $this->clickLink('Back to form'); + $textfield = $assert->waitForField('Textfield'); + $this->assertNotNull($textfield, "Clicking the 'Back to form' link on the confirmation page after an AJAX submission loads the form."); + } + + /** + * Test an AJAX submission with inline confirmation. + */ + public function testAjaxSubmissionInlineConfirmation() { + // For now use the test_element_text webform as an easy check. + $webform = $this->provider->webformWithTextfield(); + $webform->setSetting('confirmation_type', 'inline'); + $webform->save(); + // @todo Use an API call to get the form ID. + $form_id = 'webform_submission_' . $webform->id() . '_form'; + // The inline confirmation message is wrapped in the same wrapper the form + // was. + // @todo Use an API to get the prefix. + $locator = "#webform-ajax-$form_id .webform-confirmation"; + $assert = $this->assertSession(); + + $this->drupalPostForm($webform->toUrl()->getInternalPath(), ['textfield' => 'test value'], t('Submit')); + $element = $assert->waitForElement('css', $locator); + + $this->assertNotNull($element, "An AJAX submission shows an inline confirmation message."); + $assert->pageTextContains('New submission added to Test webform'); + $assert->pageTextContains('test value'); + + $this->clickLink('Back to form'); + $textfield = $assert->waitForField('Textfield'); + $this->assertNotNull($textfield, "Clicking the 'Back to form' link on the confirmation page after an AJAX submission loads the form."); + } + + /** + * Test an AJAX submission with a message confirmation. + */ + public function testAjaxSubmissionMessageConfirmation() { + $webform = $this->provider->webformWithTextfield(); + $webform->setSetting('confirmation_type', 'message'); + $webform->save(); + $assert = $this->assertSession(); + $this->drupalPostForm($webform->toUrl(), ['textfield' => 'test value'], t('Submit')); + $element = $assert->waitForElement('css', '.messages--status'); + + $this->assertNotNull($element, "An AJAX submission shows a confirmation message."); + $assert->pageTextContains('New submission added to Test webform'); + $assert->pageTextContains('test value'); + $assert->fieldValueNotEquals('textfield', 'test value'); + } + + /** + * Test an AJAX submission with a URL confirmation. + */ + public function testAjaxSubmissionUrlConfirmation() { + $webform = $this->provider + ->webformWithTextfield() + ->setSetting('confirmation_type', 'url') + ->setSetting('confirmation_url', ''); + $webform->save(); + $assert = $this->assertSession(); + + $this->drupalPostForm($webform->toUrl(), ['textfield' => 'test value'], t('Submit')); + $element = $assert->waitForElement('css', '.messages--warning'); + $actual_path = parse_url($this->getUrl(), PHP_URL_PATH); + + $this->assertNotNull($element, "An AJAX submission redirects to a custom URL."); + $assert->pageTextContains('test value'); + $this->assertEquals($actual_path, '/'); + } + + /** + * Test an AJAX submission with a URL confirmation. + */ + public function testAjaxSubmissionUrlMessageConfirmation() { + $webform = $this->provider + ->webformWithTextfield() + ->setSetting('confirmation_type', 'url_message') + ->setSetting('confirmation_url', ''); + $webform->save(); + $assert = $this->assertSession(); + + $this->drupalPostForm($webform->toUrl(), ['textfield' => 'test value'], t('Submit')); + $element = $assert->waitForElement('css', '.messages--status'); + $actual_path = parse_url($this->getUrl(), PHP_URL_PATH); + + $this->assertNotNull($element, "An AJAX submission redirects to a custom URL."); + $assert->pageTextContains('New submission added to Test webform'); + $assert->pageTextContains('test value'); + $this->assertEquals($actual_path, '/'); + } + + /** + * Test two AJAX submission with a message confirmation. + */ + public function testMultipleAjaxSubmissionMessageConfirmation() { + $webform = $this->provider->webformWithTextfield(); + $webform->setSetting('confirmation_type', 'message'); + $webform->save(); + $assert = $this->assertSession(); + + $this->drupalPostForm($webform->toUrl(), ['textfield' => 'test value'], t('Submit')); + $assert->waitForElement('css', '.messages--status'); + $this->drupalPostForm(NULL, ['textfield' => 'test value 2'], t('Submit')); + // I'm not sure there's an element I can wait on. + $assert->assertWaitOnAjaxRequest(); + $element = $assert->waitForElement('css', '.messages--status'); + + $this->assertNotNull($element, "An AJAX submission shows a confirmation message."); + $assert->pageTextContains('New submission added to Test webform'); + $assert->pageTextContains('test value 2'); + } + + /** + * Test a two page form submits on both pages. + */ + public function testMultiPageForms() { + $webform = $this->provider->multipageWebformWithTextfields(); + $webform->setSetting('confirmation_type', 'message'); + $webform->save(); + $assert = $this->assertSession(); + + $this->drupalPostForm($webform->toUrl(), ['textfield_1' => 'test value page 1'], t('Next Page >')); + $element = $assert->waitForButton('Submit'); + + $this->assertNotNull($element, "An AJAX submission on the first page of a multi-page form causes the next page to show"); + $assert->pageTextContains('test value page 1'); + + $this->drupalPostForm(NULL, ['textfield_2' => 'test value page 2'], t('Submit')); + $element = $assert->waitForElement('css', '.messages--status'); + + $this->assertNotNull($element, "An AJAX submission on the last page of a multi-page form shows the webform confirmation."); + $assert->pageTextContains('test value page 2'); + $assert->pageTextContains('Textfield Page 1'); + $assert->pageTextNotContains('Textfield Page 2'); + } + + /** + * Test that messages from submissions replace previous ones. + */ + public function testMessages() { + $webform = $this->provider->webformWithTextfield(); + $webform->setSetting('confirmation_type', 'message'); + $webform->save(); + $assert = $this->assertSession(); + + $this->drupalPostForm($webform->toUrl(), ['textfield' => 'test value'], t('Submit')); + $assert->waitForElement('css', '.messages--warning'); + $this->drupalPostForm(NULL, ['textfield' => 'test value 2'], t('Submit')); + $assert->assertWaitOnAjaxRequest(); + $element = $assert->waitForElement('css', '.messages--warning'); + + $this->assertNotNull($element, "An AJAX submission shows a confirmation message."); + $assert->elementsCount('css', '.messages--warning', 1); + } +} + +/** + * Helper object that provides webforms for tests. + * + * @todo: Move this to a sensible namespace (but where?) + * @todo: Add some helper methods for adding elements to a webform. I think it + * will be neater than FAPI arrays into ::createWebform(). + */ +class WebformProvider { + /** + * Create a webform with a textfield. + * + * The webform title is 'Test webform' and the field name is 'textfield'. The + * default textfield title is 'Textfield' but that can be overridden and + * further properties provided with $properties. + * + * @param array $properties + * Properties for the textfield; they will override any defaults. + * + * @return \Drupal\webform\WebformInterface + */ + public function webformWithTextfield($properties = []) { + return $this->createWebform([ + 'title' => 'Test webform', + 'elements' => [ + 'textfield' => $this->textfield($properties), + ] + ]); + } + + /** + * Create a two page webform with a textfield on each page. + * + * It uses the following structure. + * page_1 (Page 1): + * - textfield_1 (Textfield 1) + * page_2 (Page 2): + * - textfield_2 (Textfield 2) + * + * @return \Drupal\webform\WebformInterface + */ + public function multipageWebformWithTextfields() { + return $this->createWebform([ + 'title' => 'Test webform', + 'elements' => [ + 'page_1' => [ + '#type' => 'webform_wizard_page', + '#title' => 'Page 1', + 'textfield_1' => $this->textfield(['#title' => 'Textfield Page 1']), + ], + 'page_2' => [ + '#type' => 'webform_wizard_page', + '#title' => 'Page 2', + 'textfield_2' => $this->textfield(['#title' => 'Textfield Page 2']), + ] + ] + ]); + } + + /** + * Create a textfield FAPI array, optionally with custom properties. + * + * By default the title is 'Textfield'. + * + * @param array $properties + * Custom properties that take precedence over the defaults. + * + * @return array + * The FAPI array + */ + public function textfield($properties = []) { + return $properties + [ + '#type' => 'textfield', + '#title' => 'Textfield', + ]; + } + + /** + * Create a new webform entity. + * + * @param array $values + * The initial values to pass to the Webform constructor; if you set the + * settings key directly here then no defaults will be provided (not + * recommended). + * + * @return \Drupal\webform\WebformInterface + */ + public function createWebform($values = []) { + $values += [ + 'handlers' => [ + 'debug' => [ + 'id' => 'debug', + 'label' => 'Debug', + 'handler_id' => 'debug', + 'status' => TRUE, + 'weight' => 1, + 'settings' => [], + ] + ] + ]; + + if (empty($values['id'])) { + $random = new Random(); + $values['id'] = $random->word(8); + } + $storage = \Drupal::entityTypeManager()->getStorage('webform'); + /** @var \Drupal\webform\WebformInterface $webform */ + $webform = $storage->create($values); + return $webform; + } +} + +/** + * Helper object that provides webforms for AJAX tests. + * + * @todo Move this somewhere sensible as well. + */ +class WebformAjaxProvider extends WebformProvider { + /** + * {@inheritdoc} + */ + public function createWebform($values = []) { + $webform = parent::createWebform($values); + $webform->setThirdPartySetting('webform_ajax', 'enabled', TRUE); + return $webform; + } +} diff --git a/modules/webform_ajax/webform_ajax.info.yml b/modules/webform_ajax/webform_ajax.info.yml new file mode 100644 index 0000000..1405acf --- /dev/null +++ b/modules/webform_ajax/webform_ajax.info.yml @@ -0,0 +1,6 @@ +name: Webform AJAX +description: Adds AJAX support to Webforms +type: module +core: 8.x +dependencies: + - webform:webform (>=8.x-5.x) diff --git a/modules/webform_ajax/webform_ajax.libraries.yml b/modules/webform_ajax/webform_ajax.libraries.yml new file mode 100644 index 0000000..df8eb8e --- /dev/null +++ b/modules/webform_ajax/webform_ajax.libraries.yml @@ -0,0 +1,8 @@ +webform_ajax: + version: 1.x + js: + js/webform_ajax.js: {} + dependencies: + - core/jquery + - core/jquery.once + - core/drupalSettings \ No newline at end of file diff --git a/modules/webform_ajax/webform_ajax.module b/modules/webform_ajax/webform_ajax.module new file mode 100644 index 0000000..820537c --- /dev/null +++ b/modules/webform_ajax/webform_ajax.module @@ -0,0 +1,385 @@ +getFormObject()->getEntity()->getWebform(); + + if (!$webform->getThirdPartySetting('webform_ajax', 'enabled')) { + return; + } + + $form = _webform_ajax_wrap_element($form, WEBFORM_AJAX_PREFIX . $form_id); + $element_id = WEBFORM_AJAX_PREFIX . $form_id; + // Adjust the form to use ajax submit. + // @todo: Add draft. + foreach (['previous', 'next', 'submit'] as $button) { + if (!empty($form['actions'][$button])) { + $form['actions'][$button]['#ajax'] = array( + 'callback' => 'webform_ajax_callback', + 'wrapper' => $element_id, + 'effect' => 'fade', + ); + } + } +} + +/** + * Ajax callback for webform. + */ +function webform_ajax_callback($form, FormStateInterface &$form_state) { + /** @var \Drupal\webform\WebformSubmissionForm $form_object */ + $form_object = $form_state->getFormObject(); + /** @var \Drupal\webform\WebformSubmissionInterface $submission */ + $submission = $form_object->getEntity(); + $webform = $submission->getWebform(); + $element_id = WEBFORM_AJAX_PREFIX . $form_object->getFormId(); + $prefix = '#' . $element_id; + + if ($submission->getState() == WebformSubmissionInterface::STATE_COMPLETED) { + list($response, $redirect) = _webform_ajax_form_completed($webform, $submission, $element_id, $form_state); + } + else { + // Display the incomplete form. + $response = new AjaxResponse(); + $response->addCommand(new ReplaceCommand($prefix, $form)); + } + + // As far as I know there's no JS command for displaying status messages. + // - clear our messages every submission; + // - add the messages before the form and/or confirmation message; + // - replace the form as usual. + $message_wrapper_id = $element_id . '-status-messages'; + // Clear out any old confirmation messages... + $response->addCommand(new RemoveCommand('#' . $message_wrapper_id)); + // ...and add in any new ones as long as we're not issuing an immediate + // redirect. + if (empty($redirect) && drupal_get_messages(NULL, FALSE)) { + $messages = StatusMessages::renderMessages(NULL); + $messages = _webform_ajax_wrap_element($messages, $message_wrapper_id); + $response->addCommand(new BeforeCommand($prefix, $messages)); + } + + return $response; +} + +/** + * Respond to a webform submission being completed. + * + * @param \Drupal\webform\WebformInterface $webform + * The webform being submitted. + * @param \Drupal\webform\WebformSubmissionInterface $submission + * The submission itself. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + * + * @return array + * An indexed array with the following elements: + * - The response object for the form completion; + * - A boolean indicating whether the response is a redirect. + * + * @see webform_ajax_callback() + */ +function _webform_ajax_form_completed(WebformInterface $webform, WebformSubmissionInterface $submission, $element_id, FormStateInterface $form_state) { + $element_selector = '#' . $element_id; + $response = new AjaxResponse(); + $type = $webform->getSetting('confirmation_type'); + $type = $type ? $type : Webform::getDefaultSettings()['confirmation_type']; + $redirect = FALSE; + + if (in_array($type, ['url', 'url_message', 'page']) && $url = _webform_ajax_get_form_state_redirect_url($form_state)) { + if ($type == 'url_message') { + // Currently the message set by webform itself is being squashed by + // \Drupal\webform\WebformMessageManager::display() with an explicit + // AJAX check.. + // @todo: Neaten this up + _webform_display_confirmation_message($submission); + } + // Send two AJAX commands: + // The first disables the form's buttons, to avoid extra click while waiting + // for redirect. The second is a redirect command, giving the browser the + // URL to go to next. + $response->addCommand(new InvokeCommand($element_selector . ' input.form-submit', 'attr', array ('disabled', 'disabled'))); + $response->addCommand(new RedirectCommand($url)); + $redirect = TRUE; + } + elseif ($type == 'inline') { + $confirmation = webform_ajax_confirmation_message($submission, $element_id); + // Add the same wrapper as the form (messages go above it, and it gets + // replaced when clicking the back link). + $output = _webform_ajax_wrap_element($confirmation, $element_id); + $response->addCommand(new ReplaceCommand($element_selector, $output)); + + } + else { + if ($type == 'message') { + // Currently the message set by webform itself is being squashed by + // \Drupal\webform\WebformMessageManager::display() with an explicit + // AJAX check.. + // @todo: Neaten this up + _webform_display_confirmation_message($submission); + } + $output = _webform_ajax_get_fresh_submission_form($webform); + $response->addCommand(new ReplaceCommand($element_selector, $output)); + } + + return [$response, $redirect]; +} + +/** + * Return any redirect set on the form state. + * + * It checks both the form state response and redirect. This is a hack that I'm + * using until I know where this code will live permanently (standalone or + * webform). + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state object. + * + * @return string|false + * The redirect URL or FALSE if there isn't one. + * + * @todo Clean this up (wait until we know if it's going into webform) + */ +function _webform_ajax_get_form_state_redirect_url(FormStateInterface $form_state) { + // Hack: I can't get the redirect URL from the form state during an AJAX + // submission. So re-enable redirect, grab the URL, and then disable again. I + // think the neat way to do this depends on whether it remains a standalone + // module or if it gets folded into the main module. + $was_redirect_disabled = $form_state->isRedirectDisabled(); + $form_state->disableRedirect(FALSE); + $redirect = $form_state->getRedirect(); + $form_state->disableRedirect($was_redirect_disabled); + if (!$redirect) { + $redirect = $form_state->getResponse(); + } + if ($redirect instanceof RedirectResponse) { + return $redirect->getTargetUrl(); + } + elseif ($redirect instanceof Url) { + return $redirect->setAbsolute()->toString(); + } + return FALSE; +} + +/** + * Return a new webform submission form with no user input. + * + * It would be nice to use WebformInterface::getSubmissionForm() but that uses + * the input from the current request to populate the form. + * + * @param \Drupal\webform\WebformInterface $webform + * The webform to make a submission form from. + * + * @return array + * The built form array for the submission form. + */ +function _webform_ajax_get_fresh_submission_form(WebformInterface $webform) { + $submission = \Drupal::entityTypeManager() + ->getStorage('webform_submission') + ->create(['webform_id' => $webform->id()]); + $form_object = \Drupal::entityTypeManager() + ->getFormObject('webform_submission', 'default'); + $form_object->setEntity($submission); + $form_state = (new FormState())->setFormState([]); + // Explicitly set the user input to nothing, so the form is built on + // that, rather than the request's input. + $form_state->setUserInput([]); + /** @var \Drupal\Core\Form\FormBuilderInterface $form_builder */ + $form_builder = \Drupal::service('form_builder'); + return $form_builder->buildForm($form_object, $form_state); +} + +/** + * Returns a render array for a submission's confirmation message. + * + * @param \Drupal\webform\WebformSubmissionInterface $submission + * The webform submission + * + * @return array + * The confirmation message render array + */ +function webform_ajax_confirmation_message(WebformSubmissionInterface $submission, $element_id) { + $webform = $submission->getWebform(); + $source_entity = $submission->getSourceEntity(); + $url = Url::fromRoute('webform_ajax.return_webform', [ + 'webform' => $webform->id(), + 'wrapper_id' => $element_id + ]); + $output = [ + '#theme' => 'webform_confirmation__ajax', + '#webform' => $webform, + '#source_entity' => $source_entity, + '#webform_submission' => $submission, + '#attached' => [ + 'library' => [ + 'webform_ajax/webform_ajax' + ], + 'drupalSettings' => [ + 'webform_ajax' => [ + 'url' => $url->toString(), + 'webform' => $webform->id(), + 'wrapper_id' => $element_id, + ] + ] + ] + ]; + + /** @var \Drupal\Core\Render\RendererInterface $renderer */ + $renderer = \Drupal::service('renderer'); + $renderer->addCacheableDependency($output, $webform); + $renderer->addCacheableDependency($output, $source_entity); + + return $output; +} + +/** + * Implements hook_webform_third_party_settings_form_alter(). + * + * Add webform AJAX settings to an individual webform. + */ +function webform_ajax_webform_third_party_settings_form_alter(&$form, FormStateInterface $form_state) { + /** @var \Drupal\webform\WebformInterface $webform */ + $webform = $form_state->getFormObject()->getEntity(); + $settings = [ + '#type' => 'details', + '#title' => t('Webform AJAX'), + '#open' => TRUE, + ]; + $settings['enabled'] = [ + '#type' => 'checkbox', + '#title' => t('Use AJAX'), + '#default_value' => $webform->getThirdPartySetting('webform_ajax', 'enabled', FALSE), + ]; + $form['third_party_settings']['webform_ajax'] = $settings; +} + +/** + * Implements hook_preprocess_webform_confirmation(). + * + * Removes AJAX query parameters from the 'Back to form' link after an AJAX + * submission has caused a redirect. + * + * @todo Decide where this should live: + * - the webform submission could strip AJAX query parameters in + * \Drupal\webform\WebformSubmissionInterface::getSourceUrl(); + * - It could be folded into template_preprocess_webform_confirmation(); + * - If this remains as a standalone module the back link can remain here + * (ideally webform can be updated not to use string URLs). + */ +function webform_ajax_preprocess_webform_confirmation(&$variables) { + /** @var WebformInterface $webform */ + $webform = $variables['webform']; + if (!$webform->getThirdPartySetting('webform_ajax', 'enabled')) { + return; + } + if (!in_array($webform->getSetting('confirmation_type'), ['page', 'url', 'url_message'], TRUE)) { + return; + } + + /** @var WebformSubmissionInterface $webform_submission */ + $webform_submission = $variables['webform_submission']; + // @todo I think webform shouldn't be converting URLs to strings - that makes + // them more difficult to modify (and you can output \Drupal\Core\Url + // objects directly from Twig templates). For the time being get the URL + // from the submission rather than $variables. + $back_url = $webform_submission->getSourceUrl(); + $query = $back_url->getOption('query'); + unset($query[FormBuilderInterface::AJAX_FORM_REQUEST]); + $wrapper_format = MainContentViewSubscriber::WRAPPER_FORMAT; + if (!empty($query[$wrapper_format]) && $query[$wrapper_format] == 'drupal_ajax') { + unset($query[$wrapper_format]); + } + $back_url->setOption('query', $query); + $variables['back_url'] = $back_url->toString(); +} + +/** + * Safely wrap a render array with an ID using #prefix and #suffix. + * + * @param array $element + * The render array to wrap. + * @param $id + * The ID of the wrapping element. + * + * @return array + * The new render array. + */ +function _webform_ajax_wrap_element($element, $id) { + foreach (['#prefix', '#suffix'] as $property) { + if (!isset($element[$property])) { + $element[$property] = ''; + } + } + $element['#prefix'] = '
' . $element['#prefix']; + $element['#suffix'] .= '
'; + return $element; +} + +/** + * Display the submission confirmation message. + * + * This is ugly - I'm duplicating code from WebformMessageManager::display() + * to avoid the AJAX check. + * + * @param \Drupal\webform\WebformSubmissionInterface $submission + * The submission being confirmed. + * + * @see \Drupal\webform\WebformMessageManager::display() + */ +function _webform_display_confirmation_message(WebformSubmissionInterface $submission) { + /** @var \Drupal\webform\WebformMessageManagerInterface $message_manager */ + $message_manager = \Drupal::service('webform.message_manager'); + $message_manager->setWebform($submission->getWebform()); + $message_manager->setSourceEntity($submission->getSourceEntity()); + $message_manager->setWebformSubmission($submission); + if (!$build = $message_manager->build(WebformMessageManagerInterface::SUBMISSION_CONFIRMATION)) { + $build = $message_manager->build(WebformMessageManagerInterface::SUBMISSION_DEFAULT_CONFIRMATION); + } + if ($build) { + /** @var \Drupal\Core\Render\RendererInterface $renderer */ + $renderer = \Drupal::service('renderer'); + drupal_set_message($renderer->renderPlain($build), 'status'); + } +} diff --git a/modules/webform_ajax/webform_ajax.routing.yml b/modules/webform_ajax/webform_ajax.routing.yml new file mode 100644 index 0000000..235cfdd --- /dev/null +++ b/modules/webform_ajax/webform_ajax.routing.yml @@ -0,0 +1,6 @@ +webform_ajax.return_webform: + path: '/webform_ajax/return_webform/{webform}/{wrapper_id}' + defaults: + _controller: 'Drupal\webform_ajax\Controller\WebformAjaxController::return_webform' + requirements: + _entity_access: 'webform.submission_create' \ No newline at end of file