3

I'm using a AJAX form. This form I build from a sub-class of ContentEntityForm. The Entity edited by this form has a field MyField. To bring all the field values in a appropriate structure I'm using massageFormValues() in MyFieldWidget class (sub-class of WidgetBase).

The little problem is: the method massageFormValues() is called twice on every submit. The are no other problems with the form. I can properly edit values on this form.

So it's just the question of understanding: why Drupal does call massageFormValues() twice on every submit.

The mentioned and my (very simplified) code:

My ajax controller

class AjaxController extends ControllerBase {
  ...
  ...

  public function myController($eid) {

    $myEntity = $this->entityTypeManager()->getStorage('my_entity')->load($eid);
    $form = $this->entityFormBuilder()->getForm($myEntity, 'my_mode');
    $renderer = \Drupal::service('renderer');
    $output = $renderer->renderRoot($form);
  
    $response = new AjaxResponse();
    $response->addCommand(new OpenModalDialogCommand($title, $output));
 
    return $response;
  }
  ...
  ...
}

MyForm is a sub-class of the ContentEntityForm and it's a mode form for the mode my_mode of the Entity my_entity:

class MyForm extends ContentEntityForm {

 ...
 ...
  protected function actions(array $form, FormStateInterface $form_state) {
    $actions = parent::actions($form, $form_state);

    $actions['submit'] = [
      '#type' => 'submit',
      '#value' => t('Save'),
      '#ajax' => [
        'callback' => '::ajaxCallback',
        'event' => 'mousedown',
      ],
    ];

  return $actions;
  }
  ...
  ...
}

My field widget class:

class MyFieldWidget extends WidgetBase implements ContainerFactoryPluginInterface {
...
...

  public function massageFormValues(array $values, array $form, FormStateInterface $form_state) { 

    // THIS CODE IS CALLED TWICE ON EVERY SUBMIT OF MY SUBMIT BUTTON DEFINED ABOVE
    // BUT WHY TWICE?

    return $rightStructureValues;
  }

...
...
}

2 Answers 2

4

In an Ajax request the form is rebuilt from scratch. This was often asked here why the form build method is called two or even three times. It seems like the entity is rebuilt as well when the form is submitted via Ajax:

Drupal\Core\Entity\EntityForm::afterBuild

  /**
   * Form element #after_build callback: Updates the entity with submitted data.
   *
   * Updates the internal $this->entity object with submitted values when the
   * form is being rebuilt (e.g. submitted via AJAX), so that subsequent
   * processing (e.g. AJAX callbacks) can rely on it.
   */
  public function afterBuild(array $element, FormStateInterface $form_state) {
    // Rebuild the entity if #after_build is being called as part of a form
    // rebuild, i.e. if we are processing input.
    if ($form_state->isProcessingInput()) {
      $this->entity = $this->buildEntity($element, $form_state);
    }

    return $element;
  }

This will trigger the mentioned method in the field widgets.

3
  • Unfortunally I wasn't able to prevent calling massageFormValues() twice with behalf of afterBuild(). But if you can describe in detail, how it works, it would be nice. I will provide my own solution as an answer. Commented Oct 24, 2021 at 11:26
  • There is nothing wrong with that this method and the entire form build is being called twice. The core maintainers decided to rather rebuilt the form (and the entity in this case) than to fill up the database with cached Ajax forms which never get submitted.
    – 4uk4
    Commented Oct 24, 2021 at 11:43
  • I understand. Thank you, 4k4. I recognize, it's micro optimization I make :-). Commented Oct 24, 2021 at 12:50
2

My workaround/solution. massageFormValues() is still called twice by Drupal, but my logic in it will be executed just once, after the form validation is complete:

MyFieldWidgetClass.php:

class MyFieldWidget extends WidgetBase implements ContainerFactoryPluginInterface {
...
...

  public function massageFormValues(array $values, array $form, FormStateInterface $form_state) { 

    // massageFormValues() is called twice: on validateForm and on submitForm
    // we just need massageFormValues once, after $form_state->isValidationComplete() is TRUE
    if(!$form_state->isValidationComplete()) {
      return [];
    }
    ...
    ...
    return $rightStructureValues;
  }

...
...
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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