5

I am building an entity reference widget, which displays the reference link, next to the input field, to the target entity's edit form. I make it to open in a modal, with the following attributes on the link:

 'class' => ['use-ajax'],
          'data-dialog-type' => 'modal',
          'data-dialog-options' => json_encode([
            'height' => 700,
            'width' => 900,
          ]),

That works perfectly. However when I click submit on the modal form, I got redirected to the referenced entity view page, instead of the modal being closed and stay on the original form, where this widget is being used.

Is there any special attributes (like "data-dialog-type") which tells to the modal form's submit button to close the modal instead of redirecting to the original view?

UPDATE

Adding this to the URL actually pretty much solves the problem:

    'query' => [
      'destination' => $this->requestStack->getCurrentRequest()->getRequestUri(),
    ],

This redirects the to the original page, with the potential values being updated (referenced item title), which is actually a pretty good thing!

That being said, I am still interested in the "ajax submit" version!

3 Answers 3

5

You need to implement and use your own EntityEditForm - is this the case ? Then skip to the last code block :D

Otherwise you'll need to accomplish this first. See this answer (Overriding an entity add/edit form through alterroute) for an approach to 'inject' a custom form. Here the crucial code to it (adapted to NodeForm):

modules/custom/form_overwrite/form_overwrite.services.yml

services:
    route_subscriber:
        class: Drupal\form_overwrite\Routing\RouteSubscriber
        tags: 
 - {name: event_subscriber }

modules/custom/form_overwrite/src/Routing/RouteSubscriber.php

 <?php

/**
 * @file
 * Contains \Drupal\form_overwrite\Routing\RouteSubscriber.
 */

namespace Drupal\form_overwrite\Routing;

use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;

/**
 * Listens to the dynamic route events.
 */
class RouteSubscriber extends RouteSubscriberBase {

  /**
   * {@inheritdoc}
   */
  protected function alterRoutes(RouteCollection $collection) {
     if ($route = $collection->get('entity.node.edit_form')) {
  $route->setDefault('_entity_form', '\Drupal\form_overwrite\Form\NewEditForm');
     }

   }
}

modules/custom/form_overwrite/src/Form/NewEditForm.php

<?php

namespace Drupal\form_overwrite\Form;

use Drupal\node\NodeForm;


/**
 * Form handler for the node edit forms.
 */
class NewEditForm extends NodeForm {}

There you make sure the #ajax is set, like:

<?php

public function form(array $form, FormStateInterface $form_state) {
   ...
   $form['actions']['DESIRED_EXISTING_ACTION'] = array(
      ...
      '#type' => 'submit',
      '#ajax' => array(
        'callback' => '::ajaxSubmit',
        'event' => 'click',
      ),
      ...
    );
   ...
}

and overwrite the ajaxSubmit function, adding the CloseModalDialogCommand, like:

public function ajaxSubmit(array &$form, FormStateInterface $form_state) {
    $response = new AjaxResponse();

    if ($form_state->getErrors()) {
      ...
    }
    else {
      ...
      $response->addCommand(new \Drupal\Core\Ajax\CloseModalDialogCommand\CloseModalDialogCommand());
    }

    return $response;
  }
8
  • I want to use general entity forms, like node, and custom forms. Am not able to alter their submit (via hook) to rather make ajax response, since the modal somehow using it's own. Any suggestion on that?
    – ssibal
    Apr 26, 2017 at 10:59
  • You could fe. alter the route of the entity type your dealing with and set there your 'custom' form. see the newly linked answer.
    – rémy
    Apr 26, 2017 at 11:02
  • In the link there is a specific entity type. Here this is dynamic, so I won't able to extend from an exact Form class. Probably I will have to use composition here? Could I somehow alter the modal window process, which takes the form submit fields and ajaxifies them?
    – ssibal
    Apr 26, 2017 at 11:09
  • You'll need to do it for every Form class .. but most of them could be reused, and just the routing part will blow up .. May be a better way thoe ...
    – rémy
    Apr 26, 2017 at 11:20
  • Well, I don't want to write a code, which will need to be extended each time there is a new content entity type: \
    – ssibal
    Apr 26, 2017 at 11:27
3

I solved this by only using a event subscriber in a custom module.

modalCloseSubscriber.php

<?php

namespace Drupal\custom_module\EventSubscriber;

use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\CloseDialogCommand;
use Drupal\Core\Session\AccountProxyInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
 * Custom module event subscriber.
 */
class ModalCloseSubscriber implements EventSubscriberInterface {

  /**
   * Current logged in user.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected $currentUser;

  /**
   * Constructs event subscriber.
   *
   * @param \Drupal\Core\Session\AccountProxyInterface $current_user
   *   Current logged in user.
   */
  public function __construct(AccountProxyInterface $current_user) {
    $this->currentUser = $current_user;
  }

  /**
   * Kernel response event handler.
   *
   * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
   *   Response event.
   */
  public function onKernelResponse(FilterResponseEvent $event) {
    $response = $event->getResponse();
    if (!$response instanceof AjaxResponse) {
      return;
    }
    if ($response->getCommands()) {
      $commands = $response->getCommands();
      if (empty($commands)) {
        return;
      }
      elseif (!empty($commands)) {
        foreach ($commands as $command) {
          if ($command['command'] == 'insert' && $command['selector'] == '#layout-builder') {
            // leave blank for default modal window to close.
            $response->addCommand(new CloseDialogCommand());
          }
        }
      }
    }

  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    return [
      KernelEvents::RESPONSE => ['onKernelResponse'],
    ];
  }

}

This closes the modal after i create a block with layout builder.

0

I achieved it by hook_form_alter into the form, check if the current request is an Ajax request and then add a callback to fire the CloseDialogCommand().

/**
 * Implements hook_form_alter().
 */
function MYMODULE_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
  if ($form_id === 'MY_FORM_ID') {
    $request = \Drupal::request();
    // Check if Ajax request.
    if ($request->isXmlHttpRequest()) {
      $form['actions']['submit']['#ajax']['callback'] = '_MY_MODULE_MY_FORM_submit_ajax_callback';
    }
  }
}

/**
 * Custom Ajax callback.
 */
function _MY_MODULE_MY_FORM_submit_ajax_callback(array &$form, FormStateInterface $form_state) {
  // If form is valid then close the dialog.
  if (!$form_state->hasAnyErrors()) {
    $response = new \Drupal\Core\Ajax\AjaxResponse();
    $response->addCommand(new \Drupal\Core\Ajax\CloseDialogCommand());
    return $response;
  }
  // Otherwise call the default #ajax callback.
  $form_object = $form_state->getFormObject();
  $response = $form_object->submitAjaxForm($form, $form_state) ;
  return $response;
}
2
  • I tried your solution. The node is saved properly but your custom ajax callback throws me the following error: Error: Call to undefined method Drupal\node\NodeForm::submitAjaxForm() in cs_modals_submit_ajax_callback() (line 42 of .../public_html/modules/cs_modals/cs_modals.module)
    – Apoc
    Oct 31, 2021 at 9:57
  • @Apoc – Ah maybe because I was on a Webform. If everything works for you except this error, simply remove the last three lines of code?!
    – leymannx
    Oct 31, 2021 at 16:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.