1

I am trying to create a simple form in D8 that submits with a submit button and then replaces the entire form content via ajax.

For some reason, my AJAX submit works perfectly on the first click but then kicks in a normal submit handler.

This Works

  public function getFormId() {
    return 'my_form';
  }

  public function buildForm(array $form, FormStateInterface $form_state) {

  $form['#prefix'] = '<div id="form-test">';
  $form['#suffix'] = '</div>';
  $form['title'] = array(
    '#type' => 'textfield',
    '#title' => t('Title'),
    '#required' => TRUE,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit ').time(),
    '#ajax' => [
      'callback' => [$this, 'loadStep'],
      'wrapper' => 'form-test',
      'effect' => 'fade',
    ],
  );

return $form;
}

public function loadStep(array &$form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$form['submit']['#value'] = time();

$response->addCommand(new HtmlCommand('#form-test', $form));
return $response;

}

public function validateForm(array &$form, FormStateInterface $form_state) {

}

public function submitForm(array &$form, FormStateInterface $form_state) {
// TODO: Implement submitForm() method.
}

This Does NOT work

  public function getFormId() {
return 'my_form';
}

public function buildForm(array $form, FormStateInterface $form_state) {
  $form['#prefix'] = '<div id="form-test">';
  $form['#suffix'] = '</div>';
  $form['title'] = array(
    '#type' => 'textfield',
    '#title' => t('Title'),
    '#required' => TRUE,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit ').time(),
    '#ajax' => [
      'callback' => [$this, 'loadStep'],
      'wrapper' => 'form-test',
      'effect' => 'fade',
    ],
  );

return $form;
}

public function loadStep(array &$form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$form['#prefix'] = '<div id="form-test">';
$form['#suffix'] = '</div>';
$form['title'] = array(
  '#type' => 'textfield',
  '#title' => t('Title'),
  '#required' => TRUE,
);
$form['submit'] = array(
  '#type' => 'submit',
  '#value' => t('Submit ').time(),
  '#ajax' => [
    'callback' => [$this, 'loadStep'],
    'wrapper' => 'form-test',
    'effect' => 'fade',
  ],
);
$response->addCommand(new HtmlCommand('#form-test', $form));
return $response;

}

public function validateForm(array &$form, FormStateInterface $form_state) {

}

public function submitForm(array &$form, FormStateInterface $form_state) {
// TODO: Implement submitForm() method.
}

It seem like as soon as I add something more substantially than just changing the label on the submit button, it seems to fail from the second submit where it just invokes a regular submit.

I want to replace the entire form on every submit (basically creating a multistep form).

I am running 8.3.2 btw

Many thanks for all help.

6
  • The whole logic is wrong. See drupal.stackexchange.com/a/188752/21641
    – user21641
    Jun 17, 2017 at 3:51
  • Dear Ivan, Would you mind elaborating on 'the whole logic is wrong'? I have read the post you are referring to prior to posting here. Some specifics would help a ton. Thanks
    – Guardian
    Jun 18, 2017 at 7:11
  • 1
    For starter you are doing form logic/alteration in ajax submit handler instead of form(structure) or form submit handler(logic). You are not using any state($form_statem->set('foo', 'bar')), also you are missing $form_state->setRebuild() so you cannot reuse any submitted information since the form will be rebuilt from scratch.
    – user21641
    Jun 18, 2017 at 7:43
  • I found an example in the linked ticket. I missed that before. I have a working ajax form now by altering the $form_state in the submit callback. My ajax callback just returns the form. $form_state->has('counter_id') i keep track of the step the form is in to render accordingly.I increment the ID in the submit handler. I wanted to refactor this in to somekind of StepManager class that holds the id in a property. But for some reason the object seems to get resetted upon ajax submit, leaving me in the first step always. Is form state capable of passing around an instance of a class?
    – Guardian
    Jun 19, 2017 at 17:47
  • To my knowledge, if you try to store objects in state you'll get fatal error from serializer. SO for example instead of $form_state->set('user', $user) use $form_state->set('uid', $user->id()) and then load the user when needed via user_load($form_state->get('uid')).
    – user21641
    Jun 20, 2017 at 3:54

1 Answer 1

0

Your problem is probably here:

$form['submit']['#value'] = time();

You are changing the value of the submit button in the ajax callback. However, Drupal has already cached the form before the Ajax callback is hit. Why that matters is that Drupal uses the value of the submit button to determine which button was clicked, comparing it to the values in the cache. Since your cached value will be different than the submitted value, it cannot properly determine what it needs to do.

You need to change the value of the submit button either in the form definition, or in a form_alter hook.

2
  • Dear Jaypan, Would you mind showing me an example of how you would approach this? I have just tested to set the submit button in the form, based on a value that I set in the $form or $form_state variable in my ajax callback. For some reason this value does not end up in the $form nor $form_state variable of the buildForm() method... Which is weird because $form is passed by reference
    – Guardian
    Jun 18, 2017 at 7:51
  • The ajax callback happens after the form generation, that's why anything you do in the callback will not be available in the form generation. You'll need to make your changes based on some value in the $form_state.
    – Jaypan
    Jun 18, 2017 at 8:23

Your Answer

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

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