6

I'm using Drupal 8 and I have defined a custom entity type 'my_entity_type', with form handlers defined in the @ContentEntityType annotation:

 * @ContentEntityType(
 ...
 *   handlers = {
 *     "form" = {
 *       "add" = "Drupal\my_module\Form\MyEntityForm",
 *       "edit" = "Drupal\my_module\Form\MyEntityForm",
 *       "delete" = "Drupal\my_module\Form\MyEntityDeleteForm",
 ...

where:

  • MyEntityForm extends ContentEntityForm
  • MyEntityDeleteForm extends ContentEntityConfirmFormBase

These are routed in my_module.routing.yml leveraging the _entity_form element:

_entity_form: my_entity_type.add

I am rendering the form from another form as a modal:

$form['add_my_entity_button'] = [
  '#type' => 'link',
  '#title' => t('Add NewEntity'),
  '#url' => Url::fromRoute(
    'my_module.add_entity',
    array(
      'node' => is_numeric($node) ? $node : NULL,
    )
  ),
  '#attributes' => [
    'class' => ['button', 'use-ajax'],
    'data-dialog-type' => 'modal',
  ],
  '#attached' => [
    'library' => ['core/drupal.dialog.ajax'],
  ],
];

All works as imagined. The '#required' type validator appears in the form in the modal. Modal Validation Working

Goal

To add custom validation to this form's elements ('live' validation that does not close the modal). I recognize that this type of problem isn't novel, and I've done it often in non-modal forms. The methods I would usually leverage are:

  • '#element_validate' form element callback
  • validateForm() type form callback

Both appear to require submission of the form, close the modal, and render the errors in a flat form display. I then combed through Core for ideas and embarked on a remarkably twisted yak-shaving journey:

  1. Adding validation via BaseFieldDefinition::addConstraint in the entity baseFieldDefinitions() : This still submitted the form and led to a flat (non modal) form with errors correctly thrown. Once I solve this problem, I will likely leave this in to serve as a gatekeeper at the storage level.

  2. Extending the 'number' FieldType via Plugin, extending the widget, type or storage : This seemed overly complex for something as simple as adding a validator. I bailed on this before getting too far, since I considered:

  3. Injecting a generic '#ajax' 'replace' callback to the element inside buildForm() in MyEntityForm : I could NOT get ajax inserted in this manner to fire here.

  4. Injecting a generic '#ajax' 'replace' callback to the element in hook_form_alter : I could NOT get ajax callbacks inserted in this manner to fire either.

Questions

What have I missed? I feel this is simple. Is there something I've missed in Form that defines simple ajax validation?

Is there a standard way for validating form elements inside modals without closing the modal?

Can AJAX 'replace' callbacks be attached to forms defined and routed via the @ContentEntityType annotation? Is there a restriction here in the render pipeline?

1

This is a excellent article which you can read for "fixing" your issue: http://www.mediacurrent.com/blog/loading-and-rendering-modal-forms-drupal-8

Keep in mind this lines:

First, we wrap our form with a div

public function buildForm(array $form, FormStateInterface $form_state, $options = NULL) {
  // It is important this id
  $form['#prefix'] = '<div id="modal_example_form">';
  $form['#suffix'] = '</div>';

  // The status messages that will contain any form errors.
  $form['status_messages'] = [
    '#type' => 'status_messages',
    '#weight' => -10,
  ];

  // more code

  $form['actions']['send'] = [
      '#type' => 'submit',
      '#value' => $this->t('Submit modal form'),
      '#attributes' => [
        'class' => [
          'use-ajax',
        ],
      ],
      // Function to call when the users click on submit button.
      '#ajax' => [
        'callback' => [$this, 'submitModalFormAjax'],
        'event' => 'click',
      ],
    ];

    // more code
}

Second, we show the errors:

/**
 * AJAX callback handler that displays any errors or a success message.
 */
public function submitModalFormAjax(array $form, FormStateInterface $form_state) {
  $response = new AjaxResponse();

  // If there are any form errors, re-display the form.
  if ($form_state->hasAnyErrors()) {
    // Remember the previous id ? Here it is
    $response->addCommand(new ReplaceCommand('#modal_example_form', $form));
  }
  else {
    $response->addCommand(new OpenModalDialogCommand("Success!", 'The modal form has been submitted.', ['width' => 800]));
  }

  return $response;
}

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.