I'm loading the node/edit form in a block and I add ajax on submit.

I add the Ajax in the hook_form_alter.

$form['#prefix'] = '<div id="' . $form_wrapper . '">';
$form['#suffix'] = '</div>';
$form['actions']['submit']['#submit'][] = 'submitForm';
$form['actions']['submit']['#ajax'] = array(
  'callback' => 'my_ajax_callback',
  'wrapper' => $form_wrapper,
  'effect' => 'fade',
);

The SubmitForm:

function submitForm(array &$form, FormStateInterface $form_state){
  $input = $form_state->getUserInput();
  $form_state->setUserInput($input);
  $form_state->setRebuild(TRUE);
}

And the callback :

function my_ajax_callback(&$form, FormStateInterface $form_state, $form_id) {
   $form_state->setRebuild();
   return $form;
}

The first time works fine. The second time seems to work without any error but it doesn't submit my new data.

I checked this comment How can I implement AJAX form submission? but I don't know if I have to set the values to the form_state. Also i use this callback for multiple content types. Should I check and set the value for each field? Is there a better way?

Thanks for your help!

------ EDIT -----

I added the submitForm function and now the second time I submit I get 'The content has either been modified by another user, or you have already submitted modifications. As a result, your changes cannot be saved.'.

Also, should I get/set each one of my fields or $input = $form_state->getUserInput(); $form_state->setUserInput($input); are enough?

-------- UPDATE ------

I managed to make it work for forms that I need to be empty after the ajax submit using the below code :

In hook_form_alter:

$form['wrapper'] = array(
    '#type' => 'container',
    '#attributes' => array('id' => $form_wrapper),
  );

$prefix = '<div id="' . $form_wrapper . '">';
$form['#prefix'] = $prefix;
$form['#suffix'] = '</div>';
$form['actions']['submit']['#submit'][] = 'Drupal\MY_MODULE\Plugin\Form\FormAlter::ajaxFormEntityFormSubmitEmptyForm';
$form['actions']['submit']['#ajax'] = array(
    'callback' => 'Drupal\MY_MODULE\Plugin\Form\FormAlter::ajaxFormEntityCallbackEmptyForm',
    'wrapper' => $form_wrapper,
    'effect' => 'fade',
);

And in the FormAlter.php :

/**
 * Rebuild the form - Empty form.
 */
 public static function ajaxFormEntityFormSubmitEmptyForm($form, &$form_state){
  $entity = $form_state->getBuildInfo()['callback_object']->getEntity();
  $entity_type = $entity->getEntityTypeId();
  $bundle = $entity->bundle();
  $new_entity = \Drupal::entityTypeManager()->getStorage($entity_type)->create(['type' => $bundle]);
  $form_state->getBuildInfo()['callback_object']->setEntity($new_entity);

  // Clear user input.
  $input = $form_state->getUserInput();
  $clean_keys = $form_state->getCleanValueKeys();
  $clean_keys[] = 'ajax_page_state';
  foreach ($input as $key => $item) {
    if (!in_array($key, $clean_keys) && substr($key, 0, 1) !== '_') {
      unset($input[$key]);
    }
  }

  $input['entity'] = $entity;
  $form_state->setUserInput($input);

  // Rebuild the form state values.
  $form_state->setRebuild();
  $form_state->setStorage([]);
}

/**
 * Ajax callback to handle just returns the form
 */
 public static function ajaxFormEntityCallbackEmptyForm(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
   if ($form_state->hasAnyErrors()) {
     return $form;
   }
   else {
     return $form;
    }
  }

Now I'm trying to do something similar but I want the form populated with the new values. So I created two new functions in FormAlter:

 /**
  * Rebuild the form.
  */
  public static function ajaxFormEntityFormSubmit($form, &$form_state) {
    $entity = $form_state->getBuildInfo()['callback_object']->getEntity();
    $entity_type = $entity->getEntityTypeId();
    $bundle = $entity->bundle();

    $new_entity = \Drupal::entityTypeManager()->getStorage($entity_type)->create(['type' => $bundle]);
    $form_state->getBuildInfo()['callback_object']->setEntity($new_entity);

    $input = $form_state->getUserInput();
    $input['entity'] = $entity;

    $form_state->setUserInput($input);
    if(!$form_state->hasValue('title')){
      $form_state->setValue('title',$entity->getTitle());
    }

    // Rebuild the form state values.
    $form_state->setRebuild(TRUE);
    $form_state->setStorage([]);
  }

 /**
  * Ajax callback to handle special ajax form entity magic.
  */
  public static function ajaxFormEntityCallback(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
  // If errors, returns the form with errors and messages.
  if ($form_state->hasAnyErrors()) {
    return $form;
  }
  // Else show the result.
  else {
    $userInputs = $form_state->getUserInput();
    $entity = $userInputs['entity'];
    $entity_type = $entity->getEntityTypeId();

    $response = new AjaxResponse();
    if ($entity_type == 'node') {
      $title = $entity->getTitle();
    }
    return $form;
  }
}

If I return the $form in the callback, the first time works fine but the second I'm getting two errors in the log:

