When using the following AJAX element in a BlockBase extended class everything seems to work just jolly good in the Block-layout page.

$form['foobar_row']['add_more'] = [
  '#type' => 'submit',
  '#value' => $this->t('Add more'),
  '#submit' => [[$this, 'addOne']],
  '#ajax' => [
    'callback' => [$this, 'addmoreCallback'],
    'wrapper' => 'foobar-wrapper',
    'method' => 'replaceWith',
    'event' => 'click'
  '#id' => 'foobar__add_more'

Though, the block form will fail when used as a subform, in my case it's being used in a paragraph, which contains a block_field. And I can completely see why it's failing, since the $form in the callback method contains the entire node form. I even experimented with the following alteration to my callback method, which works, but is absolutely no solution since the entire path to the element should always be the same.

  public function addmoreCallback(array &$form, FormStateInterface $form_state) {
    $referral_field = $form_state->get('num_foobar_rows');

    // Note every parenting element being addressed in following, but I would ideally only have to define ['settings']['foobar_row'];
    return $form['field_paragraphs']['widget'][0]['subform']['field_paragraph_block_blocks']['widget'][0]['settings']['foobar_row'];

How would I make my addmoreCallback more dynamic and make sure it returns the right element?

  • tl;dr, drupal.org/node/2774077
    – user21641
    Commented Jun 23, 2017 at 12:50
  • Yeah, I've already read that, I'm not sure about what that would implicate for my BlockBase form. If I'm reading that correctly the parenting form should implement that solution, still not really sure if this addresses my issue.
    – Ambidex
    Commented Jun 23, 2017 at 14:38

1 Answer 1


Use $form_state->getTriggeringElement()!

Example for the callback:

public function addmoreCallback(array &$form, FormStateInterface $form_state) {
    $items_field = $form_state->get('num_foobar_rows');

    // Gets the button that triggers the ajax call
    $triggering_element = $form_state->getTriggeringElement();

    // Retrieve the parents, so we can climb back up the tree and do not have to hard code the unknown position of our subform
    $parents = array_slice($triggering_element['#array_parents'], 0, -1);

    // Use NestedArray to get the element in `$form` at the path that `$parents` describes.
    return NestedArray::getValue($form, $parents);

Note that I'm using #array_parents, this will contain all the parents including simple array parents or elements without #tree. This will prevent problems you are most likely to encounter when your form gets embedded in a field and ofcourse other forms.

Also note that the array_slice part, this will just slice off some parents to make sure we have the path to our first (-1) parent. If the element that you want to return is one or more parents up the tree, you'll need to adjust the -1 accordingly. Also, if your to be returned element is not actually the parent of the triggering element you'll want to alter the $parents array accordingly.

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.