0

Using Drupal 8.7.7 I have a form which consists of a paginated table of users.

Each user row has a checkbox element in the form with an ajax event attached. This is essentially to allow me to store the selection in the users session as they select users and move to different pages.

The form works correctly on the first page, however when you move to the second page, only the first selection works. Any selection made after that throws the error:

Symfony\Component\HttpKernel\Exception\HttpException: The specified #ajax callback is empty or not callable. in Drupal\Core\Form\FormAjaxResponseBuilder->buildResponse() (line 67 of /var/www/html/web/core/lib/Drupal/Core/Form/FormAjaxResponseBuilder.php).

Here is the buildForm contents:

public function buildForm(array $form, FormStateInterface $form_state) {
    /** @var \Symfony\Component\HttpFoundation\Session\Session $session */
    $session = $this->requestStack->getCurrentRequest()->getSession();
    /** @var \Drupal\node\Entity\Node $node */
    $node = $this->requestStack->getCurrentRequest()->get('node');
    if (!$session->has($node->id().'_register_selection')) {
      $this->requestStack->getCurrentRequest()->getSession()->set($node->id().'_register_selection', []);
    } else {
      $selected = $session->get($node->id().'_register_selection');
    }

    $form['selected_users'] = [
      '#type' => 'container',
      '#attributes' => ['id' => 'selected-users'],
    ];

    $users = $form_state->getValue('user_list');
    if (!empty($users)) {
      foreach ($users as $user) {
        if ($user['check'] == TRUE) {
          $selected[$user['uid']] = $user['uid'];
        } elseif (array_key_exists($user['uid'], $users)) {
            unset($selected[$user['uid']]);
        }
        $session->set($node->id() . '_register_selection', $selected);
      }
    }

    if (!empty($selected)) {
      foreach ($selected as $item) {
        $form['selected_users'][$item]['user'] = [
          '#type' => 'item',
          '#markup' => t('user: @user', ['@user' => $item]),
          '#weight' => '0',
        ];
      }
    }

    $header = [
      ['data' => $form['user_list']['check_all'] = [
          '#type' => 'checkbox',
          '#title' => t('Select all'),
          '#return_value' => TRUE,
          '#wrapper_attributes' => [
            'class' =>['align-middle', 'm-0']
          ]
        ],
        'class'=> ['py-4']
      ],
      'uid' => ['data' => t('UID'), 'class'=> ['align-middle', 'py-4']],
      'username' => ['data' => t('Username'), 'class'=> ['align-middle', 'py-4']],
      'sits_id' => ['data' => t('SITS ID'), 'class'=> ['align-middle', 'py-4']],
      'name' => ['data' => t('Name'), 'class'=> ['align-middle', 'py-4']],
      'mail' => ['data' => t('Email'), 'class'=> ['align-middle', 'py-4']],
      'college' => ['data' => t('College'), 'class'=> ['align-middle', 'py-4']],
      'programme' => ['data' => t('Programme'), 'class'=> ['align-middle', 'py-4']],
      'course' => ['data' => t('Course'), 'class'=> ['align-middle', 'py-4']],
      'year' => ['data' => t('Year'), 'class'=> ['align-middle', 'py-4']],
    ];

    $form['user_list'] = [
      '#type' => 'table',
      '#header' => $header,
      '#empty' => t('No users found with the selected filters.'),
      '#prefix' => "<div class='table-responsive mt-3'>",
      '#suffix' => "</div>",
      '#attributes' => [
        'class' => ['table-sm'],
        'style' => 'font-size: .85rem',
      ]
    ];

    $query = $this->database->select('users_field_data', 'ufd');
    $query->leftJoin('profile', 'profile', 'profile.uid = ufd.uid');
    $query->leftJoin('profile__field_prof_sits_id', 'sitsid', 'sitsid.entity_id = profile.profile_id');
    $query->leftJoin('profile__field_prof_first_name', 'fname', 'fname.entity_id = profile.profile_id');
    $query->leftJoin('profile__field_prof_last_name', 'lname', 'lname.entity_id = profile.profile_id');
    $query->leftJoin('profile__field_prof_college', 'college', 'college.entity_id = profile.profile_id');
    $query->leftJoin('profile__field_prof_programme', 'programme', 'programme.entity_id = profile.profile_id');
    $query->leftJoin('profile__field_prof_course', 'course', 'course.entity_id = profile.profile_id');
    $query->leftJoin('profile__field_prof_sits_year', 'year', 'year.entity_id = profile.profile_id');
    $query->fields('ufd', ['uid','name', 'mail']);
    $query->addField('sitsid', 'field_prof_sits_id_value', 'sits_id');
    $query->addField('fname', 'field_prof_first_name_value', 'first_name');
    $query->addField('lname', 'field_prof_last_name_value', 'last_name');
    $query->addField('college', 'field_prof_college_target_id', 'college_gid');
    $query->addField('programme', 'field_prof_programme_target_id', 'programme_gid');
    $query->addField('course', 'field_prof_course_target_id', 'course_gid');
    $query->addField('year', 'field_prof_sits_year_value', 'year');
    $query->extend('Drupal\Core\Database\Query\TableSortExtender')->orderByHeader($header);
    $pager = $query->extend('Drupal\Core\Database\Query\PagerSelectExtender')->limit(10);

    $results = $pager->execute()->fetchAll();
    if ($results) {
      foreach ($results as $data) {
        $form['user_list'][$data->uid]['check'] = [
          '#type' => 'checkbox',
          '#ajax' => [
            'callback' => '::processUser',
            'wrapper' => 'selected-users',
          ],
          '#wrapper_attributes' => [
            'class' => ['align-middle', 'm-0', 'p-0', 'text-align-center']
          ],
          '#attributes' => [
            'class' => ['m-0'],
            'style' => 'position:relative;',
          ],
          '#default_value' => isset($selected[$data->uid]) ? TRUE : FALSE,
        ];

        $form['user_list'][$data->uid]['uid'] = [
          '#type' => 'item',
          '#markup' => $data->uid,
          '#value' => $data->uid,
          '#weight' => '0',
          '#wrapper_attributes' => [
            'class' => ['align-middle', 'm-0']
          ]
        ];

        $form['user_list'][$data->uid]['username'] = [
          '#type' => 'item',
          '#markup' => $data->name,
          '#value' => $data->name,
          '#weight' => '0',
          '#wrapper_attributes' => [
            'class' => ['align-middle', 'm-0']
          ]
        ];

        $form['user_list'][$data->uid]['sits_id'] = [
          '#type' => 'item',
          '#markup' => $data->sits_id,
          '#value' => $data->sits_id,
          '#weight' => '0',
          '#wrapper_attributes' => [
            'class' => ['align-middle', 'm-0']
          ]
        ];

        $form['user_list'][$data->uid]['name'] = [
          '#type' => 'item',
          '#markup' => $data->first_name . ' ' . $data->last_name,
          '#value' => $data->first_name . ' ' . $data->last_name,
          '#weight' => '0',
          '#wrapper_attributes' => [
            'class' => ['align-middle', 'm-0']
          ]
        ];

        $form['user_list'][$data->uid]['mail'] = [
          '#type' => 'item',
          '#markup' => $data->mail,
          '#value' => $data->mail,
          '#weight' => '0',
          '#wrapper_attributes' => [
            'class' => ['align-middle', 'm-0']
          ]
        ];

        $form['user_list'][$data->uid]['college'] = [
          '#type' => 'item',
          '#markup' => $data->college_gid,
          '#value' => $data->college_gid,
          '#weight' => '0',
          '#wrapper_attributes' => [
            'class' => ['align-middle', 'm-0']
          ]
        ];

        $form['user_list'][$data->uid]['programme'] = [
          '#type' => 'item',
          '#markup' => $data->programme_gid,
          '#value' => $data->programme_gid,
          '#weight' => '0',
          '#wrapper_attributes' => [
            'class' => ['align-middle', 'm-0']
          ]
        ];

        $form['user_list'][$data->uid]['course'] = [
          '#type' => 'item',
          '#markup' => $data->course_gid,
          '#value' => $data->course_gid,
          '#weight' => '0',
          '#wrapper_attributes' => [
            'class' => ['align-middle', 'm-0']
          ]
        ];

        $form['user_list'][$data->uid]['year'] = [
          '#type' => 'item',
          '#markup' => $data->year,
          '#value' => $data->year,
          '#weight' => '0',
          '#wrapper_attributes' => [
            'class' => ['align-middle', 'm-0']
          ]
        ];
      }
    }

    $form['actions'] = [
      '#type' => 'actions',
      '#attributes' => [
        'class' => ['mb-4', 'd-flex', 'justify-content-center']
      ]
    ];

    $form['actions']['submit'] = [
      '#type' => 'submit',
      '#value' => $this->t('Register'),
      '#name' => 'update',
    ];

    $form['pager'] = ['#type' => 'pager'];

    return $form;
  }
}

