1

I am battling with D8 ajax right now, hoping someone can shed some light on where I'm badgering this implementation.

The goal is to update a value based on the on the state of a checkbox. The issue seems to be that there are multiple instances of this same form on the page. However, I've confirmed they all have unique build ids. They do have the same $form['#form_id'], but I think that's to be expected, $form['#id'] is different.

Here is the code:

public function buildForm(array $form, FormStateInterface $form_state) {
  $widget = $form_state->getBuildInfo();
  /* @var $widget Factorywidget */
  $widget = current($widget['args']);

  if ($form_state->has('widget_id')) {
    $widget_id = $form_state->get('widget_id');
  }
  else {
    $form_state->set('widget_id', $widget->id());
  }

  $ajax = [
    'wrapper' => 'widget-toggle-status-wrapper',
    'callback' => '::ajaxRebuildForm'
  ];

  $form['#prefix'] = '<div id="' . $ajax['wrapper'] . '">';
  $form['#suffix'] = '</div>';

  $form['toggle_widget_status'] = [
    '#type' => 'checkbox',
    '#title' => 'Toggle Status',
    '#default_value' => $widget->field_widget_status->value,
    '#ajax' => $ajax + ['trigger_as' => ['name' => 'widget_toggle_submit']],
  ];
  $form['widget_id'] = [
    '#type' => 'textfield',
    '#default_value' => $form_state->get('widget_id'),
    '#title' => 'widget ID is',
    '#size' => 10,
    '#disabled' => TRUE,
  ];

  $form['widget_toggle_submit'] = [
    '#type' => 'submit',
    '#name' => 'widget_toggle_submit',
    '#value' => $this->t('Submit'),
    '#submit' => ['::widgetToggleSubmit'],
    '#limit_validation_errors' => [],
    '#ajax' => $ajax,
    '#attributes' => [
      'class' => ['js-hide']
    ]
  ];

  return $form;
}

/**
 * Ajax submit handler that will return the whole form structure.
 *
 * @param array $form
 *   An associative array containing the structure of the form.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *     The current state of the form.
 *
 * @return array
 *   The form structure.
 */
public function ajaxRebuildForm(array &$form, FormStateInterface $form_state) {
  return $form;
}

public function widgetToggleSubmit($form, FormStateInterface $form_state) {
  $widget = $form_state->get('widget_id');
  $form_state->setRebuild();
  /* @var $widget Factorywidget */
  //$widget = FactorywidgetService::toggleFactorywidgetwidgetStatus($widget);

  //return new AjaxResponse();
}

Again, there are multiple instances of this form on the page. I added in widget_id as a textfield just to see if the widget Id was correct. When viewing the page, widget ID in the textfield is correct:

enter image description here

The problem is, no matter what toggle I click, the widget_id in $widget = $form_state->get('widget_id'); in widgetToggleSubmit is always the widget Id for the first form on the page. I added a shuffle to the render array which displays these to confirm - it's always set to the first form on the page.

To, hopefully illustrate, this screenshot shows the Debugger for a breakpoint I put in widgetToggleSubmit at $form_state->setRebuild():

The toggle has been triggered for the widget with id 3, but $form_state->get('widget_id') will only return the value for the first form on the page.

enter image description here

1 Answer 1

5

You can do this by setting a custom form ID for each form. So you define your form like this:

class MyForm {
  protected $formId;

  public function __construct($formId) {
    $this->formId = $formId;
  }

  public function getFormId() {
    return 'my_form-' . $formId;
  }
}

Then, when you call your form, you pass a unique Form ID for each form:

$my_form = new MyForm('some-unique-id');
$form = \Drupal::formBuilder()->getForm($my_form);

Pass a different ID for each form on the page.

3
  • Wow, thanks - this worked, and I knew about the d8 alternative to hook_forms but I didn't do it, or give it a second thought, since the form#id was different in the source code. The second one was automatically given dashboard-toggle-widget-status--2. Thanks again! Commented Feb 17, 2017 at 4:50
  • If FormInterface is an interface, it should be MyForm implements FormInterface. Only an interface can extend an interface.
    – avpaderno
    Commented Feb 27, 2017 at 12:06
  • It works, thanks. An improvement: in function getFormId() you have to use $this->formId instead of $formId: return 'my_form-' . $this->formId; Commented May 27, 2017 at 23:13

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.