Drupal\Core\Database\IntegrityConstraintViolationException: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'title' cannot be null: INSERT INTO {node_field_data} (nid, vid, type, langcode, status, title, uid, created, changed, promote, sticky, revision_translation_affected, default_langcode) VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2, :db_insert_placeholder_3, :db_insert_placeholder_4, :db_insert_placeholder_5, :db_insert_placeholder_6, :db_insert_placeholder_7, :db_insert_placeholder_8, :db_insert_placeholder_9, :db_insert_placeholder_10, :db_insert_placeholder_11, :db_insert_placeholder_12); Array ( [:db_insert_placeholder_0] => 201 [:db_insert_placeholder_1] => 258 [:db_insert_placeholder_2] => my_estate_details [:db_insert_placeholder_3] => en [:db_insert_placeholder_4] => 1 [:db_insert_placeholder_5] => [:db_insert_placeholder_6] => 5 [:db_insert_placeholder_7] => 1500889059 [:db_insert_placeholder_8] => 1500889066 [:db_insert_placeholder_9] => 0 [:db_insert_placeholder_10] => 0 [:db_insert_placeholder_11] => 1 [:db_insert_placeholder_12] => 1 ) in Drupal\Core\Database\Connection->handleQueryException()

And

Drupal\Core\Entity\EntityStorageException: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'title' cannot be null: INSERT INTO {node_field_data} (nid, vid, type, langcode, status, title, uid, created, changed, promote, sticky, revision_translation_affected, default_langcode) VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2, :db_insert_placeholder_3, :db_insert_placeholder_4, :db_insert_placeholder_5, :db_insert_placeholder_6, :db_insert_placeholder_7, :db_insert_placeholder_8, :db_insert_placeholder_9, :db_insert_placeholder_10, :db_insert_placeholder_11, :db_insert_placeholder_12); Array ( [:db_insert_placeholder_0] => 201 [:db_insert_placeholder_1] => 258 [:db_insert_placeholder_2] => my_estate_details [:db_insert_placeholder_3] => en [:db_insert_placeholder_4] => 1 [:db_insert_placeholder_5] => [:db_insert_placeholder_6] => 5 [:db_insert_placeholder_7] => 1500889059 [:db_insert_placeholder_8] => 1500889066 [:db_insert_placeholder_9] => 0 [:db_insert_placeholder_10] => 0 [:db_insert_placeholder_11] => 1 [:db_insert_placeholder_12] => 1 ) in Drupal\Core\Entity\Sql\SqlContentEntityStorage->save()

In case I return $response (which is what I prefer in order to be able to alter the message etc), the first time works fine but the second I get the message:

The content has either been modified by another user, or you have already submitted modifications. As a result, your changes cannot be saved.'.

Apologies for the long message. Any idea/help will be helpful!

Thanks in advance.

  • re-read my comment(the one you linked), you've missed a lot of stuff. – Ivan Jaros Jun 19 '17 at 12:31
  • Should I also add a submit handler except the callback? I think I'm pretty confused. Could you give me some more details please? – Ismini Jun 19 '17 at 13:23
  • I also added $input = $form_state->getUserInput(); $form_state->setUserInput($input); in order to get the user input and set it in the $form_state. Is that correct? After that I do the $form_state->setRebuild(); and then return $form; – Ismini Jun 19 '17 at 13:53
up vote 0 down vote accepted

I managed to find the solution.

In my hook_form_alter :

$form['#attributes']['class'][] = $ajax_id;
$form['actions']['submit']['#submit'][] = 'Drupal\MY_MODULE\Plugin\Form\FormAlter::ajaxFormEntityFormSubmit';
$form['actions']['submit']['#ajax'] = array(
  'callback' => 'Drupal\MY_MODULE\Plugin\Form\FormAlter::ajaxFormEntityCallback',
  'wrapper' => $ajax_id,
  'effect' => 'fade',
);
$form['ajax_form_entity'] = [
  '#type' => 'hidden',
  '#value' => [
    'reload' => '.reload',
    'form_selector' => '.' . $ajax_id,
    'show_message' => TRUE,
  ]
];

public static function ajaxFormEntityFormSubmit($form, &$form_state) {
    $entity = $form_state->getBuildInfo()['callback_object']->getEntity();

    // Clear user input.
    $input = $form_state->getUserInput();
    // We should not clear the system items from the user input.
    $clean_keys = $form_state->getCleanValueKeys();
    $clean_keys[] = 'ajax_page_state';
    foreach ($input as $key => $item) {
      if (!in_array($key, $clean_keys) && substr($key, 0, 1) !== '_') {
        unset($input[$key]);
      }
    }

    // Store new entity for display in the AJAX callback.
    $input['entity'] = $entity;
    $form_state->setUserInput($input);

    // Rebuild the form state values.
    $form_state->setRebuild();
    $form_state->setStorage([]);
  }

And the callback:

public static function ajaxFormEntityCallback(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
    // If errors, returns the form with errors and messages.
    if ($form_state->hasAnyErrors()) {
      return $form;
    }
    // Else show the result.
    else {
      $userInputs = $form_state->getUserInput();
      $entity = $userInputs['entity'];
      $entity_type = $entity->getEntityTypeId();
      $configurations = $form_state->getValue('ajax_form_entity');

      $response = new AjaxResponse();

      // Get messages even if not shown.
      $status_messages = array('#type' => 'status_messages');
      $message = \Drupal::service('renderer')->renderRoot($status_messages);

      // Reload the form.
        $response->addCommand(new ReplaceCommand($configurations['form_selector'], $form));

      return $response;
    }
  }

Your Answer

 
discard

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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