Can AJAX be enabled for a form element inside a tableselect row? Firstly, a working select element with an AJAX callback:

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

  $form['select'] = [
    '#type' =&gt; 'select',
    '#options' =&gt; [
      0 =&gt; 'ZERO',
      1 =&gt; 'ONE',
      2 =&gt; 'TWO',
    ],
    '#ajax' =&gt; [
      'callback' =&gt; [$this, 'ajaxSelectChanged'],
      'event' =&gt; 'change',
    ],
  ];

  $form['submit'] = [
    '#type' =&gt; 'submit',
    '#value' =&gt; t('Submit'),
  ];

  return $form;
}

public function ajaxSelectChanged(array &amp;$form, FormStateInterface $form_state) {
  $selection = $form_state-&gt;getValue('select');
  // Do something with the selection.
}


Now I want to put the select element inside a tableselect element...

This comment shows how form elements may be added inside the rows of a tableselect (which is also a form element).

Here's my attempt:

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

  // Build the sortable table header.
  $select_options = [
    0 =&gt; 'ZERO',
    1 =&gt; 'ONE',
    2 =&gt; 'TWO',
  ];

  $form['table'] = [
    '#type' =&gt; 'tableselect',
    '#header' =&gt; [
      'name' =&gt; $this-&gt;t('Name'),
      'select' =&gt; $this-&gt;t('Select'),
    ],
    '#options' =&gt; [
      'alice' =&gt; [
        'name' =&gt; $this-&gt;t('Alice'),
        'select' =&gt; [
          'data' =&gt; [
            '#type' =&gt; 'select',
            '#options' =&gt; $select_options,
            '#ajax' =&gt; [
              'callback' =&gt; [$this, 'ajaxSelectChanged'],
              'event' =&gt; 'change',
            ],
          ],
        ],
      ],
      'bob' =&gt; [
        'name' =&gt; $this-&gt;t('Bob'),
        'select' =&gt; [
          'data' =&gt; [
            '#type' =&gt; 'select',
            '#options' =&gt; $select_options,
            '#ajax' =&gt; [
              'callback' =&gt; [$this, 'ajaxSelectChanged'],
              'event' =&gt; 'change',
            ],
          ],
        ],
      ],
    ],
    '#empty' =&gt; $this-&gt;t('There is nothing to see here.'),
  ];

  $form['submit'] = [
    '#type' =&gt; 'submit',
    '#value' =&gt; t('Submit'),
  ];

  return $form;
}

public function ajaxSelectChanged(array &amp;$form, FormStateInterface $form_state) {
  $selection = $form_state-&gt;getValues();
  // Do something with the selection.
}


It works, except for the AJAX callback, which never runs.




Am I doing something wrong?
Is there a way to let form elements inside a table trigger AJAX callbacks when they are changed?
Here is my solution:


Add placeholders (like a simple string, or an empty array) to the table row, where the select lists should go.
Add select lists below the table, and check that they have fully functional AJAX callbacks.
Register a #pre_render function on the form which moves the select lists into the table. 


In the buildForm function:

$form['#pre_render'] = [[$this, 'preRender']];


And then in the form class:

public static function preRender(array $form) {
  foreach ($form['table']['#options'] as $delta =&gt; &amp;$row) {
    $row['select_lists']['data'] = $form['select_lists'][$delta];
    unset($form['select_lists'][$delta]);
  }
  return $form;
}


I also tried hook_form_alter, but it runs too early, and messes up the AJAX. Apparently, a pre_render function runs after the AJAX stuff has been set up by Drupal, and then we can move the element anywhere on the page without affecting the AJAX functionality.