And the processUser callback:

  public function processUser(array &$form, FormStateInterface $form_state) {
    return $form['selected_users'];
  }

This seems to work correctly without pagination. Enabling pagination makes it break. Has anyone experienced this before?

1
  • Welcome to Drupal Answers! Has anyone experienced this before? is not actually asking a question, but polling. We need a more specific question, for example What do I need to change in this code to avoid it throws an exception?
    – avpaderno
    Commented Oct 7, 2019 at 10:59

2 Answers 2

1

I have now found that if you use a different approach to pagination the ajax issue is no longer present.

I removed the pagination extender in the database query and have initialised the pager manually.

This code now works:

public function buildForm(array $form, FormStateInterface $form_state) {
  /** @var \Symfony\Component\HttpFoundation\Session\Session $session */
  $session = $this->requestStack->getCurrentRequest()->getSession();
  /** @var \Drupal\node\Entity\Node $node */
  $node = $this->requestStack->getCurrentRequest()->get('node');
  if (!$session->has($node->id().'_register_selection')) {
    $this->requestStack->getCurrentRequest()->getSession()->set($node->id().'_register_selection', []);
  } else {
    $selected = $session->get($node->id().'_register_selection');
  }

  $form['selected_users'] = [
    '#type' => 'container',
    '#attributes' => ['id' => 'selected-users'],
  ];

  $users = $form_state->getValue('user_list');
  if (!empty($users)) {
    foreach ($users as $user) {
      if ($user['check'] == TRUE) {
        $selected[$user['uid']] = $user['uid'];
      } elseif (array_key_exists($user['uid'], $users)) {
        unset($selected[$user['uid']]);
      }
      $session->set($node->id() . '_register_selection', $selected);
    }
  }

  if (!empty($selected)) {
    foreach ($selected as $item) {
      $form['selected_users'][$item]['user'] = [
        '#type' => 'item',
        '#markup' => t('user: @user', ['@user' => $item]),
        '#weight' => '0',
      ];
    }
  }

  $header = [
    ['data' => $form['user_list']['check_all'] = [
      '#type' => 'checkbox',
      '#title' => t('Select all'),
      '#return_value' => TRUE,
      '#wrapper_attributes' => [
        'class' =>['align-middle', 'm-0']
      ]
    ],
      'class'=> ['py-4']
    ],
    'uid' => ['data' => t('UID'), 'class'=> ['align-middle', 'py-4']],
    'username' => ['data' => t('Username'), 'class'=> ['align-middle', 'py-4']],
    'sits_id' => ['data' => t('SITS ID'), 'class'=> ['align-middle', 'py-4']],
    'name' => ['data' => t('Name'), 'class'=> ['align-middle', 'py-4']],
    'mail' => ['data' => t('Email'), 'class'=> ['align-middle', 'py-4']],
    'college' => ['data' => t('College'), 'class'=> ['align-middle', 'py-4']],
    'programme' => ['data' => t('Programme'), 'class'=> ['align-middle', 'py-4']],
    'course' => ['data' => t('Course'), 'class'=> ['align-middle', 'py-4']],
    'year' => ['data' => t('Year'), 'class'=> ['align-middle', 'py-4']],
  ];

  $form['user_list'] = [
    '#type' => 'table',
    '#header' => $header,
    '#empty' => t('No users found with the selected filters.'),
    '#prefix' => "<div class='table-responsive mt-3'>",
    '#suffix' => "</div>",
    '#attributes' => [
      'class' => ['table-sm'],
      'style' => 'font-size: .85rem',
    ]
  ];

  $query = $this->database->select('users_field_data', 'ufd');
  $query->leftJoin('profile', 'profile', 'profile.uid = ufd.uid');
  $query->leftJoin('profile__field_prof_sits_id', 'sitsid', 'sitsid.entity_id = profile.profile_id');
  $query->leftJoin('profile__field_prof_first_name', 'fname', 'fname.entity_id = profile.profile_id');
  $query->leftJoin('profile__field_prof_last_name', 'lname', 'lname.entity_id = profile.profile_id');
  $query->leftJoin('profile__field_prof_college', 'college', 'college.entity_id = profile.profile_id');
  $query->leftJoin('profile__field_prof_programme', 'programme', 'programme.entity_id = profile.profile_id');
  $query->leftJoin('profile__field_prof_course', 'course', 'course.entity_id = profile.profile_id');
  $query->leftJoin('profile__field_prof_sits_year', 'year', 'year.entity_id = profile.profile_id');
  $query->fields('ufd', ['uid','name', 'mail']);
  $query->addField('sitsid', 'field_prof_sits_id_value', 'sits_id');
  $query->addField('fname', 'field_prof_first_name_value', 'first_name');
  $query->addField('lname', 'field_prof_last_name_value', 'last_name');
  $query->addField('college', 'field_prof_college_target_id', 'college_gid');
  $query->addField('programme', 'field_prof_programme_target_id', 'programme_gid');
  $query->addField('course', 'field_prof_course_target_id', 'course_gid');
  $query->addField('year', 'field_prof_sits_year_value', 'year');
  $query->extend('Drupal\Core\Database\Query\TableSortExtender')->orderByHeader($header);
  $results = $query->execute()->fetchAll();

  if ($results) {
    $page = pager_find_page();
    $num_per_page = 5;
    $offset = $num_per_page * $page;
    pager_default_initialize(count($results), $num_per_page);
    $i = 0;
    foreach ($results as $data) {
      if ($i++ < $offset) continue;
      if ($i > $offset + $num_per_page) break;

      $form['user_list'][$data->uid]['check'] = [
        '#type' => 'checkbox',
        '#ajax' => [
          'callback' => '::processUser',
          'wrapper' => 'selected-users',
        ],
        '#wrapper_attributes' => [
          'class' => ['align-middle', 'm-0', 'p-0', 'text-align-center']
        ],
        '#attributes' => [
          'class' => ['m-0'],
          'style' => 'position:relative;',
        ],
        '#default_value' => isset($selected[$data->uid]) ? TRUE : FALSE,
      ];

      $form['user_list'][$data->uid]['uid'] = [
        '#type' => 'item',
        '#markup' => $data->uid,
        '#value' => $data->uid,
        '#weight' => '0',
        '#wrapper_attributes' => [
          'class' => ['align-middle', 'm-0']
        ]
      ];

      $form['user_list'][$data->uid]['username'] = [
        '#type' => 'item',
        '#markup' => $data->name,
        '#value' => $data->name,
        '#weight' => '0',
        '#wrapper_attributes' => [
          'class' => ['align-middle', 'm-0']
        ]
      ];

      $form['user_list'][$data->uid]['sits_id'] = [
        '#type' => 'item',
        '#markup' => $data->sits_id,
        '#value' => $data->sits_id,
        '#weight' => '0',
        '#wrapper_attributes' => [
          'class' => ['align-middle', 'm-0']
        ]
      ];

      $form['user_list'][$data->uid]['name'] = [
        '#type' => 'item',
        '#markup' => $data->first_name . ' ' . $data->last_name,
        '#value' => $data->first_name . ' ' . $data->last_name,
        '#weight' => '0',
        '#wrapper_attributes' => [
          'class' => ['align-middle', 'm-0']
        ]
      ];

      $form['user_list'][$data->uid]['mail'] = [
        '#type' => 'item',
        '#markup' => $data->mail,
        '#value' => $data->mail,
        '#weight' => '0',
        '#wrapper_attributes' => [
          'class' => ['align-middle', 'm-0']
        ]
      ];

      $form['user_list'][$data->uid]['college'] = [
        '#type' => 'item',
        '#markup' => $data->college_gid,
        '#value' => $data->college_gid,
        '#weight' => '0',
        '#wrapper_attributes' => [
          'class' => ['align-middle', 'm-0']
        ]
      ];

      $form['user_list'][$data->uid]['programme'] = [
        '#type' => 'item',
        '#markup' => $data->programme_gid,
        '#value' => $data->programme_gid,
        '#weight' => '0',
        '#wrapper_attributes' => [
          'class' => ['align-middle', 'm-0']
        ]
      ];

      $form['user_list'][$data->uid]['course'] = [
        '#type' => 'item',
        '#markup' => $data->course_gid,
        '#value' => $data->course_gid,
        '#weight' => '0',
        '#wrapper_attributes' => [
          'class' => ['align-middle', 'm-0']
        ]
      ];

      $form['user_list'][$data->uid]['year'] = [
        '#type' => 'item',
        '#markup' => $data->year,
        '#value' => $data->year,
        '#weight' => '0',
        '#wrapper_attributes' => [
          'class' => ['align-middle', 'm-0']
        ]
      ];
    }
  }

  $form['actions'] = [
    '#type' => 'actions',
    '#attributes' => [
      'class' => ['mb-4', 'd-flex', 'justify-content-center']
    ]
  ];

  $form['actions']['submit'] = [
    '#type' => 'submit',
    '#value' => $this->t('Register'),
    '#name' => 'update',
  ];

  $form[] = ['#type' => 'pager'];

  return $form;
}
0

When adding ajaxified form elements in a loop I always use unique array keys on the lowest level:

      foreach ($results as $data) {
        $form['user_list'][$data->uid]['check' . $data->uid] = [
          '#type' => 'checkbox',
          '#ajax' => [
            'callback' => '::processUser',
            'wrapper' => 'selected-users',
          ],
          ...
1
  • Thank you for the advice, I will keep that in mind. Unfortunately that did not solve the problem as the error still persists after changing the array key.
    – inertiahz
    Commented Oct 7, 2019 at 10: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.