Score:3

AJAX Form using a sub-class of ContentEntityForm fires/invokes/calls the WidgetBase::massageFormValues() twice

cn flag

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;
  }

...
...
}
Score:4
cn flag

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.

Hermann Schwarz avatar
cn flag
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.
4uk4 avatar
cn flag
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.
Hermann Schwarz avatar
cn flag
I understand. Thank you, 4k4. I recognize, it's micro optimization I make :-).
Score:1
cn flag

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;
  }

...
...
}
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.