diff --git a/core/modules/outside_in/js/outside_in.js b/core/modules/outside_in/js/outside_in.js index 0b8bc88..5d42199 100644 --- a/core/modules/outside_in/js/outside_in.js +++ b/core/modules/outside_in/js/outside_in.js @@ -211,8 +211,10 @@ $(toggleEditSelector).once('outsidein').on('click.outsidein', toggleEditMode); - var search = Drupal.ajax.WRAPPER_FORMAT + '=drupal_dialog'; - var replace = Drupal.ajax.WRAPPER_FORMAT + '=drupal_dialog_offcanvas'; + var searchLink = Drupal.ajax.WRAPPER_FORMAT + '=drupal_dialog'; + var searchFrom = Drupal.ajax.WRAPPER_FORMAT + '=drupal_ajax'; + var replaceLink = Drupal.ajax.WRAPPER_FORMAT + '=drupal_dialog_offcanvas'; + var replaceForm = Drupal.ajax.WRAPPER_FORMAT + '=drupal_off_canvas_ajax'; // Loop through all Ajax links and change the format to dialog-offcanvas when // needed. Drupal.ajax.instances @@ -221,21 +223,36 @@ var rendererOffcanvas = false; var wrapperOffcanvas = false; if (hasElement) { - rendererOffcanvas = $(instance.element).attr('data-dialog-renderer') === 'offcanvas'; - wrapperOffcanvas = instance.options.url.indexOf('drupal_dialog_offcanvas') === -1; + rendererOffcanvas = $(instance.element).attr('data-dialog-renderer') === 'offcanvas' + if (rendererOffcanvas) { + wrapperOffcanvas = instance.options.url.indexOf(replaceLink) === -1; + } + else { + if (instance.$form[0] && instance.$form[0].hasAttribute('data-off-canvas-form')) { + rendererOffcanvas = true; + wrapperOffcanvas = instance.options.url.indexOf(replaceForm) === -1; + } + } + } return hasElement && rendererOffcanvas && wrapperOffcanvas; }) .forEach(function (instance) { // @todo Move logic for data-dialog-renderer attribute into ajax.js // https://www.drupal.org/node/2784443 - instance.options.url = instance.options.url.replace(search, replace); - // Check to make sure existing dialogOptions aren't overridden. - if (!('dialogOptions' in instance.options.data)) { - instance.options.data.dialogOptions = {}; + if (instance.hasOwnProperty('dialogType')) { + instance.options.url = instance.options.url.replace(searchLink, replaceLink); + // Check to make sure existing dialogOptions aren't overridden. + if (!('dialogOptions' in instance.options.data)) { + instance.options.data.dialogOptions = {}; + } + instance.options.data.dialogOptions.outsideInActiveEditableId = $(instance.element).parents('.outside-in-editable').attr('id'); + instance.progress = {type: 'fullscreen'}; + } + else { + instance.options.url = instance.options.url.replace(searchFrom, replaceForm); + instance.options.data.formOptions = {messagesSelector: '.messages__wrapper'}; } - instance.options.data.dialogOptions.outsideInActiveEditableId = $(instance.element).parents('.outside-in-editable').attr('id'); - instance.progress = {type: 'fullscreen'}; }); } }; diff --git a/core/modules/outside_in/outside_in.services.yml b/core/modules/outside_in/outside_in.services.yml index 48f5824..41879c4 100644 --- a/core/modules/outside_in/outside_in.services.yml +++ b/core/modules/outside_in/outside_in.services.yml @@ -5,6 +5,12 @@ services: tags: - { name: render.main_content_renderer, format: drupal_dialog_offcanvas } + main_content_renderer.off_canvas_ajax: + class: Drupal\outside_in\Render\MainContent\AjaxRenderer + arguments: ['@element_info'] + tags: + - { name: render.main_content_renderer, format: drupal_off_canvas_ajax } + outside_in.manager: class: Drupal\outside_in\OutsideInManager arguments: ['@router.admin_context', '@current_route_match', '@current_user'] diff --git a/core/modules/outside_in/src/Block/BlockEntityOffCanvasForm.php b/core/modules/outside_in/src/Block/BlockEntityOffCanvasForm.php index a77f51f..92fe391 100644 --- a/core/modules/outside_in/src/Block/BlockEntityOffCanvasForm.php +++ b/core/modules/outside_in/src/Block/BlockEntityOffCanvasForm.php @@ -36,6 +36,7 @@ public function title(BlockInterface $block) { */ public function form(array $form, FormStateInterface $form_state) { $form = parent::form($form, $form_state); + $form['#attributes']['data-off-canvas-form'] = TRUE; // Create link to full block form. $query = []; @@ -97,4 +98,23 @@ protected function getPluginForm(BlockPluginInterface $block) { return $block; } + /** + * {@inheritdoc} + * + * @todo Remove this function. Temp to demo validation error in OffCanvas. + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + parent::validateForm($form, $form_state); + $form_state->setErrorByName('none', 'All your submit belong to us!'); + } + + /** + * {@inheritdoc} + */ + protected function actionsElement(array $form, FormStateInterface $form_state) { + $actions_element = parent::actionsElement($form, $form_state); + $actions_element['submit']['#attributes']['class'][] = 'use-ajax-submit'; + return $actions_element; + } + } diff --git a/core/modules/outside_in/src/Render/MainContent/AjaxRenderer.php b/core/modules/outside_in/src/Render/MainContent/AjaxRenderer.php new file mode 100644 index 0000000..31e1f14 --- /dev/null +++ b/core/modules/outside_in/src/Render/MainContent/AjaxRenderer.php @@ -0,0 +1,66 @@ +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)) { + // If there are any status messages replace the existing messages. + $options = $request->request->get('formOptions', []); + $messages_selector = isset($options['messagesSelector']) ? $options['messagesSelector'] : NULL; + $response->addCommand(new ReplaceCommand($messages_selector, $output)); + } + else { + // @todo If no messages then the dialog should be close, the page refreshed + // the same same dialog should be then be re-opened. + // A command will need to be created. + } + return $response; + } + + +} diff --git a/core/modules/outside_in/src/Render/MainContent/OffCanvasRender.php b/core/modules/outside_in/src/Render/MainContent/OffCanvasRender.php index 5d24c09..34cce18 100644 --- a/core/modules/outside_in/src/Render/MainContent/OffCanvasRender.php +++ b/core/modules/outside_in/src/Render/MainContent/OffCanvasRender.php @@ -41,6 +41,12 @@ public function __construct(TitleResolverInterface $title_resolver, RendererInte public function renderResponse(array $main_content, Request $request, RouteMatchInterface $route_match) { $response = new AjaxResponse(); + // Add place holder form messages from Ajax form requests. + $main_content['off_canvas_messages'] = [ + '#markup' => '
', + '#weight' => -100, + ]; + // First render the main content, because it might provide a title. $content = $this->renderer->renderRoot($main_content); diff --git a/core/modules/outside_in/tests/modules/offcanvas_test/offcanvas_test.routing.yml b/core/modules/outside_in/tests/modules/offcanvas_test/offcanvas_test.routing.yml index 761693b..96319dc 100644 --- a/core/modules/outside_in/tests/modules/offcanvas_test/offcanvas_test.routing.yml +++ b/core/modules/outside_in/tests/modules/offcanvas_test/offcanvas_test.routing.yml @@ -27,3 +27,10 @@ offcanvas_test.dialog_links: _controller: '\Drupal\offcanvas_test\Controller\TestController::otherDialogLinks' requirements: _access: 'TRUE' + +offcanvas_test.form: + path: '/offcanvas-form' + defaults: + _form: '\Drupal\offcanvas_test\Form\TestForm' + requirements: + _access: 'TRUE' diff --git a/core/modules/outside_in/tests/modules/offcanvas_test/src/Controller/TestController.php b/core/modules/outside_in/tests/modules/offcanvas_test/src/Controller/TestController.php index 9ee8e17..518233b 100644 --- a/core/modules/outside_in/tests/modules/offcanvas_test/src/Controller/TestController.php +++ b/core/modules/outside_in/tests/modules/offcanvas_test/src/Controller/TestController.php @@ -92,6 +92,21 @@ public function linksDisplay() { ], ], ], + 'offcanvas_form' => [ + '#title' => 'Show form!', + '#type' => 'link', + '#url' => Url::fromRoute('offcanvas_test.form'), + '#attributes' => [ + 'class' => ['use-ajax'], + 'data-dialog-type' => 'dialog', + 'data-dialog-renderer' => 'offcanvas', + ], + '#attached' => [ + 'library' => [ + 'outside_in/drupal.outside_in', + ], + ], + ], ]; } diff --git a/core/modules/outside_in/tests/modules/offcanvas_test/src/Form/TestForm.php b/core/modules/outside_in/tests/modules/offcanvas_test/src/Form/TestForm.php new file mode 100644 index 0000000..8f8d6a0 --- /dev/null +++ b/core/modules/outside_in/tests/modules/offcanvas_test/src/Form/TestForm.php @@ -0,0 +1,60 @@ + 'submit', + '#value' => 'Valid', + '#attributes' => ['class' => ['use-ajax-submit']], + ]; + $form['invalid'] = [ + '#type' => 'submit', + '#value' => 'Invalid', + '#attributes' => ['class' => ['use-ajax-submit']], + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + parent::validateForm($form, $form_state); + if ($form_state->getValue('op') == 'Invalid') { + $form_state->setErrorByName('invalid', 'Validation error'); + } + } + + /** + * Form submission handler. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + drupal_set_message('submitted'); + } + +} diff --git a/core/modules/outside_in/tests/src/FunctionalJavascript/OffCanvasTest.php b/core/modules/outside_in/tests/src/FunctionalJavascript/OffCanvasTest.php index b81c723..8fe8f74 100644 --- a/core/modules/outside_in/tests/src/FunctionalJavascript/OffCanvasTest.php +++ b/core/modules/outside_in/tests/src/FunctionalJavascript/OffCanvasTest.php @@ -103,4 +103,17 @@ public function testNarrowWidth() { } } + /** + * Test form errors in the Off-Canvas dialog. + */ + public function testFormErrors() { + $this->drupalGet('/offcanvas-test-links'); + $page = $this->getSession()->getPage(); + $page->clickLink('Show form!'); + $this->waitForOffCanvasToOpen(); + $page->pressButton('Invalid'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->assertSession()->elementContains('css', '#drupal-offcanvas', 'Validation error'); + } + }