1

I have a configuration form for a Condition plugin I want to have ajax in. When selecting from this field, I want to get a list of displays for that View:

  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
    $form = parent::buildConfigurationForm($form, $form_state);
    $views = $this->entityTypeManager->getStorage('view')->loadMultiple();
    $options = [];

    /** @var \Drupal\views\Views $view */
    foreach ($views as $view) {
      $options[$view->id()] = $view->label();
    }

    $form['#prefix'] = '<div id="views-settings">';
    $form['#suffix'] = '</div>';

    $view_id = $form_state->getValue('view_id');

    if (empty($view_id)) {
      $view_id = $form_state->getUserInput()["conditions"]["sfp_condition_view_not_empty"]["view_id"] ?? NULL;
    }

    if (empty($view_id)) {
      $view_id = $this->configuration['view_id'] ?? NULL;
    }

    $displays = isset($view_id) ? $this->getViewsDisplays($view_id) : [];

    $form['view_id'] = [
      '#type' => 'select',
      '#title' => $this->t('View'),
      '#required' => TRUE,
      '#options' => $options,
      '#default_value' => $view_id ? $options[$view_id] : '',
      '#ajax' => [
        'wrapper' => 'views-settings',
        'callback' => '::updateViewsDisplay',
        'event' => 'change',
      ],
    ];

    if (!empty($displays)) {
      $form['view_display'] = [
        '#type' => 'select',
        '#title' => $this->t('View display'),
        '#required' => TRUE,
        '#options' => $displays,
        '#default_value' => $displays[$this->configuration['view_display']] ?? $displays['default'],
      ];
    }

    return $form;
  }

      /**
       * Trigger form rebuild.
       *
       * @param array $form
       * @param \Drupal\Core\Form\FormStateInterface $form_state
       * @return array
       */
      public function updateViewsDisplay(array $form, FormStateInterface $form_state) {
        return $form;
      }

Xdebug hits the method fine, but I get this AJAX error:

"The specified #ajax callback is empty or not callable."

and nothing happens after that.

I checked elsewhere where I have done this and the only difference I can see is the ones that worked are regular Drupal Form API forms, and this is a plugin form passed from buildConfigurationForm. Outside of that, I am not seeing the issue.

6
  • Any chance you're using this in a Context UI form (as opposed to e.g. block visibility)? I remember having awful trouble trying to get AJAX working with the Context module
    – Clive
    May 13 at 19:36
  • Yeah, that is exactly where I am. Changing the callback to a procedural function in a .module works, but I don't know why. Shouldn't this still be a reachable method if made static? Nothing seemed to work. Whats bizarre is xdebug never failed to hit it. Form state also does not always have the selected value, unlike usual forms.
    – Kevin
    May 13 at 19:38
  • It was a while ago but IIRC it had something to do with context UI having its own entry point for form AJAX, it doesn't use /system/ajax. I'm sure I remember the same thing happening, the callback definitely being hit but the results not being included in the new form. Whatever I ended up doing to get around it is somewhere in this module if it helps: drupal.org/project/request_data_conditions
    – Clive
    May 13 at 19:40
  • Wow. Yeah. Who knew? Posting answer.
    – Kevin
    May 13 at 19:47
  • That actually fixed the AJAX, but wound up getting 'illegal choice detected' error. I wound up just axing AJAX and supplying the entire array in a list.
    – Kevin
    May 13 at 20:53

1 Answer 1

3

Thanks to Clives link, for reasons I am not 100% on the configuration forms for a Context plugin the AJAX callback needs to do this:

  /**
   * Trigger form rebuild.
   *
   * @param array $form
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   * @return array
   */
  public function updateViewsDisplay(array $form, FormStateInterface $form_state) {
    $triggering_element = $form_state->getTriggeringElement();
    $parents = array_slice($triggering_element['#array_parents'], 0, -1);
    return NestedArray::getValue($form, $parents);
  }

Then it began to work.

Also, the AJAX declaration needed to be:

'callback' => [$this, 'updateViewsDisplay'],

I assume this has to do with it being a 'form within a form'.

2
  • Yes, a form declaration in a plugin usually is not standalone, it's a subtree which the code using the plugin can place in its own form. You see this trick with array slicing also in field widgets. BTW basing the callback on $this is not recommended, you might not get the form instance you are expecting. I would use '[static::class, 'updateViewsDisplay'] if '::updateViewsDisplay' doesn't work. If you then need to reference the form object you can get it from $form_state.
    – 4uk4
    May 15 at 9:38
  • I see, also note that static::class requires the callback to be static as well, or the error persists.
    – Kevin
    May 27 at 14:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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